Android 高级UI解密 :结合Activity启动源码剖析View的诞生
Posted 鸽一门
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 高级UI解密 :结合Activity启动源码剖析View的诞生相关的知识,希望对你有一定的参考价值。
自定义控件的UI绘制流程,势必继承View或者ViewGroup,即android系统提供的控件或Layout容器,接下来就是喜闻乐见的layout摆放、measure测量、draw绘制三件套。看似简单的过程,但开发中总会遇到一些奇奇怪怪的bug:位置摆放有问题?设置属性不起作用?控件显示出错?
以上类型的问题,不同于之前文章篇幅讲解的绘制绚丽UI效果类型,更注重自定义整个过程,除了图形绘制之外,你需要去考虑图形之间的测量、摆放,此部分在自定义Layout中体现的淋漓尽致。因此完成一个全面的自定义控件,View绘制原理(onMeasure、onLayout、onDraw)流程掌握必不可少,擒贼先擒王,每个开发者心里对于绘制流程必须有个大致的轮廓,到时候出现bug时你才能对症下药,知道问题出在哪个地方,debug过程对此要求极高。从此篇文章开始将从源码的角度入手分析View绘制渲染的原理。
(此系列文章知识点相对独立,可分开阅读,不过笔者建议按照顺序阅读,理解更加深入清晰)
Android 高级UI解密 (五) :PathMeasure截取片段 与 切线(新思路实现轨迹变换)
Android 高级UI解密 (四) :花式玩转贝塞尔曲线(波浪、轨迹变换动画
Android 高级UI解密 (三) :Canvas裁剪 与 二维、三维Camera几何变换(图层Layer原理)
Android 高级UI解密 (二) :Paint滤镜 与 颜色过滤(矩阵变换)
Android 高级UI解密 (一) :Paint图形文字绘制 与 高级渲染
此篇涉及到的知识点如下:
- Activity中ContentView布局加载流程;
- Window、PhoneWindow、DecorView顶级View概念解析;
- 结合Activity启动流程分析布局加载过程;
- Activity与Window,DecorView与WindowManager的联系?
- ViewRoot、ViewRootImpl开启三大流程绘制
performTraverslas
过程;
一. Activity的setContentView视图加载
UI绘制流程,将其分成两个部分来解析,首先以Activity的角度入手,它是如何渲染出界面UI的?
1. 源码步骤
Step 1. 调用setContentView加载布局
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
首先回到加载UI界面的源点,也就是探索的入口:setContentView
方法,追踪其源码,此方法有多个重载的方法,任意查看其中一个即可:
Step 2. Activity的setContentView方法
【Activity.java】
public void setContentView(@LayoutRes int layoutResID)
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
public Window getWindow()
return mWindow;
可以发现,Activity实现的setContentView
方法非常简单,调用getWindow
方法获取一个Window类型的对象,并调用其setContentView
方法,将资源id传入其中。
Step 3. Window抽象类的setContentView方法
【Window.java】
/**
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window
public static final int FEATURE_OPTIONS_PANEL = 0;
public static final int FEATURE_NO_TITLE = 1;
......
public abstract void setContentView(int layoutResID);
public abstract void setContentView(View view);
public abstract void setContentView(View view, ViewGroup.LayoutParams params);
点进去详细查看这个Window对象,发现其setContentView
方法只是一个抽象方法,不仅如此,它本身就是一个抽象类,注释为“顶级窗口外观和行为策略的抽象基类。它提供标准的UI策略,如背景、标题区域、默认密钥处理等。”后面的注释也指出了: 唯一的实现类就是android.view.PhoneWindow
。
因此可以理解为Activity呈现出的页面就是一个PhoneWindow,即Window的一种呈现显示,例如弹出的窗口也是Window的一种形式。Window中的静态final成员变量有十几个,定义了多种不同窗口类型。
Step 4. PhoneWindow(DecorView内部类)实现父类的setContentView方法
之前提到PhoneWindow是Window窗体唯一的实现类,那它必定实现了父类的三个setContentView
抽象方法,来查看其具体实现,揭开界面渲染的奥秘:
【PhoneWindow.java】
public class PhoneWindow extends Window implements MenuBuilder.Callback
......
@Override
public void setContentView(int layoutResID)
if (mContentParent == null)
installDecor();
else
mContentParent.removeAllViews();
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed())
cb.onContentChanged();
@Override
public void setContentView(View view)
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
@Override
public void setContentView(View view, ViewGroup.LayoutParams params)
if (mContentParent == null)
installDecor();//处理布局渲染的重点方法
else
mContentParent.removeAllViews();
mContentParent.addView(view, params);
final Callback cb = getCallback();
if (cb != null && !isDestroyed())
cb.onContentChanged();
......
可以发现这是哪个方法内的逻辑都是先判断mContentParent是否为null(注意mContentParent是一个ViewGroup):
- 若不为null,则移除所有的View再将我们传入的View添加进去(若传入的是资源ID可通过LayoutInflater转化)
- 若为null,在添加我们传入的View到mContentParent之前,需要先创建mContentParent,重点究其创建过程:
- 调用
installDecor()
方法,其内部又创建了一个DecorView(继承于FrameLayout,是PhoneWindow的内部类),相当于在PhoneWindow内又嵌套了一层View。 - 接着调用
generateLayout(mDecor)
方法,传入上一步骤创建好的DecorView参数,(此方法可谓是一个神级方法,内部处理的逻辑量大到不亚于ViewRoot的performTraversals
方法,此处只重点讲解DecorView部分)此方法内部根据属性判断创建了一个LinearLayout的布局,布局内分为标题栏和内容栏,DecorView添加此布局,并将其视为“顶级View”; - 接着findViewById获取内容栏部分返回,内容栏也就是“ID_ANDROID_CONTENT”部分会用来显示我们调用
setContentView
中传入的布局。(这也是为何我们传入布局时调用的是setContentView
方法而不是setView
方法命名的缘由)
- 调用
获取ViewGroup赋值给mContentParent成员变量,此处的ViewGroup就是我们传入的布局,也是ID_ANDROID_CONTENT
【PhoneWindow.java】
private void installDecor()
if (mDecor == null)
mDecor = generateDecor();//⭐️生成一个DecorView(继承的FrameLayout)
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
......
if (mContentParent == null)
mContentParent = generateLayout(mDecor);//⭐️将生成的布局赋值给示页面
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent)mDecor.findViewById(R.id.decor_content_parent);
......
protected DecorView generateDecor()
return new DecorView(getContext(), -1);
//⭐️⭐️⭐️⭐️⭐️处理了大量逻辑
protected ViewGroup generateLayout(DecorView decor)
//① 获取当前窗体的style
TypeArray a = getWindowStyle();
......
//② 根据设置的属性指定style(判断当前窗体是否需要显示标题栏、ActionBar)
if(a.getBoolean(R.styleable.Window_windowNoTitle, false))
requestFeature(FEATURE_NO_TITLE);
else if(a.getBoolean(R.styleable.Window_winodwActionBar, false)
requestFeature(FEATURE_ACTION_BAR);
......
//③根据判断初始化DecorView的布局ID
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0)
layoutResource = R.layout.screen_swipe_dismiss;
else if(...)
...
//④LayoutInflater加载资源ID为View,该View是一个LinearLayout,内部分为标题栏和状态栏;decor添加此布局,并将此布局赋值为“顶级View”。
View in = mLayoutInflater.inflate(layoutResource, null);//⭐️DecorView给自己创建布局Layout
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//⑤ 获取DecorView布局中的Content部分赋值给contentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
......
return contentParent;
generateLayout方法逻辑
以上方法的重点流程在上面已经总结过,此处要特地强调generateLayout
方法,其内部处理大量的逻辑,例如设置StatusBar、NavigationBar、状态栏显示、输入法类型、根据当前Style类型为当前Window选择不同的布局文件,为“根布局”指定一个用来存放我们自定义布局文件(“根布局”是个ViewGroup,例如常用的LinearLayout)。
上述代码示例中调用的requestFeature
方法,大家应该不陌生,在早期会在Activity的onCreate
中调用getWindow().requestFeature(featureId)
去除ToolBar,此处有一个重点就是一定要在setContentView
之前设置此方法,不然去除ToolBar没有效果!而原因就在generateLayout
源码中,内部首先会去判断这些相关属性,即DecorView刚被创建,内部ViewGroup(即mContentParent成员变量)还未创建,在此之前设置NO_TITLE相关属性才有作用,否则等到布局都创建好之后再去设置Window的相关属性已无用。
DecorView
继承于FrameLayout的DecorView,可见generateLayout
方法中创建的布局:new了一个LinearLayout,并传入了layoutResource,这个layoutResource是前部分style判断而决定的layout布局,有R.layout_screen_action_bar
R.layout.screen_title
、R.layout_screen_simple_overlay_action_mode
、R.layout_screen_simple
等等选择,通常情况下布局则是如下图所示,一个简单的titlebar和content内容布局。
后续几行代码的目的更是显而易见,DecorView将这个LinearLayout布局添加进来,并将LinearLayout布局这个作为mContentRoot 顶级View 。
- 如何得到content呢?
ViewGroup content = (ViewGroup)findViewById(android.R.id.content)
- 如何得到我们设置的View呢?
content.getChildAt(0);
最后补充一点: View层的事件都要经过DecorView,再传递给View.
2. 总结
- Window:一个抽象类,提供了一组绘制窗口的通用API;
- PhoneWindow:是Window的唯一实现类,处理了大量视图加载相关逻辑。
- DecorView:是PhoneWindow的内部类,是FrameLayout的子类,是对FrameLayout进行功能的修饰,是所有应用窗口的根View。
以上就是Activity加载布局的大致过程,做一下总结:
- 首先调用Activity的
setContentView
方法,其内部则是获取Window成员变量,调用它的setContentView
方法。 - Window类实则是一个抽象类,其
setContentView
方法也是抽象方法,它唯一的实现类就是PhoneWindow。 - 继而又将重点转换到PhoneWindow类,它实现了三个重载的
setContentView
方法,内部逻辑中创建了一个继承于FrameLayout的DecorView,并根据指定style创建了LinearLayout布局添加进来,将其视为“顶级View”,此布局中又分为标题栏和内容栏,而后者用于存放我们传入的布局。
二. 结合Activity启动流程分析布局加载过程
以上第一大点源码分析了Activity加载xml布局的一个过程,并引入了“窗体”的概念,让我们了解Activity –> PhoWindow –> DecorView这样一个嵌套View的流程。但心细的你会发现上一部分丝毫未提及绘制相关事件,意味着界面UI还未渲染出来,呈不可见状态。
此时我们已经了解Activity加载xml布局的一个过程,得知其中涉及到了Window、WindowPhone类,但这只是View加载原理的中间一节,我们不禁有以下疑问:
- Winodow的唯一实现类PhoneWindow是何时被创建的?Activity是如何附属在Window上的?
- 身为顶级View的DecorView是如何与WindowManager联系起来的?
- 开启View三大绘制流程的ViewRootImpl何时被创建?是如何与DecorView联系起来的?
- ViewRoot的
performTraverslas
又是何时被调用的?
要想解决以上问题,还是得回归到根本,我们最初调用加载布局的setContentView
方法是在Activity的onCreate
生命周期中调用的,需要追溯到Activity启动相关源码,彻头彻尾捋清楚,开始!
(安利米娜桑去看笔者之前写的Android:图解Activity启动流程源码(整体流程),对后续理解有帮助)
Step 1. ActivityThread的handleLaunchActivity方法
Activity启动后到一连串不重要调用在此不在叙述的,建议读者阅读上面链接文章,直袭Activity启动重点 —— ActivityThread。
【ActivityThread.java】
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent)
......
//⭐️⭐️⭐️⭐️⭐️调用Activity的onCreate方法,开启生命周期
Activity a = performLaunchActivity(r, customIntent);
if (a != null)
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
//⭐️⭐️⭐️⭐️⭐️调用Activity的onResume方法,关联decor和WindowManager
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
......
可见ActivityThread的handleLaunchActivity
方法中做了两件重要的事,从Activity的生命周期角度来看的话,分别调用了performLaunchActivity
、handleResumeActivity
方法,对应着Activity的onCreate
、onResume
方法。
从大方向看就这么点回事,可我们的重点在于Window、DecorView、ViewRootImpl这些与界面渲染有关的部分,因此首先深入查看performLaunchActivity
方法。
Step 2. ActivityThread的performLaunchActivity方法
【ActivityThread.java】
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent)
......
//① ClassLoader创建Activity对象
Activity activity = null;
try
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null)
r.state.setClassLoader(cl);
......
try
//② 创建应用程序Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
......
if (activity != null)
//③ 创建Activity专属的Context上下文环境
Context appContext = createBaseContextForActivity(r, activity);
......
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow)
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
//④⭐️⭐️⭐️⭐️⭐️ 调用Activity的`attach`方法,完成一些重要数据的初始化。(Activity与ContextImpl关联,Activity创建Window并建立自己和Window的关联)
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window);
......
//⑤ 调用Activity的onCreate方法,正式启动Activity
if (r.isPersistable())
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
......
return activity;
笔者已经将ActivityThread的performLaunchActivity
方法重点源码都贴出来,主要逻辑再次列举:
- ClassLoader创建Activity对象;
- 创建应用程序Application;
- 创建Activity专属的Context上下文环境;
- 调用Activity的
attach
方法,完成一些重要数据的初始化;(Activity与ContextImpl关联,Activity创建Window并建立自己和Window的关联) - 调用Activity的onCreate方法,正式启动Activity;
以上列举中的重点显而易见,得知Activity的attach
方法有关渲染UI重点类的初始化,深入源码:
Step 3. Activity的attach方法
其中创建流程后期会调用Activity的attach
方法来完成一些重要数据的初始化:
【Activity.java】
final void attach(Context context, ActivityThread aThread,
......
//创建PhoneWindow对象
mWindow = PolicyManager.makeNewWindow(this);
//设置相关属性
mWindow.setCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED)
mWindow.setSoftInputMode(info.softInputMode);
if (info.uiOptions != 0)
mWindow.setUiOptions(info.uiOptions);
......
以上代码发现在Activity被创建时,就已经初始化好PhoneWIndow对象,也就是界面呈现的载体已准备好。此处作为拓展,继续查看PhoneWIndow被创建的过程,查看以下代码:
【PolicyManager.java】
public final class PolicyManager
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";
private static final IPolicy sPolicy;
static
try
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
catch (ClassNotFoundException ex)
......
public static Window makeNewWindow(Context context)
return sPolicy.makeNewWindow(context);
......
【Policy.java】
public Window makeNewWindow(Context context)
return new PhoneWindow(context);
以上两段代码即可了解PhoneWindow的创建过程,首先在PolicyManager中通过反射的方式而获取com.android.internal.policy.impl.Policy
的实例,再调用Policy的makeNewWindow
方法创建,即new了一个PhoneWindow对象。
————————————————————
问:第一个问题,PhoneWIndow何时被创建的?如何与Activity关联上?
答:在Activity初始化时的attach
方法中。
————————————————————
在解决第一个问题后,查看第二个问题:Window又是如何与Activity联系起来的呢?继续研究源码,仍旧回到最初的ActivityThread的handleLaunchActivity
方法中,分析完启动Activity的onCreate生命周期的performLaunchActivity
方法后,接下来分析handleResumeActivity
方法。
Step 4. ActivityThread的handleResumeActivity方法
【ActivityThread.java】
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason)
......
if (r != null)
final Activity a = r.activity;
......
if (r.window == null && !a.mFinished && willBeVisible)
//① 获取Activity依附的Window对象
r.window = r.activity.getWindow();
//② 获取Window中的DecorView对象
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//③ 获取此Activity联系的WindowManager对象
ViewManager wm = a.getWindowManager();
//④ 将Window里的DecorView对象赋值于Activity上
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow)
a.mWindowAdded = true;
r.mPreserveWindow = false;
// 通常,ViewRoot使用addView-> ViewRootImpl#setView中的Activity设置回调。 如果我们要重新使用装饰视图,则必须通知视图根据回调可能已经改变。
if (a.mVisibleFromClient && !a.mWindowAdded)
a.mWindowAdded = true;
//⑤ 将DecorView与WindowManager关联上。⭐️⭐️⭐️
wm.addView(decor, l);
......
......
照例,在此列举一下此方法的逻辑:
- 获取Activity依附的Window对象;
- 获取Window中的DecorView对象;
- 获取此Activity联系的WindowManager对象;
- 将Window里的DecorView对象赋值于Activity上;
- 将DecorView与WindowManager关联上;
这初学者看下来估计已经懵了,我们来好好捋一捋,首先列举上面出现的重点类:Activity、Window、DecorView、WindowManager、ViewRootImpl。来理清这5个大佬之间的关系:
- 首先第一大点已经讲解过,DecorView是Window子类PhoneWindow的内部类,ViewRootImpl对象又是从DecorView中获取的。其实ViewRootImpl才是View真正的实现类,具体的绘制操作都是由它执行,因此一个Window对应一个View和一个ViewRootImpl;
- WindowManager与View同理,具体的操作实现都是由WindowManagerImpl实现的;
- Window和View是通过ViewRootImpl联系起来的;
最后归纳一下:handleResumeActivity
方法最大的作用就是将顶层视图DecorView通过WindowManager挂载到Window中,便于后续DecorView中的ViewRootImpl实行界面绘制操作。
————————————————————
问:第二个问题,身为顶级View的DecorView是如何与WindowManager联系起来的?
答:在ActivityThread的handleResumeActivity
方法中。
————————————————————
此部分的重点那是相当多,好好消化一下,明白这些之后相信很多人的重点还是在绘制三大流程的ViewRootImpl上,第三个问题:它是何时被创建的?何时才开始绘制?重点查看此方法的第5个步骤wm.addView(decor, l);
,将DecorView与WindowManager关联上,深入研究。
Step 5. WindowManagerImpl、WindowManagerGlobal的 addView方法
【WindowManagerImpl.java】
public void addView(View view, ViewGroup.LayoutParams params)
mGlobal.addView(view, params, misplay, mParentWindow);
可以发现WindowManagerImpl的addView
方法中并没有实际处理逻辑,而是托付给mGloba成员变量处理,此处的mGloba就是WindowManagerGlobal的一个内部实例,继续查看其具体实现:
【WindowManagerGlobal.java】
public void addView(View view, ViewGroup.LayoutParams params)
......
//开启三大绘制的BOSS
ViewrootImpl root;
View panelParentView = null;
synchronized(mLock)
......
//⭐️⭐️⭐️通过DecorView对象的上下文环境Context实例化new了一个ViewrootImpl对象
root = new ViewrootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try
//⭐️⭐️⭐️调用ViewrootImpl的setView方法,将DecorView与ViewrootImpl关联起来
root.setView(view, wparams, panelParentView);
......
可喜可贺,我们找到了控制View三大绘制流程的BOSS诞生点,即ViewrootImpl的创建实例化,创建时传入其构造方法的参数是DecorView的上下文环境Context,后续调用ViewrootImpl的root.setView
方法,将DecorView与自己关联起来。因此此处可以证实Step 4总结的一个理论:一个Window对应一个View和一个ViewRootImpl。
————————————————————
问:第三个问题,开启View三大绘制流程的ViewRootImpl何时被创建?是如何与DecorView联系起来的?
答:在WindowManagerGlobal的 addView
方法中。
————————————————————
amazing,真相已经呼之欲出,继续深入最后一个步骤,即ViewRootImpl 的 setView
方法,揭开绘制流程开启入口的面纱~
Step 6. ViewRootImpl 的 setView方法 —— 即将开启绘制流程⭐️
【ViewRootImpl.java】
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView)
synchronized (this)
if (mView == null)
......
//⭐️⭐️⭐️绘制view的入口
requestLayout();
......
try
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
//⭐️涉及到IPC,WindowSession完成Window的添加,此处牵扯过多,读者感兴趣可自行研究
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
......
ViewRootImpl 的 setView方法内部处理了大量重点逻辑,例如注册DisplayManager、添加布局到WindowManager、设置Input管道等等,但此处只重点关注绘制部分,查看五角星标记处,内部调用了requestLayout()
方法,令人激动的来了,此方法就是开启View绘制的入口,继续查看:
【ViewRootImpl.java】
@Override
public void requestLayout()
if (!mHandlingLayoutInLayoutRequest)
checkThread();
mLayoutRequested = true;
//⭐️⭐️⭐️准备开启绘制流程
scheduleTraversals();
void scheduleTraversals()
if (!mTraversalScheduled)
mTraversalScheduled = true;
//⭐️⭐️⭐️发起一个异步消息mTraversalRunnable
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
在requestLayout()
中调用了scheduleTraversals
方法,后者方法中会发起一个异步消息mTraversalRunnable,挺住!继续查看其实现:
【ViewRootImpl.java】
final class TraversalRunnable implements Runnable
@Override
public void run()
//⭐️⭐️⭐️
doTraversal();
void doTraversal()
if (mTraversalScheduled)
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
......
//⭐️⭐️⭐️开启绘制流程
performTraversals();
......
在异步消息TraversalRunnable中调用了doTraversal()
方法,最后一步了,在此方法中调用了performTraversals()
,即绘制View的入口。吐血ing,连环调问你怕不怕,可见逻辑分离的多么细致,要参透ViewRootImpl这个类不简单~
————————————————————
问:第四个问题,ViewRoot的performTraverslas又是何时被调用的?
答:在ViewRootImpl类中scheduleTraversals()
方法中调用的异步消息TraversalRunnable中。
————————————————————
Step 7. ViewRootImpl 的 performTraversals方法 —— 开启绘制流程
【ViewRootImpl.java】
private void performTraversals()
......
if (!mStopped || mReportNextDraw)
......
//① 获取顶级Layout的宽高MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
//②执行测量工作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
if (didLayout)
//③执行布局工作
performLayout(lp, mWidth, mHeight);
......
if (!cancelDraw && !newSurface)
......
//④执行绘制工作
performDraw();
......
撒花~终于找到你,还好我没放弃~上图所示的代码意图再明显不过了,是启动三大绘制流程的方法调用。
总结
上图是结合了Activity启动流程源码,分析出的视图加载相关的时序图,其重点逻辑在于:在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联,此过程可参考以下源码:
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);
ViewRoot,它对应于ViewRootImpl类,是连接WindowManager和DecorView的纽带,View最重要的三大流程都是通过ViewRoot来完成的!(ViewRoot的
performTraversals
方法开始三大流程,具体在第二部分讲解)
注:此篇文章暂不深入解析三大流程绘制具体细节,留给下一篇,此篇文章的重点就是讲解Android界面绘制的“世界观”环境。
若有错误,虚心指教~
以上是关于Android 高级UI解密 :结合Activity启动源码剖析View的诞生的主要内容,如果未能解决你的问题,请参考以下文章
Android 高级UI解密 :结合Activity启动源码剖析View的诞生
Android 高级UI解密 :Paint图形文字绘制 与 高级渲染
Android 高级UI解密 :Paint图形文字绘制 与 高级渲染
Android 高级UI解密 :Paint滤镜 与 颜色过滤(矩阵变换)