在androID当中对于UI体系当中往往我们会在绘制UI的时候碰到各种各样的问题而不知道从何解决, 也有时需要开发更改自定义组件时,需要做自己的调整,或者是实现某个自定义特效时的思路不明确,因此了解UI绘制流程及原理是十分必要的,本文就UI绘制流程之前的相关知识进行简单的分析和梳理,便于后续进一步了解UI绘制原理
2.VIEw是如何添加到屏幕窗口上的要弄清楚UI绘制流程和原理,我们首先要了解的就是VIEw是如何被添加到屏幕窗口上的。带着这个问题我们来进行源码分析,关于界面的展示,立马浮现在脑海的就是这样一段代码:
public class MainActivity extends AppCompatActivity { @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); }}
通过传入布局资源ID,setContentVIEw方法又做了什么事情呢?经过一系列线索最终找到了的位置也就是Window的唯一实现类PhoneWindow:
public class PhoneWindow extends Window implements MenuBuilder.Callback { ... // This is the top-level vIEw of the window, containing the window decor. // 这是在窗口当中的顶层VIEw,包含窗口的decor private DecorVIEw mDecor; // This is the vIEw in which the window contents are placed. It is either // mDecor itself, or a child of mDecor where the contents go. // 这是窗口内容放置的视图,它要么是mDecor本身,要么是mDecor的子类的内容 VIEwGroup mContentParent; ... @OverrIDe public voID setContentVIEw(int layoutResID) { if (mContentParent == null) { // 注释1 installDecor(); } ... // 注释2 mLayoutInflater.inflate(layoutResID, mContentParent); ... }}
注释1处installDecor方法顾名思义就是初始化 *** 作,注释2处就是将布局资源ID填充到mContentParent内容布局容器。先进入installDecor方法:
private voID installDecor() { mForceDecorInstall = false; if (mDecor == null) { // 注释1 mDecor = generateDecor(-1); ... } if (mContentParent == null) { // 注释2 mContentParent = generateLayout(mDecor); ... }}
注释1处,如果mDecor为空,就调用generateDecor方法,进入该方法就发现通过返回一个new出来DecorVIEw,然后赋值给mDecor。注释2处调用generateLayout方法,那么该方法是如何给mContentParent赋值的呢?
protected VIEwGroup generateLayout(DecorVIEw decor) { ... // 注释1 // 根据系统主题的属性设置了许多了特性 if (a.getBoolean(R.styleable.Window_windowActionbarOverlay, false)) { requestFeature(FEATURE_ACTION_bar_OVERLAY); } if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) { requestFeature(FEATURE_ACTION_MODE_OVERLAY); } if (a.getBoolean(R.styleable.Window_windowswipetodismiss, false)) { requestFeature(FEATURE_SWIPE_TO_disMISS); } if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) { setFlags(FLAG_FulLSCREEN, FLAG_FulLSCREEN & (~getForceDWindowFlags())); } ... // 注释2 // Inflate the window decor. int layoutResource;// 布局资源ID int features = getLocalFeatures(); if ((features & (1 << FEATURE_SWIPE_TO_disMISS)) != 0) { // 注释3 // 根据不同feature, 对layoutResource进行不同的赋值 *** 作 // 即后续加载不同的布局,这就很好的解释了为什么我们自己要去getwindow.requestFeature时 // 必须在setContent之前的原因 layoutResource = R.layout.screen_swipe_dismiss; setCloSEOnSwipeEnabled(true); } else if ((features & ((1 << FEATURE_left_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { ... } mDecor.startChanging(); // 注释4 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); // 注释5 // ID_ANDROID_CONTENT = com.androID.internal.R.ID.content; VIEwGroup contentParent = (VIEwGroup)findVIEwByID(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window Couldn't find content container vIEw"); } ... return contentParent;}
省去了很多类似的特性设置代码,在注释1处我们发现根据系统属性的不同,通过requestFeature和setFlag方法设置了许多属性。在注释2处,看到解析窗口decor的提示,继续往下看,如注释3处,会根据不同的特性对布局资源进行不同的赋值,即后续加载不同的布局(就是不同的Actionbar,Titlebar之类的)。这就是为什么我们自己要去getwindow.requestFeature时必须在 setContent之前的原因。再看注释4处的onResourcesLoaded方法:
voID onResourcesLoaded(LayoutInflater inflater, int layoutResource) { ... addVIEw(root, 0, new VIEwGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); ... mContentRoot = (VIEwGroup) root; initializeElevation();}
主要逻辑就是将传入layoutResource即布局资源通过addVIEw添加到DecorVIEw中。在回到注释5处,通过findVIEwByID,获取ID为com.androID.internal.R.ID.content的contentVIEw,即内容布局容器,最后返回。分析完installDecor方法,再回到PhoneWindow的setContentVIEw方法的注释2处,调用inflate方法,就是将MainActivity的layoutResID即对应的资源布局,添加到mContentParent内容布局容器。至此setContentVIEw的分析就告一段落。
方法内部逻辑比较多,主要做了以下几件事:
installDecor方法内部的generateDecor方法初始化DecorVIEw
installDecor方法内部的generateLayout
根据不同的系统属性,通过requestFeature和setFlag方法设置不同(feature)特性
根据不同的feature,通过onResourcesLoaded方法的addVIEw加载不同的layoutResource(布局资源,一般是Actionbar,Title等)
通过findVIEwByID获取固定ID为com.androID.internal.R.ID.content的内容布局容器contentParent
返回contentParent
setContentVIEw方法内通过inflate方法将初始的layoutResID对于的布局添加到contentParent布局容器(androID.R.ID.content)
总结一下,VIEw是如何添加到屏幕窗口上的,主要分为三个步骤:
创建顶层布局容器DecorVIEw在顶层布局中加载基础布局容器VIEwGroup将ContentVIEw添加到基础布局中的FrameLayout中3.VIEw的绘制流程3.1绘制入口谈到VIEw的绘制入口,就需要知晓Activity的启动过程,如果还不太清楚可以查阅下面两篇文章了解相关细节
Activity的启动流程分析与总结
Application创建流程分析
受篇幅所限,就不具体分析了。就Activity启动过程的部分与VIEw绘制相关的流程进行简单的梳理,如下图
在handleLaunchActivity方法中调用performlaunchActivity后续会调用Activity的onCreate方法,在performlaunch之后会调用handleResumeActivity方法,顾名思义就知道它会是onResume方法的入口,走进该方法:
final voID handleResumeActivity(IBinder token, boolean clearHIDe, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClIEntRecord r = mActivitIEs.get(token); ... // 注释1 // 回调Activity的生命周期方法onResume r = performResumeActivity(token, clearHIDe, reason); if (r != null) { final Activity a = r.activity; boolean willBeVisible = !a.mStartedActivity; if (!willBeVisible) { try { willBeVisible = ActivityManager.getService().willActivityBeVisible( a.getActivityToken()); } catch (remoteexception e) { throw e.rethrowFromSystemServer(); } } if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getwindow(); // 注释2 VIEw decor = r.window.getDecorVIEw(); decor.setVisibility(VIEw.INVISIBLE); // 注释3 // 调用Activity的getwindowManager获取wm VIEwManager wm = a.getwindowManager(); // 注释4 // 获取窗口的布局属性对象 WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPliCATION; l.softinputMode |= forwardBit; if (r.mPreserveWindow) { a.mWindowAdded = true; r.mPreserveWindow = false; VIEwRootImpl impl = decor.getVIEwRootImpl(); if (impl != null) { impl.notifyChildRebuilt(); } } if (a.mVisibleFromClIEnt) { if (!a.mWindowAdded) { a.mWindowAdded = true; // 注释5 wm.addVIEw(decor, l); } else { a.onWindowAttributesChanged(l); } } ...}
在注释2处调用window的getDecorVIEw方法,最终还是调用PhoneWindow的相关方法获取DecorVIEw,在注释3处调用Activity的getwindowManager方法获取VIEwManager,在注释4处获取窗口的布局属性对象,在注释5处调用WindowManager的addVIEw方法,进入Activity的getwindowmanger方法:
public WindowManager getwindowManager() { return mWindowManager;}
在Activity中搜索mWindowManager赋值的逻辑:
final voID attach(Context context, ActivityThread aThread, ... mWindowManager = mWindow.getwindowManager(); ...}
接着进入Window中查找mWindowManager赋值的地方
public voID setwindowManager(WindowManager wm, IBinder appToken, String appname, boolean harDWareAccelerated) { ... mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);}
接着进入createLocalWindowManager方法,来到了WindowManagerImpl 即WindowManager的实现类:
public WindowManagerImpl createLocalWindowManager(Window parentwindow) { return new WindowManagerImpl(mContext, parentwindow);}
进入WindowManagerImpl的addVIEw方法:
@OverrIDepublic voID addVIEw(@NonNull VIEw vIEw, @NonNull VIEwGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addVIEw(vIEw, params, mContext.getdisplay(), mParentwindow);}
接着进入mGlobal即WindowManagerGlobal的addVIEw方法:
public voID addVIEw(VIEw vIEw, VIEwGroup.LayoutParams params, display display, Window parentwindow) { ... VIEwRootImpl root; ... // 注释1 root = new VIEwRootImpl(vIEw.getContext(), display); // 注释2 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 { // 注释3 root.setVIEw(vIEw, wparams, panelParentVIEw); } catch (RuntimeException e) { ... throw e; } }}
在注释1处,实例化了一个VIEwRootImpl,在注释2处,设置布局参数,添加到相关集合,在注释3处通过VIEwRootImpl的setVIEw方法将VIEw和布局参数等进行了关联,进入setVIEw方法:
public voID setVIEw(VIEw vIEw, WindowManager.LayoutParams attrs, VIEw panelParentVIEw) { ... // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); ...}
需要关心的代码就这一句requestLayout,我们知道该方法会触发VIEw的绘制流程,进入该方法:
@OverrIDepublic voID requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }}
进入scheduleTravels方法:
@UnsupportedAppUsagevoID scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalbarrIEr = mHandler.getLooper().getQueue().postsyncbarrIEr(); // 注释1 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedinputdispatch) { scheduleConsumeBatchedinput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}
注释1处方法参数mTraversalRunnable是一个Runnable,进入查看它的run方法:
final class TraversalRunnable implements Runnable { @OverrIDe public voID run() { doTraversal(); }}
继续追踪进入doTraversal方法
voID doTraversal() { ... performTraversals(); ...}
进入performTraversals方法, 正式进入VIEw绘制的三大流程
private voID performTraversals() { ... // 执行测量 performMeasure(xxx) ... // 执行布局 performlayout(xxx); ... // 执行绘制 performDraw(); ...}
绘制入口的简单小结
ActivityThread.handleResumeActivity()WindowManagerImpl.addVIEw(decorVIEw, layoutParams)WindowManagerGlobal.addVIEw()VIEwRootImpl.addVIEw()3.2绘制涉及的类及方法VIEwRootImpl.setVIEw(decorVIEw, layoutParams, parentVIEw)VIEwRootImple.requestLayout()–>scheduleTraversals()–>doTraversal()–>performTraversals()3.3绘制三大步骤测量:VIEwRootImpl.performMeasure()布局:VIEwRootImpl.performlayout()绘制:VIEwRootImpl.performDraw()结语VIEwRootImpl是连接WindowManager和DecorVIEw的纽带,VIEw绘制的三大流程均是通过它来完成的,在ActivityThread中,当Activity对象被创建完毕后,会将DecorVIEw添加到Window中,同时会创建VIEwRootImple对象,并将VIEwRootImpl和DecorVIEw建立关联。到performTraversals方法的主要调用流程大致如下图:
VIEw的具体绘制从VIEwRootImpl的performTraversals方法开始的,它经过measure、layout和draw三个过程才能最终将一个VIEw绘制出来,其中measure用来测量VIEw的宽和高,layout用来确定VIEw在父容器中的放置位置,而draw则负责将VIEw绘制在屏幕上。
总结
以上是内存溢出为你收集整理的Android UI绘制流程前奏全部内容,希望文章能够帮你解决Android UI绘制流程前奏所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)