android 8.0 啟動前臺service相關源碼分析

android8.0開始對于service的使用又收緊了權限,8.0之前startservice可以在后臺運行很長一段時間不會被殺死,但在8.0之后如果不顯式啟動前臺service,可能會出現啟動崩潰的異常。關于8.0后的service啟動兼容性處理網上應該能找到不少文章,但關于8.0service啟動源碼分析結合8.0之后的啟動前臺service似乎沒找到特別完整的分析,索性根據以往對于service源碼的理解結合8.0源碼整理出了這篇文章,溫故而知新,刷新下對于service的掌握。

service啟動方式

service啟動方式有兩種,一種就是比較常見的startService,另一種就是bindService。startService個人理解主要是用來處理同一個app內的操作,而bindservice更加偏向于進程間通信的形式,雖然同一個app同進程中也可以使用bindservice,但是有種殺雞用牛刀的感覺。8.0之后對于service的影響是針對startservice的啟動過程,bindservice的啟動沒有受到影響。所以文章主要分析startservice的啟動。通過一個完整的啟動流程從源碼的角度理解為什么8.0之后需要使用前臺service。

測試demo

比較簡單準備了兩個apk,一個apk作為service端,另一個apk作為客戶端發(fā)起service請求,區(qū)分兩個apk的目的僅僅是為了讓service進程和客戶端進程處在不同的進程中(實際上使用一個apk,設置service的processName屬性應該也可以,未測)。service聲明如下

<service
            android:name="mandy.com.services.MyService"
            android:exported="true" />

MyService代碼如下

public class MyService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationChannel mChannel;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            mChannel = new NotificationChannel("100100", "渠道名", NotificationManager.IMPORTANCE_HIGH);
            if (notificationManager != null) {
                notificationManager.createNotificationChannel(mChannel);
                Notification notification = new Notification.Builder(getApplicationContext(), "100100").build();
                startForeground(1, notification);
            }
        }
        Log.e("mandy", "onCreate");
    }
}

上述代碼是針對8.0進行兼容性處理的代碼,startForeground就是為了標明啟動的service是一個前臺service。8.0之后對于通知欄也進行了整改,增加了通知渠道,通知組的概念,所以notification的生成就是兼容8.0通知欄進行的適配,這塊不太了解的可以去查看下相關文章。service.apk中的代碼就這點東西,client.apk也很簡單一個按鈕,觸發(fā)啟動service,如下

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                    startForegroundService(intent);
} else {
                    startService(intent);
}

以上代碼就處理完了8.0之后的service,當然這不是文章重點,我們接下來是要從源碼的角度來分析為什么需要這么處理。

startForegroundService

8.0之后通過startForegroundService來啟動前臺service,跟蹤對比下和startservice的區(qū)別,源碼在contextimpl當中

@Override
    public ComponentName startService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, false, mUser);
    }

    @Override
    public ComponentName startForegroundService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, true, mUser);
    }

很明顯兩者的區(qū)別就在于requireForeground參數一個為false,一個為true。實際上這個參數最終會傳遞到ams,ams正是通過該參數確定要啟動的service是前臺的還是后臺的。來看下startServiceCommon源碼

private ComponentName startServiceCommon(Intent service, boolean requireForeground,
            UserHandle user) {
        try {
            validateServiceIntent(service);
            service.prepareToLeaveProcess(this);
            ComponentName cn = ActivityManager.getService().startService(
                mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                            getContentResolver()), requireForeground,
                            getOpPackageName(), user.getIdentifier());
            if (cn != null) {
                if (cn.getPackageName().equals("!")) {
                    throw new SecurityException(
                            "Not allowed to start service " + service
                            + " without permission " + cn.getClassName());
                } else if (cn.getPackageName().equals("!!")) {
                    throw new SecurityException(
                            "Unable to start service " + service
                            + ": " + cn.getClassName());
                } else if (cn.getPackageName().equals("?")) {
                    throw new IllegalStateException(
                            "Not allowed to start service " + service + ": " + cn.getClassName());
                }
            }
            return cn;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

很明顯通過ActivityManager.getService().startService來調用ams當中的服務,這里能看到會把requireForeground給傳遞給ams。還有一個地方需要注意的就是當返回的cn!=null時的各種檢查,8.0手機上如果沒有進行兼容性處理的啟動崩潰就是在這里發(fā)出的,舉個簡單的例子,當安裝好我前面提到的兩個apk之后,殺死service.apk所在進程的情況下去調用clientt.apk中的啟動service,你就會發(fā)現程序崩潰了!,崩潰信息大致如下:

java.lang.IllegalStateException: Not allowed to start service Intent { cmp=mandy.com.uiviews/mandy.com.services.MyService }: app is in background uid null

對照下上述源碼是不是找到了崩潰的出處。這里有幾個問題需要明確下:
(1)按照我上述操作,不一定會在真機上出現崩潰,國內手機廠商對于framework層源碼都有改動存在一定出入,實測在vivo x20手機上不會出現啟動service崩潰,但是點擊啟動沒有任何反應。
(2)必須是在service所在進程未啟動的情況下才有導致上述問題,這點可以在源碼中找到答案。
(3)未做兼容處理,也就是使用startService,使用startForegroundService不會出現崩潰

跟蹤到ams的startservice當中來看下具體操作

synchronized(this) {
            final int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            ComponentName res;
            try {
                res = mServices.startServiceLocked(caller, service,
                        resolvedType, callingPid, callingUid,
                        requireForeground, callingPackage, userId);
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
            return res;
        }

主要代碼見上,調用mServices.startServiceLocked來進一步處理,mServices是ActiveServices實例,requireForeground就是client進程中傳遞過來的參數。來看下startServiceLocked中代碼,代碼比較多,直接刪除了很多無關代碼

ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
            int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
            throws TransactionTooLargeException {
        ......

        ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType, callingPackage,
                    callingPid, callingUid, userId, true, callerFg, false);
       ...
        ServiceRecord r = res.record;
        ...
       if (!r.startRequested && !fgRequired) {
            // Before going further -- if this app is not allowed to start services in the
            // background, then at this point we aren't going to let it period.
            final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
                    r.appInfo.targetSdkVersion, callingPid, false, false);
            if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                Slog.w(TAG, "Background start not allowed: service "
                        + service + " to " + r.name.flattenToShortString()
                        + " from pid=" + callingPid + " uid=" + callingUid
                        + " pkg=" + callingPackage);
                if (allowed == ActivityManager.APP_START_MODE_DELAYED) {
                    // In this case we are silently disabling the app, to disrupt as
                    // little as possible existing apps.
                    return null;
                }
                // This app knows it is in the new model where this operation is not
                // allowed, so tell it what has happened.
                UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
                return new ComponentName("?", "app is in background uid " + uidRec);
            }
        }

       ......
      
        r.startRequested = true;
        r.delayedStop = false;
        r.fgRequired = fgRequired;
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                service, neededGrants, callingUid));

        ......

        ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
        return cmp;
    }

從上到下進行簡單分析,retrieveServiceLocked是一個查詢service信息的方法,手機上各個apk中的service通過pms都可以查詢到,retrieveServiceLocked內部實際上就是通過pms進行查詢,得到相關service最終保存到ServiceLookupResult返回,并將得到的ServiceRecord賦值給一個r變量,ServiceRecord是一個很重要的類,里面保存了和service相關的很多重要變量,包括和該service相關的進程名,包名,啟動的intent等。

接下來代碼就來到了一個關鍵if分支,通過startRequested和fgRequired來確定是否進行到分支內部。startRequested用來確定一個service是否已經被啟動,該變量在service啟動的時候會置為true,在service被停止或者被殺死的時候重置為false。fgRequired即為上述文章提到client傳遞過來的參數。

前臺service流程

先來看一下正常情況下即fgRequired為true的邏輯,不進入if分支往下走,可以看到將startRequested設置為了true,并在pendingStarts中添加了一個StartItem,可以理解為每次啟動service都會添加一個StartItem,來標記此次的service,后面會用到。最終調用進入了startServiceInnerLocked。

ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
            boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
       ...
        String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false);
      ...
    }

調用bringUpServiceLocked,比較重要的一個方法,同樣保留關鍵代碼

private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
            boolean whileRestarting, boolean permissionsReviewRequired)
            throws TransactionTooLargeException {
        ...
        if (r.app != null && r.app.thread != null) {
            sendServiceArgsLocked(r, execInFg, false);
            return null;
        }
       ...
        ProcessRecord app;

        if (!isolated) {
            app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
            
            if (app != null && app.thread != null) {
                try {
                    app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
                    realStartServiceLocked(r, app, execInFg);
                    return null;
                } catch (TransactionTooLargeException e) {
                    throw e;
                } catch (RemoteException e) {
                    Slog.w(TAG, "Exception when starting service " + r.shortName, e);
                }

                // If a dead object exception was thrown -- fall through to
                // restart the application.
            }
            }
        } 

        // Not running -- get it started, and enqueue this service record
        // to be executed when the app comes up.
        if (app == null && !permissionsReviewRequired) {
            if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                    hostingType, r.name, false, isolated, false)) == null) {
                String msg = "Unable to launch app "
                        + r.appInfo.packageName + "/"
                        + r.appInfo.uid + " for service "
                        + r.intent.getIntent() + ": process is bad";
                Slog.w(TAG, msg);
                bringDownServiceLocked(r);
                return msg;
            }
            if (isolated) {
                r.isolatedProc = app;
            }
        }

       ...

        if (!mPendingServices.contains(r)) {
            mPendingServices.add(r);
        }
        ...
        return null;
    }

這個方法里面有幾點需要注意的地方,首先會判斷下r.app != null && r.app.thread != null,app是一個ProcessRecord用來保存和該service相關的進程信息,在service進程沒啟動或者service進程已經啟動但是還沒啟動service的情況下app都為null,所以不會進入到if分支內部。繼續(xù)往下走會調用getProcessRecordLocked獲取和service進程相關ProcessRecord,同樣會得到null。所以接下來要處理的就是先去啟動service所在的進程,調用startProcessLocked。startProcessLocked內部就不展開了,概括起來就是會調用newProcessRecordLocked去創(chuàng)建一個ProcessRecord,然后又會調用另一個startProcessLocked方法去啟動service所在進程。明白startProcessLocked作用后回到bringUpServiceLocked當中,由于此時service所在進程還沒啟動,會將serviceRecord先保存到mPendingServices。到此關于startForegroundService相關源碼就分析完了。

看完會不會有點懵,service的后繼操作oncreate,onStartCommand都哪里去了,實際上service的這些生命周期回調都是ams和service所在進程進行交互了,和client關系已經不大了,這里也可以解答之前自己的一個疑惑為什么在service的oncreate方法中進行耗時操作(僅測試,實際開發(fā)不推薦),client不會被阻塞在啟動service處直到oncreate執(zhí)行完畢。

那么service的生命周期是在哪里被調用到的,實際上是在service所在進程啟動后,會主動去通知ams,ams在得知進程啟動后會從mPendingServices中取出要啟動的serviceRecord,然后去調用相關的service生命周期方法。可以看出除了startservice是client發(fā)起的,后繼的生命周期交互工作都是ams和service進程之間進行的,client發(fā)出一個啟動命令后就可以去干自己的事情了,這就是一種典型的異步調用方式。bindservice的方式和startservice有些類似,也是異步調用,只不過bindservice會將一個回調參數serviceconn傳遞給ams,等ams和service進程交互完畢后會把binder通過serviceconn回調給client。

上述整個流程就是調用startForegroundService的情況,8.0以下的startservice啟動也大致是這個流程,也是在service進程不存在的情況下先拉起,如果進程已經存在但還沒啟動service則調用realStartServiceLocked。

fgRequired為false

以上分析的都是在fgRequired為true的情況下,現在分析下在fgRequire為false,也就是8.0之后直接調用startservice會發(fā)生什么,把之前的代碼再添一下

if (!r.startRequested && !fgRequired) {
            // Before going further -- if this app is not allowed to start services in the
            // background, then at this point we aren't going to let it period.
            final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
                    r.appInfo.targetSdkVersion, callingPid, false, false);
            if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                Slog.w(TAG, "Background start not allowed: service "
                        + service + " to " + r.name.flattenToShortString()
                        + " from pid=" + callingPid + " uid=" + callingUid
                        + " pkg=" + callingPackage);
                if (allowed == ActivityManager.APP_START_MODE_DELAYED) {
                    // In this case we are silently disabling the app, to disrupt as
                    // little as possible existing apps.
                    return null;
                }
                // This app knows it is in the new model where this operation is not
                // allowed, so tell it what has happened.
                UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
                return new ComponentName("?", "app is in background uid " + uidRec);
            }
        }

進入if分支內部調用getAppStartModeLocked

int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
            int callingPid, boolean alwaysRestrict, boolean disabledOnly) {
        UidRecord uidRec = mActiveUids.get(uid);
       
        if (uidRec == null || alwaysRestrict || uidRec.idle) {
            boolean 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);
                               }
                ...
                return startMode;
            }
        }
        return ActivityManager.APP_START_MODE_NORMAL;
    }

UidRecord uidRec = mActiveUids.get(uid);見名知意通過service所在的uid來查詢UidRecord,在service所在進程沒啟動的情況下返回null,否則返回具體值。返回具體值的情況下會直接跳過if分支然后返回ActivityManager.APP_START_MODE_NORMAL,接下來的流程就和startForegroundService保持一致了。重點看下返回null的情況,ephemeral最終值為false,會進入到else分支內部。
disabledOnly和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 criteria
        return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
    }

源碼內部有3個if判斷,只要滿足其中一個就是返回APP_START_MODE_NORMAL,否則會進入到appRestrictedInBackgroundLocked內部。這3個if判斷源碼已經給出了解釋,正常情況下app是不會進入到if內部,除非你的app是一個特殊app,關于省電白名單我特地在vivo真機的設置中查找了下,并未找到相關可以將app添加到省電白名單中的操作,所以最終會調用到appRestrictedInBackgroundLocked。

int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
        // Apps that target O+ are always subject to background check
        if (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;
        }
        ...
        }
    }

答案已經很明顯了,在8.0及以上直接就返回APP_START_MODE_DELAYED_RIGID,最終又回到方法中

if (!r.startRequested && !fgRequired) {
            // Before going further -- if this app is not allowed to start services in the
            // background, then at this point we aren't going to let it period.
            final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
                    r.appInfo.targetSdkVersion, callingPid, false, false);
            if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                Slog.w(TAG, "Background start not allowed: service "
                        + service + " to " + r.name.flattenToShortString()
                        + " from pid=" + callingPid + " uid=" + callingUid
                        + " pkg=" + callingPackage);
                if (allowed == ActivityManager.APP_START_MODE_DELAYED) {
                    // In this case we are silently disabling the app, to disrupt as
                    // little as possible existing apps.
                    return null;
                }
                // This app knows it is in the new model where this operation is not
                // allowed, so tell it what has happened.
                UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
                return new ComponentName("?", "app is in background uid " + uidRec);
            }
        }

返回一個ComponentName("?", "app is in background uid " + uidRec)給client,回過頭再去看contextimpl中的這段判斷邏輯就比較清楚了

 if (cn != null) {
                if (cn.getPackageName().equals("!")) {
                    throw new SecurityException(
                            "Not allowed to start service " + service
                            + " without permission " + cn.getClassName());
                } else if (cn.getPackageName().equals("!!")) {
                    throw new SecurityException(
                            "Unable to start service " + service
                            + ": " + cn.getClassName());
                } else if (cn.getPackageName().equals("?")) {
                    throw new IllegalStateException(
                            "Not allowed to start service " + service + ": " + cn.getClassName());
                }
            }

cn就是ams返回的ComponentName,對照著看下就能知道會拋出一個IllegalStateException異常,到此應該就能明白為什么8.0直接使用startservice去啟動一個不存在進程的service時為什么會拋出這個異常了。

service ANR問題分析

回頭看下測試demo中的MyService就能知道,啟動的Service必須要在5秒內調用startForeground,否則會拋出一個anr的異常,又或者當在service的oncreate方法中去處理耗時操作,如果不能在一定時間內完成也會拋出anr異常,拋異常的原理比較簡單就是通過handler發(fā)送一個延遲處理的message,如果能在規(guī)定時間內執(zhí)行完成就remove掉該message,否則就執(zhí)行該message拋出一個異常。知道原理后剩下的就是在源碼中找到何時發(fā)起這個message,又是何時去remove掉該message。關鍵代碼在bumpServiceExecutingLocked這個方法內

private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
        if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
                + why + " of " + r + " in app " + r.app);
        else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
                + why + " of " + r.shortName);
        long now = SystemClock.uptimeMillis();
        if (r.executeNesting == 0) {
           ...
            if (r.app != null) {
                r.app.executingServices.add(r);
                r.app.execServicesFg |= fg;
                if (r.app.executingServices.size() == 1) {
                    scheduleServiceTimeoutLocked(r.app);
                }
            }
        } else if (r.app != null && fg && !r.app.execServicesFg) {
            r.app.execServicesFg = true;
            scheduleServiceTimeoutLocked(r.app);
        }
       ...
    }

調用scheduleServiceTimeoutLocked發(fā)起一個延遲message,內部代碼很容易理解

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
        if (proc.executingServices.size() == 0 || proc.thread == null) {
            return;
        }
        Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_TIMEOUT_MSG);
        msg.obj = proc;
        mAm.mHandler.sendMessageDelayed(msg,
                proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
    }

可以看出會通過service是前臺還是后臺來決定延遲執(zhí)行的時間,如果在規(guī)定時間內service還沒處理完畢則最終會執(zhí)行到

if (anrMessage != null) {
            mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
        }

即拋出一個anr,那么剩下的問題還有兩個(1)bumpServiceExecutingLocked在哪里被調用(2)又是在哪里remove掉message。
第一個問題可以直接在源碼中就能搜到調用處,比較常見的調用方法是realStartServiceLocked和sendServiceArgsLocked
如果對于service啟動流程比較熟悉應該能明白這兩個方法的調用。realStartServiceLocked最終會觸發(fā)client中的oncreate,而
sendServiceArgsLocked會最終觸發(fā)client的onStartCommand被調用。

第一個問題搞明白后剩下的就是在哪里remove掉message,實際上removemessage的發(fā)起時機在client端,當執(zhí)行完畢oncreate或者onStartCommand后都會調用ams中的serviceDoneExecuting方法,該方法最終就會調用到removemesage方法
移除掉拋anr異常的message。

8.0中的serviceANR

上述分析的anr都是早已存在的service中的anr,8.0之后啟動service后必須在5秒內調用startForeground,是在原有代碼的基礎上額外添加的anr邏輯,原理其實都是類似的,在sendServiceArgsLocked中可以找到對應的代碼塊

if (r.fgRequired && !r.fgWaiting) {
                if (!r.isForeground) {
                    if (DEBUG_BACKGROUND_CHECK) {
                        Slog.i(TAG, "Launched service must call startForeground() within timeout: " + r);
                    }
                    scheduleServiceForegroundTransitionTimeoutLocked(r);
                } else {
                    if (DEBUG_BACKGROUND_CHECK) {
                        Slog.i(TAG, "Service already foreground; no new timeout: " + r);
                    }
                    r.fgRequired = false;
                }
            }

可以看出如果是一個前臺service最終會調用到一個巨長名字的方法,內部很簡單就是發(fā)起一個延遲message

void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
        if (r.app.executingServices.size() == 0 || r.app.thread == null) {
            return;
        }
        Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
        msg.obj = r;
        r.fgWaiting = true;
        mAm.mHandler.sendMessageDelayed(msg, SERVICE_START_FOREGROUND_TIMEOUT);
    }

而SERVICE_START_FOREGROUND_TIMEOUT的值正是5秒,正也就是為什么必須要在5秒內調用startForeground的原因了,startForeground內部做的事相信都能猜到就是remove掉這個message。

總結

到此關于8.0service啟動前臺service的相關代碼就分析完畢了,對于整個service啟動流程梳理了一遍,對于service anr的原理也進行了說明。當然上述的所有理解都是基于自己對于service的理解,所以文中難免有些理解不到位的地方,如果有什么誤導的地方還需多擔待。分析過程中源碼中還有很多的細節(jié)問題在和分析原理沒有太大聯系的基礎上都一一忽略了,畢竟太細節(jié)的東西容易遺忘,除非你是一個framework層開發(fā),需要對源碼就是二次開發(fā),作為一個應用層開發(fā)閱讀framework層源碼點到為止,明白整體原理即可。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容