关于 Android 的渲染机制,大厂面试官最喜欢问的 7 个问题【建议收藏
原来进程启动以后就会先去执行 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
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
评论