Android 8.0(O)+後臺服務適配

問題Not allowed to start service Intent XXX : app is in background uid UidRecord

項目很早targetSdkVersion升到了28(Android 9.0)P,一直忙業務,最近查看了下後臺報錯信息,發現幾個崩潰次數比較多的問題,共同點都是項目中的後臺service,報錯信息如下

Not allowed to start service Intent XXX : app is in background uid UidRecord

這個報錯是8.0做的限制,不允許其創建的後臺服務使用startService()函數,該函數會出現IllegalStateException錯誤;查看官方如下

Android8.0行爲變更.png

問題分析

問題復現,首先是app處於後臺發起的starService,如果app處於前臺期間starService不會報錯。於是先簡單寫個demo,在activity點擊返回鍵時,在onDestory中去starService,但是點擊之後並沒有出現錯誤。

後來我開啓服務之後,置於後臺一段時間後報了同樣異常。於是我就在onDestory中加了一個延時測試,延時5s,也沒有出現,讓人頭大,直到把延時加到60s,發現報了異常,Not allowed to start service Intent XXX : app is in background uid UidRecord。

解決方法

官網提示也說了只需要判斷下系統爲8.0+的情況下,用startForegroundService開啓一個前臺服務。而且需要在service裏面添加移除的通知的方法。

//9.0之後要求加入權限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
Intent intent = new Intent(MainActivity.this,MyIntentService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    MainActivity.this.startForegroundService(intent);
} else {
    MainActivity.this.startService(intent);
}
   public static final String CHANNEL_ID_STRING = "service_02";
    public static final String CHANNEL_ID_NAME = "亮屏";
    public static final int NOTIFICATION_ID = 2;
    void startForeground() {
        String ns = Context.NOTIFICATION_SERVICE;
        NotificationManager notificationManager = (NotificationManager) getSystemService(ns);
        NotificationChannel mChannel = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mChannel = new NotificationChannel(CHANNEL_ID_STRING, CHANNEL_ID_NAME,
                    NotificationManager.IMPORTANCE_LOW);
            notificationManager.createNotificationChannel(mChannel);
            Notification notification = new Notification.Builder(getApplicationContext(), CHANNEL_ID_STRING).build();
            startForeground(NOTIFICATION_ID, notification);
        }
    }

Context.startForegroundService() did not then call Service.startForeground()?

需要注意的是service創建5s後必須調用startForeground,否則報Context.startForegroundService() did not then call Service.startForeground()

我的處理是在service中onCreat和onDestory中成對的出現startForeground和stopForeground(true),並且在啓動service中會判斷該service是否處於run的狀態,避免多次啓動已經存在service的。

   public static boolean isRunningService(Context context, Class<?> serviceClass) {
        ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (serviceClass.getName().equals(service.service.getClassName())) {
                Log.i(TAG, true + "");
                return true;
            }
        }
        Log.i(TAG, false + "");
        return false;
    }

####如何關閉通知?
startService開啓的服務,要通過stopService關閉服務;bindService的服務需要通過unbindService(serviceConnection)關閉服務;如果service既startService開啓又bindService,這時候就需要同事調用stopService又要調用unbindService(serviceConnection)。如果不這樣做,在app中使用開關,關閉狀態欄的通知不能實時展示或者消失。

思考

問題解決後,回想下,之所以線上沒有反饋,是因爲這個service是後臺執行,用戶是不可見的,而且報錯的情況,也是部分機型,停留一段時間造成anr或者crash。用戶看到的最壞結果就是,將app置於後臺一段時間,重新再開app,不再是熱啓而是冷啓。

XXX正在運行,如何隱藏通知欄?

這樣處理之後的確不會出現Not allowed to start service,但前臺狀態欄會有一個常駐通知(XXX正在運行),清理通知,也不會被清理掉,除非殺死App。這樣用戶體驗度上會特別差,更容易有殺App的衝動。那麼新問題來了,XXX正在運行,是否可以隱藏這條消息?

一番折騰之後,在8.0+上隱藏並沒有合適的方法。而且谷歌的目的就是爲了讓開發者不能瞞着用戶在後臺做一些耗電耗內存任務,如果做也可以就是發通知到狀態欄,讓用戶能看到。至於用戶看到會不會煩,就看產品設計了,例如百度地圖通知欄上提示正在導航,keep通知欄上提示keep正在記錄你的運動,如果用戶還是殺手app就證明就是不想用app,也沒有辦法。於是,我就把前臺服務要幹什麼老老實實告訴用戶即可,並且我在app中提供了,用戶可關閉常駐通知的開關。

   public static final String CHANNEL_ID_STRING = "service_02";
    public static final String CHANNEL_ID_NAME = "亮屏";
    public static final int NOTIFICATION_ID = 2;
    void startForeground() {
        String ns = Context.NOTIFICATION_SERVICE;
        NotificationManager notificationManager = (NotificationManager) getSystemService(ns);
        NotificationChannel mChannel = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mChannel = new NotificationChannel(CHANNEL_ID_STRING, CHANNEL_ID_NAME,
                    NotificationManager.IMPORTANCE_LOW);
            notificationManager.createNotificationChannel(mChannel);
          Notification notification =new NotificationCompat.Builder(getApplicationContext(),CHANNEL_ID_STRING)
                    .setContentTitle("亮屏開門")
                    .setContentText("更方便您開門")
                    .setWhen(System.currentTimeMillis())
                    .setSmallIcon(R.drawable.icon)
                    .build();
            startForeground(NOTIFICATION_ID, notification);
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章