概述
上节完成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"}
测试结果如下: