Android手把手编写儿童手机远程监控App之前台服务通信

0 阅读5分钟

概述

上节完成Android任务栏消息功能。通过代码测试发现,app发送消息到通知栏,不提示无声音,会被折叠到不重要一栏,让用户无感。原因在于app默认安装配置时,由系统默认决定消息的显示方式,当然用户可自行设置对app消息的提示方式。app发送消息分三步

  • 创建一个通道,名为channel,该通道被系统记录,可在应用程序设置通知方式
  • 创建多个消息,共享一个通道。消息需指明通道id,消息标题内容
  • 发布消息 嘟宝通过mqtt在后台与服务器 长久连接,嘟妈通过信令与嘟宝交互数据,建立音视频通信。
  • 嘟妈与嘟宝的第一次通信,应从扫描二维码开始。两者交互流程如下:
  • 嘟宝app启动,显示二维码,并启动前台服务建立MQTT连接,订阅主题/dubao/dubaoID
  • 嘟妈扫描二维码,向嘟宝发送绑定指令
  • 嘟宝收到绑定指令后,向通知栏发送消息,引导用户手动确认绑定 在这里插入图片描述

目前嘟宝项目完成的开发工作:

  • 开机自启动
  • 前台任务,后台驻留
  • MQTT长连接,订阅、推送、医嘱
  • 嘟宝身份识别码uuid
  • 嘟宝身份二维码
  • 发送消息任务栏
  • 数据格式Gson

后台服务根据mqtt订阅事件发送任务栏消息

嘟宝长期在后台活动,使用MQTT协议与服务器建立长久连接。当嘟妈通过二维码扫描嘟宝建立两者绑定关系时,会发送一条数据给嘟宝,嘟宝收到数据发送消息到任务栏,引导用户确定绑定。 在这里插入图片描述 流程如下,在前台服务服务中启动mqtt,订阅事件,响应消息

消息类NotificationMsg封装

NotificationMsg源码

package com.zilong.dubao;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import androidx.core.app.NotificationCompat;
public class NotificationMsg {
    private String channelID="DUBAO";
    private String channelName="嘟宝安心守护";
    private Context context;
    private NotificationManager manager=null;
    static int id=0;

    NotificationMsg(Context context){
        this.context=context;
    }

    private void createNotificationChannel() {
        manager =(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationChannel channel = new NotificationChannel(channelID, channelName, NotificationManager.IMPORTANCE_HIGH);
        manager.createNotificationChannel(channel);
    }
    public void sendNotification(String title,String content) {
        if (manager==null){
            createNotificationChannel();
        }
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelID)
                .setSmallIcon(R.drawable.logo)
                .setContentTitle(title)
                .setContentText(content);
        manager.notify(id, builder.build());
        id++;
    }
    public void sendNotification(String title, String content, Intent intent) {
        if (manager==null){
            createNotificationChannel();
        }
        PendingIntent pendingIntent = PendingIntent.getActivity(context, id, intent, PendingIntent.FLAG_IMMUTABLE);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelID)
                .setSmallIcon(R.drawable.logo)
                .setContentIntent(pendingIntent)
                .setContentTitle(title)
                .setContentText(content).setPriority(NotificationCompat.PRIORITY_MAX);
        manager.notify(id, builder.build());
        id++;
    }
}

在mqtt封装类中,含有NotificationMsg使用方式

通信类MyMqttClient封装

package com.zilong.dubao;

import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;

import com.google.gson.Gson;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

import java.nio.charset.StandardCharsets;

public class MyMqttClient {
    private Handler mWorkHandler;
    private MqttClient client;
    private MqttConnectOptions connOpts;
    private String uuid;
    private  Context context;
    private class Msg{
        String code;
        String dumaName;
        String dumaId;
        String dubaoId;
    }

    private void HandleMsg(String json){
        Gson gson = new Gson();
        Msg msg = gson.fromJson(json, Msg.class);
        if (msg.dubaoId.equals(uuid)){
            Log.d("mqtt",msg.dumaId);
            Log.d("mqtt",msg.dumaName);
            Intent intent = new Intent(context, MessageActivity.class);
            intent.putExtra("dumaName",msg.dumaName);
            intent.putExtra("dumaId",msg.dumaId);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            MyService.notificationMsg.sendNotification(msg.dumaName,msg.dumaId,intent);

        }

    }
    MyMqttClient(String uuid, Context context){
        HandlerThread handlerThread = new HandlerThread("worker");
        handlerThread.start();
        mWorkHandler = new Handler(handlerThread.getLooper());
        this.uuid=uuid;
        this.context=context;
        Log.d("mqtt",uuid);
    }

    public void connect() {
        mWorkHandler.post(()->{
            _connect();

        });

    }
    private MqttCallbackExtended callbackExtendedallback =new MqttCallbackExtended() {
        @Override
        public void connectComplete(boolean reconnect, String serverURI) {
            Log.d("mqtt","connectComplete");
            try {
                client.subscribe("/dubao/"+uuid);
            } catch (MqttException e) {
                e.printStackTrace();
            }


        }

        @Override
        public void connectionLost(Throwable cause) {
            Log.d("mqtt","connectionLost");

        }

        @Override
        public void messageArrived(String topic, MqttMessage message) throws Exception {
            String s=new String(message.getPayload());
            HandleMsg(s);
        }

        @Override
        public void deliveryComplete(IMqttDeliveryToken token) {

        }
    };

    protected void _connect(){
        try {
            String url= "tcp://"+MyConfig.mqttip+":"+MyConfig.mqttport;
            String dubaoID=uuid;
            client=new MqttClient(url, "dubao_server"+dubaoID, new MemoryPersistence());
            connOpts=new MqttConnectOptions();
            connOpts.setCleanSession(true);// 重连接是否清理会话
            connOpts.setConnectionTimeout(10);
            connOpts.setKeepAliveInterval(60);//心跳间隔(秒)
            connOpts.setAutomaticReconnect(true);
            String s="嘟宝异常掉线了";
            connOpts.setWill("/dubao/will",s.getBytes(StandardCharsets.UTF_8),0,false);
            client.setCallback(callbackExtendedallback);
            _connectionMQTTServer();

        } catch (MqttException e) {
            e.printStackTrace();
        }

    }

    protected  void _connectionMQTTServer(){
        while (true){
            try {
                client.connect(connOpts);
                break;
            } catch (MqttException e) {
                Log.d("mqtt","连接失败");
                e.printStackTrace();
            }
            try {
                Thread.sleep(5*1000);
                Log.d("mqtt","准备重新连接");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void publish(String topic,String msg){
        publish(topic,msg,0,false);
    }
    public void publish(String topic,String msg,int qos,boolean retained){
        mWorkHandler.post(()->{
            try {
                MqttMessage message=new MqttMessage(msg.getBytes(StandardCharsets.UTF_8));
                message.setQos(qos);
                message.setRetained(retained);
                client.publish(topic,message);
            } catch (MqttException e) {
                e.printStackTrace();
            }

        });
    }
    public void colse(){
        mWorkHandler.post(()->{
            try {
                client.disconnect();
                client.close();
            } catch (MqttException e) {
                e.printStackTrace();
            }

        });

    }

}

在其中messageArrived回调函数中收到嘟妈发来的数据,通过HandleMsg函数处理json数据,再把HandleMsg看下

 private void HandleMsg(String json){
//        String json="{\"code\":\"bind\",\"dumaName\":\"嘟妈\",\"dumaId\":\"f1122aeb-f2b0-400d-9919-eddd2eaebaa2\",\"dubaoId\":\"cfb20ccc-8c53-4434-85bb-a171c3ca7c0c\"}";
        Gson gson = new Gson();
        Msg msg = gson.fromJson(json, Msg.class);
        if (msg.dubaoId.equals(uuid)){
            Intent intent = new Intent(context, MessageActivity.class);
            intent.putExtra("dumaName",msg.dumaName);
            intent.putExtra("dumaId",msg.dumaId);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            MyService.notificationMsg.sendNotification(msg.dumaName,msg.dumaId,intent);

        }

    }

注释是json目前的基本格式。Gson 可将字符串解析成对象,但对象类需要提前定义。解析完数据,通过MyService类中静态对象notificationMsg向通知栏发送消息并传递数据,msg.dumaName、msg.dumaId给MessageActivity页面。

前台服务代码

前台服务需要在AndroidManifest文件声明

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.zilong.dubao">

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.intent.action.BOOT_COMPLETED" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@drawable/fav"
        android:label="@string/app_name"
        android:roundIcon="@drawable/favround"
        android:supportsRtl="true"
        android:theme="@style/Theme.DuBao"
        tools:targetApi="31">
        <activity
            android:name=".MessageActivity"
            android:exported="false" />
        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true" />

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

前台服务MyService源码

package com.zilong.dubao;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

import androidx.core.app.NotificationCompat;

public class MyService extends Service {
    MyMqttClient myMqttClient;
    static NotificationMsg notificationMsg;
    public MyService() {
    }


    @Override
    public void onCreate() {
        super.onCreate();
        createNotificationChannel();
        uuid u=new uuid();
        String uuid= u.getuuid(this);
        notificationMsg=new NotificationMsg(this);
        myMqttClient=new MyMqttClient(uuid,this);
        myMqttClient.connect();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);

    }

    @Override
    public void onDestroy() {

        myMqttClient.colse();
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }


    private void createNotificationChannel() {
        NotificationChannel channel = new NotificationChannel(
                "DUBAO",
                "嘟宝安心守护",
                NotificationManager.IMPORTANCE_LOW
        );
        NotificationManager manager = getSystemService(NotificationManager.class);
        manager.createNotificationChannel(channel);

        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(
                this, 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );

        Notification notification= new NotificationCompat.Builder(this, "DUBAO")
                .setContentTitle("嘟宝")
                .setContentText("嘟宝安心守护孩子安全...")
                .setSmallIcon(android.R.drawable.ic_menu_info_details)
                .setContentIntent(pendingIntent)
                .setOngoing(true)  // 不可滑动删除
                .build();
        startForeground(10001, notification);
    }
}

在前台服务onCreate函数中,生成实例三个实例

  • notificationMsg
  • myMqttClient
  • uuid MyService实例是唯一,三个实例也是唯一,由于在myMqttClient需要发送消息给通知栏,需要调用实例notificationMsg,将其设置静态变量,这虽然不好,但是个解决方案。 uuid类源码
package com.zilong.dubao;

import static android.content.Context.MODE_PRIVATE;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;

import java.util.UUID;

public class uuid {
    private String createUUID(){
        String s="";
        s=UUID.randomUUID().toString();
        return s;

    }
    public String getuuid(Context context){
        SharedPreferences  preferences=context.getSharedPreferences("uuid",MODE_PRIVATE);
        String uuid= preferences.getString("id","");
        if (uuid.equals("")){
            uuid=createUUID();
            SharedPreferences.Editor editor=context.getSharedPreferences("uuid",MODE_PRIVATE).edit();
            editor.putString("id",uuid);
            editor.apply();
            return uuid;
        }
        return uuid;
    }
}

运行效果

用MQTT测试工具,模拟嘟妈发送绑定消息给嘟宝。 发送主题:

/dubao/f25690b9-40eb-4acc-90a2-8a39712117b5

发送数据:

{"code":"bind","dumaName":"嘟妈","dumaId":"f1122aeb-f2b0-400d-9919-eddd2eaebaa2","dubaoId":"f25690b9-40eb-4acc-90a2-8a39712117b5"}

测试结果如下: 在这里插入图片描述