本文是 Systrace 系列文章的第五篇,主要是是介绍 Android App 的 MainThread 和 RenderThread,也就是你们熟悉的主线程和渲染线程。文章会从 Systrace 的角度来看 MainThread 和 RenderThread 的工做流程,以及涉及到的相关知识:卡顿、软件渲染、掉帧计算等html
本系列的目的是经过 Systrace 这个工具,从另一个角度来看待 Android 系统总体的运行,同时也从另一个角度来对 Framework 进行学习。也许你看了不少讲 Framework 的文章,可是老是记不住代码,或者不清楚其运行的流程,也许从 Systrace 这个图形化的角度,你能够理解的更深刻一些java
这里以滑动列表为例 ,咱们截取主线程和渲染线程一帧的工做流程(每一帧都会遵循这个流程,不过有的帧须要处理的事情多,有的帧须要处理的事情少) ,重点看 “UI Thread ” 和 RenderThread 这两行android
这张图对应的工做流程以下git
上面这个流程在 Android 基于 Choreographer 的渲染机制详解 这篇文章里面已经介绍的很详细了,包括每一帧的 doFrame 都在作什么、卡顿计算的原理、APM 相关. 没有看过这篇文章的同窗,建议先去扫一眼github
那么这篇文章咱们主要从 Android 基于 Choreographer 的渲染机制详解 这篇文章没有讲到的几个点来入手,帮你更好地理解主线程和渲染线程数据库
Android App 的进程是基于 Linux 的,其管理也是基于 Linux 的进程管理机制,因此其建立也是调用了 fork 函数浏览器
frameworks/base/core/jni/com_android_internal_os_Zygote.cppbash
pid_t pid = fork();
复制代码
Fork 出来的进程,咱们这里能够把他看作主线程,可是这个线程尚未和 Android 进行链接,因此没法处理 Android App 的 Message ;因为 Android App 线程运行基于消息机制 ,那么这个 Fork 出来的主线程须要和 Android 的 Message 消息绑定,才能处理 Android App 的各类 Message网络
这里就引入了 ActivityThread ,确切的说,ActivityThread 应该起名叫 ProcessThread 更贴切一些。ActivityThread 链接了 Fork 出来的进程和 App 的 Message ,他们的通力配合组成了咱们熟知的 Android App 主线程。因此说 ActivityThread 其实并非一个 Thread,而是他初始化了 Message 机制所须要的 MessageQueue、Looper、Handler ,并且其 Handler 负责处理大部分 Message 消息,因此咱们习惯上以为 ActivityThread 是主线程,其实他只是主线程的一个逻辑处理单元。app
App 进程 fork 出来以后,回到 App 进程,查找 ActivityThread 的 Main函数
com/android/internal/os/ZygoteInit.java
static final Runnable childZygoteInit(
int targetSdkVersion, String[] argv, ClassLoader classLoader) {
RuntimeInit.Arguments args = new RuntimeInit.Arguments(argv);
return RuntimeInit.findStaticMain(args.startClass, args.startArgs, classLoader);
}
复制代码
这里的 startClass 就是 ActivityThread,找到以后调用,逻辑就到了 ActivityThread的main函数
android/app/ActivityThread.java
public static void main(String[] args) {
//1. 初始化 Looper、MessageQueue
Looper.prepareMainLooper();
// 2. 初始化 ActivityThread
ActivityThread thread = new ActivityThread();
// 3. 主要是调用 AMS.attachApplicationLocked,同步进程信息,作一些初始化工做
thread.attach(false, startSeq);
// 4. 获取主线程的 Handler,这里是 H ,基本上 App 的 Message 都会在这个 Handler 里面进行处理
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// 5. 初始化完成,Looper 开始工做
Looper.loop();
}
复制代码
注释里面都很清楚,这里就不详细说了,main 函数处理完成以后,主线程就算是正式上线开始工做,其 Systrace 流程以下:
另外咱们常常说的,Android 四大组件都是运行在主线程上的,其实这里也很好理解,看一下 ActivityThread 的 Handler 的 Message 就知道了
class H extends Handler { //摘抄了部分
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int STOP_SERVICE = 116;
public static final int BIND_SERVICE = 121;
public static final int UNBIND_SERVICE = 122;
public static final int DUMP_SERVICE = 123;
public static final int REMOVE_PROVIDER = 131;
public static final int DISPATCH_PACKAGE_BROADCAST = 133;
public static final int DUMP_PROVIDER = 141;
public static final int UNSTABLE_PROVIDER_DIED = 142;
public static final int INSTALL_PROVIDER = 145;
public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
}
复制代码
能够看到,进程建立、Activity 启动、Service 的管理、Receiver 的管理、Provider 的管理这些都会在这里处理,而后进到具体的 handleXXX
主线程讲完了咱们来说渲染线程,渲染线程也就是 RenderThread ,最初的 Android 版本里面是没有渲染线程的,渲染工做都是在主线程完成,使用的也都是 CPU ,调用的是 libSkia 这个库,RenderThread 是在 Android Lollipop 中新加入的组件,负责承担一部分以前主线程的渲染工做,减轻主线程的负担
咱们通常提到的硬件加速,指的就是 GPU 加速,这里能够理解为用 RenderThread 调用 GPU 来进行渲染加速 。 硬件加速在目前的 Android 中是默认开启的, 因此若是咱们什么都不设置,那么咱们的进程默认都会有主线程和渲染线程(有可见的内容)。咱们若是在 App 的 AndroidManifest 里面,在 Application 标签里面加一个
android:hardwareAccelerated="false"
复制代码
咱们就能够关闭硬件加速,系统检测到你这个 App 关闭了硬件加速,就不会初始化 RenderThread ,直接 cpu 调用 libSkia 来进行渲染。其 Systrace 的表现以下
与这篇文章开头的开了硬件加速的那个图对比,能够看到主线程因为要进行渲染工做,因此执行的时间边长了,也更容易出现卡顿,同时帧与帧直接的空闲间隔也变短了,使得其余 Message 的执行时间被压缩
正常状况下,硬件加速是开启的,主线程的 draw 函数并无真正的执行 drawCall ,而是把要 draw 的内容记录到 DIsplayList 里面,同步到 RenderThread 中,一旦同步完成,主线程就能够被释放出来作其余的事情,RenderThread 则继续进行渲染工做
渲染线程初始化在真正须要 draw 内容的时候,通常咱们启动一个 Activity ,在第一个 draw 执行的时候,会去检测渲染线程是否初始化,若是没有则去进行初始化
android/view/ViewRootImpl.java
mAttachInfo.mThreadedRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
复制代码
后续直接调用 draw
android/graphics/HardwareRenderer.java
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
choreographer.mFrameInfo.markDrawStart();
updateRootDisplayList(view, callbacks);
if (attachInfo.mPendingAnimatingRenderNodes != null) {
final int count = attachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
registerAnimatingRenderNode(
attachInfo.mPendingAnimatingRenderNodes.get(i));
}
attachInfo.mPendingAnimatingRenderNodes.clear();
attachInfo.mPendingAnimatingRenderNodes = null;
}
int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
setEnabled(false);
attachInfo.mViewRootImpl.mSurface.release();
attachInfo.mViewRootImpl.invalidate();
}
if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
attachInfo.mViewRootImpl.invalidate();
}
}
复制代码
上面的 draw 只是更新 DIsplayList ,更新结束后,调用 syncAndDrawFrame ,通知渲染线程开始工做,主线程释放。渲染线程的核心实如今 libhwui 库里面,其代码位于 frameworks/base/libs/hwui
frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
int RenderProxy::syncAndDrawFrame() {
return mDrawFrameTask.drawFrame();
}
复制代码
关于 RenderThread 的工做流程这里就不细说了,后续会有专门的篇幅来说解这个,目前 hwui 这一块的流程也有不少优秀的文章,你们能够对照文章和源码来看,其核心流程在 Systrace 上的表现以下:
主线程负责处理进程 Message、处理 Input 事件、处理 Animation 逻辑、处理 Measure、Layout、Draw ,更新 DIsplayList ,可是不涉及 SurfaceFlinger 打交道;渲染线程负责渲染渲染相关的工做,一部分工做也是 CPU 来完成的,一部分操做是调用 OpenGL 函数来完成的
当启动硬件加速后,在 Measure、Layout、Draw 的 Draw 这个环节,Android 使用 DisplayList 进行绘制而非直接使用 CPU 绘制每一帧。DisplayList 是一系列绘制操做的记录,抽象为 RenderNode 类,这样间接的进行绘制操做的优势以下
RenderThread 的具体流程你们能够看这篇文章 : www.cocoachina.com/articles/35…
游戏大多使用单独的渲染线程,有单独的 Surface ,直接跟 SurfaceFlinger 进行交互,其主线程的存在感比较低,绝大部分的逻辑都是本身在本身的渲染线程里面实现的。
你们能够看一下王者荣耀对应的 Systrace ,重点看应用进程和 SurfaceFlinger 进程(30fps)
能够看到王者荣耀主线程的主要工做,就是把 Input 事件传给 Unity 的渲染线程,渲染线程收到 Input 事件以后,进行逻辑处理,画面更新等。
这里提一下 Flutter App 在 Systrace 上的表现,因为 Flutter 的渲染是基于 libSkia 的,因此它也没有 RenderThread ,而是他自建的 RenderEngine , Flutter 比较重要的两个线程是 ui 线程和 gpu 线程,对应到下面提到的 Framework 和 Engine 两层
Flutter 中也会监听 Vsync 信号 ,其 VsyncView 中会以 postFrameCallback 的形式,监听 doFrame 回调,而后调用 nativeOnVsync ,将 Vsync 到来的信息传给 Flutter UI 线程,开始一帧的绘制。
能够看到 Flutter 的思路跟游戏开发的思路差很少,不依赖具体的平台,自建渲染管道,更新快,跨平台优点明显。
Flutter SDK 自带 Skia 库,不用等系统升级就能够用到最新的 Skia 库,并且 Google 团队在 Skia 上作了不少优化,因此官方号称性能能够媲美原生应用
Flutter 的框架分为 Framework 和 Engine 两层,应用是基于 Framework 层开发的,Framework 负责渲染中的 Build,Layout,Paint,生成 Layer 等环节。Engine 层是 C++实现的渲染引擎,负责把 Framework 生成的 Layer 组合,生成纹理,而后经过 Open GL 接口向 GPU 提交渲染数据。
当须要更新 UI 的时候,Framework 通知 Engine,Engine 会等到下个 Vsync 信号到达的时候,会通知 Framework,而后 Framework 会进行 animations, build,layout,compositing,paint,最后生成 layer 提交给 Engine。Engine 会把 layer 进行组合,生成纹理,最后经过 Open Gl 接口提交数据给 GPU,GPU 通过处理后在显示器上面显示。整个流程以下图:
若是主线程须要处理全部任务,则执行耗时较长的操做(例如,网络访问或数据库查询)将会阻塞整个界面线程。一旦被阻塞,线程将没法分派任何事件,包括绘图事件。主线程执行超时一般会带来两个问题
对于用户来讲,这两个状况都是用户不肯意看到的,因此对于 App 开发者来讲,两个问题是发版本以前必需要解决的,ANR 这个因为有详细的调用栈,因此比较好定位;可是间歇性卡顿这个,可能就须要使用工具来进行分析了:Systrace + TraceView,因此理解主线程和渲染线程的关系和他们的工做原理是很是重要的,这也是本系列的一个初衷
另外关于卡顿,能够参考下面三篇文章,你的 App 卡顿不必定是你 App 的问题,也有多是系统的问题,不过无论怎么说,首先要会分析卡顿问题。
本文涉及到的附件也上传了,各位下载后解压,使用 Chrome 浏览器打开便可
小厂系统研发工程师 , 更多信息能够点击 关于我 , 很是但愿和你们一块儿交流 , 共同进步 .
一我的能够走的更快 , 一群人能够走的更远