阅读 343

Android 设置向导启动分析

一、Android 系统启动流程

  1. Bootloader 系统引导

  2. 启动 Linux 内核

  3. 启动 init 进程

  4. 启动 Zygote 进程

  5. 启动 SystemServer 进程

    • 启动 Binder 线程池
    • 创建 SystemServiceManager 并启动各种 SystemService

二、启动设置向导或 Launcher

SystemServer 会在 startBootstrapServices() 方法中会启动 ActivityManagerService 。

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() 方法。

public void systemReady(final Runnable goingCallback) {
    ...
    synchronized (this) {
        ...
        startHomeActivityLocked(currentUserId, "systemReady");
        ...
    }
    ...
}
复制代码

startHomeActivityLocked() 方法中会获取到 Action 为 Intent.ACTION_MAIN,Category 为 Intent.CATEGORY_HOME 的 Intent,根据该 Intent 获取到符合条件的应用,并判断该应用是否已经启动,没有启动则启动该应用。

...
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。

<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() 方法中。

三、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 的应用。

四、设置向导完成

既然设置向导的优先级高于 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);
复制代码
关注下面的标签,发现更多相似文章
评论