本文旨在将Framework的框架描绘出来,主要是记录我一段时间关于android framework的学习,希望抛砖引玉,对于读者有一定的帮助。
前言
写在前面:
1、有没有必要学习linux内核?
我认为是很有必要的。学习linux内核有助于我们加深对一些概念的理解,比如“进程”、“线程”。推荐入门的教程:中国大学MOOC李治军老师的操作系统课程
2、有没有必要自己编译android源码?
非必须,可以直接用android studio查看sdk源码,除非要调试一些功能。但是要亲自操练起来才能更熟悉。
android framework与我们的开发息息相关,本文将从开机,即framework的初始化讲起,然后再涉及android运行过程中的几个使用场景。比如用户启动app(点击桌面图标后发生了什么),用户使用app(一次触摸,Android到底干了啥)。其中会涉及主线程、anr、handler、binder、zygote、app的入口是什么、自定义view为什么要三步走等一些我们经常接触的概念,并一一解答。涉及源码为api 27。
一、初始化篇
当按开机键的时候,设备首先执行BootLoader,BootLoader负责把Linux内核从加载到内存,并执行内核的初始化,最后内核将读取init.rc文件,并启动该文件中定义的各种服务程序。Android framework对于内核而言就是一个Linux程序而已,而该程序就在init.rc文件中被定义。Android framework的初始化过程由此开始。
首先被创建的是zygote进程,这是系统中运行的第一个Dalvik虚拟机程序,顾名思义,后面所有Dalvik虚拟机进程都是通过它“孵化”而来(学过生物的我们都知道,人体所有的细胞都是由受精卵分裂而来,所以本人觉得这个名称取得非常准确巧妙)。
zygote孵化出的第一个 Dalvik1 进程叫做 SystemServer,是Framework相当重要的进程。 SystemServer 仅仅是该进程的别名,而该进程具休对应的程序依然是 app\_process, 因为 SystemServer 是从 app\_process中孵化出来的。Ams、Wms、Pms等等都在此进程中创建,可以说SystemServer管理Framework所有的活动。
注1:Andoird 4.4引入ART
SystemServer 中创建了一个 Socket2 客户端,并有AmS负责管理该客户端,之后所有的 Dalvik 进程都将通过该 Socket 客户端间接被启动。当要启动新的 APK 进程时 ,AmS 中会通过该 Socket 客户端向 zygote 进程的 Socket服务端发送一个启动命令,然后zygote会孵化出新的进程。
注2:此处涉及Android进程中通信的一种方法Socket,学过计算机网络的读者应该对此有一定的概念。以后还会提及pipe、binder两种进程通信方法,无论如何,它们最终的目的都是为了让开发者跨进程调用时都像是在进行本地调用。至于它们的优缺点以及实现方式,读者可以自行探究。
1、zygote的启动
前面我们提到内核初始化时,会启动在init.rc文件中配置的程序,zygote相关的配置信息如下:
service zygote /system/bin/app\_process -Xzygote /system/bin --zygote --start-system-server
简单说明一下这个语句的意思,内核会执行/system/bin/app\_process3目录下的程序,启动一个叫zygote的服务。其中参数--start-system-server, 仅在指定 -- zygote 参数时才有效,意思是告知Zygotelnit启动完毕后孵化出第一个进程SystemServer。由此可知,zygote启动后做的事情之一就是启动SystemServer。
注3:Android支持64位以后,会根据不同的ABI分别执行/system/bin/app\_process32和/system/bin/app\_process64目录。
当 zygote 服务从 app\_process 开始启动后,会启动一个 Dalvik 虚拟机,而虚拟机执行的第一个 Java类就是 ZygoteInit.java。(app进程fork自zygote进程,所以ZygoteInit.main同样也是app的入口,只不过会根据进程的不同执行不同的逻辑。这就是有时候我们程序错误日志的调用栈里面可以看到"…ZygoteInit.main……"的原因。)ZygoteInit会做另外两件事:一是前面提到的,启动一个Socket服务端口,该Socket端口用于接收启动新进程的命令;二是预加载的Framework大部分类及资源供后续app使用。zygote fork app进程时,并不需要复制这一部分,而是使用共享内存的方式。
总结: zygote的进程启动后主要做了三件事:分别是启动一个Socket服务,用于接收启动新进程的命令、预加载的Framework大部分类及资源以及启动SystemServer。
2、SystemServer的启动
SystemServer是在zygote进程中最终调用到Zygote.forkSystemServer方法启动的。启动后会做一些初始的配置,比如关闭Socket服务端(非zygote进程不需要),配置SystemServer运行环境。然后调用SystemServer.main。
SystemServer启动后,主要做两件事:一是通过SystemServerManager启动各种服务线程,比如AMS、WMS、PMS等等,并将其注册到ServiceManager(AMS、WMS与app的运行息息相关,其具体内容后面再展开);二是启动HomeActivity,也就是启动launcher,launcher与普通app的启动大同小异,后面再详细介绍。
3、ServiceManager的启动
此处的ServiceManager不是java世界的,而是native世界的。它也是通过init.rc配置启动的,其功能相当于service4的DNS服务器。SystemServer启动的各个服务都会注册于其中,我们在使用binder进行跨进程调用时,首先回去查询ServiceManager获取到对应service的binder引用,然后再进行后续操作。这个过程与我们通过域名查询dns服务器获得ip最后访问网站类似。
注意:我们查看Framework代码时候会发现SystemServiceRegistry类,这个类和系统服务的注册没有半毛钱关系,它只不过是将查询各种service的工具类缓存起来。
注4:这里不是指Android的四大组件之一的Service。
二、运行时篇
我们在使用android设备时,就是Framework的运行时。下面我会从两个关键场景说起:第一个场景,点击桌面图标,这个场景会涉及到android的消息机制、app的启动、activity的创建、window的创建和view的绘制。第二个场景,我们在滑动屏幕或者点击按钮等等,这个场景会涉及到Framework怎么获得硬件的事件以及app的事件分发机制。
1、点击桌面图标后发生了什么
Activity的启动
我们都知道桌面程序也就是launcher和普通的app基本没什么差别,我们点击桌面图标其实调用了Activity的startActivity方法。Activity是Context的子类5,所以本来应该是调用了Context的startActivity方法,不过Activity重载了该方法,和Context区别是少了是否有Intent.FLAG\_ACTIVITY\_NEW\_TASK的判断。这也是为什么我们在非Activity的Context(比如Service)启动时要加Intent.FLAG\_ACTIVITY\_NEW\_TASK。不论是Activity还是Context,最终都会调用到Instrumentation的execStartActivity方法,然后通过Binder跨进程调用Ams的startActivity方法。
注5:更准确的说Activity是ContextWrapper的子类,而ContextWrapper不过是个代理类。实际上Activity具体的操作都是由其成员变量mBase完成,而mBase是一个ContextImpl类(继承Context)。所以如果Context是一个Interface的话,那么这就是一个标准的静态代理模式。
Ams会调用到startActivityAsUser方法,然后调用ActivityStarter的startActivityMayWait方法。
ActivityStarter.java
final int startActivityMayWait(...){
...
//根据intent通过PMS查找activity相关信息
//如何没有在AndroidManifest.xml注册过就无法找到activity
ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
...
//见下方
int res = startActivityLocked(...);
...
}
int startActivityLocked(...){
...
//见下方
mLastStartActivityResult = startActivity(...);
...
}
private int startActivity(...){
...
//ActivityRecord是Activity在Ams的记录,与我们app的activity是一一对应的,
//它有一个成员变量appToken是一个binder类,后面app的activity就是通过这个
//类与Ams的activity通信的
ActivityRecord r = new ActivityRecord(...);
...
//调用startActivity的另一个重载,见下方
return startActivity(...);
}
private int startActivity(...){
...
//见下方
result = startActivityUnchecked(...);
...
}
private int startActivityUnchecked(...){
//初始化一些参数,比如mLaunchSingleTop(launchMode是否为singletop),调整mLaunchFlags等
setInitialState(...);
//进一步调整mLaunchFlags,比如原activity为singleinstance或者要启动的activity为
//singleinstance或singletask时,确保mLaunchFlags拥有 FLAG_ACTIVITY_NEW_TASK属性
computeLaunchingTaskFlags();
...
//查找是否有已启动的activity
ActivityRecord reusedActivity = getReusableIntentActivity();
if (reusedActivity != null) {
if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| isDocumentLaunchesIntoExisting(mLaunchFlags)
|| mLaunchSingleInstance || mLaunchSingleTask) {
//清理task使得目标activity在顶部,这里我们就可以明白
//FLAG_ACTIVITY_CLEAR_TOP或者singletask的原理。
...
if (top != null) {
...
//回调onNewIntent
deliverNewIntent(top);
}
...
}
}
...
//调整好stack以及task
mTargetStack.startActivityLocked(...);
...
//见下方
mSupervisor.resumeFocusedStackTopActivityLocked(...);
}
至此我们先总结一下,Ams根据intent通过PMS查找activity相关信息,这解释了为什么没有在AndroidManifest.xml注册就无法被启动。然后根据activity的launchMode、taskAffinity以及intent的launchFlags为activity找到合适的stack和task。stack、task以及ActivityRecord的关系如下图。Ams通过ActivityRecord保存activity的各种状态信息,以及通过其成员变量appToken(binder类)来和app的activity通信。
图片来自互联网
我们接着讲ActivityStackSupervisor的resumeFocusedStackTopActivityLocked方法,该方法会接着调用ActivityStack的resumeTopActivityUncheckedLocked方法,接着调用resumeTopActivityInnerLocked方法,然后再返回到ActivityStackSupervisor的startSpecificActivityLocked方法。
ActivityStackSupervisor.java
void startSpecificActivityLocked(...) {
if (app != null && app.thread != null) {
...
//如果该activity对应的app已启动,则直接启动activity
//具体见后面
realStartActivityLocked(...);
...
}
//通过Ams启动进程,具体见下方
mService.startProcessLocked(...);
}
final boolean realStartActivityLocked(...){
//这里的app.thread是一个binder类,用于与app的ActivityThread通信
//通过binder跨进程调用ActivityThread的scheduleLaunchActivity方法。
app.thread.scheduleLaunchActivity();
}
这里我们先接着讲通过Ams启动进程,Ams调用startProcessLocked后会紧接着调用另一个startProcessLocked重载
ActivityManagerService.java
final ProcessRecord startProcessLocked(...){
...
if (app != null && app.pid > 0) {
//如果app已启动且满足一些条件则直接返回
}
...
//见下方
startProcessLocked(...);
...
}
private final void startProcessLocked(...){
...
//entryPoint将作为参数通过socket传递,后面成为进程java代码执行的入口
if (entryPoint == null) entryPoint = "android.app.ActivityThread";
...
//见下方
startResult = Process.start(entryPoint,...);
...
}
Process的start方法会紧接着调用ZygoteProcess的start方法,然后调用startViaZygote方法。
ZygoteProcess.java
private Process.ProcessStartResult startViaZygote(...){
...
//openZygoteSocketIfNeeded方法作用是和zygote进程建立socket连接
//之前我们提到zygote进程会扮演socket服务端的角色接受命令然后fork出进出
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
...
}
private static Process.ProcessStartResult zygoteSendArgsAndGetResult(...){
//发送fork的命令以及上面提到entryPoint等其他参数
...
}
我们回到zygote进程,在zygote进程启动时,我们是调用到ZygoteInit的main方法进行初始化,其中会开启ZygoteServer的runSelectLoop线程一直循环接收命令。而其中的主要方法时ZygoteConnection的processOneCommand方法。
ZygoteConnection.java
Runnable processOneCommand(...){
//读取命令和参数
...
//fork进程
pid = Zygote.forkAndSpecialize(...);
...
//对linux下c编程有一定了解的朋友会知道,fork后子进程的pid为0
if (pid == 0) {
...
//处理子进程,见下方
return handleChildProc(parsedArgs, descriptors, childPipeFd);
}
}
private Runnable handleChildProc(...){
...
return ZygoteInit.zygoteInit(...);
}
ZygoteInit.java
public static final Runnable zygoteInit(...){
...
return RuntimeInit.applicationInit(...);
}
RuntimeInit.java
protected static Runnable applicationInit(...) {
//args.startClass就是我们之前提到的entryPoint,也就是"android.app.ActivityThread"
//由此可知app第一个被调用的方法是ActivityThread的main方法,这就是应用程序的入口。
return findStaticMain(args.startClass, args.startArgs, classLoader);
}
我们终于回到了自己的进程,也很明确ActivityThread的main方法(提到main方法,总是有一种无以言表的亲切感)就是应用程序的入口。接着继续探索。
ActivityThread.java
public static void main(String[] args) {
//创建looper,looper其实很好理解,就是一直在循环,一直在取指执行。
//(我们的计算机的原理不也是一直取指执行吗)
Looper.prepareMainLooper();
//创建ActivityThread,一开始我们看到这个名字会以为它是一个Thread的类
//事实上它也完全可以代表app的主线程,因为它拥sMainLooper,
//拥有sMainThreadHandler,它会和Ams以及其他系统服务打交道
//而我个人的理解,activity即活动,thread即线,它就是一条线串起了所有app的活动。
ActivityThread thread = new ActivityThread();
//建立与Ams的Binder通道,见下方
thread.attach(false);
//创建handler,handler其实就是一个工具,让我们往MessageQueue放消息,移除消息以及处理消息
//looper才是主角,looper会从MessageQueue一直取消息,然后执行
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
}
private void attach(boolean system) {
...
//mgr是一个binder类用于与Ams通信
mgr.attachApplication(mAppThread);
...
}
看到这我们已经很清楚什么是主线程了,进程最初运行的那个线程就是主线程。而后面我们会发现一个“恐怖”的事实,我们的app会一直运行在sMainLooper之中(也就是主线程之中,当然排除我们创建的其他线程),包括启动activity,发送触摸消息、按键消息,我们都会通过sMainThreadHandler交由sMainLooper处理(我们开发时通过handler进行同步也是这个原理)。既然清楚了主线程的概念,那么anr的原理也就很好理解了,sMainLooper在处理这个消息的时候如果超过5s(activity)或者20s(service)就会anr,这种确保用户体验的一个做法。
接下来我们回到Ams,Ams会紧接着调用attachApplicationLocked方法。
ActivityManagerService.java
private final boolean attachApplicationLocked(...){
...
//通过binder IPC通知ActivityThread创建Application
thread.bindApplication(...);
...
mStackSupervisor.attachApplicationLocked(app);
...
}
ActivityStackSupervisor.java
boolean attachApplicationLocked(...) throws RemoteException {
...
//看我们发现了什么,似曾相识。没错就是上面我们留下来的问题。
//道理很简单,我们在启动一个activity的时候发现进程未启动,
//当我们启动进程后当然得重新启动activity
realStartActivityLocked(...);
...
}
final boolean realStartActivityLocked(...){
...
//这里的thread是一个binder类,和ActivityThread是对应的
app.thread.scheduleLaunchActivity(...);
...
}
重新回到app进程,先看创建Application的流程
ActivityThread$ApplicationThread.java
public final void bindApplication(...){
//通过handler调用到handleBindApplication方法,接着调用到performLaunchActivity方法
sendMessage(H.BIND_APPLICATION, data);
}
ActivityThread.java
private void handleBindApplication(AppBindData data) {
...
//创建Instrumentation
mInstrumentation = new Instrumentation();
...
//创建Application,回调attachBaseContext方法
app = data.info.makeApplication(data.restrictedBackupMode, null);
...
//回调onCreate方法
mInstrumentation.callApplicationOnCreate(app);
...
}
接着看创建Activity的流程
ActivityThread$ApplicationThread.java
public final void scheduleLaunchActivity(...){
//通过handler调用到handleLaunchActivity方法,接着调用到performLaunchActivity方法
sendMessage(H.LAUNCH_ACTIVITY, r);
}
ActivityThread.java
private Activity performLaunchActivity(...){
//真正的Context类,也就是我们上面提到的mBase
ContextImpl appContext = createBaseContextForActivity(r);
...
//利用发射创建activity
activity = mInstrumentation.newActivity(...);
...
//将appContext赋给mBase,并且回调attachBaseContext(context);
//getInstrumentation()之前提到过调用Instrumentation的execStartActivity方法
//r.token为binder类与Ams的ActivityRecord对应,我的另一篇文章(见注6)提到它有重要作用
activity.attach(appContext, this, getInstrumentation(),r.token,...,app,...,window,...);
...
//回调onCreate
mInstrumentation.callActivityOnCreate(...);
}
至此startActivity的框架已描述完毕。
View的绘制
这一部分并不是要讲自定义view,而是将窗口的创建(包括添加与绘制)。 从WmS的角度来观察,一个窗口并不是一个Window类,而是一个View类。当WmS收到用户的消息后,需要把消息派发到窗口,View类本身并不能直接接收WmS传递过来的消息,真正接收用户消息的必须是IWindow类(binder类),而实现IWindow类的是ViewRoot.W类,每一个W内部都包含了一个View变量。这两句话引用自《Android内核剖析》,从后面讲解可知,Window类更多是窗口的抽象,而其中的view才是窗口的内容。
Framework中定义了三种窗口,分别为应用窗口、子窗口和系统窗口。其中应用窗口对应一个Activity,接下来就是讲解应用窗口(下面简称为窗口)的创建。既然窗口对应一个Activity,那么窗口就是在startActivity的过程中创建的。上面提到Activity的创建会回调onCreate的,而我们在开发的时候会在其中调用setContentView方法。而setContentView会调用Window类的setContentView方法,如果你去查看Activity的attach方法时,会发现Window类实际上是一个PhoneWindow类。
PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
...
installDecor();
...
//加载app自定义的布局,由下可知我们的布局至少包裹着两个view,
//先是由mContentParent,然后再由mDecor包裹
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}
private void installDecor() {
...
//创建DecorView(继承自FrameLayout),Decor顾名思义,窗口的所有内容都在这个view中展示,
//这个View是Activity所有View的root。
//这也是我们查看Activity view hierarchy,最外层都是FrameLayout的原因
mDecor = generateDecor(-1);
...
//根据不同的style配置inflate不同的xml布局,
//这些xml有个共同特点:都有一个id为ID_ANDROID_CONTEN的view
//所以可以通ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)
//为contentParent赋值
mContentParent = generateLayout(mDecor);
...
}
这一步只是将View创建出来,接下来还会涉及到两步:1、将窗口添加到Wms,Wms也会和Ams一样将窗口以一定形式管理起来。2、将View要展示的内容转成数据保存于屏幕缓冲区内存,交由系统绘制出来。
在startActivity的过程中,创建Activity后会接着调用handleResumeActivity。
ActivityThread.java
private void handleLaunchActivity(...){
...
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
...
//见下方
handleResumeActivity(...);
...
}
}
final void handleResumeActivity(...){
...
//回调onResume
r = performResumeActivity(token, clearHide, reason);
...
//将decor设为不可见
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
...
//调用Activity的makeVisible
r.activity.makeVisible();
...
}
Activity.java
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
//最后会调用到WindowManagerGlobal的addView
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
WindowManagerGlobal.java
public void addView(...) {
...
//创建ViewRootImpl(View root的抽象)
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//将view(这里其实是mDecor)、root(View的抽象)以及layoutParam缓存起来
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
...
//见下方
root.setView(view, wparams, panelParentView);
...
}
ViewRootImpl.java
public void setView(...) {
...
//下面小节接着讲
requestLayout();
...
//ipc到Wms,Session类为服务端
//mInputChannel是一个InputChannel类,是管道在java层的实现,后面讲到Android事件的时候会细说
res = mWindowSession.addToDisplay(...,mInputChannel);
...
}
Session.java
@Override
public int addToDisplay(...) {
//见下方
return mService.addWindow(...);
}
WindowManagerService.java
public int addWindow(...) {
//创建WindowState
//其中session是Session的Binder服务端
//client是IWindow的Binder客户端
final WindowState win = new WindowState(..., session, client,...);
...
//会调用到Session的windowAddedLocked方法,见下方
win.attach();
//将win缓存起来
mWindowMap.put(client.asBinder(), win);
...
if (win.canReceiveKeys()) {
//如果该窗口是可交互窗口(比如Toast为不可交互窗口),则更新聚焦窗口
focusChanged = updateFocusedWindowLocked(...);
...
}
}
Session.java
void windowAddedLocked(String packageName) {
...
if (mSurfaceSession == null) {
//创建SurfaceSession,该类是直接和Surfaceflinger交互的类,
//用于向SurfaceFlinger中添加、删除、变换窗口。由千每个应用程序仅对应一个Session对象,
//因此,mSurfaceSession实际上只会被创建一次,
//即应用程序中的第一个窗口创建时会构造一个SurfaceSession对象,
//同一个应用程序中后续的窗口添加不会再构造 SurfaceSession 对象.
mSurfaceSession = new SurfaceSession();
...
//保存Session
mService.mSessions.add(this);
...
}
...
}
到这里可以看到Wms将窗口的信息保存下来,也就是管理起来,是事件分发的基础。
回到上面的requestLayout方法,requestLayout调用了scheduleTraversals方法,该方法发起一个View树遍历的消息,该消息是异步处理的,对应的处理函数为performTraversals方法。
ViewRootImpl.java
private void performTraversals() {
final View host = mView;
if (mFirst) {
...
//如果是窗口第一次显示,为mAttachInfo初始化,并赋给mView,
//调用ViewGroup的dispatch方法,将mAttachInfo递归地传递给子View
host.dispatchAttachedToWindow(mAttachInfo, 0);
}
...
//如果窗口尺寸发生了改变,则调用 host.measure()重新计算窗口中视图的大小
//Android的View和ViewGroup是很经典的组合模式
//measure过程会遍历整个View tree,会调用每个View的measure以及回调onMeasure
//layout和draw的过程也是类似
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//根据以七步骤的执行结果,判断是否需要进行重新布局。比如当任意视图的大小发生变化时,
//它会影响其他视图的布局
performLayout(lp, mWidth, mHeight);
...
//判断没有取消绘制,并且不是 newSurface, 则开始绘制
//newSurface变拉的含义是指,ViewRoot中创建了Surface对象,但是该对象还没有被WmS分配真正的显存,
//ViewRoot中是调用sWindowSession.relayoutWindow()为该Surface对象中分配真正的显存,
//在一般情况下,此处的newSurface都是false。
performDraw();
...
}
第一次由于窗口设置不可见,所以前面的代码可以看到,在Activity的makeVisible方法会调用mDecor.setVisibility(View.VISIBLE),经过一系列调用会再次调用到ViewRootImpl的performTraversals方法,然后调用performDraw方法。
在讲绘制之前,首先我们要清楚几个概念。
- Surface:
Surface是原始图像缓冲区(raw buffer)的一个句柄。也就是说Surface对应一段内存,这段内存的数据就是要绘制的内容,Surface 类本质上就是表示一个平面
- Canvas:
我们知道绘制不同图案显然是一种橾作,而不是一段数据。Android用Canvas类来表示这些操作,也就是说Canvas就是绘制的功能类。看Canvas的描述:A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap)——Canvas的功能就是响应draw的调用,并将其写入bitmap,而这个bitmap用于保存所有像素,你就会更清楚Canvas的概念。
ViewRootImpl.java
private void performDraw() {
...
draw(fullRedrawNeeded);
...
}
private void draw(boolean fullRedrawNeeded) {
//之前向Wms申请的
Surface surface = mSurface;
...
if (!drawSoftware(...)) {
return;
}
...
}
private boolean drawSoftware(...) {
...
//获取canvas并锁定
canvas = mSurface.lockCanvas(dirty);
...
//遍历子类的draw,很显然这一过程就是将所有子类的内容转成像素写入Canvas的bitmap中
mView.draw(canvas);
...
//将保存所有view内容的Canvas提交给surface,交由系统底层绘制。
surface.unlockCanvasAndPost(canvas);
...
}
至此view绘制的框架已描述完毕。
2、一次触摸,Android到底干了啥
这个标题来自《一次触摸,Android到底干了啥》,所以下面很多内容会引用自这篇文章。
一次触摸意味着什么?我们在使用Android设备的过程中,点击、长按、滑动(TouchEvent)以及按实体按键(KeyEvent)都可以成为“一次触摸”。因此,一次触摸可以代表所有我们使用过程中的操作。
我们的触摸当然是从硬件开始,硬件会将事件传递到内核,内核传递到Framework。前面提到SystemServer启动时会启动各种服务:
SystemServer.java
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
...
startOtherServices();
...
}
private void startOtherServices() {
...
//创建InputManagerService
inputManager = new InputManagerService(context);
...
//创建WindowManagerService,且持有InputManagerService
wm = WindowManagerService.main(..., inputManager,...);
...
//将InputMonitor设为回调,且启动InputManagerService
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
}
InputManagerService.java
public InputManagerService(Context context) {
...
//初始化native对象(mPtr是long,保存native对象的指针,这是jni常用的保持native对象的方法)
//InputManagerService对应native的InputManager.cpp
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
...
}
public void start() {
...
//用于启动下面提到的两个线程
nativeStart(mPtr);
...
}
InputManager.cpp
InputManager::InputManager(...) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
/**
由上面的代码,我们可以看到InputManager初始化时创建了InputDispatcher、InputReader
以及InputReaderThread、InputDispatcherThread。InputReader用来读取输入信息(也就是各种事件),
InputDispatcher用于分发事件。而两个线程很显然就是来运行这两个功能。
*/
InputReader线程会持续调用输入设备的驱动,读取所有用户输入的消息该线程和InputDispatcher线程都在系统进程(system\_process)空间中运行。InputDispatcher线程从自己的消息队列中取出原始消息,取出的消息有可能经过两种方式进行派发。第一种是经过管道(Pipe)直接派发到客户窗口中,另一种则是先派发到WmS中,由WmS经过一定的处理,如果WmS没有处理该消息,则再派发到客户窗口中,否则 ,不派发到客户窗口(引用自《Android内核剖析》)。如图(图片同样来自《Android内核剖析》):
如果是按键消息,InputDispatcher会先回调InputManager中定义的回调函数,这既会回调InputMonitor中的回调函数,这又会调用WmS中定义的相关函数。对于系统按键消息,比如"Home"键、电话按键等,WmS内部会按照默认的方式处理,如果事件被消耗,InputDispatcher则不会继续把这些按键消息传递给客户窗口对于触摸屏消息,InputDispatcher则直接传递给客户窗口。
InputManagerService.java
//按键事件的回调,前面我们提到回调对象是InputMonitor
// Native callback.
private long interceptKeyBeforeDispatching(InputWindowHandle focus,
KeyEvent event, int policyFlags) {
return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags);
}
InputMonitor.java
@Override
public long interceptKeyBeforeDispatching(
InputWindowHandle focus, KeyEvent event, int policyFlags) {
WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
//回调Wms,mPolicy在SystemServer初始化时创建,为PhoneWindowManager类,可以看到其中对各种按键的处理
return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
}
当InputDispatcher直接传递给窗口时,通过findTouchedWindowAtLocked方法找到当前获得焦点的窗口,然后通过Pipe(管道)进行ipc。也就是说在native层也会维护一组窗口相关的信息。我们回到Wms添加窗口的过程:
WindowManagerService.java
public int addWindow(...) {
//创建WindowState,构造方法中会创建InputWindowHandle
final WindowState win = new WindowState(...);
...
//建立管道通信,其中outInputChannel是从app进程传递过来的
win.openInputChannel(outInputChannel是从app进程);
...
//更新窗口焦点改变的信息到native层,同理当窗口切换或者销毁时也会更新
mInputMonitor.updateInputWindowsLw(false /*force*/);
...
}
WindowState.java
void openInputChannel(InputChannel outInputChannel) {
...
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
/*Channel[0]保存在server端*/
mInputWindowHandle.inputChannel = inputChannels[0];
...
/* Channel[1]返回给app的ViewRootImpl端*/
mClientChannel.transferTo(outInputChannel);
...
/*注册到InputManagerService native层*/
mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
}
可以看到,在Wms中维护一组WindowState,用于窗口的创建、销毁切换,而在InputManagerService则维护一组InputWindowHandle,用于事件的分发。
我们回到ViewRootImpl中。在添加窗口时,我们调用了setView方法。
ViewRootImpl.java
public void setView(...) {
...
//InputChannel类,是管道在java层的实现
mInputChannel = new InputChannel();
...
//查看IWindowSession.aidl会发现这里的mInputChannel标注着out,
//也就是在另一个进程的改变都会同步到本进程
res = mWindowSession.addToDisplay(...,mInputChannel);
...
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
//建立管道的回调,见下方
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());
}
}
ViewRootImpl$WindowInputEventReceiver.java
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
//见下方
super(inputChannel, looper);
}
InputEventReceiver.java
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
...
//建立管道的回调,native层收到事件就会回调给InputEventReceiver
mReceiverPtr = nativeInit(new WeakReference(this),
inputChannel, mMessageQueue);
...
}
当InputManagerService的InputDispatcher通过管道将消息传递给app进程,app进程的管道会回调InputEventReceiver(也就是WindowInputEventReceiver),进行事件分发。后面的代码可以说是责任链模式的标准答案,非常精彩读者可以自行学习。
总结
Android Framework可以说是一个庞大的工程,如果我们在一开始的过程中就陷入细节,就无法走通一条路。君子善假于物也,借助大佬的研究学习成果,我们可以先学习整体的框架,有必要时再各个击破。非常感谢各个大佬,下面的参考文献可能有所遗漏,在此致歉!希望本篇文章对于读者有所帮助。
本文转自 https://juejin.cn/post/6844903717301387272,如有侵权,请联系删除。本文旨在将Framework的框架描绘出来,主要是记录我一段时间关于android framework的学习,希望抛砖引玉,对于读者有一定的帮助。
前言
写在前面:
1、有没有必要学习linux内核?
我认为是很有必要的。学习linux内核有助于我们加深对一些概念的理解,比如“进程”、“线程”。推荐入门的教程:中国大学MOOC李治军老师的操作系统课程
2、有没有必要自己编译android源码?
非必须,可以直接用android studio查看sdk源码,除非要调试一些功能。但是要亲自操练起来才能更熟悉。
android framework与我们的开发息息相关,本文将从开机,即framework的初始化讲起,然后再涉及android运行过程中的几个使用场景。比如用户启动app(点击桌面图标后发生了什么),用户使用app(一次触摸,Android到底干了啥)。其中会涉及主线程、anr、handler、binder、zygote、app的入口是什么、自定义view为什么要三步走等一些我们经常接触的概念,并一一解答。涉及源码为api 27。
一、初始化篇
当按开机键的时候,设备首先执行BootLoader,BootLoader负责把Linux内核从加载到内存,并执行内核的初始化,最后内核将读取init.rc文件,并启动该文件中定义的各种服务程序。Android framework对于内核而言就是一个Linux程序而已,而该程序就在init.rc文件中被定义。Android framework的初始化过程由此开始。
首先被创建的是zygote进程,这是系统中运行的第一个Dalvik虚拟机程序,顾名思义,后面所有Dalvik虚拟机进程都是通过它“孵化”而来(学过生物的我们都知道,人体所有的细胞都是由受精卵分裂而来,所以本人觉得这个名称取得非常准确巧妙)。
zygote孵化出的第一个 Dalvik1 进程叫做 SystemServer,是Framework相当重要的进程。 SystemServer 仅仅是该进程的别名,而该进程具休对应的程序依然是 app\_process, 因为 SystemServer 是从 app\_process中孵化出来的。Ams、Wms、Pms等等都在此进程中创建,可以说SystemServer管理Framework所有的活动。
注1:Andoird 4.4引入ART
SystemServer 中创建了一个 Socket2 客户端,并有AmS负责管理该客户端,之后所有的 Dalvik 进程都将通过该 Socket 客户端间接被启动。当要启动新的 APK 进程时 ,AmS 中会通过该 Socket 客户端向 zygote 进程的 Socket服务端发送一个启动命令,然后zygote会孵化出新的进程。
注2:此处涉及Android进程中通信的一种方法Socket,学过计算机网络的读者应该对此有一定的概念。以后还会提及pipe、binder两种进程通信方法,无论如何,它们最终的目的都是为了让开发者跨进程调用时都像是在进行本地调用。至于它们的优缺点以及实现方式,读者可以自行探究。
1、zygote的启动
前面我们提到内核初始化时,会启动在init.rc文件中配置的程序,zygote相关的配置信息如下:
service zygote /system/bin/app\_process -Xzygote /system/bin --zygote --start-system-server
简单说明一下这个语句的意思,内核会执行/system/bin/app\_process3目录下的程序,启动一个叫zygote的服务。其中参数--start-system-server, 仅在指定 -- zygote 参数时才有效,意思是告知Zygotelnit启动完毕后孵化出第一个进程SystemServer。由此可知,zygote启动后做的事情之一就是启动SystemServer。
注3:Android支持64位以后,会根据不同的ABI分别执行/system/bin/app\_process32和/system/bin/app\_process64目录。
当 zygote 服务从 app\_process 开始启动后,会启动一个 Dalvik 虚拟机,而虚拟机执行的第一个 Java类就是 ZygoteInit.java。(app进程fork自zygote进程,所以ZygoteInit.main同样也是app的入口,只不过会根据进程的不同执行不同的逻辑。这就是有时候我们程序错误日志的调用栈里面可以看到"…ZygoteInit.main……"的原因。)ZygoteInit会做另外两件事:一是前面提到的,启动一个Socket服务端口,该Socket端口用于接收启动新进程的命令;二是预加载的Framework大部分类及资源供后续app使用。zygote fork app进程时,并不需要复制这一部分,而是使用共享内存的方式。
总结: zygote的进程启动后主要做了三件事:分别是启动一个Socket服务,用于接收启动新进程的命令、预加载的Framework大部分类及资源以及启动SystemServer。
2、SystemServer的启动
SystemServer是在zygote进程中最终调用到Zygote.forkSystemServer方法启动的。启动后会做一些初始的配置,比如关闭Socket服务端(非zygote进程不需要),配置SystemServer运行环境。然后调用SystemServer.main。
SystemServer启动后,主要做两件事:一是通过SystemServerManager启动各种服务线程,比如AMS、WMS、PMS等等,并将其注册到ServiceManager(AMS、WMS与app的运行息息相关,其具体内容后面再展开);二是启动HomeActivity,也就是启动launcher,launcher与普通app的启动大同小异,后面再详细介绍。
3、ServiceManager的启动
此处的ServiceManager不是java世界的,而是native世界的。它也是通过init.rc配置启动的,其功能相当于service4的DNS服务器。SystemServer启动的各个服务都会注册于其中,我们在使用binder进行跨进程调用时,首先回去查询ServiceManager获取到对应service的binder引用,然后再进行后续操作。这个过程与我们通过域名查询dns服务器获得ip最后访问网站类似。
注意:我们查看Framework代码时候会发现SystemServiceRegistry类,这个类和系统服务的注册没有半毛钱关系,它只不过是将查询各种service的工具类缓存起来。
注4:这里不是指Android的四大组件之一的Service。
二、运行时篇
我们在使用android设备时,就是Framework的运行时。下面我会从两个关键场景说起:第一个场景,点击桌面图标,这个场景会涉及到android的消息机制、app的启动、activity的创建、window的创建和view的绘制。第二个场景,我们在滑动屏幕或者点击按钮等等,这个场景会涉及到Framework怎么获得硬件的事件以及app的事件分发机制。
1、点击桌面图标后发生了什么
Activity的启动
我们都知道桌面程序也就是launcher和普通的app基本没什么差别,我们点击桌面图标其实调用了Activity的startActivity方法。Activity是Context的子类5,所以本来应该是调用了Context的startActivity方法,不过Activity重载了该方法,和Context区别是少了是否有Intent.FLAG\_ACTIVITY\_NEW\_TASK的判断。这也是为什么我们在非Activity的Context(比如Service)启动时要加Intent.FLAG\_ACTIVITY\_NEW\_TASK。不论是Activity还是Context,最终都会调用到Instrumentation的execStartActivity方法,然后通过Binder跨进程调用Ams的startActivity方法。
注5:更准确的说Activity是ContextWrapper的子类,而ContextWrapper不过是个代理类。实际上Activity具体的操作都是由其成员变量mBase完成,而mBase是一个ContextImpl类(继承Context)。所以如果Context是一个Interface的话,那么这就是一个标准的静态代理模式。
Ams会调用到startActivityAsUser方法,然后调用ActivityStarter的startActivityMayWait方法。
ActivityStarter.java
final int startActivityMayWait(...){
...
//根据intent通过PMS查找activity相关信息
//如何没有在AndroidManifest.xml注册过就无法找到activity
ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
...
//见下方
int res = startActivityLocked(...);
...
}
int startActivityLocked(...){
...
//见下方
mLastStartActivityResult = startActivity(...);
...
}
private int startActivity(...){
...
//ActivityRecord是Activity在Ams的记录,与我们app的activity是一一对应的,
//它有一个成员变量appToken是一个binder类,后面app的activity就是通过这个
//类与Ams的activity通信的
ActivityRecord r = new ActivityRecord(...);
...
//调用startActivity的另一个重载,见下方
return startActivity(...);
}
private int startActivity(...){
...
//见下方
result = startActivityUnchecked(...);
...
}
private int startActivityUnchecked(...){
//初始化一些参数,比如mLaunchSingleTop(launchMode是否为singletop),调整mLaunchFlags等
setInitialState(...);
//进一步调整mLaunchFlags,比如原activity为singleinstance或者要启动的activity为
//singleinstance或singletask时,确保mLaunchFlags拥有 FLAG_ACTIVITY_NEW_TASK属性
computeLaunchingTaskFlags();
...
//查找是否有已启动的activity
ActivityRecord reusedActivity = getReusableIntentActivity();
if (reusedActivity != null) {
if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| isDocumentLaunchesIntoExisting(mLaunchFlags)
|| mLaunchSingleInstance || mLaunchSingleTask) {
//清理task使得目标activity在顶部,这里我们就可以明白
//FLAG_ACTIVITY_CLEAR_TOP或者singletask的原理。
...
if (top != null) {
...
//回调onNewIntent
deliverNewIntent(top);
}
...
}
}
...
//调整好stack以及task
mTargetStack.startActivityLocked(...);
...
//见下方
mSupervisor.resumeFocusedStackTopActivityLocked(...);
}
至此我们先总结一下,Ams根据intent通过PMS查找activity相关信息,这解释了为什么没有在AndroidManifest.xml注册就无法被启动。然后根据activity的launchMode、taskAffinity以及intent的launchFlags为activity找到合适的stack和task。stack、task以及ActivityRecord的关系如下图。Ams通过ActivityRecord保存activity的各种状态信息,以及通过其成员变量appToken(binder类)来和app的activity通信。
图片来自互联网
我们接着讲ActivityStackSupervisor的resumeFocusedStackTopActivityLocked方法,该方法会接着调用ActivityStack的resumeTopActivityUncheckedLocked方法,接着调用resumeTopActivityInnerLocked方法,然后再返回到ActivityStackSupervisor的startSpecificActivityLocked方法。
ActivityStackSupervisor.java
void startSpecificActivityLocked(...) {
if (app != null && app.thread != null) {
...
//如果该activity对应的app已启动,则直接启动activity
//具体见后面
realStartActivityLocked(...);
...
}
//通过Ams启动进程,具体见下方
mService.startProcessLocked(...);
}
final boolean realStartActivityLocked(...){
//这里的app.thread是一个binder类,用于与app的ActivityThread通信
//通过binder跨进程调用ActivityThread的scheduleLaunchActivity方法。
app.thread.scheduleLaunchActivity();
}
这里我们先接着讲通过Ams启动进程,Ams调用startProcessLocked后会紧接着调用另一个startProcessLocked重载
ActivityManagerService.java
final ProcessRecord startProcessLocked(...){
...
if (app != null && app.pid > 0) {
//如果app已启动且满足一些条件则直接返回
}
...
//见下方
startProcessLocked(...);
...
}
private final void startProcessLocked(...){
...
//entryPoint将作为参数通过socket传递,后面成为进程java代码执行的入口
if (entryPoint == null) entryPoint = "android.app.ActivityThread";
...
//见下方
startResult = Process.start(entryPoint,...);
...
}
Process的start方法会紧接着调用ZygoteProcess的start方法,然后调用startViaZygote方法。
ZygoteProcess.java
private Process.ProcessStartResult startViaZygote(...){
...
//openZygoteSocketIfNeeded方法作用是和zygote进程建立socket连接
//之前我们提到zygote进程会扮演socket服务端的角色接受命令然后fork出进出
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
...
}
private static Process.ProcessStartResult zygoteSendArgsAndGetResult(...){
//发送fork的命令以及上面提到entryPoint等其他参数
...
}
我们回到zygote进程,在zygote进程启动时,我们是调用到ZygoteInit的main方法进行初始化,其中会开启ZygoteServer的runSelectLoop线程一直循环接收命令。而其中的主要方法时ZygoteConnection的processOneCommand方法。
ZygoteConnection.java
Runnable processOneCommand(...){
//读取命令和参数
...
//fork进程
pid = Zygote.forkAndSpecialize(...);
...
//对linux下c编程有一定了解的朋友会知道,fork后子进程的pid为0
if (pid == 0) {
...
//处理子进程,见下方
return handleChildProc(parsedArgs, descriptors, childPipeFd);
}
}
private Runnable handleChildProc(...){
...
return ZygoteInit.zygoteInit(...);
}
ZygoteInit.java
public static final Runnable zygoteInit(...){
...
return RuntimeInit.applicationInit(...);
}
RuntimeInit.java
protected static Runnable applicationInit(...) {
//args.startClass就是我们之前提到的entryPoint,也就是"android.app.ActivityThread"
//由此可知app第一个被调用的方法是ActivityThread的main方法,这就是应用程序的入口。
return findStaticMain(args.startClass, args.startArgs, classLoader);
}
我们终于回到了自己的进程,也很明确ActivityThread的main方法(提到main方法,总是有一种无以言表的亲切感)就是应用程序的入口。接着继续探索。
ActivityThread.java
public static void main(String[] args) {
//创建looper,looper其实很好理解,就是一直在循环,一直在取指执行。
//(我们的计算机的原理不也是一直取指执行吗)
Looper.prepareMainLooper();
//创建ActivityThread,一开始我们看到这个名字会以为它是一个Thread的类
//事实上它也完全可以代表app的主线程,因为它拥sMainLooper,
//拥有sMainThreadHandler,它会和Ams以及其他系统服务打交道
//而我个人的理解,activity即活动,thread即线,它就是一条线串起了所有app的活动。
ActivityThread thread = new ActivityThread();
//建立与Ams的Binder通道,见下方
thread.attach(false);
//创建handler,handler其实就是一个工具,让我们往MessageQueue放消息,移除消息以及处理消息
//looper才是主角,looper会从MessageQueue一直取消息,然后执行
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
}
private void attach(boolean system) {
...
//mgr是一个binder类用于与Ams通信
mgr.attachApplication(mAppThread);
...
}
看到这我们已经很清楚什么是主线程了,进程最初运行的那个线程就是主线程。而后面我们会发现一个“恐怖”的事实,我们的app会一直运行在sMainLooper之中(也就是主线程之中,当然排除我们创建的其他线程),包括启动activity,发送触摸消息、按键消息,我们都会通过sMainThreadHandler交由sMainLooper处理(我们开发时通过handler进行同步也是这个原理)。既然清楚了主线程的概念,那么anr的原理也就很好理解了,sMainLooper在处理这个消息的时候如果超过5s(activity)或者20s(service)就会anr,这种确保用户体验的一个做法。
接下来我们回到Ams,Ams会紧接着调用attachApplicationLocked方法。
ActivityManagerService.java
private final boolean attachApplicationLocked(...){
...
//通过binder IPC通知ActivityThread创建Application
thread.bindApplication(...);
...
mStackSupervisor.attachApplicationLocked(app);
...
}
ActivityStackSupervisor.java
boolean attachApplicationLocked(...) throws RemoteException {
...
//看我们发现了什么,似曾相识。没错就是上面我们留下来的问题。
//道理很简单,我们在启动一个activity的时候发现进程未启动,
//当我们启动进程后当然得重新启动activity
realStartActivityLocked(...);
...
}
final boolean realStartActivityLocked(...){
...
//这里的thread是一个binder类,和ActivityThread是对应的
app.thread.scheduleLaunchActivity(...);
...
}
重新回到app进程,先看创建Application的流程
ActivityThread$ApplicationThread.java
public final void bindApplication(...){
//通过handler调用到handleBindApplication方法,接着调用到performLaunchActivity方法
sendMessage(H.BIND_APPLICATION, data);
}
ActivityThread.java
private void handleBindApplication(AppBindData data) {
...
//创建Instrumentation
mInstrumentation = new Instrumentation();
...
//创建Application,回调attachBaseContext方法
app = data.info.makeApplication(data.restrictedBackupMode, null);
...
//回调onCreate方法
mInstrumentation.callApplicationOnCreate(app);
...
}
接着看创建Activity的流程
ActivityThread$ApplicationThread.java
public final void scheduleLaunchActivity(...){
//通过handler调用到handleLaunchActivity方法,接着调用到performLaunchActivity方法
sendMessage(H.LAUNCH_ACTIVITY, r);
}
ActivityThread.java
private Activity performLaunchActivity(...){
//真正的Context类,也就是我们上面提到的mBase
ContextImpl appContext = createBaseContextForActivity(r);
...
//利用发射创建activity
activity = mInstrumentation.newActivity(...);
...
//将appContext赋给mBase,并且回调attachBaseContext(context);
//getInstrumentation()之前提到过调用Instrumentation的execStartActivity方法
//r.token为binder类与Ams的ActivityRecord对应,我的另一篇文章(见注6)提到它有重要作用
activity.attach(appContext, this, getInstrumentation(),r.token,...,app,...,window,...);
...
//回调onCreate
mInstrumentation.callActivityOnCreate(...);
}
至此startActivity的框架已描述完毕。
View的绘制
这一部分并不是要讲自定义view,而是将窗口的创建(包括添加与绘制)。 从WmS的角度来观察,一个窗口并不是一个Window类,而是一个View类。当WmS收到用户的消息后,需要把消息派发到窗口,View类本身并不能直接接收WmS传递过来的消息,真正接收用户消息的必须是IWindow类(binder类),而实现IWindow类的是ViewRoot.W类,每一个W内部都包含了一个View变量。这两句话引用自《Android内核剖析》,从后面讲解可知,Window类更多是窗口的抽象,而其中的view才是窗口的内容。
Framework中定义了三种窗口,分别为应用窗口、子窗口和系统窗口。其中应用窗口对应一个Activity,接下来就是讲解应用窗口(下面简称为窗口)的创建。既然窗口对应一个Activity,那么窗口就是在startActivity的过程中创建的。上面提到Activity的创建会回调onCreate的,而我们在开发的时候会在其中调用setContentView方法。而setContentView会调用Window类的setContentView方法,如果你去查看Activity的attach方法时,会发现Window类实际上是一个PhoneWindow类。
PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
...
installDecor();
...
//加载app自定义的布局,由下可知我们的布局至少包裹着两个view,
//先是由mContentParent,然后再由mDecor包裹
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}
private void installDecor() {
...
//创建DecorView(继承自FrameLayout),Decor顾名思义,窗口的所有内容都在这个view中展示,
//这个View是Activity所有View的root。
//这也是我们查看Activity view hierarchy,最外层都是FrameLayout的原因
mDecor = generateDecor(-1);
...
//根据不同的style配置inflate不同的xml布局,
//这些xml有个共同特点:都有一个id为ID_ANDROID_CONTEN的view
//所以可以通ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)
//为contentParent赋值
mContentParent = generateLayout(mDecor);
...
}
这一步只是将View创建出来,接下来还会涉及到两步:1、将窗口添加到Wms,Wms也会和Ams一样将窗口以一定形式管理起来。2、将View要展示的内容转成数据保存于屏幕缓冲区内存,交由系统绘制出来。
在startActivity的过程中,创建Activity后会接着调用handleResumeActivity。
ActivityThread.java
private void handleLaunchActivity(...){
...
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
...
//见下方
handleResumeActivity(...);
...
}
}
final void handleResumeActivity(...){
...
//回调onResume
r = performResumeActivity(token, clearHide, reason);
...
//将decor设为不可见
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
...
//调用Activity的makeVisible
r.activity.makeVisible();
...
}
Activity.java
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
//最后会调用到WindowManagerGlobal的addView
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
WindowManagerGlobal.java
public void addView(...) {
...
//创建ViewRootImpl(View root的抽象)
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//将view(这里其实是mDecor)、root(View的抽象)以及layoutParam缓存起来
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
...
//见下方
root.setView(view, wparams, panelParentView);
...
}
ViewRootImpl.java
public void setView(...) {
...
//下面小节接着讲
requestLayout();
...
//ipc到Wms,Session类为服务端
//mInputChannel是一个InputChannel类,是管道在java层的实现,后面讲到Android事件的时候会细说
res = mWindowSession.addToDisplay(...,mInputChannel);
...
}
Session.java
@Override
public int addToDisplay(...) {
//见下方
return mService.addWindow(...);
}
WindowManagerService.java
public int addWindow(...) {
//创建WindowState
//其中session是Session的Binder服务端
//client是IWindow的Binder客户端
final WindowState win = new WindowState(..., session, client,...);
...
//会调用到Session的windowAddedLocked方法,见下方
win.attach();
//将win缓存起来
mWindowMap.put(client.asBinder(), win);
...
if (win.canReceiveKeys()) {
//如果该窗口是可交互窗口(比如Toast为不可交互窗口),则更新聚焦窗口
focusChanged = updateFocusedWindowLocked(...);
...
}
}
Session.java
void windowAddedLocked(String packageName) {
...
if (mSurfaceSession == null) {
//创建SurfaceSession,该类是直接和Surfaceflinger交互的类,
//用于向SurfaceFlinger中添加、删除、变换窗口。由千每个应用程序仅对应一个Session对象,
//因此,mSurfaceSession实际上只会被创建一次,
//即应用程序中的第一个窗口创建时会构造一个SurfaceSession对象,
//同一个应用程序中后续的窗口添加不会再构造 SurfaceSession 对象.
mSurfaceSession = new SurfaceSession();
...
//保存Session
mService.mSessions.add(this);
...
}
...
}
到这里可以看到Wms将窗口的信息保存下来,也就是管理起来,是事件分发的基础。
回到上面的requestLayout方法,requestLayout调用了scheduleTraversals方法,该方法发起一个View树遍历的消息,该消息是异步处理的,对应的处理函数为performTraversals方法。
ViewRootImpl.java
private void performTraversals() {
final View host = mView;
if (mFirst) {
...
//如果是窗口第一次显示,为mAttachInfo初始化,并赋给mView,
//调用ViewGroup的dispatch方法,将mAttachInfo递归地传递给子View
host.dispatchAttachedToWindow(mAttachInfo, 0);
}
...
//如果窗口尺寸发生了改变,则调用 host.measure()重新计算窗口中视图的大小
//Android的View和ViewGroup是很经典的组合模式
//measure过程会遍历整个View tree,会调用每个View的measure以及回调onMeasure
//layout和draw的过程也是类似
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//根据以七步骤的执行结果,判断是否需要进行重新布局。比如当任意视图的大小发生变化时,
//它会影响其他视图的布局
performLayout(lp, mWidth, mHeight);
...
//判断没有取消绘制,并且不是 newSurface, 则开始绘制
//newSurface变拉的含义是指,ViewRoot中创建了Surface对象,但是该对象还没有被WmS分配真正的显存,
//ViewRoot中是调用sWindowSession.relayoutWindow()为该Surface对象中分配真正的显存,
//在一般情况下,此处的newSurface都是false。
performDraw();
...
}
第一次由于窗口设置不可见,所以前面的代码可以看到,在Activity的makeVisible方法会调用mDecor.setVisibility(View.VISIBLE),经过一系列调用会再次调用到ViewRootImpl的performTraversals方法,然后调用performDraw方法。
在讲绘制之前,首先我们要清楚几个概念。
- Surface:
Surface是原始图像缓冲区(raw buffer)的一个句柄。也就是说Surface对应一段内存,这段内存的数据就是要绘制的内容,Surface 类本质上就是表示一个平面
- Canvas:
我们知道绘制不同图案显然是一种橾作,而不是一段数据。Android用Canvas类来表示这些操作,也就是说Canvas就是绘制的功能类。看Canvas的描述:A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap)——Canvas的功能就是响应draw的调用,并将其写入bitmap,而这个bitmap用于保存所有像素,你就会更清楚Canvas的概念。
ViewRootImpl.java
private void performDraw() {
...
draw(fullRedrawNeeded);
...
}
private void draw(boolean fullRedrawNeeded) {
//之前向Wms申请的
Surface surface = mSurface;
...
if (!drawSoftware(...)) {
return;
}
...
}
private boolean drawSoftware(...) {
...
//获取canvas并锁定
canvas = mSurface.lockCanvas(dirty);
...
//遍历子类的draw,很显然这一过程就是将所有子类的内容转成像素写入Canvas的bitmap中
mView.draw(canvas);
...
//将保存所有view内容的Canvas提交给surface,交由系统底层绘制。
surface.unlockCanvasAndPost(canvas);
...
}
至此view绘制的框架已描述完毕。
2、一次触摸,Android到底干了啥
这个标题来自《一次触摸,Android到底干了啥》,所以下面很多内容会引用自这篇文章。
一次触摸意味着什么?我们在使用Android设备的过程中,点击、长按、滑动(TouchEvent)以及按实体按键(KeyEvent)都可以成为“一次触摸”。因此,一次触摸可以代表所有我们使用过程中的操作。
我们的触摸当然是从硬件开始,硬件会将事件传递到内核,内核传递到Framework。前面提到SystemServer启动时会启动各种服务:
SystemServer.java
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
...
startOtherServices();
...
}
private void startOtherServices() {
...
//创建InputManagerService
inputManager = new InputManagerService(context);
...
//创建WindowManagerService,且持有InputManagerService
wm = WindowManagerService.main(..., inputManager,...);
...
//将InputMonitor设为回调,且启动InputManagerService
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
}
InputManagerService.java
public InputManagerService(Context context) {
...
//初始化native对象(mPtr是long,保存native对象的指针,这是jni常用的保持native对象的方法)
//InputManagerService对应native的InputManager.cpp
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
...
}
public void start() {
...
//用于启动下面提到的两个线程
nativeStart(mPtr);
...
}
InputManager.cpp
InputManager::InputManager(...) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
/**
由上面的代码,我们可以看到InputManager初始化时创建了InputDispatcher、InputReader
以及InputReaderThread、InputDispatcherThread。InputReader用来读取输入信息(也就是各种事件),
InputDispatcher用于分发事件。而两个线程很显然就是来运行这两个功能。
*/
InputReader线程会持续调用输入设备的驱动,读取所有用户输入的消息该线程和InputDispatcher线程都在系统进程(system\_process)空间中运行。InputDispatcher线程从自己的消息队列中取出原始消息,取出的消息有可能经过两种方式进行派发。第一种是经过管道(Pipe)直接派发到客户窗口中,另一种则是先派发到WmS中,由WmS经过一定的处理,如果WmS没有处理该消息,则再派发到客户窗口中,否则 ,不派发到客户窗口(引用自《Android内核剖析》)。如图(图片同样来自《Android内核剖析》):
如果是按键消息,InputDispatcher会先回调InputManager中定义的回调函数,这既会回调InputMonitor中的回调函数,这又会调用WmS中定义的相关函数。对于系统按键消息,比如"Home"键、电话按键等,WmS内部会按照默认的方式处理,如果事件被消耗,InputDispatcher则不会继续把这些按键消息传递给客户窗口对于触摸屏消息,InputDispatcher则直接传递给客户窗口。
InputManagerService.java
//按键事件的回调,前面我们提到回调对象是InputMonitor
// Native callback.
private long interceptKeyBeforeDispatching(InputWindowHandle focus,
KeyEvent event, int policyFlags) {
return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags);
}
InputMonitor.java
@Override
public long interceptKeyBeforeDispatching(
InputWindowHandle focus, KeyEvent event, int policyFlags) {
WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
//回调Wms,mPolicy在SystemServer初始化时创建,为PhoneWindowManager类,可以看到其中对各种按键的处理
return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
}
当InputDispatcher直接传递给窗口时,通过findTouchedWindowAtLocked方法找到当前获得焦点的窗口,然后通过Pipe(管道)进行ipc。也就是说在native层也会维护一组窗口相关的信息。我们回到Wms添加窗口的过程:
WindowManagerService.java
public int addWindow(...) {
//创建WindowState,构造方法中会创建InputWindowHandle
final WindowState win = new WindowState(...);
...
//建立管道通信,其中outInputChannel是从app进程传递过来的
win.openInputChannel(outInputChannel是从app进程);
...
//更新窗口焦点改变的信息到native层,同理当窗口切换或者销毁时也会更新
mInputMonitor.updateInputWindowsLw(false /*force*/);
...
}
WindowState.java
void openInputChannel(InputChannel outInputChannel) {
...
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
/*Channel[0]保存在server端*/
mInputWindowHandle.inputChannel = inputChannels[0];
...
/* Channel[1]返回给app的ViewRootImpl端*/
mClientChannel.transferTo(outInputChannel);
...
/*注册到InputManagerService native层*/
mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
}
可以看到,在Wms中维护一组WindowState,用于窗口的创建、销毁切换,而在InputManagerService则维护一组InputWindowHandle,用于事件的分发。
我们回到ViewRootImpl中。在添加窗口时,我们调用了setView方法。
ViewRootImpl.java
public void setView(...) {
...
//InputChannel类,是管道在java层的实现
mInputChannel = new InputChannel();
...
//查看IWindowSession.aidl会发现这里的mInputChannel标注着out,
//也就是在另一个进程的改变都会同步到本进程
res = mWindowSession.addToDisplay(...,mInputChannel);
...
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
//建立管道的回调,见下方
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());
}
}
ViewRootImpl$WindowInputEventReceiver.java
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
//见下方
super(inputChannel, looper);
}
InputEventReceiver.java
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
...
//建立管道的回调,native层收到事件就会回调给InputEventReceiver
mReceiverPtr = nativeInit(new WeakReference(this),
inputChannel, mMessageQueue);
...
}
当InputManagerService的InputDispatcher通过管道将消息传递给app进程,app进程的管道会回调InputEventReceiver(也就是WindowInputEventReceiver),进行事件分发。后面的代码可以说是责任链模式的标准答案,非常精彩读者可以自行学习。
总结
Android Framework可以说是一个庞大的工程,如果我们在一开始的过程中就陷入细节,就无法走通一条路。君子善假于物也,借助大佬的研究学习成果,我们可以先学习整体的框架,有必要时再各个击破。非常感谢各个大佬,下面的参考文献可能有所遗漏,在此致歉!希望本篇文章对于读者有所帮助。
本文转自 https://juejin.cn/post/6844903717301387272,如有侵权,请联系删除。