Android 設置嚮導啓動分析

1、Android 系統啓動流程

  1. Bootloader 系統引導android

  2. 啓動 Linux 內核app

  3. 啓動 init 進程ide

  4. 啓動 Zygote 進程ui

  5. 啓動 SystemServer 進程this

    • 啓動 Binder 線程池
    • 建立 SystemServiceManager 並啓動各類 SystemService

2、啓動設置嚮導或 Launcher

SystemServer 會在 startBootstrapServices() 方法中會啓動 ActivityManagerService 。spa

private void startBootstrapServices() {
    ...
    // Activity manager runs the show.
    mActivityManagerService = mSystemServiceManager.startService(
                ActivityManagerService.Lifecycle.class).getService();
    ...
}
複製代碼

在 startOtherServices() 方法中會調用 ActivityManagerService 的 systemReday() 方法。線程

private void startOtherServices() {
...
    mActivityManagerService.systemReady(new Runnable() {
    @Override
    public void run() {
        Slog.i(TAG, "Making services ready");
        mSystemServiceManager.startBootPhase(
                SystemService.PHASE_ACTIVITY_MANAGER_READY);
    ...
    }
...
}
複製代碼

ActivityManagerService 的 systemReday() 方法中會調用 startHomeActivityLocked() 方法。rest

public void systemReady(final Runnable goingCallback) {
    ...
    synchronized (this) {
        ...
        startHomeActivityLocked(currentUserId, "systemReady");
        ...
    }
    ...
}
複製代碼

startHomeActivityLocked() 方法中會獲取到 Action 爲 Intent.ACTION_MAIN,Category 爲 Intent.CATEGORY_HOME 的 Intent,根據該 Intent 獲取到符合條件的應用,並判斷該應用是否已經啓動,沒有啓動則啓動該應用。code

...
String mTopAction = Intent.ACTION_MAIN;
...
// 獲取 Action 爲 Intent.ACTION_MAIN,Category 爲 Intent.CATEGORY_HOME 的 Intent
Intent getHomeIntent() {
    Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
    intent.setComponent(mTopComponent);
    intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
    if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
        intent.addCategory(Intent.CATEGORY_HOME);
    }
    return intent;
}

boolean startHomeActivityLocked(int userId, String reason) {
    if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
            && mTopAction == null) {
        // We are running in factory test mode, but unable to find
        // the factory test app, so just sit around displaying the
        // error message and don't try to start anything.
        return false;
    }
    Intent intent = getHomeIntent();
    // 獲取符合條件的應用
    ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
    if (aInfo != null) {
        intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
        // Don't do this if the home app is currently being
        // instrumented.
        aInfo = new ActivityInfo(aInfo);
        aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
        ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                aInfo.applicationInfo.uid, true);
        // 判斷應用是否啓動,未啓動則啓動該應用程序
        if (app == null || app.instrumentationClass == null) {
            intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
            // 啓動應用程序
            mActivityStarter.startHomeActivityLocked(intent, aInfo, reason);
        }
    } else {
        Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
    }
    return true;
}
複製代碼

通常狀況下,被啓動的應用就是 Launcher,由於 Launcher 的 Manifest 文件中有匹配了 Action 爲 Intent.ACTION_MAIN,Category 爲 Intent.CATEGORY_HOME 的 Activity。component

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.launcher3">
    <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
...
    <application android:backupAgent="com.android.launcher3.LauncherBackupAgent" android:fullBackupOnly="true" android:fullBackupContent="@xml/backupscheme" android:hardwareAccelerated="true" android:icon="@mipmap/ic_launcher_home" android:label="@string/derived_app_name" android:largeHeap="@bool/config_largeHeap" android:restoreAnyVersion="true" android:supportsRtl="true" >

        <activity android:name="com.android.launcher3.Launcher" android:launchMode="singleTask" android:clearTaskOnLaunch="true" android:stateNotNeeded="true" android:theme="@style/LauncherTheme" android:windowSoftInputMode="adjustPan" android:screenOrientation="nosensor" android:configChanges="keyboard|keyboardHidden|navigation" android:resumeWhilePausing="true" android:taskAffinity="" android:enabled="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MONKEY"/>
            </intent-filter>
        </activity>
    ...
    </application>
</manifest>
複製代碼

可是,當首次開機時,被啓動的應用就是設置嚮導,由於設置嚮導的 Manifest 文件中也有匹配了 Action 爲 Intent.ACTION_MAIN,Category 爲 Intent.CATEGORY_HOME 的 Activity,而且優先級高於 Launcher。

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.provision">
    ...
    <application>
        <activity android:name="DefaultActivity" android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" android:excludeFromRecents="true">
            <intent-filter android:priority="1">
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.SETUP_WIZARD" />
            </intent-filter>
        </activity>
    </application>
</manifest>
複製代碼

Launcher 的 Manifest 中 intent-filter 沒有設置優先級,默認爲 0;設置嚮導的 Manifest 中 intent-filter 的優先級爲 1;因此在 resolveActivityInfo() 方法獲取符合的應用時會優先獲取到設置嚮導。

private ActivityInfo resolveActivityInfo(Intent intent, int flags, int userId) {
    ActivityInfo ai = null;
    ComponentName comp = intent.getComponent();
    try {
        if (comp != null) {
            // Factory test.
            ai = AppGlobals.getPackageManager().getActivityInfo(comp, flags, userId);
        } else {
            ResolveInfo info = AppGlobals.getPackageManager().resolveIntent(
                    intent,
                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
                    flags, userId);
            if (info != null) {
                ai = info.activityInfo;
            }
        }
    } catch (RemoteException e) {
        // ignore
    }
    return ai;
}
複製代碼

獲取最優 Activity 的具體實如今 PackageManagerService 的 chooseBestActivity() 方法中。

3、priority 及 android.intent.category.SETUP_WIZARD

Manifest 中 Activity 的 intent-filter 的優先級設置只有系統應用纔會生效,非系統應用會被 PackageManagerService 調整爲 0。

/** * Adjusts the priority of the given intent filter according to policy. * <p> * <ul> * <li>The priority for non privileged applications is capped to '0'</li> * <li>The priority for protected actions on privileged applications is capped to '0'</li> * <li>The priority for unbundled updates to privileged applications is capped to the * priority defined on the system partition</li> * </ul> * <p> * <em>NOTE:</em> There is one exception. For security reasons, the setup wizard is * allowed to obtain any priority on any action. */
private void adjustPriority( List<PackageParser.Activity> systemActivities, ActivityIntentInfo intent) {
    ...
    final boolean privilegedApp =
            ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0);
    if (!privilegedApp) {
        // non-privileged applications can never define a priority >0
        Slog.w(TAG, "Non-privileged app; cap priority to 0;"
                + " package: " + applicationInfo.packageName
                + " activity: " + intent.activity.className
                + " origPrio: " + intent.getPriority());
        intent.setPriority(0);
        return;
    }
    if (systemActivities == null) {
        // the system package is not disabled; we're parsing the system partition
        if (isProtectedAction(intent)) {
            if (mDeferProtectedFilters) {
                // We can't deal with these just yet. No component should ever obtain a
                // >0 priority for a protected actions, with ONE exception -- the setup
                // wizard. The setup wizard, however, cannot be known until we're able to
                // query it for the category CATEGORY_SETUP_WIZARD. Which we can't do
                // until all intent filters have been processed. Chicken, meet egg.
                // Let the filter temporarily have a high priority and rectify the
                // priorities after all system packages have been scanned.
                mProtectedFilters.add(intent);
                if (DEBUG_FILTERS) {
                    Slog.i(TAG, "Protected action; save for later;"
                            + " package: " + applicationInfo.packageName
                            + " activity: " + intent.activity.className
                            + " origPrio: " + intent.getPriority());
                }
                return;
            } else {
                if (DEBUG_FILTERS && mSetupWizardPackage == null) {
                    Slog.i(TAG, "No setup wizard;"
                        + " All protected intents capped to priority 0");
                }
                if (intent.activity.info.packageName.equals(mSetupWizardPackage)) {
                    if (DEBUG_FILTERS) {
                        Slog.i(TAG, "Found setup wizard;"
                            + " allow priority " + intent.getPriority() + ";"
                            + " package: " + intent.activity.info.packageName
                            + " activity: " + intent.activity.className
                            + " priority: " + intent.getPriority());
                    }
                    // setup wizard gets whatever it wants
                    return;
                }
                Slog.w(TAG, "Protected action; cap priority to 0;"
                        + " package: " + intent.activity.info.packageName
                        + " activity: " + intent.activity.className
                        + " origPrio: " + intent.getPriority());
                intent.setPriority(0);
                return;
            }
        }
        // privileged apps on the system image get whatever priority they request
        return;
    }
    ...
}
複製代碼

在 adjustPriority 方法中,若是 packageName 爲 mSetupWizardPackage 就不會調整其優先級,保持其 Manifest 中設置的優先級。mSetupWizardPackage 的值從 getSetupWizardPackageName() 方法中獲取。

final @Nullable String mSetupWizardPackage;
...

public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) {
    ...
    mSetupWizardPackage = getSetupWizardPackageName();
    ...
}

private @Nullable String getSetupWizardPackageName() {
    final Intent intent = new Intent(Intent.ACTION_MAIN);
    intent.addCategory(Intent.CATEGORY_SETUP_WIZARD);
    final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null,
            MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE
                    | MATCH_DISABLED_COMPONENTS,
            UserHandle.myUserId());
    if (matches.size() == 1) {
        return matches.get(0).getComponentInfo().packageName;
    } else {
        Slog.e(TAG, "There should probably be exactly one setup wizard; found " + matches.size()
                + ": matches=" + matches);
        return null;
    }
}
複製代碼

因此 mSetupWizardPackage 就是有添加了 Category 爲 android.intent.category.SETUP_WIZARD 的 Activity 的應用。

4、設置嚮導完成

既然設置嚮導的優先級高於 Launcher,那每次開機時不是都會先啓動設置嚮導麼,爲何設置嚮導完成後再次開機直接進入了 Launcher?

由於設置嚮導在最後退出時會禁用掉添加了 Category 爲 Intent.CATEGORY_HOME 的 Activity,因此 ActivityManagerService 在 resolveActivityInfo() 獲取匹配的應用時就不會獲取到設置嚮導,直接獲取到了 Launcher。

// remove this activity from the package manager.
PackageManager pm = getPackageManager();
ComponentName name = new ComponentName(this, DefaultActivity.class);
pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
        PackageManager.DONT_KILL_APP);
複製代碼
相關文章
相關標籤/搜索