Android系统启动(六)-Launcher篇

经过之前Android系统一系列启动流程,终于到了用户可视化操作界面了,即:Home桌面了,它是一个应用程序,叫Launcher。它主要展示一个个应用的快捷图标,并且通过点击图标来启动相应的应用程序。那么这篇文章就来解读下它。

一、Launcher的启动流程

接上篇AMS最后讲到的startHomeActivityLocked

public void systemReady(final Runnable goingCallback){
 ...
   startHomeActivityLocked(currentUserId, "systemReady");
 ...
}

从这个方法开始开启Launcher的启动流程,那么就进去看看:

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](http://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;
}

首先是调用getHomeIntent()方法,看一下getHomeIntent是如何实现构造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;
}

启动Launcher的Intent对象中添加了Intent.CATEGORY_HOME常量,这个其实是一个launcher的标志,一般系统的启动页面Activity都会在androidmanifest.xml中配置这个标志。

看下Launcher的AndroidManifest.xml:

//packages/apps/Launcher3/AndroidManifest.xml
<activity
    android:name="com.android.launcher3.Launcher"
    android:launchMode="singleTask"
    android:clearTaskOnLaunch="true"
    android:stateNotNeeded="true"
    android:theme="@style/Theme"
    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>

最后通过如下代码启动home的Activity:

mActivityStarter.startHomeActivityLocked(intent, aInfo, reason);

具体流程牵扯到Activity的启动流程,之后会有专门章节来总结,这里就简单列举下之后的执行路径:

ActivityStarter.startHomeActivityLocked 
|
ActivityStarter.startActivityLocked  验证intent、Class、Permission等 ,保存将要启动的Activity的Record
|
ActivityStarter.doPendingActivityLaunchesLocked
|
ActivityStarter.startActivityUnchecked 检查将要启动的Activity的launchMode和启动Flag ,根据launcheMode和Flag配置task
|
ActivityStack.startActivityLocked
|
ActvityStack.startActivityLocked 任务栈历史栈配置
|
ActivityStack.resumeTopActivityInnerLocked() 查找要进入暂停的Activity
|
ActivityStack.startPausingLocked() 通过ipc告诉要暂停的Activity进入暂停
|
ActivityThread.handlePauseActivity() 1正式让之前的Activity暂停 2 告诉AMS已经暂停完成
|
ActivityManagerService.activityPaused()
|
ActivityStack.activityPausedLocked()
|
ActivityStackSuperVisor.resumeTopActivitiesLocked()
|
ActivityStack.resumeTopActivityLocked() 验证是否该启动的Activity所在进程和app是否存在,若存在,直接启动,否则,准备创建该进程
|
ActivityStackSuperVisor.startSpecificActivityLocked() 该进程不存在,创建进程
|
ActivityManagerService.startProcessLocked()
|
ActivityManagerService.startProcessLocked() 通过Process.start(“android.app.ActivityThread”)启动进程
|
ActivityThread.main()
|
ActivityThread.attach()  创建了 Instrumentation()
|
IActivityManager.attachApplication()
|
ActivityStackSuperVisor.attachApplicationLocked() 准备启动应用,先查找MainActivity
|
ActivityStackSuperVisor.realStartActivityLocked() IPC通知ActivityThread
|
ActivityThread.scheduleLaunchActivity()  H 发消息 sendMessage(H.LAUNCH_ACTIVITY, r);
|
ActivityThread.handleLaunchActivity()
|
ActivityThread.performLaunchActivity()
|
Instrumentation.newActivity() 创建activity
|
activity.attach( )
|
Instrumentation.callActivityOnCreate 最终执行Activity onCreate

(太懒了,就不画时序图了 - - ||| )

二、launcher中应用图标显示流程

经过前面的启动分析,我们终于进入到Launcher的主Activity了

public class Launcher extends Activity{
    private LauncherModel mModel;
    onCreate(Bundle savedInstanceState){
       ...
      LauncherAppState app = LauncherAppState.getInstance(); //LauncherAppState创建单例对象
       …
       mModel = app.setLauncher(this);
       setContentView(R.layout.launcher); //设置布局
        …
//load显示数据
if (!mRestoring) {
    if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
        // If the user leaves launcher, then we should just load items asynchronously when
        // they return.
        mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
    } else {
        // We only load the page synchronously if the user rotates (or triggers a
        // configuration change) while launcher is in the foreground
        mModel.startLoader(mWorkspace.getRestorePage());
    }
} 
...
   }
}

看样子核心功能都在LauncherModel里:
先看看 mModel = app.setLauncher(this);

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

主要做了个初始化操作:

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

在initialize函数中会将Callbacks,也就是传入的Launcher 封装成一个弱引用对象。因此我们得知mCallbacks变量指的就是封装成弱引用对象的Launcher,这个mCallbacks后文会用到它。

回到onCreate 看看后面LauncherModel.startLoader方法:

...
@Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");//创建了具有消息循环的线程HandlerThread对象
    static {
        sWorkerThread.start();
    }
    @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());//创建了Handler,并且传入HandlerThread的Looper。
...
   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);//创建LoaderTask
                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
                        && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
                } else {
                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                    sWorker.post(mLoaderTask);//将LoaderTask作为消息发送给HandlerThread 
                }
            }
        }
    }

LoaderTask类实现了Runnable接口,当LoaderTask所描述的消息被处理时则会调用它的run函数,代码如下所示:

//LauncherModel
private class LoaderTask implements Runnable {
...
final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
...
mHandler.post(new Runnable() {
       public void run() {
           synchronized (mLock) {
               if (mStopped) {
                   return;
               }
               mIsLoaderTaskRunning = true;
           }
           keep_running: {
               if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
               loadAndBindWorkspace();//调用loadAndBindWorkspace函数用来加载工作区信息
               if (mStopped) {
                   break keep_running;
               }
               waitForIdle();
               if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
               loadAndBindAllApps();//用来加载系统已经安装的应用程序信息
           }
           mContext = null;
           synchronized (mLock) {
               if (mLoaderTask == this) {
                   mLoaderTask = null;
               }
               mIsLoaderTaskRunning = false;
               mHasLoaderCompletedOnce = true;
           }
       }
   }
  ...    
}

mLauncherApps.getActivityList执行的是

//LauncherApps
mService.getLauncherActivities(packageName, user);

而mService 是:

ILauncherApps.Stub.asInterface(
        ServiceManager.getService(Context.LAUNCHER_APPS_SERVICE)));

也就是最终由LauncherAppService执行getLauncherActivities:

@Override
public ParceledListSlice<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
        throws RemoteException {
    ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user);
    if (!isUserEnabled(user)) {
        return null;
    }
    final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
    mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
    mainIntent.setPackage(packageName);
    long ident = Binder.clearCallingIdentity();
    try {
        List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent,
                PackageManager.MATCH_DIRECT_BOOT_AWARE
                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
                user.getIdentifier());
        return new ParceledListSlice<>(apps);
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}

最终由PMS 执行queryIntentActivitiesAsUser 去获取要展示的APP信息。(这部分之后总结PMS再细说)

Launcher是用工作区的形式来显示系统安装的应用程序的快捷图标,每一个工作区都是来描述一个抽象桌面的,它由n个屏幕组成,每个屏幕又分n个单元格,每个单元格用来显示一个应用程序的快捷图标。这里loadAndBindWorkspace不分析,直接看加载数据相关的loadAndBindAllApps, loadAndBindAllApps又会调用loadAllApps:

//LauncherModel
  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);
                    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");
                }
            }
        });
       ...
    }

调用callbacks的bindAllApplications函数,在前面我们得知这个callbacks实际是指向Launcher的,因此我们来查看Launcher的bindAllApplications函数:

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

那mAppsView是什么?
mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
apps_view对应的是一个自定义控件:
com.android.launcher3.allapps.AllAppsContainerView

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

再看看mApps初始化的地方:

public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    Resources res = context.getResources();
    mLauncher = (Launcher) context;
    mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
    mApps = new AlphabeticalAppsList(context);
    mAdapter = new AllAppsGridAdapter(mLauncher, mApps, this, mLauncher, this);
    mApps.setAdapter(mAdapter);
    mLayoutManager = mAdapter.getLayoutManager();
    mItemDecoration = mAdapter.getItemDecoration();
    mRecyclerViewTopBottomPadding =
            res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding);
    mSearchQueryBuilder = new SpannableStringBuilder();
    Selection.setSelection(mSearchQueryBuilder, 0);
}

最后再看看AllAppsContainerView的onFinishInflate方法:

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

onFinishInflate函数在加载完xml文件时就会调用。AllAppsRecyclerView设置数据,并setAdapter。

一张图总结Launcher整个启动过程以及数据显示过程:

from fu_kevin

参考:
https://blog.csdn.net/fu_kevin0606/article/details/54931704
http://liuwangshu.cn/framework/booting/4-launcher.html

系列文章:
Android系统启动(一)-开篇
Android系统启动(二)-Init篇
Android系统启动(三)-Zygote篇
Android系统启动(四)-SystemServer篇
Android系统启动(五)-ActivityManagerService篇
Android系统启动(六)-Launcher篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,042评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,996评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,674评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,340评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,404评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,749评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,902评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,662评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,110评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,577评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,258评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,848评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,726评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,952评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,271评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,452评论 2 348

推荐阅读更多精彩内容