Android O service保活方案(startForeground/notification)

android 8.0

一.問題

問題:從android O版本開始,google爲了控制資源使用,對service後臺服務作了限制。

1. 第一個問題:後臺service只能存活幾分鐘。

現象就是,service啓動後,應用退到後臺運行,大概過幾分鐘service就會中止。堆棧信息: ActivityManager: Stopping service due to app idle: xxxxService.java

分析源碼:android

// UID is now in the background (and not on the temp whitelist).  Was it
	// previously in the foreground (or on the temp whitelist)?
	if (!ActivityManager.isProcStateBackground(uidRec.setProcState)
			|| uidRec.setWhitelist) {
		uidRec.lastBackgroundTime = nowElapsed;
		if (!mHandler.hasMessages(IDLE_UIDS_MSG)) {
			// Note: the background settle time is in elapsed realtime, while
			// the handler time base is uptime.  All this means is that we may
			// stop background uids later than we had intended, but that only
			// happens because the device was sleeping so we are okay anyway.
			mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
					mConstants.BACKGROUND_SETTLE_TIME);
		}
	}
複製代碼

如何是後臺service而且再也不白名單內,會發送一個延遲消息,延遲時間是BACKGROUND_SETTLE_TIME。bash

public long BACKGROUND_SETTLE_TIME = DEFAULT_BACKGROUND_SETTLE_TIME;
	private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
複製代碼

延遲時間大概是1分鐘app

也就是:8.0及以上版本手機中有一個機制,app退到後臺後1分鐘後會清理再也不白名單中的後臺service.ide


2.第二個問題:後臺應用不能經過startService啓動服務。

拋出的異常信息:Not allowed to start service Intent XXX : app is in background uid UidRecordui

**分析源碼: **this

<!--檢測當前app是否容許後臺啓動-->
        final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
                r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
                <!--若是不容許  Background start not allowed-->
        if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
            ...
            <!--返回 ? 告訴客戶端如今處於後臺啓動狀態,禁止你-->
            return new ComponentName("?", "app is in background uid " + uidRec);
        }

		 <!--返回值是?的狀況下就是後臺啓動service的異常-->
		 if (cn.getPackageName().equals("?")) {
			throw new IllegalStateException(
					"Not allowed to start service " + service + ": " + cn.getClassName());
		}
複製代碼

二.解決方案(startForeground/notification):

回收優先級:前臺進程<可視進程<服務進程<後臺進程<內容供應根節點<空進程google

開啓前臺Service,會在通知欄顯示 經過notification方式 如音樂播放spa

kotlin代碼片斷:code

啓動/中止服務

if (!CallingStateListener.isCallingStateListener()) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    startForegroundService(Intent(this, CallingStateListener::class.java))
                } else {
                    startService(Intent(this, CallingStateListener::class.java))
                }
            }

//關閉監聽電話狀態服務
        if (CallingStateListener.isCallingStateListener()) {
            stopService(Intent(this, CallingStateListener::class.java))
        }
		
複製代碼

service 類

class CallingStateListener : Service() {
    private val TAG = "CallingStateListener"
    private var phoneStateListener: PhoneStateListener? = null
    private var telephonyManager: TelephonyManager? = null
    private val notificationId = "callingChannelId"
    private val notificationName = "callingChannelName"

    override fun onCreate() {
        super.onCreate()

        initCallingStateListener()

        val notificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        //建立NotificationChannel
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                notificationId,
                notificationName,
                NotificationManager.IMPORTANCE_HIGH
            )
            notificationManager.createNotificationChannel(channel)
        }
        startForeground(1, getNotification())

    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    private fun initCallingStateListener() {

        phoneStateListener = object : PhoneStateListener() {
            @SuppressLint("MissingPermission")
            override fun onCallStateChanged(state: Int, incomingNumber: String) {
                super.onCallStateChanged(state, incomingNumber)

                when (state) {
                    TelephonyManager.CALL_STATE_IDLE // 待機,即無電話時,掛斷時觸發
                    -> {
                        Log.i(TAG, "callingState:掛斷,對方手機號$incomingNumber")
                       
                    }

                    TelephonyManager.CALL_STATE_RINGING // 響鈴,來電時觸發
                    -> {
                        Log.i(TAG, "callingState:來電,對方手機號$incomingNumber")
                    }

                    TelephonyManager.CALL_STATE_OFFHOOK // 摘機,接聽或打電話時觸發
                    -> {
                        Log.i(TAG, "callingState:撥打電話,對方手機號$incomingNumber")

                    }

                    else -> {
                        Log.i(TAG, "callingState:其餘")
                    }
                }
            }
        }

        // 設置來電監聽器
        telephonyManager = getSystemService(TELEPHONY_SERVICE) as TelephonyManager?
        telephonyManager?.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
    }


    private fun getNotification(): Notification {
        val builder = Notification.Builder(this)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle("通話服務")
            .setContentText("服務正在運行")
        //設置Notification的ChannelID,不然不能正常顯示
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            builder.setChannelId(notificationId)
        }
        return builder.build()
    }

    override fun onDestroy() {
        stopForeground(true)
        super.onDestroy()
    }


    companion object {
        fun isCallingStateListener() =
            ServiceUtils.isServiceRunning(CallingStateListener::class.java)
    }

}
	
複製代碼
相關文章
相關標籤/搜索