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生命周期时是如何操作,以及是通过哪几个关键的类进行操作的就可以了,以后遇到相关的问题之道从哪块下手即可。
最后来一张高清无码大图,方便大家记忆:
彩蛋
不要使用 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?