本文旨在将Framework的框架描绘出来,主要是记录我一段时间关于android framework的学习,希望抛砖引玉,对于读者有一定的帮助。

前言

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所有的活动。

SystemServer 中创建了一个 Socket2 客户端,并有AmS负责管理该客户端,之后所有的 Dalvik 进程都将通过该 Socket 客户端间接被启动。当要启动新的 APK 进程时 ,AmS 中会通过该 Socket 客户端向 zygote 进程的 Socket服务端发送一个启动命令,然后zygote会孵化出新的进程。

1、zygote的启动

前面我们提到内核初始化时,会启动在init.rc文件中配置的程序,zygote相关的配置信息如下:

简单说明一下这个语句的意思,内核会执行/system/bin/app\_process3目录下的程序,启动一个叫zygote的服务。其中参数--start-system-server, 仅在指定 -- zygote 参数时才有效,意思是告知Zygotelnit启动完毕后孵化出第一个进程SystemServer。由此可知,zygote启动后做的事情之一就是启动SystemServer。

当 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的工具类缓存起来。

二、运行时篇

我们在使用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方法。

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(...);
}
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是原始图像缓冲区(raw buffer)的一个句柄。也就是说Surface对应一段内存,这段内存的数据就是要绘制的内容,Surface 类本质上就是表示一个平面

我们知道绘制不同图案显然是一种橾作,而不是一段数据。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<InputEventReceiver>(this),
                    inputChannel, mMessageQueue);
    ...
}

当InputManagerService的InputDispatcher通过管道将消息传递给app进程,app进程的管道会回调InputEventReceiver(也就是WindowInputEventReceiver),进行事件分发。后面的代码可以说是责任链模式的标准答案,非常精彩读者可以自行学习。

总结

Android Framework可以说是一个庞大的工程,如果我们在一开始的过程中就陷入细节,就无法走通一条路。君子善假于物也,借助大佬的研究学习成果,我们可以先学习整体的框架,有必要时再各个击破。非常感谢各个大佬,下面的参考文献可能有所遗漏,在此致歉!希望本篇文章对于读者有所帮助。

本文转自 https://juejin.cn/post/6844903717301387272,如有侵权,请联系删除。本文旨在将Framework的框架描绘出来,主要是记录我一段时间关于android framework的学习,希望抛砖引玉,对于读者有一定的帮助。

前言

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所有的活动。

SystemServer 中创建了一个 Socket2 客户端,并有AmS负责管理该客户端,之后所有的 Dalvik 进程都将通过该 Socket 客户端间接被启动。当要启动新的 APK 进程时 ,AmS 中会通过该 Socket 客户端向 zygote 进程的 Socket服务端发送一个启动命令,然后zygote会孵化出新的进程。

1、zygote的启动

前面我们提到内核初始化时,会启动在init.rc文件中配置的程序,zygote相关的配置信息如下:

简单说明一下这个语句的意思,内核会执行/system/bin/app\_process3目录下的程序,启动一个叫zygote的服务。其中参数--start-system-server, 仅在指定 -- zygote 参数时才有效,意思是告知Zygotelnit启动完毕后孵化出第一个进程SystemServer。由此可知,zygote启动后做的事情之一就是启动SystemServer。

当 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的工具类缓存起来。

二、运行时篇

我们在使用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方法。

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(...);
}
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是原始图像缓冲区(raw buffer)的一个句柄。也就是说Surface对应一段内存,这段内存的数据就是要绘制的内容,Surface 类本质上就是表示一个平面

我们知道绘制不同图案显然是一种橾作,而不是一段数据。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<InputEventReceiver>(this),
                    inputChannel, mMessageQueue);
    ...
}

当InputManagerService的InputDispatcher通过管道将消息传递给app进程,app进程的管道会回调InputEventReceiver(也就是WindowInputEventReceiver),进行事件分发。后面的代码可以说是责任链模式的标准答案,非常精彩读者可以自行学习。

总结

Android Framework可以说是一个庞大的工程,如果我们在一开始的过程中就陷入细节,就无法走通一条路。君子善假于物也,借助大佬的研究学习成果,我们可以先学习整体的框架,有必要时再各个击破。非常感谢各个大佬,下面的参考文献可能有所遗漏,在此致歉!希望本篇文章对于读者有所帮助。

本文转自 https://juejin.cn/post/6844903717301387272,如有侵权,请联系删除。

03-05 21:09