目录
为实现第三方移动端控制的要求,我需要实现一个在个人局域网内控制设备的移动端APP。
一个已经实现好通信控制的硬件和相关外设(单片机)。
一块stm32主控板,若干块通信模块(esp32,lora模块)。
一个emqx的mqtt消息服务器。
一个自己写网络接口的java客户端(一个基于springboot整合mybatis的后端服务器,提供命令调用的接口和监听硬件采集的数据并转储于数据库)。
这个java客户端连接mqtt服务器,订阅数据获取的主题,发布对应主题的消息以控制硬件。
在以上的前提都准备后,理论上我已经干的差不多的,安卓只是一个写界面来更好地服务与我控制的操作而已。。。。我可以在网页上直接访问java客户端提供的接口,来访问硬件采集的数据,控制硬件的外设,以及干更多你想干的事情(比如RF高频卡感应来模拟门禁,通过发送的信息实现语音播报和语音控制等等)
我的想法就是越简单越好,这会让人更加地清晰和明白,我没有像网上一般的那样让安卓去连接
mqtt服务器,然后通过安卓与mqtt服务器直接打交道,(获取数据和命令控制)。但是,我只是想直接简单明了的调用接口,以最简单的http请求的方式实现我的目的。为了这个,java客户端干了所有的事,再提供访问的接口。
参考我的java客户端
局域网内用JAVA建立MQTT客户端监听MQTT服务器消息并持久化到数据库_昊月光华的博客-CSDN博客_java mqtt服务端
java客户端运行

提供接口(http请求)访问
发布命令

当然安卓连接mqtt服务器也不是一件难事,问题在于假如我有多个应用程序都要访问数据和控制呢?哪不是每一个都得连接mqtt服务器,每一个都要写监听对应主题以及发布主题消息的方法?
当然,目前很多的外网云平台正是提供第三方访问的接口请求。也就是API
比如巴法云平台,华为云平台等等。

首先是编写界面和交互逻辑代码,最主要的也就是主线程与子线程的通信,也就是子线程获取数据,主线程更新界面。
这里有常见几种的情形:
在首页中
- 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
然后就是界面布局,我不追求美观,只要能用就行。