写点什么

关于 Android 的渲染机制,大厂面试官最喜欢问的 7 个问题【建议收藏

用户头像
Android架构
关注
发布于: 10 小时前

原来进程启动以后就会先去执行 ActivityThread:main 这个入口,应用自此开始了自己启动流程,这点 systrace 展示的非常清晰:



看到上面 PostFork 色块,很明显是 Process 创建成功后的打印,然后代码继续执行到 ZygoteInit,ZygoteInit 真正来查找 entrypoint,应用程序跳转到 ActivityThread.Main 开始执行:


public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {if (RuntimeInit.DEBUG) {Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");}


Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");RuntimeInit.redirectLogStreams();


RuntimeInit.commonInit();ZygoteInit.nativeZygoteInit();return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);}


上面代码 RuntimeInit.applicationInit 内部执行 findStaticMain 查找入口函数:


protected static Runnable applicationInit(int targetSdkVersion, String[] argv,ClassLoader classLoader) {// If the application calls System.exit(), terminate the process// immediately without running any shutdown hooks. It is not possible to// shutdown an Android application gracefully. Among other things, the// Android runtime shutdown hooks close the Binder driver, which can cause// leftover running threads to crash before the process actually exits.nativeSetExitWithoutCleanup(true);


// We want to be fairly aggressive about heap utilization, to avoid// holding on to a lot of memory that isn't needed.VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);


final Arguments args = new Arguments(argv);


// The end of of the RuntimeInit event (see #zygoteInit).Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);


// Remaining arguments are passed to the start class's static mainreturn findStaticMain(args.startClass, args.startArgs, classLoader);}


OK,至此进入 systrace 显示 ActivityThread.main 函数执行,也就是达到了 P0 的第 3 步骤。

ActivityThread 对象

ActivityThread main 执行的第一件事是调用 AMS 的 attacApplicationLock(P0 :6)向大管家汇报:“进程已经启动好了,继续往下启动吧”。AMS 收到汇报就回调了(P0:7)ActvityThread 的 bindApplication,这里“绑定”理解起来比较抽象,到底是要把哪些东西跟应用程序“绑定”起来呢?其实是把 app 本身的“上下文(context)”信息跟刚刚创建的进程绑定起来,噢,又出来一个“上下文(context)”概念,用大白话讲就是应用的 apk 包包含应用的所有身家信息,这些个信息就可以称为是应用的“上下文(context)”,应用可以通过这个 Context 访问自己的家当,此处会创建 Application Context(具体关于应用程序几种 context 区别自行 google,此处不予展开)


private void handleBindApplication(AppBindData data) {


mBoundApplication = data;mConfiguration = new Configuration(data.config);mCompatConfiguration = new Configuration(data.config);


final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);updateLocaleListFromAppContext(appContext,mResourcesManager.getConfiguration().getLocales());if (ii != null) {final ApplicationInfo instrApp = new ApplicationInfo();ii.copyTo(instrApp);instrApp.initForUser(UserHandle.myUserId());final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,appContext.getClassLoader(), false, true, false);final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);final ComponentName component = new ComponentName(ii.packageName, ii.name);mInstrumentation.init(this, instrContext, appContext, component,data.instrumentationWatcher, data.instrumentationUiAutomationConnection);} else {mInstrumentation = new Instrumentation();}


Application app;try {app = data.info.makeApplication(data.restrictedBackupMode, null);mInitialApplication = app;try {mInstrumentation.onCreate(data.instrumentationArgs);}try {mInstrumentation.callApplicationOnCreate(app);}}}


上面回调到应用程序 Application.onCreate 函数,很多应用会在此处做初始化动作,如果初始化模块过多可以考虑延迟加载,应用继续启动来到 P0:12/P0:13

Activity 对象

Activity 的构建开始窗口显示之旅,上面“Android 应用进程核心组成”架构图中可以看到 Activity 核心是 PhoneWindow,P0 图中步骤 13 performLauncherActivity 中包含了 14/15 两个重要的操作,attach 函数创建了“PhoneWindow”,这个窗口具体承载了什么信息?用大白话来说点击启动一个应用以后,可以说是显示了一个”窗口”(Window),这个“窗口”至少要承载两个功能:


  • 显示内容

  • 可以操作


窗口显示的内容就是 android 的布局(layout),布局信息需要有个“房间”存放,PhoneWindow:mDecor 就是这个“房间”,attach 首先将布局的“房间”建好,等到后续 15 onCreate 调用到就会调用 setContentView 使用应用程序开发者提供的布局(layout)“装饰、填充”这个“房间”。


“房间”填充、装饰好后,还需要能够接收用户的操作,这就要看 PhoneWindow 中 mWindowManager 对象,这个对象最终包含一个 ViewRootImpl 对象,“窗口”正是因为构建了 ViewRootImpl 才安装上了发动机。

attach 函数

final void attach(...) {mWindow = new PhoneWindow(this, window, activityConfigCallback);mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken,mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);}


mWindowManager 最后是一个 WindowManagerImpl 对象,WindowManagerImpl 对象的 mParentWindow 对应了 Activity 中的 PhoneWindow 对象。

setWindowManager 函数

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,boolean hardwareAccelerated) {mAppToken = appToken;mAppName = appName;mHardwareAccelerated = hardwareAccelerated|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);if (wm == null) {wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);}//this 对象对应 Activity 中的 PhoneWindow 对象 mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);}


OK,上面的 perfomrLaunchActivity 一顿操作已经完成两个“窗口(Activity)”中两个重要变量的初始化,流程走到 15 Activity:onCreate 函数。

onCreate 函数

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}


空的 HellWorld 工程都默认包含上面两行代码,setContentView 就是操作系统给开发机会告诉系统“到底让我显示什么?”就是这么简单的一行代码很可能就是导致应用性能卡顿,那么 setContentView 干啥了?

setContentView 函数

该函数的作用就是使用布局文件填充“房间”mDecor,如果布局文件非常复杂会导致“房间”装饰的费时费力(豪装),装修过程中从原理说就是讲布局文件 activity_main 中的控件实例化,Android 这个过程称作 inflate,systrace 展示如下:


![](//upload-images.jianshu.io/upload_images/1


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


095900-c77cc5fb17aff658?imageMogr2/auto-orient/strip|imageView2/2/w/1080/format/webp)


上面只是操作系统从让开发给填充、装饰了房间,但是这个房间还没“开灯”,看不见,也没开门(窗口无法操作),因为需要真正把这个窗口注册到 WindowManagerService 后,WMS 同 SurfaceFlinger 取得联系才能看到,后面我们来分析这个窗口是如何开灯显示,并且能开门迎客接收按键消息的。


随后应用启动流程来到 handleResumeActivity:


final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {...;//回调应用程序的 onResumer = performResumeActivity(token, clearHide, reason);...;if (r.window == null && !a.mFinished && willBeVisible) {r.window = r.activity.getWindow();View decor = r.window.getDecorView();decor.setVisibility(View.INVISIBLE);ViewManager wm = a.getWindowManager();WindowManager.LayoutParams l = r.window.getAttributes();a.mDecor = decor;l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;...if (a.mVisibleFromClient) {if (!a.mWindowAdded) {a.mWindowAdded = true;wm.addView(decor, l);}}}


上面 performResumeActivity 会回调应用程序的 onResume 函数,从这里可以看到 onResume 被回调时用户是看不到窗口的。wm.addView 是重点,这一步就要把“房间”亮灯,也就是把窗口注册到 wms 中着手显示出来,并且开门接收用户操作,这里是调用的 WindowManagerImpl.java:addView:

addView 函数

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {...;ViewRootImpl root;View panelParentView = null;synchronized (mLock) {root = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);// do this last because it fires off messages to start doing thingstry {root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {// BadTokenException or InvalidDisplayException, clean up.if (index >= 0) {removeViewLocked(index, true);}throw e;}}}


从这里开始创建应用进程最核心的:ViewRootImpl 类,它负责与 WMS 通信,负责管理 Surface,负责触发控件的测量、布局、绘制,同时也是输入事件的中转站,可以说 ViewRootImpl 是整个控件系统运转的中枢,应用进程中最为重要的一个组件,有了 ViewRootImpl 这个窗口才能开始渲染被用户看到,并且接受用户操作(开灯、开门)。

ViewRootImpl 剖析

上面的框架图提到 ViewRootImpl 有个非常重要的对象 Choreographer,整个应用布局的渲染依赖这个对象发动,应用要求渲染动画或者更新画面布局时都会用到 Choreographer,接收 vsync 信号也依赖于 Choreographer,我们以一个 View 控件调用 invalidate 函数来分析应用如何接收 vsync、以及如何更新 UI 的。


Activity 中的某个控件调用 invalidate 以后,会逆流到根控件,最终到达调用到 ViewRootImpl.java : Invalidate

invalidate 函数

void invalidate() {mDirty.set(0, 0, mWidth, mHeight);if (!mWillDrawSoon) {scheduleTraversals();}}

scheduleTraversals 函数

void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}


从上面的代码看到 Invalidate 最终调用到 mChoreographer.postCallback,这代码的含义:应用程序请求 vsync 信号,收到 vsync 信号以后会调用 mTraversalRunnable,接下来看下应用程序如何通过 Choreographer 接收 vsync 信号:


//Choreographer.javaprivate final class FrameDisplayEventReceiver extends DisplayEventReceiverimplements Runnable {private boolean mHavePendingVsync;private long mTimestampNanos;private int mFrame;


public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {super(looper, vsyncSource);}


@Overridepublic void onVsync(long timestampNanos, int builtInDisplayId, int frame) {//应用请求 vsync 信号以后,vsync 信号分发就会回调到这里 if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {Log.d(TAG, "Received vsync from secondary display, but we don't support "


  • "this case yet. Choreographer needs a way to explicitly request "

  • "vsync for a specific display to ensure it doesn't lose track "

  • "of its scheduled vsync.");scheduleVsync();return;}


mTimestampNanos = timestampNanos;mFrame = frame;Message msg = Message.obtain(mHandler, this);msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);}


@Overridepublic void run() {mHavePendingVsync = false;doFrame(mTimestampNanos, mFrame);}}


上面 onVsync 会往消息队列放一个消息,通过下面的 FrameHandler 进行处理:


private final class FrameHandler extends Handler {public FrameHandler(Looper looper) {super(looper);}@Override

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
关于Android的渲染机制,大厂面试官最喜欢问的7个问题【建议收藏