一篇文章读懂Android Framework

news2024/10/7 16:27:33

本文旨在将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) {
                   ...
                   //回调onNewIntentdeliverNewIntent(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(...);
    ...
}
privatefinalvoidstartProcessLocked(...){
    ...
    //entryPoint将作为参数通过socket传递,后面成为进程java代码执行的入口if (entryPoint == null) entryPoint = "android.app.ActivityThread";
    ...
    //见下方
    startResult = Process.start(entryPoint,...);
    ...
}
复制代码

Process的start方法会紧接着调用ZygoteProcess的start方法,然后调用startViaZygote方法。

ZygoteProcess.java
privateProcess.ProcessStartResult startViaZygote(...){
    ...
    //openZygoteSocketIfNeeded方法作用是和zygote进程建立socket连接//之前我们提到zygote进程会扮演socket服务端的角色接受命令然后fork出进出returnzygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
    ...
}

privatestaticProcess.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
publicstaticvoidmain(String[] args){
    //创建looper,looper其实很好理解,就是一直在循环,一直在取指执行。//(我们的计算机的原理不也是一直取指执行吗)
    Looper.prepareMainLooper();
    //创建ActivityThread,一开始我们看到这个名字会以为它是一个Thread的类//事实上它也完全可以代表app的主线程,因为它拥sMainLooper,//拥有sMainThreadHandler,它会和Ams以及其他系统服务打交道//而我个人的理解,activity即活动,thread即线,它就是一条线串起了所有app的活动。
    ActivityThread thread = newActivityThread();
    //建立与Ams的Binder通道,见下方
    thread.attach(false);
    //创建handler,handler其实就是一个工具,让我们往MessageQueue放消息,移除消息以及处理消息//looper才是主角,looper会从MessageQueue一直取消息,然后执行if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    ...
    Looper.loop();
}

 privatevoidattach(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
privatefinalbooleanattachApplicationLocked(...){
    ...
    //通过binder IPC通知ActivityThread创建Application
    thread.bindApplication(...);
    ...
    mStackSupervisor.attachApplicationLocked(app);
    ...
}

ActivityStackSupervisor.java
booleanattachApplicationLocked(...) throws RemoteException {
    ...
    //看我们发现了什么,似曾相识。没错就是上面我们留下来的问题。//道理很简单,我们在启动一个activity的时候发现进程未启动,//当我们启动进程后当然得重新启动activityrealStartActivityLocked(...);
    ...
}

finalbooleanrealStartActivityLocked(...){
    ...
    //这里的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(...);
}
复制代码
注6:文章: 一个极简的RePlugin

至此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.javaprivatevoidperformDraw() {
    ...
    draw(fullRedrawNeeded);
    ...
}

privatevoiddraw(boolean fullRedrawNeeded) {
    //之前向Wms申请的Surface surface = mSurface;
    ...
    if (!drawSoftware(...)) {
        return;
    }
    ...
}

privatebooleandrawSoftware(...) {
    ...
    //获取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.privatelonginterceptKeyBeforeDispatching(InputWindowHandle focus,
           KeyEvent event, int policyFlags) {
   return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags);
}

InputMonitor.java
@Override
publiclonginterceptKeyBeforeDispatching(
           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可以说是一个庞大的工程,如果我们在一开始的过程中就陷入细节,就无法走通一条路。 在这里特别提供一份 Android 高级开发学习笔记, 里面包含了这些年学习 Android Framework精编内核,有需要这份 Android Framework高级开发笔记的朋友: 可以点击此处传送门 希望大家阅读过后,能够 查漏补缺;早日成为高级开发者

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/365896.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JVM18运行时参数

4. JVM 运行时参数 4.1. JVM 参数选项 官网地址&#xff1a;https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html 4.1.1. 类型一&#xff1a;标准参数选项 > java -help 用法: java [-options] class [args...](执行类)或 java [-options] -jar …

图解LeetCode——剑指 Offer 47. 礼物的最大价值

一、题目 在一个 m*n 的棋盘的每一格都放有一个礼物&#xff0c;每个礼物都有一定的价值&#xff08;价值大于 0&#xff09;。你可以从棋盘的左上角开始拿格子里的礼物&#xff0c;并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值&…

mongoTemplate Aggregation 多表联查 排序失效问题解决

目录说明说明 接着上一个文章的例子来说&#xff1a;mongoTemplate支持多表联查 排序 条件筛选 分页 去重分组 在按照上一个demo的代码执行后&#xff0c;可能会发生排序失效的问题&#xff0c;为什么说可能呢&#xff1f;每个人负责业务不同&#xff0c;不可能是最简单的dem…

树莓派CM4基础设置

安装系统1.1 软件和硬件准备硬件&#xff1a;CM4&#xff08;4GB DDR32GB EMMC 板载WIFI和蓝牙&#xff09;CM4-to-Pi4-Adapter软件&#xff1a;Raspberry Pi或者 Win32DiskImagerRaspberry Pi下载链接&#xff1a;点击直接下载Win32DiskImager下载链接&#xff1a;链接&#x…

el-table大数据量渲染卡顿问题

1、场景描述 在项目开发中&#xff0c;遇到在表格中一次性加载完的需求&#xff0c;且加载数量不少&#xff0c;有几百几千条&#xff0c;并且每条都可能有自己的下拉框&#xff0c;输入框来做编辑功能&#xff0c;此时普通的el-table肯定会导致浏览器卡死&#xff0c;那么怎么…

【Python小程序】怀旧经典 | 特色玩法,代码版本的钢琴小游戏了解下?初学钢琴,能提高双手协调与反应能力哦~(源码分享)

导语 哈喽&#xff0c;我是木木子鸭&#xff01; 最近给大家悄悄的更新了一些关于爬虫的内容呢~有想学习爬虫的小可爱可以学习一整子啦。 今天来给大家写一款界面化的&#xff08;Tkinter&#xff09;电子钢琴小程序。 ​ 所有文章完整的素材源码都在&#x1f447;&#x1…

Qt程序使用路径方式和注意事项

Qt程序使用路径方式和注意事项 更多精彩内容&#x1f449;个人内容分类汇总 &#x1f448;&#x1f449;Qt开发经验 &#x1f448;文章目录Qt程序使用路径方式和注意事项[toc]前言一、Windows下Qt程序使用路径1.准备工作2.测试结果二、Linux下Qt程序使用路径1.准备工作2.测试结…

Python如何实现自动登录和下单的脚本,请看selenium的表演

前言 学python对selenium应该不陌生吧 Selenium 是最广泛使用的开源 Web UI&#xff08;用户界面&#xff09;自动化测试套件之一。Selenium 支持的语言包括C#&#xff0c;Java&#xff0c;Perl&#xff0c;PHP&#xff0c;Python 和 Ruby。目前&#xff0c;Selenium Web 驱动…

某餐厅系统网络故障分析案例

背景 针对食堂经营企业&#xff0c;某堂食软件为客户提供优化堂食就餐流程、提高食堂服务水平和管理效率。 某上海客户使用该堂食系统&#xff0c;在就餐高峰时段&#xff0c;总是出现支付、点餐等操作缓慢&#xff0c;动辄一个操作需要等待几十秒。该客户联系软件厂商&#…

浮点数在内存中的存储——“C”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰的内容是浮点数在内存中的存储&#xff0c;昨天我们已经写过了整型在内存中的存储&#xff0c;那么&#xff0c;浮点数在内存中是怎样存储的呢&#xff1f;现在&#xff0c;就让我们进入浮点数在内存中的存储的世界吧…

超实用的公众号运营攻略分享,纯干货

很多小伙伴抱怨&#xff0c;公众号运营真的越来越难做了&#xff01; 每天会因为少得可怜的阅读量发愁&#xff0c;每天会因为纠结写什么选题发愁&#xff0c;每天更会因为公众号没有什么起色而感到无力。 现阶段公众号运营趋于饱和状态&#xff0c;公众号创建门槛低&#xf…

在Pandas中通过时间频率来汇总数据的三种常用方法

当我们的数据涉及日期和时间时&#xff0c;分析随时间变化变得非常重要。Pandas提供了一种方便的方法&#xff0c;可以按不同的基于时间的间隔(如分钟、小时、天、周、月、季度或年)对时间序列数据进行分组。 在Pandas中&#xff0c;有几种基于日期对数据进行分组的方法。我们将…

spark08-spark任务启动环境准备

内容来自尚硅谷1.submitApplication当spark执行任务时会启动java虚拟机&#xff0c;启动一个进程&#xff0c;该进程的名称为SparkSubmit&#xff0c;会执行SparkSubmit中的main方法&#xff0c;该方法中调用了super.doSubmit方法。org.apache.spark.deploy.SparkSubmitdoSubmi…

Python abs() 函数

Python abs() 函数Python 数字描述abs() 函数返回数字的绝对值。语法以下是 abs() 方法的语法:abs( x )参数x -- 数值表达式。返回值函数返回x&#xff08;数字&#xff09;的绝对值。实例以下展示了使用 abs() 方法的实例&#xff1a;#!/usr/bin/python print "abs(-45) …

百度西交大大数据菁英班目标检测竞赛

来源&#xff1a;投稿 作者&#xff1a;LSC 编辑&#xff1a;学姐 数据介绍 数据集共包括40000张训练图像和1000张测试图像&#xff0c;每张训练图像对应xml标注文件&#xff1a; 共包含3类&#xff1a;0:head, 1:helmet, 2:person。 提交格式要求&#xff0c;提交名为pred_r…

如何为Java文件代码签名及添加时间戳?

Java是一种流行的编程语言&#xff0c;大多数组织都使用它来开发业务应用程序。由于其高使用率&#xff0c;攻击者总是试图找到其中的漏洞并基于它利用软件。为了防止此类攻击&#xff0c; 为 Java 文件&#xff08;.jar&#xff09;进行代码签名并添加时间戳&#xff0c;可以防…

Netty网络编程实战:基于Netty的Http服务器开发

Netty网络编程实战&#xff1a;基于Netty的Http服务器开发 文章目录Netty网络编程实战&#xff1a;基于Netty的Http服务器开发介绍功能需求服务端代码实现基于Netty的WebSocket开发网页版聊天室WebSocket简介WebSocket和HTTP的区别基础环境准备服务端开发Netty中粘包和拆包的解…

关于PHP的webshell免杀小结

0X00普通的一句话木马&#xff08;适用于CTF和小站&#xff09; <?php eval($_POST[a]); ?> //函数的相似替换 <?php assert($_POST[a]); ?><?php eval($_POST[110]);?>与第一个一句话木马相比多了一个"“字符&#xff0c;我们发现这个字符的含义…

【科研】测试速通:python不同文件夹下同名图像拼接

论文必备图像拼接笔记 速通结果&#xff1a; 现有&#xff1a;测试样本相同&#xff08;名命相同&#xff09;&#xff0c;测试模型不同&#xff0c;测试结果分别保存至不同文件夹 目标&#xff1a;结果显示在同一张图像上 目录 论文必备图像拼接笔记 1.如果图像格式不一致…

怎么维护Linux VPS 服务器?简单7个步骤

维护VPS的目的是为了确保服务器网络始终畅通无阻。请注意&#xff0c;此列表中的任务并不是服务器维护所需完成的唯一任务。以下是 Linux VPS 服务器所有者可以做些什么来维护他们的服务器。 1.监控磁盘空间 服务器是个人服务器还是具有多个用户帐户的服务器并不重要&#xff0…