Android Framework的框架描绘~

news2024/11/16 13:48:31

前言

写在前面:
1、有没有必要学习linux内核?
我认为是很有必要的。学习linux内核有助于我们加深对一些概念的理解,比如“进程”、“线程”。
2、有没有必要自己编译android源码?
非必须,可以直接用android studio查看sdk源码,除非要调试一些功能。但是要亲自操练起来才能更熟悉。

android framework与我们的开发息息相关,本文将从开机,即framework的初始化讲起,然后再涉及android运行过程中的几个使用场景。比如用户启动app(点击桌面图标后发生了什么),用户使用app(一次触摸,Android到底干了啥)。其中会涉及主线程、anr、handler、binder、zygote、app的入口是什么、自定义view为什么要三步走等一些我们经常接触的概念,并一一解答。

一、初始化篇

当按开机键的时候,设备首先执行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方法。

在讲绘制之前,首先我们要清楚几个概念。

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

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

总结

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

为了帮助大家可以更深入的了解** Framework底层知识点,下面整理好了《Framework 知识点汇总》相关的学习文档!既能够夯实底层原理、源码解析等核心技术点,又能够掌握普通开发者,难以触及的复杂系统问题设计方案

每个知识点都有左侧导航书签页,看的时候十分方便,由于内容较多,这里就截取一部分图吧。需要的读者朋友们可以进行参考:https://0a.fit/acnLL

《Framework 核心知识点汇总手册》

Handler 机制实现原理部分
1.宏观理论分析与Message源码分析
2.MessageQueue的源码分析
3.Looper的源码分析
4.handler的源码分析
5.总结

Binder 原理

1.学习Binder前必须要了解的知识点
2.ServiceManager中的Binder机制
3.系统服务的注册过程
4.ServiceManager的启动过程
5.系统服务的获取过程
6.Java Binder的初始化
7.Java Binder中系统服务的注册过程

Zygote

  1. Android系统的启动过程及Zygote的启动过程
  2. 应用进程的启动过程

AMS源码分析

  1. Activity生命周期管理
  2. onActivityResult执行过程
  3. AMS中Activity栈管理详解

深入PMS源码

1.PMS的启动过程和执行流程
2.APK的安装和卸载源码分析
3.PMS中intent-filter的匹配架构

WMS
1.WMS的诞生
2.WMS的重要成员和Window的添加过程
3.Window的删除过程

《Android Framework学习手册》:

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

每个知识点都有左侧导航书签页,看的时候十分方便,由于内容较多,这里就截取一部分图吧。需要的读者朋友们可以进行参考:https://0a.fit/acnLL

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

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

相关文章

【图表利剑】开发复杂Echarts时,visualMap视觉组件中属性seriesIndex必须用起来

一、背景 据说90%的可视化是用Echarts开发&#xff0c;没错&#xff0c;笔者也没例外&#xff0c;在新的开发项目中&#xff0c;遇到了这个开发神器Echarts&#xff0c;想要的功能就是在省份上显示动态效果图&#xff0c;比如涟漪。原来的功能已有范围视觉组件visualMap。 二…

【Matplotlib绘制图像大全】(十九):Matplotlib绘制等高线

前言 大家好,我是阿光。 本专栏整理了《Matplotlib绘制图像大全》,内包含了各种常见的绘图方法,以及Matplotlib各种内置函数的使用方法,帮助我们快速便捷的绘制出数据图像。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmMatp…

SAP ABAP——数据类型(四)【TYPE系列关键字】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

gitHub不能用密码推送了,必须要使用令牌

有一段时间没使用github去push项目了&#xff0c;今天push之后&#xff0c;根据提示输入账号密码&#xff0c;但是失败了&#xff0c;报错如下&#xff1a; support for password authentication was removed on August 13, 2021. remote: Please see https://docs.github.com…

【Swin Transformer原理和源码解析】Hierarchical Vision Transformer using Shifted Windows

目录前言一、动机和改进点二、整体架构&#xff1a;SwinTransformer三、输入设置&#xff1a;PatchEmbed四、4个重复的Stage&#xff1a;BasicLayer4.1、SwinTransformerBlock4.1.1、创建mask4.1.2、shift特征4.1.3、为shift后的特征划分窗口4.1.4、W-MSA VS SW-MSA4.2、PatchM…

Android APP 停止运行报错弹窗

一个APP经常性的报错,然后就会弹出一个"很抱歉,xxx已停止运行"这样的弹窗,在Android系统层临时将这个弹窗屏蔽.弹窗如下: 在没做过此类修改之前,不知到如何下手的情况下,请做如下几步: 在Android目录下全局搜索关键字"很抱歉",然后会有一个路径frameworks…

ICS计算系统概论实验3—LC3汇编代码实现最长重复子字符串Longest-duplicate-substring

Lab03 Longest-duplicate-substring Purpose 子字符串是字符串中至少出现一次的连续字符序列。重复子字符串是一种由相同字符组成的子字符串。例如&#xff0c;“aabbbc”的重复子字符串是“aa”&#xff0c;“bbb”和“c”。 给定一个字符串及其长度&#xff0c;计算出它最长…

全球DTC品牌纷纷奔走线下,价值岂止于用户体验和品牌形象

走向线下&#xff0c;开设新零售门店/旗舰店/体验店/快闪店&#xff0c;已成为很多全球品牌的共同做法&#xff1a;从海外巨头亚马逊的Amazon go、国内的盒马O2O&#xff0c;到DTC经典品牌的Warby Parker、Allbirds们遍地开花的线下实体店&#xff0c;是什么让全球DTC品牌纷纷走…

【Matplotlib绘制图像大全】(二十三):Matplotlib保存图像

前言 大家好,我是阿光。 本专栏整理了《Matplotlib绘制图像大全》,内包含了各种常见的绘图方法,以及Matplotlib各种内置函数的使用方法,帮助我们快速便捷的绘制出数据图像。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmMatp…

Sqli-Libs 速通

Sqli-Libs持续更新...目标Less-1Less-2Less-3Less-4Less-5Less-6Less-7Less-8Less-9Less-10Less-11Less-12Less-13Less-14Less-15Less-16Less-17Less-18Less-19 bp处理Less-20目标 直接写payload&#xff0c;sql语句非预期执行就算成功 表&#xff1a;emails,referers,uagent…

scratch绘制旋转六边形 电子学会图形化编程scratch等级考试二级真题和答案解析2022年9月

目录 scratch绘制旋转六边形 一、题目要求 1、准备工作 2、功能实现

12月2日:thinkphp中数据库完结

数据库的查询 聚合查询 聚合查询的几种方法其中将count作为重点来说&#xff0c;由图所示&#xff0c;即为使用count()方法中需要注意的点 count(*)的使用count()中字段名为具体值的使用方法时间查询 官方文档中列举的是使用wheretime()进行查询的方法&#xff0c;但是在日常的…

如何在Windows上安装并启动MySql

如何在Windows上安装并启动MySql一、MySql 安装包下载二、MySql 初始化三、启动MySql服务四、登录MySql五、修改MySql的root密码六、关于远程登录七、设置环境变量一、MySql 安装包下载 首先进入以下网址&#xff0c;选择合适的版本进行下载即可。 https://dev.mysql.com/dow…

Bootstrap5 安装使用

我们可以通过以下两种方式来安装 Bootstrap5&#xff1a; 使用 Bootstrap5 CDN。 从官网 getbootstrap.com 下载 Bootstrap 5。 Bootstrap 5 CDN 国内推荐使用 Staticfile CDN 上的库&#xff1a; Bootstrap5 CDN <!-- 新 Bootstrap5 核心 CSS 文件 --> <link r…

基于vue-simple-uploader的断点续传

方案&#xff1a; 分片上传&#xff0c;再次上传时&#xff0c;查询已上传分片&#xff0c;继续上传剩余分片 实现效果&#xff1a; 1. 安装uploader和spark-md5的依赖 npm install --save vue-simple-uploader npm install --save spark-md5 2.mainjs导入uploader impo…

日期格式化 YYYY-MM-DD 出现时间偏移量

在js中&#xff0c;很多时候需要把日期字符串转换为一个 Date 对象。 如果得到的日期字符串有时间还好办&#xff0c;如果没有时间&#xff0c;只有日期的格式&#xff0c;例如 2022-12-01 这样的字符串呢&#xff1f; 大部分人可能什么都没想&#xff0c;直接就调用了 new D…

MyBatis-Plus分页查询(快速上手运用)

系列文章目录 Mybatis-Plus知识点[MyBatisMyBatis-Plus的基础运用]_心态还需努力呀的博客-CSDN博客 Mybatis-PlusSpringBoot结合运用_心态还需努力呀的博客-CSDN博客MyBaits-Plus中TableField和TableId用法_心态还需努力呀的博客-CSDN博客 MyBatis-Plus中的更新操作&#…

【实操篇】Linux实用指令总结

目录 1.运行级别类 ●运行级别 ●指定运行级别 2.帮助指令类 ●帮助指令 1.man获得帮助信息 2.help指令 3.文件目录类 ●pwd指令 ●ls指令 ●cd指令 ●mkdir指令 ●rmdir指令 ●touch指令 ●cp指令 ●rm指令 ●mv指令 ●cat指令 ●more指令 ●less指令 ●>指令和>>…

如此简单的K8S,来玩下pv和pvc,利用nfs来实现持久化存储(内网环境,非常详细)

如此简单的K8S,来玩下pv和pvc&#xff0c;利用nfs来实现持久化存储(内网环境&#xff0c;非常详细) k8s很简单&#xff0c;怎么个简单法呢&#xff0c;来给小编一起再来复习一边吧。今天主要来了解下pv和pvc的概念&#xff0c;小编也是当过多次的面试官&#xff0c;小编悄悄的告…

低代码助力制造型企业管理:项目管理系统

制造业企业经过近两个世纪的发展&#xff0c;已经成为世界各国经济发展的支柱产业&#xff0c;要增强一个国家的综合国力&#xff0c;就必须首先建设一个强大的制造业。因此&#xff0c;在一些率先进入知识经济的工业发达国家&#xff0c;汽车、航空、装备等制造业依然保持支柱…