目录
写在前面
前提准备
本次干的事情——编写安卓app
正文
比如 子线程定时获取数据库信息实时更新UI
比如子线程判断主线程事件进行数据操作实现对设备的控制
写在前面
为实现第三方移动端控制的要求,我需要实现一个在个人局域网内控制设备的移动端APP。
前提准备
一个已经实现好通信控制的硬件和相关外设(单片机)。
一块stm32主控板,若干块通信模块(esp32,lora模块)。
一个emqx的mqtt消息服务器。
一个自己写网络接口的java客户端(一个基于springboot整合mybatis的后端服务器,提供命令调用的接口和监听硬件采集的数据并转储于数据库)。
这个java客户端连接mqtt服务器,订阅数据获取的主题,发布对应主题的消息以控制硬件。
在以上的前提都准备后,理论上我已经干的差不多的,安卓只是一个写界面来更好地服务与我控制的操作而已。。。。我可以在网页上直接访问java客户端提供的接口,来访问硬件采集的数据,控制硬件的外设,以及干更多你想干的事情(比如RF高频卡感应来模拟门禁,通过发送的信息实现语音播报和语音控制等等)
本次干的事情——编写安卓app
我的想法就是越简单越好,这会让人更加地清晰和明白,我没有像网上一般的那样让安卓去连接
mqtt服务器,然后通过安卓与mqtt服务器直接打交道,(获取数据和命令控制)。但是,我只是想直接简单明了的调用接口,以最简单的http请求的方式实现我的目的。为了这个,java客户端干了所有的事,再提供访问的接口。
参考我的java客户端
局域网内用JAVA建立MQTT客户端监听MQTT服务器消息并持久化到数据库_昊月光华的博客-CSDN博客_java mqtt服务端
java客户端运行
提供接口(http请求)访问
发布命令
当然安卓连接mqtt服务器也不是一件难事,问题在于假如我有多个应用程序都要访问数据和控制呢?哪不是每一个都得连接mqtt服务器,每一个都要写监听对应主题以及发布主题消息的方法?
当然,目前很多的外网云平台正是提供第三方访问的接口请求。也就是API
比如巴法云平台,华为云平台等等。
正文
首先是编写界面和交互逻辑代码,最主要的也就是主线程与子线程的通信,也就是子线程获取数据,主线程更新界面。
这里有常见几种的情形:
- 在一个页面内(activity)内,创建一个内部类(继承线程类,重写run方法),子线程不断地获取数据,然后通过runonUIthread 更新界面。
- 主线程持有子线程的handler引用,子线程收到主线程发过来的message ,通过message.what判断是哪个控件发过来的,然后进行数据操作(访问数据库等等),数据操作完后,通过runonUIthread 更新UI界面。
比如 子线程定时获取数据库信息实时更新UI
在首页中
package com.example.yuezhenhao;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.example.yuezhenhao.HttpRequest.DAO;
import java.security.PrivateKey;
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity extends AppCompatActivity {
private View exit,mainpage,control,chart,querypage;
private TextView runt, temp, humi, ls,tl,hl,ll,state,isc;
private String TAG="MAIN";
int i=0;
private Handler mHandler= new Handler(Looper.getMainLooper()) {
@Override
//重写handleMessage方法,根据msg中what的值判断是否执行后续操作
public void handleMessage(Message msg) {
if (msg.what == 0) {
i++;
runt.setText(String.valueOf(i));
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//开启工作线程
WorkThread workThread = new WorkThread();
workThread.start();
querypage=findViewById(R.id.querypage);
init(); //初始化控件
//转主页
mainpage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.e(TAG, "ToMainPage ");
}
});
//转控制页面
control.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.e(TAG, "To Control");
Intent intent = new Intent(MainActivity.this, Conrrol.class);
startActivity(intent);
}
});
//转图表页面
chart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.e(TAG, "To Chart");
Intent intent = new Intent(MainActivity.this, DatesTable.class);
startActivity(intent);
}
});
//退出程序
exit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//退出整个应用程序
Log.e(TAG, "exit" );
finishAffinity();
System.exit(0);
}
});
// 转查询页面
querypage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.e(TAG, "query" );
Intent intent = new Intent(MainActivity.this, QuertDate.class);
startActivity(intent);
}
});
}
/**
* 初始化组件
*/
public void init() {
runt = findViewById(R.id.conf);
temp = findViewById(R.id.temp);
humi= findViewById(R.id.humi);
ls = findViewById(R.id.ls);
tl = findViewById(R.id.tl);
hl= findViewById(R.id.hl);
ll = findViewById(R.id.ll);
isc=findViewById(R.id.isc);
state=findViewById(R.id.state);
state.setText("连接中");
mainpage=findViewById(R.id.mainpage);
control=findViewById(R.id.control);
chart=findViewById(R.id.table);
exit=findViewById(R.id.exit);
}
//工作线程刷新主页数据
class WorkThread extends Thread {
private String _temp;
private String _humi;
private String _ls;
//定义阈值
private String TempLimit;
private String HumiLimit;
private String LsLimit;
private String _isc; //是否自动控制
private boolean cons=false;
private Handler workhandler;
private int time=0;
public void run() {
new Timer().schedule(new TimerTask() {
@SuppressLint("ResourceAsColor")
@Override
public void run() {
time++;
JSONArray limits= null;
JSONObject LatestDates=null;
try {
limits = DAO.GetLimit();
LatestDates=(JSONObject) DAO.GetLatest().get(0);
cons=true;
TempLimit=((JSONObject)limits.get(0)).getString("val");
HumiLimit=((JSONObject)limits.get(1)).getString("val");
LsLimit=((JSONObject)limits.get(2)).getString("val");
_isc=((JSONObject)limits.get(3)).getString("val");
_temp=LatestDates.getString("msg1")+"℃";
_humi=LatestDates.getString("msg2")+"%";
_ls=LatestDates.getString("msg3")+"lx";
if(_isc.equals("1")){
_isc="开启";
}
else
_isc="关闭";
} catch (Exception e) {
e.printStackTrace();
cons=false;
}
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
runt.setText(time+"s");
tl.setText(TempLimit);
hl.setText(HumiLimit);
ll.setText(LsLimit);
isc.setText(_isc);
temp.setText(_temp);
humi.setText(_humi);
ls.setText(_ls);
if(cons)state.setText("已连接");
else {
state.setText("断开连接");
state.setTextColor(R.color.red);
}
}
});
}
}, 0, 1000);
}
}
}
比如子线程判断主线程事件进行数据操作实现对设备的控制
package com.example.yuezhenhao;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.StrictMode;
import android.text.Editable;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.Toast;
import com.example.yuezhenhao.HttpRequest.DAO;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class Conrrol extends AppCompatActivity {
private Switch dev1,dev2,dev3,dev4;
private Button msgbn1,msgbn2,msgbn3,pbt1,pbt2;
private EditText msg1,msg2,msg3,mtor1,mtor2;
private boolean flag1=false;
public Handler mHandler;
private String TAG="CONTROL";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_conrrol);
init();
SubThread subThread=new SubThread();
subThread.start();
mHandler=new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
if(msg.what == 1){
Toast.makeText(Conrrol.this, "操作成功", Toast.LENGTH_SHORT).show();
}
else
Toast.makeText(Conrrol.this, "操作失败", Toast.LENGTH_SHORT).show();
}
};
//控制设备1-3的常规开和关
dev1.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (dev1.isChecked()) {
Log.e(TAG, "ON DEV1");
PackageDate(subThread.subhandler,"dev1","20");
}
else {
Log.e(TAG, "OFF DEV1");
PackageDate(subThread.subhandler,"dev1","0");
}
}
});
dev2.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (dev2.isChecked()) {
Log.e(TAG, "ON DEV2");
PackageDate(subThread.subhandler,"dev2","20");
}
else {
Log.e(TAG, "OFF DEV2");
PackageDate(subThread.subhandler,"dev2","0");
}
}
});
dev3.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (dev3.isChecked()) {
Log.e(TAG, "ON DEV3");
PackageDate(subThread.subhandler,"dev3","5000");
}
else {
Log.e(TAG, "OFF DEV3");
PackageDate(subThread.subhandler,"dev3","0");
}
}
});
//设置是否自动控制
dev4.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (dev4.isChecked()) {
Log.e(TAG, "自动控制开启");
PkLimitAndIsc(subThread.subhandler,"4","1");
} else {
Log.e(TAG, "自动控制关闭");
PkLimitAndIsc(subThread.subhandler,"4","0");
}
}
});
//给字段msg1设置阈值
msgbn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String val=msg1.getText().toString();
PkLimitAndIsc(subThread.subhandler,"1",val);
}
});
//给字段msg2设置阈值
msgbn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String val=msg2.getText().toString();
PkLimitAndIsc(subThread.subhandler,"2",val);
}
});
//给字段msg3设置阈值
msgbn3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String val=msg3.getText().toString();
PkLimitAndIsc(subThread.subhandler,"3",val);
}
});
//电机1控制, 支持PWM和正反转
pbt1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String val=mtor1.getText().toString();
PackageDate(subThread.subhandler,"dev1",val);
}
});
//电机2控制, 支持PWM和正反转
pbt2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String val=mtor2.getText().toString();
PackageDate(subThread.subhandler,"dev2",val);
}
});
}
/**
* 基于不同id的按钮控件打包数据
* 数据:控制执行部件的数据
*/
public void PackageDate(Handler handler,String name,String val){
Map[] li=new Map[2];
try {
li[0]=new HashMap();
li[1]=new HashMap();
li[0].put("name",name);
li[1].put("val",val);
Message msg=Message.obtain();
msg.what=1;
msg.obj=li;
handler.sendMessage(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 基于不同id的按钮控件打包数据
* 数据:设置阈值的数据或者是是否自动控制的标记
*/
public void PkLimitAndIsc(Handler handler,String name,String val){
Message msg=Message.obtain();
Object[] obj=new Object[2];
obj[0]=new Object();
obj[1]=new Object();
obj[0]=name;
obj[1]=val;
msg.what=2;
msg.obj=obj;
handler.sendMessage(msg);
}
/**
* 初始化控件
*/
public void init(){
dev1=findViewById(R.id.dev1);
dev2=findViewById(R.id.dev2);
dev3=findViewById(R.id.dev3);
dev4=findViewById(R.id.dev4);
msg1=findViewById(R.id.msg1);
msg2=findViewById(R.id.msg2);
msg3=findViewById(R.id.msg3);
msgbn1=findViewById(R.id.msg_bn1);
msgbn2=findViewById(R.id.msg_bn2);
msgbn3=findViewById(R.id.msg_bn3);
pbt1=findViewById(R.id.pbn1);
pbt2=findViewById(R.id.pbn2);
mtor1=findViewById(R.id.mtor1); //两个电机的可编辑框的值的获取
mtor2=findViewById(R.id.mtor2);
}
//子线程协调主线程发起网络请求
class SubThread extends Thread {
public Handler subhandler;
public void run() {
Looper.prepare();
subhandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
//解包发起请求
Map[] li= (Map[]) msg.obj;
try {
DAO.ControlDec((String) li[0].get("name"),(String) li[1].get("val"));
Log.e(TAG, "success" );
mHandler.sendEmptyMessage(1);
} catch (Exception e) {
e.printStackTrace();
mHandler.sendEmptyMessage(0);
}
break;
case 2:
try {
Object[] objects= (Object[]) msg.obj;
DAO.SetLimitAndIsc(objects[0].toString(),objects[1].toString());
Log.e(TAG, "success" );
mHandler.sendEmptyMessage(1);
} catch (Exception e) {
e.printStackTrace();
mHandler.sendEmptyMessage(0);
}
break;
default:
break;
}
}
};
Looper.loop();
}
}
}
这两点是最主要的,另外就是数据统计,比如画折线图对数据进行实时显示,还有对历史数据(传感器信息)进行查询等等。
参考
Android的handler消息收发处理——子线程与主线程(UI线程)间的通信_昊月光华的博客-CSDN博客_android获取主线程handler
然后就是界面布局,我不追求美观,只要能用就行。
本文章来源于互联网,如有侵权,请联系删除!原文地址:基于局域网内物联网的androidAPP开发——实现消息上报与命令下达
阿里云物联网平台实现MQTT通信 一、环境搭建 一、阿里云物联网平台 二、MQTT.fx 总结 一、环境搭建 一、阿里云物联网平台 1.首先进入阿里云官网https://www.aliyun.com/?utm_content=se_1000301881注册并登…