Launcher是如何实现开启一个App的?

Launcher是如何实现开启一个App的?

虽然已经学习Android很长时间了,但是下面这些问题的答案你是否都清楚呢?

  • Launcher到底是什么神奇的东西?

  • 一个App是怎么启动起来的?

  • App的程序入口到底是哪里?

  • Binder是什么?他是如何进行IPC通信的?

  • Activity生命周期到底是被谁管理的?如何调用的?

  • 等等...

下面我将结合着源码,来寻找以上这些问题的答案。

Launcher是什么?什么时候启动的?

当我们点击手机桌面上的图标时,App就开始启动了。但是,你有没有思考过Launcher的本质是什么?

Launcher本质上是一个App,而与用户进行交互的则是一个Activity。

packages/apps/Launcher2/src/com/android/launcher2/Launcher.java

public final class Launcher extends Activity
        implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
                   View.OnTouchListener {
                   }

Launcher实现了点击、长按等回调接口,来接收用户的输入。既然是普通的App,那么我们的开发经验在这里就仍然适用,比如,我们点击图标的时候,是怎么开启的应用呢?如果是你,你怎么做这个功能呢?捕捉图标点击事件,然后startActivity()发送对应的Intent请求!

是的,Launcher也是这么做的!

那么到底是处理的哪个对象的点击事件呢?既然Launcher是App,并且有Activity,那么肯定有布局文件,下面就是布局文件launcher.xml

为了方便查看,删除了很多代码,从上面这些我们应该可以看出一些东西来:Launcher大量使用include标签来实现界面的复用,而且定义了很多的自定义控件实现界面效果,dock_divider从布局的参数声明上可以猜出,是底部操作栏和上面图标布局的分割线,而paged_view_indicator则是页面指示器。

当然,我们最关心的是Workspace这个布局,因为注释里面说在这里面包含了5个屏幕的单元格,想必你也猜到了,这个就是在首页存放我们图标的那五个界面(不同的ROM会做不同的DIY,数量不固定)。

接下来,我们应该打开workspace_screen布局,看看里面有什么。

workspace_screen.xml

里面就一个CellLayout,也是一个自定义布局,那么我们就可以猜到了,既然可以存放图标,那么这个自定义的布局很有可能是继承自ViewGroup或者是其子类,实际上,CellLayout确实是继承自ViewGroup。在CellLayout里面,只放了一个子View,那就是ShortcutAndWidgetContainer。从名字也可以看出来,ShortcutAndWidgetContainer这个类就是用来存放快捷图标Widget小部件的,那么里面放的是什么对象呢?

在桌面上的图标,使用的是BubbleTextView对象,这个对象在TextView的基础之上,添加了一些特效,比如你长按移动图标的时候,图标位置会出现一个背景(不同版本的效果不同),所以我们找到BubbleTextView对象的点击事件,就可以找到Launcher如何开启一个App了。

除了在桌面上有图标之外,在程序列表中点击图标,也可以开启对应的程序。这里的图标使用的不是BubbleTextView对象,而是PagedViewIcon对象,我们如果找到它的点击事件,就也可以找到Launcher如何开启一个App。

其实说这么多,和今天的主题隔着十万八千里,上面这些东西,你有兴趣就看,没兴趣就直接跳过,不影响本章内容的阅读。

BubbleTextView的点击事件在哪里呢?在Launcher.onClick(View v)里面。

从上面的代码我们可以看到,在桌面上点击快捷图标的时候,会调用

那么从程序列表界面,点击图标的时候会发生什么呢?实际上,程序列表界面使用的是AppsCustomizePagedView对象,所以我在这个类里面找到了onClick(View v)。

com.android.launcher2.AppsCustomizePagedView.java

可以看到,调用的是

和上面一样!这叫什么?这叫殊途同归!

所以咱们现在又明白了一件事情:不管从哪里点击图标,调用的都是Launcher.startActivitySafely()。

下面我们就可以一步步的来看一下Launcher.startActivitySafely()到底做了什么事情。

调用了startActivity(v, intent, tag)

这里会调用Activity.startActivity(intent, opts.toBundle()),这就是我们经常用到的Activity.startActivity(Intent)的重载函数。而且由于设置了

所以这个Activity会添加到一个新的Task栈中,而且,startActivity()调用的其实是startActivityForResult()这个方法。

我们现在明确了,Launcher中开启一个App,其实和我们在Activity中直接startActivity()基本一样,都是调用了Activity.startActivityForResult()。

Instrumentation是什么?和ActivityThread是什么关系?

每个Activity都持有Instrumentation对象的一个引用,但是整个进程只会存在一个Instrumentation对象。

当startActivityForResult()调用之后,实际上还是调用了mInstrumentation.execStartActivity()

下面是mInstrumentation.execStartActivity()的实现。

所以当我们在程序中调用startActivity()的时候,实际上调用的是Instrumentation的相关的方法。

Instrumentation意为“仪器”,我们先看一下这个类里面包含哪些方法吧

我们可以看到,这个类里面的方法大多数和Application和Activity有关,是的,这个类就是完成对Application和Activity初始化和生命周期的工具类。比如说,单独挑一个callActivityOnCreate()看看

activity.performCreate(icicle)这一行代码熟悉吗?这一行里面就调用了Activity的入口函数onCreate(),接着往下看

onCreate在这里调用了吧。但是有一件事情必须说清楚,那就是这个Instrumentation类这么重要,为什么在开发的过程中,没有发现它的踪迹呢?

是的,Instrumentation这个类很重要,对Activity生命周期方法的调用根本就离不开他,他可以说是一个大管家,但是,这个大管家比较害羞,是一个女的,管内不管外,是老板娘~

那么你可能要问了,老板是谁呀?老板当然是大名鼎鼎的ActivityThread了!

ActivityThread所在线程就是UI线程。前面说过,App和AMS是通过Binder传递信息的,ActivityThread就是专门与AMS的外交工作的。

这里模拟一个打开Activity的场景:

  • AMS说:“ActivityThread,你给我打开一个Activity!”

  • ActivityThread就说:“没问题!”

  • 然后ActivityThread转身和Instrumentation说:“老婆,AMS让打开一个Activity,我这里忙着呢,你快去帮我把这事办了把~”

  • 于是,Instrumentation就去把事儿搞定了。

所以说,AMS是董事会,负责指挥和调度;ActivityThread是老板,虽然说家里的事自己说了算,但是需要听从AMS的指挥;Instrumentation则是老板娘,负责家里的大事小事,但是一般不抛头露面,听一家之主ActivityThread的安排。

如何理解AMS和ActivityThread之间的Binder通信?

前面我们说到,在调用startActivity()的时候,实际上调用的是

但是到这里还没完呢!里面又调用了下面的方法

这里的ActivityManagerNative.getDefault返回的就是ActivityManagerService的远程接口,即ActivityManagerProxy。

怎么知道的呢?往下看

再看ActivityManagerProxy.startActivity(),在这里面做的事情就是IPC通信,利用Binder对象,调用transact(),把所有需要的参数封装成Parcel对象,向AMS发送数据进行通信。

Binder本质上只是一种底层通信方式,和具体服务没有关系。为了提供具体服务,Server必须提供一套接口函数以便Client通过远程访问使用各种服务。这时通常采用Proxy设计模式:将接口函数定义在一个抽象类中,Server和Client都会以该抽象类为基类实现所有接口函数,所不同的是Server端是真正的功能实现,而Client端是对这些函数远程调用请求的包装。

为了更方便的说明客户端和服务器之间的Binder通信,下面以ActivityManagerServices和他在客户端的代理类ActivityManagerProxy为例。

ActivityManagerServices和ActivityManagerProxy都实现了同一个接口——IActivityManager。

虽然都实现了同一个接口,但是代理对象ActivityManagerProxy并不会对这些方法进行真正地实现,ActivityManagerProxy只是通过这种方式对方法的参数进行打包(因为都实现了相同接口,所以可以保证同一个方法有相同的参数,即对要传输给服务器的数据进行打包),真正实现的是ActivityManagerService。

但是这个地方并不是直接由客户端传递给服务器,而是通过Binder驱动进行中转。可以把Binder驱动当做一个中转站,客户端调用ActivityManagerProxy接口里面的方法,把数据传送给Binder驱动,然后Binder驱动就会把这些东西转发给服务器的ActivityManagerServices,由ActivityManagerServices去真正的实施具体的操作。

但是Binder只能传递数据,并不知道是要调用ActivityManagerServices的哪个方法,所以在数据中会添加方法的唯一标识码,比如前面的startActivity()方法:

上面的START_ACTIVITY_TRANSACTION就是方法标示,data是要传输给Binder驱动的数据,reply则接受操作的返回值。

这种通信模型可以表示为:

客户端:ActivityManagerProxy =====>Binder驱动=====> AMS:服务器

而且由于继承了同样的公共接口类,ActivityManagerProxy提供了与AMS一样的函数原型,使用户感觉不出Server是运行在本地还是远端,从而可以更加方便的调用这些系统服务。

但是,这里Binder通信是单方向的,即从App指向AMS,如果AMS想要通知App做一些事情,应该怎么办呢?

还是通过Binder通信,不过是换了另外一对,换成了ApplicationThread和ApplicationThreadProxy。

这种通信模型可以表示为:

客户端:ApplicationThread <=====Binder驱动<===== ApplicationThreadProxy:服务器

他们也都实现了相同的接口IApplicationThread

剩下的就不必多说了,和前面是一样。

AMS接收到客户端的请求之后,会如何开启一个Activity?

至此,点击桌面图标调用startActivity(),终于把数据和要开启Activity的请求发送到了AMS了。说了这么多,其实这些都在一瞬间完成了,下面咱们研究下AMS到底做了什么。

由于下面整个过程很繁琐,所以不会深入源码介绍每个细节。

AMS收到startActivity的请求之后,会按照如下的方法链进行调用,

调用startActivity()

调用startActivityAsUser()

在这里又出现了一个新对象ActivityStackSupervisor,通过这个类可以实现对ActivityStack的部分操作。

继续调用startActivityLocked()

调用startActivityUncheckedLocked(),此时要启动的Activity已经通过检验,被认为是一个正当的启动请求。

终于,在这里调用到了ActivityStack的startActivityLocked(ActivityRecord r, boolean newTask,boolean doResume, boolean keepCurTransition, Bundle options)。

ActivityRecord代表的就是要开启的Activity对象,里面封装了很多信息,比如所在的ActivityTask等,如果这是首次打开应用,那么这个Activity会被放到ActivityTask的栈顶,

调用的是ActivityStack.startActivityLocked()

从ActivityStackSupervisor到ActivityStack,又回到了ActivityStackSupervisor,这到底是在折腾什么!

咱们再一起看下StackSupervisor.resumeTopActivitiesLocked(this, r, options)

又调回ActivityStack去了。ActivityStack.resumeTopActivityLocked()

咱们坚持住,看一下ActivityStack.resumeTopActivityInnerLocked()到底进行了什么操作

在这个方法里,prev.app为记录启动Lancher进程的ProcessRecord,prev.app.thread为Lancher进程的远程调用接口IApplicationThead,所以可以调用prev.app.thread.schedulePauseActivity,到Lancher进程中暂停指定Activity。

在Lancher进程中消息传递,调用ActivityThread.handlePauseActivity(),最终调用ActivityThread.performPauseActivity()暂停指定Activity。接着通过Binder通信,通知AMS已经完成暂停的操作。

上面这些调用过程非常复杂,源码中各种条件判断让人眼花缭乱,所以说如果你没记住也没关系,你只要记住这个流程,理解了Android在控制Activity生命周期时是如何操作,以及是通过哪几个关键的类进行操作的就可以了,以后遇到相关的问题之道从哪块下手即可。

最后来一张高清无码大图,方便大家记忆:

请戳这里(图片3.3M,请用电脑观看)

彩蛋

不要使用 startActivityForResult(intent,RESULT_OK)

这是因为startActivity()是这样实现的

所以

你不可能从onActivityResult()里面收到任何回调。而这个问题是相当难以被发现的,就是因为这个坑,我工作一年多来第一次加班到9点 (ˇˍˇ)

一个App的程序入口到底是什么?

是ActivityThread.main()。

整个App的主线程的消息循环是在哪里创建的?

是在ActivityThread初始化的时候,就已经创建消息循环了,所以在主线程里面创建Handler不需要指定Looper,而如果在其他线程使用Handler,则需要单独使用Looper.prepare()和Looper.loop()创建消息循环。

Application是在什么时候创建的?onCreate()什么时候调用的?

也是在ActivityThread.main()的时候,再具体点呢,就是在thread.attach(false)的时候。

我们先看一下ActivityThread.attach()

这里需要关注的就是mgr.attachApplication(mAppThread),这个就会通过Binder调用到AMS里面对应的方法

然后就是

thread是IApplicationThread,实际上就是ApplicationThread在服务端的代理类ApplicationThreadProxy,然后又通过IPC就会调用到ApplicationThread的对应方法

我们需要关注的其实就是最后的sendMessage(),里面有函数的编号H.BIND_APPLICATION,然后这个Messge会被H这个Handler处理

最后就在下面这个方法中,完成了实例化,拨那个企鹅通过mInstrumentation.callApplicationOnCreate实现了onCreate()的调用。

data.info是一个LoadeApk对象。 LoadeApk.data.info.makeApplication()

所以最后还是通过Instrumentation.makeApplication()实例化的,这个老板娘真的很厉害呀!

而且通过反射拿到Application对象之后,直接调用attach(),所以attach()调用是在onCreate()之前的。

参考文章

下面的这些文章都是这方面比较精品的,希望你抽出时间研究,这可能需要花费很长时间,但是如果你想进阶为中高级开发者,这一步是必须的。

再次感谢下面这些文章的作者的分享精神。

Binder

zygote

ActivityThread、Instrumentation、AMS

Launcher

Last updated

Was this helpful?