Android View的绘制机制前世今生---前世

但使书种多,会有岁稔时。这篇文章主要讲述Android View的绘制机制前世今生---前世相关的知识,希望能为你提供帮助。
就像上个文章说的,触摸事件的传递机制是从外层到内层的过程。
我们想来看看这个页面里面的层级关系:

Android View的绘制机制前世今生---前世

文章图片

以下我们就用what-how-why三部曲的方式来分析View的绘制过程。
由于篇幅很大,所以分几篇来解析这个过程。
这篇主要是自定义view/viewgroup,以及从Activity到DecorView的加载过程。
1.what:怎么自定义一个View 1.1自定义View自定义View的话,常见过程如下:
/** *@authorDemanMath *@date2020-02-16 * */ class CustomView : View {constructor(context: Context):super(context) constructor(context: Context,attributeSet: AttributeSet):super(context,attributeSet) constructor(context: Context,attributeSet: AttributeSet,def:Int):super(context,attributeSet,def)override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) AppLog.i() }override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) AppLog.i() }override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) AppLog.i() }}

三个构造方法+三个可以复写的方法。
我们先看下这3个方法的顺序:
2020-02-16 13:50:28.212 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onMeasure:[at (CustomView.kt:32)] 2020-02-16 13:50:28.222 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onMeasure:[at (CustomView.kt:32)] 2020-02-16 13:50:28.253 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onMeasure:[at (CustomView.kt:32)] 2020-02-16 13:50:28.255 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onMeasure:[at (CustomView.kt:32)] 2020-02-16 13:50:28.259 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onLayout:[at (CustomView.kt:27)] 2020-02-16 13:50:28.403 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onDraw:[at (CustomView.kt:22)]

1.2自定义ViewGroup上代码
package com.joyfulmath.androidarchitecture.viewimport android.content.Context import android.util.AttributeSet import android.view.ViewGroup import com.joyfulmath.androidarchitecture.base.AppLog import kotlin.math.PI import kotlin.math.cos import kotlin.math.min import kotlin.math.sin/** *@authorDemanMath *@date2020-02-16 * */ class FerrisWheel:ViewGroup {var count = 12 var a = 2*PI/count var startA = PI/2constructor(context: Context):super(context){ initViews() } constructor(context: Context,attributeSet: AttributeSet):super(context,attributeSet){ initViews() } constructor(context: Context,attributeSet: AttributeSet,def:Int):super(context,attributeSet,def){ initViews() }private fun initViews() { for(i in 0 until count){ this.addView(CustomView(context)) } }override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { var mViewWidth = measuredWidth var mViewHeight = measuredHeight AppLog.i(" $mViewWidth,$mViewHeight" ) var cx = mViewWidth/2 var cy = mViewHeight/2 var r= min(measuredWidth,measuredHeight)*0.5f -20 AppLog.i(" r:$r,cx:$cx" ) for(i in 0 until count){ var view = getChildAt(i) var width = view.measuredWidth var height = view.measuredHeight var cx1 = r* sin(startA+a*i) var cy1 = -r* cos(startA+a*i) AppLog.i(" width:$width,height:$height" ) AppLog.i(" cx1:$cx1,cy1:$cy1" ) view.layout(cx+(cx1-width/2).toInt(), cy+(cy1-height/2).toInt(), cx+(cx1+width/2).toInt(), cy+(cy1+height/2).toInt()) } }override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { measureChildren(widthMeasureSpec,heightMeasureSpec) super.onMeasure(widthMeasureSpec, heightMeasureSpec) } }

效果如下:
Android View的绘制机制前世今生---前世

文章图片

这里override了layout方法。可见View的绘制跟他的父View只有一个关系,ViewGroup指定了子View的位置。
关于View/ViewGroup绘制的机制,在下一节讨论。
2.How:View的绘制机制是什么从上一节看出:整个绘制流程三个过程,measure,layout,draw这三个过程。
下面我们从源码的角度来分析下是不是这个过程。
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { .......// TODO Push resumeArgs into the activity for consideration r = performResumeActivity(token, clearHide, reason); if (r != null) { final Activity a = r.activity; if (localLOGV) Slog.v( TAG, " Resume " + r + " started activity: " + a.mStartedActivity + " , hideForNow: " + r.hideForNow + " , finished: " + a.mFinished); final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; // If the window hasn' t yet been added to the window manager, // and this guy didn' t finish itself or start another activity, // then go ahead and add the window. 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(); 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; l.softInputMode |= forwardBit; if (r.mPreserveWindow) { a.mWindowAdded = true; r.mPreserveWindow = false; // Normally the ViewRoot sets up callbacks with the Activity // in addView-> ViewRootImpl#setView. If we are instead reusing // the decor view we have to notify the view root that the // callbacks may have changed. ViewRootImpl impl = decor.getViewRootImpl(); if (impl != null) { impl.notifyChildRebuilt(); } } if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); } else { // The activity will get a callback for this {@link LayoutParams} change // earlier. However, at that time the decor will not be set (this is set // in this method), so no action will be taken. This call ensures the // callback occurs with the decor set. a.onWindowAttributesChanged(l); } }// If the window has already been added, but during resume // we started another activity, then don' t yet make the // window visible. } else if (!willBeVisible) { if (localLOGV) Slog.v( TAG, " Launch " + r + " mStartedActivity set" ); r.hideForNow = true; }// Get rid of anything left hanging around. cleanUpPendingRemoveWindows(r, false /* force */); // The window is now visible if it has been added, we are not // simply finishing, and we are not starting another activity. if (!r.activity.mFinished & & willBeVisible & & r.activity.mDecor != null & & !r.hideForNow) { if (r.newConfig != null) { performConfigurationChangedForActivity(r, r.newConfig); if (DEBUG_CONFIGURATION) Slog.v(TAG, " Resuming activity " + r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig); r.newConfig = null; } if (localLOGV) Slog.v(TAG, " Resuming " + r + " with isForward=" + isForward); WindowManager.LayoutParams l = r.window.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != forwardBit) { l.softInputMode = (l.softInputMode & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) | forwardBit; if (r.activity.mVisibleFromClient) { ViewManager wm = a.getWindowManager(); View decor = r.window.getDecorView(); wm.updateViewLayout(decor, l); } }r.activity.mVisibleFromServer = true; mNumVisibleActivities++; if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } }if (!r.onlyLocalRequest) { r.nextIdle = mNewActivities; mNewActivities = r; if (localLOGV) Slog.v( TAG, " Scheduling idle handler for " + r); Looper.myQueue().addIdleHandler(new Idler()); } r.onlyLocalRequest = false; // Tell the activity manager we have resumed. if (reallyResume) { try { ActivityManager.getService().activityResumed(token); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } }} else { // If an exception was thrown when trying to resume, then // just end this activity. try { ActivityManager.getService() .finishActivity(token, Activity.RESULT_CANCELED, null, Activity.DONT_FINISH_TASK_WITH_ACTIVITY); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } }

2.1 关键是页面绘制流程整个的过程就是一开始讲的层级关系。
第一点:performResumeActivity 比wm.addView(decor, l)先执行。所以Activity是先获取焦点,才绘制view。
performResumeActivity-> r.activity.performResume()-> mInstrumentation.callActivityOnResume(this)-> activity.onResume()
在performResume最后可以看到onPostResume
final void performResume() { performRestart(); ... // mResumed is set by the instrumentation mInstrumentation.callActivityOnResume(this); ... onPostResume(); ... }protected void onPostResume() { final Window win = getWindow(); if (win != null) win.makeActive(); if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true); mCalled = true; }

window出现了,这个就是phonewindow。
下面我们去看docorview的过程。
//2020.02.18 phonewindow在这里获取 if (r.window == null & & !a.mFinished & & willBeVisible) { r.window = r.activity.getWindow(); //2020.02.18 docorview在这里获取 View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; ... if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); } else { // The activity will get a callback for this {@link LayoutParams} change // earlier. However, at that time the decor will not be set (this is set // in this method), so no action will be taken. This call ensures the // callback occurs with the decor set. a.onWindowAttributesChanged(l); } }

【Android View的绘制机制前世今生---前世】我们来看下wm.addView(decor, l); 这个的过程。wm的实现就是WindowManagerImpl
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); }

mGlobal是WindowManagerGlobal, addview的核心代码如下
root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); root.setView(view, wparams, panelParentView);

关于从ViewGroup开始的绘制流程,请看下篇。
更多内容:demanmath
公共号:
Android View的绘制机制前世今生---前世

文章图片


    推荐阅读