Launcher Startup Process Analysis of Android Framework Learning (4)

Posted by jonex on Thu, 04 Jul 2019 20:40:30 +0200

In the previous blog, we learned about the init process, Zygote process, and SayetemServer process. We know that the SystemServer process is mainly used to start various services of the system, including Launcher AppService, which is responsible for Launcher startup. We will learn Launcher related in this blog.Knowledge.

Overview of Launcher

Launcher is the desktop application we usually see. It is also an Android application. However, this application is the first application to start by default. The last step to start an Android system is to start Launcher application, which Launcher will request PackageManagerS during startup.Ervice returns information about the applications already installed in the system and encapsulates it into a list of shortcut icons that are displayed on the system screen so that users can start the application by clicking on the shortcut icons.

Launcher Start Process

During the startup of a SystemServer process, its main static method is called to start the entire SystemServer startup process, in which boot service, core service and other service are started by calling three internal methods.The startOtherService method is called by calling the mActivityManagerService.systemReady() method, and the systemReady function of the ActivityManagerService is the entry to start Launcher.
frameworks/base/services/Java/com/android/server/SystemServer.java

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

...
}
...
}

You can see that this method passes a Runnable parameter that implements the systemReady method for various other services, which is not the focus of our attention. Let's look at the implementation of the systemReady method in the Activity Manager Service
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public void systemReady(final Runnable goingCallback) {
        ...
        // Start up initial activity.
        mBooting = true;
        startHomeActivityLocked(mCurrentUserId, "systemReady");
        ...
    }

The important point is that the startHomeActivityLocked method is called in this body of the method, whose name means to start the homeActivity operation

boolean startHomeActivityLocked(int userId, String reason) {
        if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
                && mTopAction == null) {//1
            return false;
        }
        Intent intent = getHomeIntent();//2
        ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
        if (aInfo != null) {
            intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
            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) {//3
                intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
                mActivityStarter.startHomeActivityLocked(intent, aInfo, reason);//4
            }
        } else {
            Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
        }

        return true;
    }

The mFactoryTest at Note 1 represents the operating mode of the system, which is divided into three modes: non-factory mode, low-level factory mode, and high-level factory mode. The mTopAction describes the Action of the first started Activity component, which is Intent.ACTION_MAIN.So the code in Note 1 means that if mFactoryTest is FactoryTest.FACTORY_TEST_LOW_LEVEL (low-level factory mode) and mTopAction=null, it returns false directly, then it calls the getHomeIntent() method

Intent getHomeIntent() {
        Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
        intent.setComponent(mTopComponent);
        if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
            intent.addCategory(Intent.CATEGORY_HOME);
        }
        return intent;
    }

Intent is created in the getHomeIntent function and mTopAction and mTopData are passed in.The mTopAction value is Intent.ACTION_MAIN, and the intent Category is set to Intent.CATEGORY_HOME if the system is not in low-level factory mode.Launcher's Intent object adds the Intent.CATEGORY_HOME constant, which is actually a launcher flag that is configured in androidmanifest.xml by the system's startup page Activity.

Let's go back to the startHomeActivityLocked function of the Activity Manager Service, assuming that the system is not running in a low-level factory mode, and at Comment 3 determine if the application that matches Action is Intent.ACTION_MAIN, Category is Intent.CATEGORY_HOME has started, and if not, call the method in Comment 4 to start the application.Application.

In the startHomeActivityLocked method, after a series of judgment logic, the mStackSupervisor.startHomeActivity method is finally called

void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason) {
        moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE, reason);
        startActivityLocked(null /* caller */, intent, null /* resolvedType */, aInfo,
                null /* voiceSession */, null /* voiceInteractor */, null /* resultTo */,
                null /* resultWho */, 0 /* requestCode */, 0 /* callingPid */, 0 /* callingUid */,
                null /* callingPackage */, 0 /* realCallingPid */, 0 /* realCallingUid */,
                0 /* startFlags */, null /* options */, false /* ignoreTargetSecurity */,
                false /* componentSpecified */,
                null /* outActivity */, null /* container */,  null /* inTask */);
        if (inResumeTopActivity) {
            // If we are in resume section already, home activity will be initialized, but not
            // resumed (to avoid recursive resume) and will stay that way until something pokes it
            // again. We need to schedule another resume.
            scheduleResumeTopActivities();
        }
    }

It was found that it invoked the scheduleResumeTopActivities() method, which is the logic of the Activity's starting process and is not expanded here.

Launcher's launched Intent is a hermit's Intent, so we will launch the same catogory configured in androidmanifest.xml, which is Launcher activity in android M.

LauncherActivity inherits from ListActivity, so let's look at its Layout layout file:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

    <TextView
        android:id="@android:id/empty"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="@string/activity_list_empty"
        android:visibility="gone"
        android:textAppearance="?android:attr/textAppearanceMedium"
        />

</FrameLayout>

You can see that our real desktop is actually a ListView control. With intent, the application Launcher is started and its onCreate function executed.

Applying icon display process in Launcher

packages/apps/Launcher3/src/com/android/launcher3/Launcher.java

  @Override
    protected void onCreate(Bundle savedInstanceState) {
       ...
        LauncherAppState app = LauncherAppState.getInstance();//1
        mDeviceProfile = getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE ?
                app.getInvariantDeviceProfile().landscapeProfile
                : app.getInvariantDeviceProfile().portraitProfile;

        mSharedPrefs = Utilities.getPrefs(this);
        mIsSafeModeEnabled = getPackageManager().isSafeMode();
        mModel = app.setLauncher(this);//2
        ....
        if (!mRestoring) {
            if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
                mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);//2
            } else {
                mModel.startLoader(mWorkspace.getRestorePage());
            }
        }
...
    }

Get an instance of LauncherAppState at Comment 1 and call its setLauncher function at Comment 2 and pass the Launcher object in
packages/apps/Launcher3/src/com/android/launcher3/LauncherAppState.java

 LauncherModel setLauncher(Launcher launcher) {
        getLauncherProvider().setLauncherProviderChangeListener(launcher);
        mModel.initialize(launcher);//1
        mAccessibilityDelegate = ((launcher != null) && Utilities.ATLEAST_LOLLIPOP) ?
            new LauncherAccessibilityDelegate(launcher) : null;
        return mModel;
    }

Comment 1 calls LauncherModel's initialize function:

public void initialize(Callbacks callbacks) {
    synchronized (mLock) {
        unbindItemInfosAndClearQueuedBindRunnables();
        mCallbacks = new WeakReference<Callbacks>(callbacks);
    }
}

In the initialize function, Callbacks, which is the Launcher passed in, are encapsulated as a weak reference object.So we know that the mCallbacks variable refers to Launcher encapsulated as a weakly referenced object, which is used later in this mCallbacks.
Return to Launcher's onCreate function and call LauncherModel's startLoader function at comment 2:

packages/apps/Launcher3/src/com/android/launcher3/LauncherModel.java

...
 @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");//1
    static {
        sWorkerThread.start();
    }
    @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());//2
...
   public void startLoader(int synchronousBindPage, int loadFlags) {s
        InstallShortcutReceiver.enableInstallQueue();
        synchronized (mLock) {
            synchronized (mDeferredBindRunnables) {
                mDeferredBindRunnables.clear();
            }
            if (mCallbacks != null && mCallbacks.get() != null) {
                stopLoaderLocked();
                mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);//3
                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
                        && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
                } else {
                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                    sWorker.post(mLoaderTask);//4
                }
            }
        }
    }

A thread HandlerThread object with a message loop was created at comment 1.Note 2 creates the Handler and passes into the ooper of the Handler Thread.Hander is used to send messages to HandlerThread.Create LoaderTask at Comment 3 and send LoaderTask as a message to HandlerThread at Comment 4.
The LoaderTask class implements the Runnable interface and calls its run function when the message described by LoaderTask is processed

private class LoaderTask implements Runnable {
 ...
        public void run() {
            synchronized (mLock) {
                if (mStopped) {
                    return;
                }
                mIsLoaderTaskRunning = true;
            }
            keep_running: {
                if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                loadAndBindWorkspace();//1
                if (mStopped) {
                    break keep_running;
                }
                waitForIdle();
                if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                loadAndBindAllApps();//2
            }
            mContext = null;
            synchronized (mLock) {
                if (mLoaderTask == this) {
                    mLoaderTask = null;
                }
                mIsLoaderTaskRunning = false;
                mHasLoaderCompletedOnce = true;
            }
        }
   ...     
  }      

Launcher is a workspace that displays shortcut icons for applications installed by the system. Each workspace describes an abstract desktop. It consists of n screens, each screen is divided into n cells, and each cell is used to display a shortcut icon for an application.Note 1 calls the loadAndBindWorkspace function to load workspace information, and Note 2 calls the loadAndBindAllApps function to load system installed application information

private void loadAndBindAllApps() {
    if (DEBUG_LOADERS) {
        Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
    }
    if (!mAllAppsLoaded) {
        loadAllApps();//1
        synchronized (LoaderTask.this) {
            if (mStopped) {
                return;
            }
        }
        updateIconCache();
        synchronized (LoaderTask.this) {
            if (mStopped) {
                return;
            }
            mAllAppsLoaded = true;
        }
    } else {
        onlyBindAllApps();
    }
}

If the system does not load the installed application information, the loadAllApps function at Note 1 is called:

 private void loadAllApps() {
...
        mHandler.post(new Runnable() {
            public void run() {
                final long bindTime = SystemClock.uptimeMillis();
                final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                if (callbacks != null) {
                    callbacks.bindAllApplications(added);//1
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "bound " + added.size() + " apps in "
                                + (SystemClock.uptimeMillis() - bindTime) + "ms");
                    }
                } else {
                    Log.i(TAG, "not binding apps: no Launcher activity");
                }
            }
        });
       ...
    }

The bindAllApplications function of callbacks is called at comment 1. We know earlier that this callbacks actually point to Launcher, so let's look at Launcher's bindAllApplications function
packages/apps/Launcher3/src/com/android/launcher3/Launcher.java

public void bindAllApplications(final ArrayList<AppInfo> apps) {
    if (waitUntilResume(mBindAllApplicationsRunnable, true)) {
        mTmpAppsList = apps;
        return;
    }
    if (mAppsView != null) {
        mAppsView.setApps(apps);//1
    }
    if (mLauncherCallbacks != null) {
        mLauncherCallbacks.bindAllApplications(apps);
    }
}

Comment 1 calls the setApps function of AllAppsContainerView and passes in the list apps containing the application information
packages/apps/Launcher3/src/com/android/launcher3/allapps/AllAppsContainerView.java

  public void setApps(List<AppInfo> apps) {
        mApps.setApps(apps);
    }

The list apps containing application information has been set to the AlphabeticalAppsList of AllAppsContainerView to view the onFinishInflate function of AllAppsContainerView:

@Override
    protected void onFinishInflate() {
        super.onFinishInflate();
...
        // Load the all apps recycler view
        mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);//1
        mAppsRecyclerView.setApps(mApps);//2
        mAppsRecyclerView.setLayoutManager(mLayoutManager);
        mAppsRecyclerView.setAdapter(mAdapter);//3
        mAppsRecyclerView.setHasFixedSize(true);
        mAppsRecyclerView.addOnScrollListener(mElevationController);
        mAppsRecyclerView.setElevationController(mElevationController);
...
    }

The onFinishInflate function is called when the xml file is loaded, gets AllAppsRecyclerView at Comment 1 to display the App list, passes in the apps information list at Comment 2, and sets the Adapter for AllAppsRecyclerView at Comment 3.A list of application shortcut icons is displayed on the screen.

summary
Launcher Startup Process

Zygote process -> SystemServer process -> startOtherService method -> ActivityManagerService's systemReady method -> startHomeActivityLocked method -> ActivityStackSupervisor's startHomeActivity method -> Execute Activity's startup logic, execute scheduleResumeTopActivities() method

Since it's a cryptic startup activity, the startup Activity configures catogery in AndroidManifest.xml with a value of:

public static final String CATEGORY_HOME = "android.intent.category.HOME";

Topics: Android Java xml