Problem a.

Problem: Since android O, Google has limited the service backend service to control resource usage.

1. First problem: Background services only survive for a few minutes.

After the service is started, the application is running in the background. After a few minutes, the service will stop. ActivityManager: Stopping service due to app Idle: xxxxService.

Analyze the source code:

	// 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 isin 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); }}Copy the code

If a background service is no longer in the whitelist, a delay message will be sent. The delay time is BACKGROUND_SETTLE_TIME.

	public long BACKGROUND_SETTLE_TIME = DEFAULT_BACKGROUND_SETTLE_TIME;
	private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
Copy the code

The delay time is about 1 minute.

That is to say, there is a mechanism in the 8.0 and later versions of the mobile phone, the app will be back to the background 1 minute after the whitelist will be cleared of the background service.


2. The second problem: The background application cannot start the service through startService.

Exception information thrown: Not allowed to start service Intent XXX: App is in background UID UidRecord

** Analyze the source code: **

<! - check whether the current app allows background start -- > final int allowed = mAm. GetAppStartModeLocked (state Richard armitage ppInfo. The uid, r.p ackageName, r.appInfo.targetSdkVersion, callingPid,false.false, forcedStandby); <! Background start not allowed-->if(allowed ! = ActivityManager.APP_START_MODE_NORMAL) { ... <! - return? Tell the client to start in the background and disable you -->return new ComponentName("?"."app is in background uid "+ uidRec); } <! -- What is the return value? Background startup service exception -->if (cn.getPackageName().equals("?")) {
			throw new IllegalStateException(
					"Not allowed to start service " + service + ":" + cn.getClassName());
		}
Copy the code

2. Solution (startForeground/notification) :

Reclaim priority: foreground process < visual process < service process < background process < content supply root node < empty process

After the foreground Service is enabled, it is displayed in the notification bar by means of notification, such as music playing

Code snippet for Kotlin:

Start/stop services

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))}} / / close to monitor telephone serviceif (CallingStateListener.isCallingStateListener()) {
            stopService(Intent(this, CallingStateListener::class.java))
        }
		
Copy the code

The service class

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 // Create NotificationChannelif(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: Hang up, the phone number of the caller$incomingNumber"} telephonymanager.call_state_ringing -> {log.i (TAG,"CallingState: The number of the caller's cell$incomingNumber")} telephonyManager.call_state_offhook // Trigger when picking up, answering or making a call -> {log.i (TAG,"CallingState: The number of the number to be dialed$incomingNumber")}else -> {
                        Log.i(TAG, "CallingState: Others")}}}} // Set the caller listener 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("Call Service")
            .setContentText("Service running"// Set the ChannelID of the Notification. Otherwise, it cannot be displayed properlyif (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)
    }

}
	
Copy the code