原文地址:Android8.0 後臺服務保活的一種思路 | Stars-One的雜貨小窩
專案中有個MQ服務,需要一直連著,接收到訊息會傳送語音,且手機要在鎖屏也要實現此功能
目前是使用廣播機制實現,每次MQ收到訊息,觸發一次啟動服務操作邏輯
在Android11版本測試成功,可實現上述功能
步驟
具體流程:
- 進入APP
- 開啟後臺服務Service
- 後臺服務Service開啟執行緒,連線MQ
- MQ的消費事件,傳送廣播
- 廣播接收器中,處理啟動服務(若服務已被關閉)和文字語音播放功能
1.廣播註冊
<receiver
android:name=".receiver.MyReceiver"
android:enabled="true"
android:exported="true">
</receiver>
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//匹配下之前定義的action
if ("OPEN_SERVICE".equals(action)) {
if (!ServiceUtils.isServiceRunning(MqMsgService.class)) {
Log.e("--test", "服務未啟動,先啟動服務");
Intent myIntent = new Intent(context, MqMsgService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
String text = intent.getStringExtra("text");
Log.e("--test", "廣播傳的訊息"+text);
EventBus.getDefault().post(new SpeakEvent(text));
}
}
}
語音初始化的相關操作都在服務中進行的,這裡不再贅述(通過EventBus轉發時間事件)
這裡需要注意的是,Android8.0版本,廣播不能直接startService()
啟動服務,而是要通過startForegroundService()
方法,而呼叫了startForegroundService()
方法,則是需要服務在5s內呼叫一個方法startForeground()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification notification = NotifyUtil.sendNotification(this, "平板", "後臺MQ服務執行中", NotificationCompat.PRIORITY_HIGH);
startForeground(1, notification);
}
上面這段程式碼,就是寫在Service中的onCreate方法內,之前也是找到有資料說,需要有通知欄,服務才不會被Android系統給關閉,也不知道有沒有起到作用?
還需要注意的是,需要宣告許可權
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
NotifyUtil工具類程式碼
public class NotifyUtil {
private static String channel_id="myChannelId";
private static String channel_name="新訊息";
private static String description = "新訊息通知";
private static int notifyId = 0;
private static NotificationManager notificationManager;
public static void createNotificationChannel(){
if (notificationManager != null) {
return;
}
//Android8.0(API26)以上需要呼叫下列方法,但低版本由於支援庫舊,不支援呼叫
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(channel_id,channel_name,importance);
channel.setDescription(description);
notificationManager = (NotificationManager) ActivityUtils.getTopActivity().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
}else{
notificationManager = (NotificationManager) ActivityUtils.getTopActivity().getSystemService(Context.NOTIFICATION_SERVICE);
}
}
public static void sendNotification(String title,String text){
createNotificationChannel();
Notification notification = new NotificationCompat.Builder(ActivityUtils.getTopActivity(),channel_id)
.setContentTitle(title)
.setContentText(text)
.setWhen(System.currentTimeMillis())
.setSmallIcon(ResourceUtils.getMipmapIdByName("ic_launcher"))
.setLargeIcon(BitmapFactory.decodeResource(ActivityUtils.getTopActivity().getResources(), ResourceUtils.getMipmapIdByName("ic_launcher")))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build();
notificationManager.notify(notifyId++,notification);
}
public static Notification sendNotification(Context context,String title,String text,int priority){
createNotificationChannel();
Notification notification = new NotificationCompat.Builder(context,channel_id)
.setContentTitle(title)
.setContentText(text)
.setWhen(System.currentTimeMillis())
.setSmallIcon(ResourceUtils.getMipmapIdByName("ic_launcher"))
.setLargeIcon(BitmapFactory.decodeResource(ActivityUtils.getTopActivity().getResources(), ResourceUtils.getMipmapIdByName("ic_launcher")))
.setPriority(priority)
.build();
notificationManager.notify(notifyId++,notification);
return notification;
}
public static void sendNotification(String title, String text, int priority, PendingIntent pendingIntent){
createNotificationChannel();
Notification notification = new NotificationCompat.Builder(ActivityUtils.getTopActivity(),channel_id)
.setContentTitle(title)
.setContentText(text)
.setWhen(System.currentTimeMillis())
.setSmallIcon(ResourceUtils.getMipmapIdByName("ic_launcher"))
.setLargeIcon(BitmapFactory.decodeResource(ActivityUtils.getTopActivity().getResources(), ResourceUtils.getMipmapIdByName("ic_launcher")))
.setPriority(priority)
.setContentIntent(pendingIntent)
.build();
notificationManager.notify(notifyId++,notification);
}
}
2.服務
宣告一個服務,然後在服務中開啟一個執行緒,用來連線MQ,MQ的消費事件中,傳送廣播
//發出一條廣播
String ALARM_ACTION_CODE = "OPEN_SERVICE";
Intent intent = new Intent(ALARM_ACTION_CODE);
//適配8.0以上(不然沒法發出廣播) 顯式宣告元件
if (DeviceUtils.getSDKVersionCode() > Build.VERSION_CODES.O) {
intent.setComponent(new ComponentName(context, MyReceiver.class));
}
intent.putExtra("text", msg);
context.sendBroadcast(intent);
之後大體上就是測試了,開啟APP,然後直接返回桌面,大概1分鐘後,APP就無法播放語音
而使用了上述的思路,不管是鎖屏還是回到桌面(測試使用的是Android11,谷歌官方系統),都可以實現語音播放,不過未在其他系統的手機上嘗試過
原本現場的裝置也就是一個華為平板,而且是鴻蒙系統的