SufaceFlinger工作机制
组成架构
-
EventControlThread: 控制硬件vsync的开关
-
DispSyncThread: 软件产生vsync的线程
-
SF EventThread: 该线程用于SurfaceFlinger接收vsync信号用于渲染
-
App EventThread: 该线程用于接收vsync信号并且上报给App进程,App开始画图
-
HW vsync, 真实由硬件产生的vsync信号
-
SW vsync, 由DispSync产生的vsync信号
-
vsync-sf, SF接收到的vsync信号
-
vsync-app, App接收到的vsync信号
应用程序基本架构
Android应用进程核心组成
上图列举了Android应用进程侧的几个核心类,PhoneWindow的构建是一个非常重要的过程,应用启动显示的内容装载到其内部的mDecor,Activity(PhoneWindow)要能接收控制也需要mWindowManager发挥作用。ViewRootImpl是应用进程运转的发动机,可以看到ViewRootImpl内部包含mView、mSurface、Choregrapher,mView代表整个控件树,mSurfacce代表画布,应用的UI渲染会直接放到mSurface中,Choregorapher使得应用请求vsync信号,接收信号后开始渲染流程,下面介绍上图构建的流程。
应用启动流程图(下文称该图为P0)
进程启动
应用冷启动第一步就是要先创建进程,这跟linux类似C/C++程序是一致的,Android亦是通过fork来孵化应用进程,我们知道Linux fork的子进程继承父进程很多的资源,即所谓的COW。应用进程同样会从其父进程zygote处继承资源,比如art虚拟机实例、预加载的class/drawable资源等,以付出一些开机时间为代价,一来能够节省内存,二来能够加速应用性能,下面结合systrace介绍Android如何启动一个应用进程,应用启动第一个介入的管理者是AMS,应用启动过程中AMS发现没有process创建,就会请求zygote fork进程,下图就是AMS中创建进程的耗时:
AMS(ActivityManagerService)请求zygote创建进程的流程如下:
##ActvityManager:startProcessLocked
private final void startProcessLocked(ProcessRecord app, String hostingType,
String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
boolean isActivityProcess = (entryPoint == null);
if (entryPoint == null) entryPoint = “android.app.ActivityThread”;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +app.processName);
checkTime(startTime, “startProcess: asking zygote to start proc”);
ProcessStartResult startResult;
if (hostingType.equals(“webview_service”)) {
startResult = startWebView(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, entryPointArgs);
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, entryPointArgs);
}
checkTime(startTime, “startProcess: returned from zygote!”);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
前面systrace打印的proc创建时间就是来自与此,Process.start是请求zygote来创建创建进程,这其中有几个很重要问题,比如新建进程入口函数在哪?这个新建进程如何做到创建以后能够不退出,且能不断响应外部输入的等,接下来介绍下入口函数这个点,正如C/C++跑起来去找main函数一样,可以看到startProcess函数有个entrypoint参数:
if (entryPoint == null) entryPoint = “android.app.ActivityThread”;
原来进程启动以后就会先去执行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 main
return 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展示如下:
上面只是操作系统从让开发给填充、装饰了房间,但是这个房间还没“开灯”,看不见,也没开门(窗口无法操作),因为需要真正把这个窗口注册到WindowManagerService后,WMS同SurfaceFlinger取得联系才能看到,后面我们来分析这个窗口是如何开灯显示,并且能开门迎客接收按键消息的。
随后应用启动流程来到handleResumeActivity:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
…;
//回调应用程序的onResume
r = 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 things
try {
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() {
尾声
你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?
改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
尾声
你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?
改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-SsLyag2x-1714584062218)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!