日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

探讨8.0版本下后台service存活机制及保活

發布時間:2024/4/15 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 探讨8.0版本下后台service存活机制及保活 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前段時間時間對targetsdkversion進行升級,結果發現了一個問題:

在不升級前,app退出后,后臺service可以存活很長一段時間;而升級后,8.0以下版本手機還是一樣,但是8.0及以上版本的手機上,app退出一分鐘后后臺service就被殺死了。

經過對比發現升級后當service被殺死時有這樣一條日志:

Stopping service due to app idle: u0a309 -1m19s437ms xxx.xxx.xxx/xxx.xxxx.xxxx

通過相關文章得知,一分鐘后停止后臺service的邏輯在ActivityManagerService中,也就是AMS。

(AMS的源碼在android sdk根目錄下的sources/android-xx/com/android/server/am中)

這里有個函數updateOomAdjLocked,代碼過多這里就不全貼出來了,其中有一個sendMsg的操作,代碼如下:

// 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且不在臨時白名單中(臨時白名單未找到更多的文檔,不過DOZE模式有一個調試命令,可以將一個app放入臨時白名單,所以這個臨時白名單應該只與調試有關),會發送一個延遲消息,這個延遲與常量BACKGROUND_SETTLE_TIME有關,根據命名就知道這是service在后臺的存活時間,它的值是:

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

可以看到這個是默認是一分鐘,但是這個值可以改變: 在ActivityManagerConstants下

private void updateConstants() {final String setting = Settings.Global.getString(mResolver,Settings.Global.ACTIVITY_MANAGER_CONSTANTS);synchronized (mService) {try {mParser.setString(setting);} catch (IllegalArgumentException e) {// Failed to parse the settings string, log this and move on// with defaults.Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e);}MAX_CACHED_PROCESSES = mParser.getInt(KEY_MAX_CACHED_PROCESSES,DEFAULT_MAX_CACHED_PROCESSES);BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME,DEFAULT_BACKGROUND_SETTLE_TIME);...} }

而Settings.Global.ACTIVITY_MANAGER_CONSTANTS是被隱藏的,而且由于是全局的設置,大家了解一下即可,這里我們不繼續討論。 回到開始,我們來繼續看看BACKGROUND_SETTLE_TIME這條消息的處理。 同樣在AMS中:

case IDLE_UIDS_MSG: {idleUids(); } break;

這樣我們就來看看idleUids函數:

final void idleUids() {synchronized (this) {final int N = mActiveUids.size();if (N <= 0) {return;}final long nowElapsed = SystemClock.elapsedRealtime();final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;long nextTime = 0;if (mLocalPowerManager != null) {mLocalPowerManager.startUidChanges();}for (int i=N-1; i>=0; i--) {final UidRecord uidRec = mActiveUids.valueAt(i);final long bgTime = uidRec.lastBackgroundTime;if (bgTime > 0 && !uidRec.idle) {if (bgTime <= maxBgTime) {EventLogTags.writeAmUidIdle(uidRec.uid);uidRec.idle = true;uidRec.setIdle = true;doStopUidLocked(uidRec.uid, uidRec);} else {if (nextTime == 0 || nextTime > bgTime) {nextTime = bgTime;}}}}if (mLocalPowerManager != null) {mLocalPowerManager.finishUidChanges();}if (nextTime > 0) {mHandler.removeMessages(IDLE_UIDS_MSG);mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed);}} }

拋去一些判斷邏輯,看到停止服務實際是執行了doStopUidLocked函數:

final void doStopUidLocked(int uid, final UidRecord uidRec) {mServices.stopInBackgroundLocked(uid);enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE); }

執行了ActivityService的stopInBackgroundLocked函數:

void stopInBackgroundLocked(int uid) {// Stop all services associated with this uid due to it going to the background// stopped state.ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));ArrayList<ServiceRecord> stopping = null;if (services != null) {for (int i=services.mServicesByName.size()-1; i>=0; i--) {ServiceRecord service = services.mServicesByName.valueAt(i);if (service.appInfo.uid == uid && service.startRequested) {if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName,service.appInfo.targetSdkVersion, -1, false, false)!= ActivityManager.APP_START_MODE_NORMAL) {if (stopping == null) {stopping = new ArrayList<>();}String compName = service.name.flattenToShortString();EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);StringBuilder sb = new StringBuilder(64);sb.append("Stopping service due to app idle: ");UserHandle.formatUid(sb, service.appInfo.uid);sb.append(" ");TimeUtils.formatDuration(service.createTime- SystemClock.elapsedRealtime(), sb);sb.append(" ");sb.append(compName);Slog.w(TAG, sb.toString());stopping.add(service);}}}if (stopping != null) {for (int i=stopping.size()-1; i>=0; i--) {ServiceRecord service = stopping.get(i);service.delayed = false;services.ensureNotStartingBackgroundLocked(service);stopServiceLocked(service);}}} }

這里可以看到首先遍歷service,經過一些條件判斷,將滿足條件的service放入stopping列表中,然后遍歷這個列表停止service。

同時這里我們可以看到文章開頭那段日志的由來,當滿足條件可以放入stopping列表中才打印日志。

這里我們得到了一個小結論:8.0及以上版本手機中有一個機制,app退出后一分鐘后會清理后臺service(滿足條件的),但是foreground service不會。所以在8.0上可以通過foreground?service的形式提高存活。


那么為什么targetsdkversion不同,有的不會停止?
那么targetsdkversion一定影響了判斷條件,我們注意到其中一行代碼:

if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName,service.appInfo.targetSdkVersion, -1, false, false)!= ActivityManager.APP_START_MODE_NORMAL) {

這樣的判斷中有targetsdkversion的參與,所以猜測在答案這個函數里。
這個函數是在AMS中,源碼如下:

int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,int callingPid, boolean alwaysRestrict, boolean disabledOnly) {UidRecord uidRec = mActiveUids.get(uid);if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg="+ packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="+ (uidRec != null ? uidRec.idle : false));if (uidRec == null || alwaysRestrict || uidRec.idle) {boolean ephemeral;if (uidRec == null) {ephemeral = getPackageManagerInternalLocked().isPackageEphemeral(UserHandle.getUserId(uid), packageName);} else {ephemeral = uidRec.ephemeral;}if (ephemeral) {// We are hard-core about ephemeral apps not running in the background.return ActivityManager.APP_START_MODE_DISABLED;} else {if (disabledOnly) {// The caller is only interested in whether app starts are completely// disabled for the given package (that is, it is an instant app). So// we don't need to go further, which is all just seeing if we should// apply a "delayed" mode for a regular app.return ActivityManager.APP_START_MODE_NORMAL;}final int startMode = (alwaysRestrict)? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk): appServicesRestrictedInBackgroundLocked(uid, packageName,packageTargetSdk);if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid+ " pkg=" + packageName + " startMode=" + startMode+ " onwhitelist=" + isOnDeviceIdleWhitelistLocked(uid));if (startMode == ActivityManager.APP_START_MODE_DELAYED) {// This is an old app that has been forced into a "compatible as possible"// mode of background check. To increase compatibility, we will allow other// foreground apps to cause its services to start.if (callingPid >= 0) {ProcessRecord proc;synchronized (mPidsSelfLocked) {proc = mPidsSelfLocked.get(callingPid);}if (proc != null &&!ActivityManager.isProcStateBackground(proc.curProcState)) {// Whoever is instigating this is in the foreground, so we will allow it// to go through.return ActivityManager.APP_START_MODE_NORMAL;}}}return startMode;}}return ActivityManager.APP_START_MODE_NORMAL; }

這里也有很多判斷,我們看首先判斷是不是ephemeral apps,短暫應用?這個我沒有找到更多的文檔,只有一篇說chrome團隊開發一款無需下載直接使用的,也不確定就是這里這個。不過我們看注釋可以看到,ephemeral apps是完全不允許后臺運行的,所以我們的app一定不是ephemeral apps。(這里以后有機會我們再仔細調查一下)繼續,disabledOnly在前面的調用可以看到這個參數是false;繼續,alwaysRestrict同樣參數是false,所以這樣startMode就是函數appServicesRestrictedInBackgroundLocked的返回值,這個函數如下:

int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {// Persistent app?if (mPackageManagerInt.isPackagePersistent(packageName)) {if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "App " + uid + "/" + packageName+ " is persistent; not restricted in background");}return ActivityManager.APP_START_MODE_NORMAL;}// Non-persistent but background whitelisted?if (uidOnBackgroundWhitelist(uid)) {if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "App " + uid + "/" + packageName+ " on background whitelist; not restricted in background");}return ActivityManager.APP_START_MODE_NORMAL;}// Is this app on the battery whitelist?if (isOnDeviceIdleWhitelistLocked(uid)) {if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "App " + uid + "/" + packageName+ " on idle whitelist; not restricted in background");}return ActivityManager.APP_START_MODE_NORMAL;}// None of the service-policy criteria apply, so we apply the common criteriareturn appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk); }

這里有三個判斷,是否Persistent app;是否在允許后臺運行白名單;是否在省電(耗電)白名單。
我們看都是返回ActivityManager.APP_START_MODE_NORMAL
回到開始的判斷我們知道當不等于ActivityManager.APP_START_MODE_NORMAL時,才會將service放入stopping列表,所以這三種情況都不會停掉service
省電(耗電)白名單比較好理解,在系統設置的省電模式中可以設置允許某個app后臺運行。


那么第一個Persistent app是什么意思?

實際上在Manifest中,我們可以為application設置android:persistent=”true”,但是前提是系統應用,也就是說我們第三方應用設置這個也沒效果。關于Persistent app我們以后另開一篇文章細說。

第二個白名單又是什么意思?

我們來看看uidOnBackgroundWhitelist的代碼:

private boolean uidOnBackgroundWhitelist(final int uid) {final int appId = UserHandle.getAppId(uid);final int[] whitelist = mBackgroundAppIdWhitelist;final int N = whitelist.length;for (int i = 0; i < N; i++) {if (appId == whitelist[i]) {return true;}}return false; }

我們再來看看哪里為mBackgroundAppIdWhitelist賦值,在backgroundWhitelistUid函數中:

@Override public void backgroundWhitelistUid(final int uid) {if (Binder.getCallingUid() != Process.SYSTEM_UID) {throw new SecurityException("Only the OS may call backgroundWhitelistUid()");}if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "Adding uid " + uid + " to bg uid whitelist");}synchronized (this) {final int N = mBackgroundAppIdWhitelist.length;int[] newList = new int[N+1];System.arraycopy(mBackgroundAppIdWhitelist, 0, newList, 0, N);newList[N] = UserHandle.getAppId(uid);mBackgroundAppIdWhitelist = newList;} }

第一行代碼就明確表明了,只能系統應用使用這個方法,所以我們知道第三方應用無法使用這個白名單


繼續,這三個條件我們都不滿足,就執行了appRestrictedInBackgroundLocked函數:

int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {// Apps that target O+ are always subject to background checkif (packageTargetSdk >= Build.VERSION_CODES.O) {if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");}return ActivityManager.APP_START_MODE_DELAYED_RIGID;}// ...and legacy apps get an AppOp checkint appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,uid, packageName);if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop);}switch (appop) {case AppOpsManager.MODE_ALLOWED:return ActivityManager.APP_START_MODE_NORMAL;case AppOpsManager.MODE_IGNORED:return ActivityManager.APP_START_MODE_DELAYED;default:return ActivityManager.APP_START_MODE_DELAYED_RIGID;} }

終于看到我們尋找的了,第一段代碼就可以看到當targetsdkversion大于等于Build.VERSION_CODES.O = 26,即8.0時,返回

ActivityManager.APP_START_MODE_DELAYED_RIGID

根據前面我們知道這個值service一定會放入stopping列表。所以當targetsdkversion升級到8.0后,在8.0及以上版本的手機上app退出后一分鐘service就會被停掉。

當targetsdkversion小于26,會檢查app是否有后臺運行的權限AppOpsManager.OP_RUN_IN_BACKGROUND,如果有權限則返回ActivityManager.APP_START_MODE_NORMAL,則根據前面的判斷不會停止服務。

注意:這里AppOpsManager權限是SDK19引入的,是google隱藏的,不推薦我們使用。這里我們通過app的運行情況得知默認OP_RUN_IN_BACKGROUND權限是允許的。

這樣我們基本上弄清楚8.0上service的存活機制了,按順序經歷下面幾個判斷

1、是否后臺service,如果是foreground service則不停,否則繼續

2、是否在臨時白名單中,如果是則不停,否則繼續

3、是否是ephemeral apps,如果是則停,否則繼續

4、是否是Persistent app,如果是則不停,否則繼續

5、是否在允許后臺運行白名單,在則不停,否則繼續

6、是否在省電白名單,在則不停,否則繼續

7、是否targetsdkversion大于等于26,是則停,否則繼續

8、如果targetsdkversion小于26,是否有OP_RUN_IN_BACKGROUND權限,有則不停,否則停

而在8.0上,service的停止則有一分鐘的延遲。

那么如果升級了targetsdkversion,怎么才能讓后臺service存活?

這里有幾個條件不需要考慮了,如:

臨時白名單用于調試;Persistent app需要系統app;后臺運行白名單也需要系統app

那么剩下的有哪些呢?

1、改成前臺service,這個相關文章比較多,就不細說了

2、加入省電白名單

怎么將app加入省電白名單?

首先使用PowerManager.isIgnoringBatteryOptimizations判斷是否有已經加入了

private boolean isIgnoringBatteryOptimizations(){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {String packageName = getPackageName();PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);return pm.isIgnoringBatteryOptimizations(packageName);}return false; }

如果未加入,則需要通過Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS拉起請求彈窗

private void gotoSettingIgnoringBatteryOptimizations() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {try {Intent intent = new Intent();String packageName = getPackageName();intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);intent.setData(Uri.parse("package:" + packageName));startActivityForResult(intent, REQUEST_IGNORE_BATTERY_CODE);} catch (Exception e) {e.printStackTrace();}} }

注意還要添加權限

<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>

然后可以在onActivityResult中處理結果

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if(resultCode == RESULT_OK){if (requestCode == REQUEST_IGNORE_BATTERY_CODE) {Log.d("Hello World!","開啟省電模式成功");}}else if (resultCode == RESULT_CANCELED) {if (requestCode == REQUEST_IGNORE_BATTERY_CODE) {Toast.makeText(this, "請用戶開啟忽略電池優化~", Toast.LENGTH_LONG).show();}} }

編譯運行,會彈窗請求白名單的彈窗,允許。開啟后臺service,退出app,會發現一分鐘后service依然存活。成功!

但是這個方案需要用戶主動行為,所以如果有強烈存活需求還是建議改用foreground service

???????

總結

以上是生活随笔為你收集整理的探讨8.0版本下后台service存活机制及保活的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。