Android8.0 後臺服務保活的一種思路

one發表於2022-05-05

原文地址:Android8.0 後臺服務保活的一種思路 | Stars-One的雜貨小窩

專案中有個MQ服務,需要一直連著,接收到訊息會傳送語音,且手機要在鎖屏也要實現此功能

目前是使用廣播機制實現,每次MQ收到訊息,觸發一次啟動服務操作邏輯

在Android11版本測試成功,可實現上述功能

步驟

具體流程:

  1. 進入APP
  2. 開啟後臺服務Service
  3. 後臺服務Service開啟執行緒,連線MQ
  4. MQ的消費事件,傳送廣播
  5. 廣播接收器中,處理啟動服務(若服務已被關閉)和文字語音播放功能

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,谷歌官方系統),都可以實現語音播放,不過未在其他系統的手機上嘗試過

原本現場的裝置也就是一個華為平板,而且是鴻蒙系統的

相關文章