Activity視圖從創建到顯示
就算是鹹魚也要做最鹹的那條。
沒錯,入口點當然是onCreate()中的SetContentView(R.layout.xxx)
- 調用mWindow.setContentView(R.layout.xxx),mWindow是Activity被創建時在attach()中創建的PhoneWindow對象.
- PhoneWindow中生成DecorView,具體是new了一個DecorView,這個DecorView是一個FrameLayout,即viewGroup.
- ViewGroup mContentParent = decorView.findViewById(com.android.internal.R.id.content)
- 下一步 LayoutInflate.inflate(R.layout.xxx,mContentParent),成功將我們傳入的佈局,加入到名爲content的這個佈局之中。
LayoutInflate是如何工作的?或者說LayoutInflate.inflate(R.layout.xx,root,true/false),這幾個參數有啥效果?
-
LayoutInflate是如何創建的?
-
mLayoutInflater = LayoutInflater.from(context);
//可見,LayoutInflate是一個服務,因爲要加載app裏面的資源,當然需要用服務去搞事情。 public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater;}
-
-
常見的添加布局是如何搞的?
-
mLayoutInflater.inflate(layoutResID, mContentParent);
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { //這邊傳入的就是 id,ViewParent,true. return inflate(resource, root, root != null); } final Resources res = getContext().getResources(); XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } //這個res的實現是ResoursesImpl,其中使用了AssertManager去獲取這個佈局,先確定這個佈局資源是存在的,然後,加載這個資源佈局 XmlResourceParser parser = res.getLayout(resource); final ResourcesImpl impl = mResourcesImpl; impl.getValue(id, value, true); if (value.type == TypedValue.TYPE_STRING) { return impl.loadXmlResourceParser(value.string.toString(), id, value.assetCookie, type); //loadXmlResourceParser //load這個xml資源的時候,ResourcesImpl中有一個大小爲4的數組,用於緩存 //native 方法去尋找這個資源佈局 final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file); if (block != null) { //緩存處理 final int pos = (mLastCachedXmlBlockIndex + 1) % num;
-
-
inflate(parser, root, attachToRoot); 已經找到這個資源文件,並且轉換成XmlResourceParser,下一步,inflate
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final AttributeSet attrs = Xml.asAttributeSet(parser); View result = root; try { advanceToRootNode(parser); final String name = parser.getName(); if (TAG_MERGE.equals(name)) { rInflate(parser, root, inflaterContext, attrs, false); } else { // Temp is the root view that was found in the xml //這一步主要是生成根View final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { //root不爲空,那麼就獲取到傳入root的佈局屬性 // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } //開始解析指定佈局的xml文件 rInflateChildren(parser, temp, attrs, true); // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { //佈局文件生成的View添加進根Root root.addView(temp, params); } // 僅僅是根據佈局文件生成View,就返回這個View if (root == null || !attachToRoot) { result = temp; } } return result; }
}
-
rInflateChildren() 調用了rInflate() void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; boolean pendingRequestFocus = false; //解析tag的循環 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); ...else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); //解析我們經常使用的include標籤 parseInclude(parser, context, parent, attrs); } else{ //解析xml文件裏面的控件 final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); //本身外層是while循環,而這下一句,產生遞歸效果,因爲,可能這個節點是一個ViewGroup,那麼就需要進去遍歷 rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); } } //注意這個onFinishInflate(),它代表了佈局解析完畢,自定義ViewGroup,有時就會用到這個方法。 if (finishInflate) { parent.onFinishInflate(); }
}
-
createViewFromTag()
View view = tryCreateView(parent, name, context, attrs); //以下爲tryCreateView()的代碼 if (mFactory2 != null) { //這個Factory爲LayoutInflate的一個接口,返回的是個View,也就是更具名字生成View,具體怎麼生成,這個過程交給了這個工廠,我們去找一下在哪裏實現的 view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory != null) { view = mFactory.onCreateView(name, context, attrs); } else { view = null; } return view;}
-
尋找Factory在哪裏初始化進來的
//同時我們在LayotInflate中還發現了setFactory()和setFactory2()來設置的方法。 public void setFactory2(Factory2 factory) { //Factory只能設置一個 if (mFactorySet) { throw new IllegalStateException("A factory has already been set on this LayoutInflater"); } if (factory == null) { throw new NullPointerException("Given factory can not be null"); } mFactorySet = true;
-
找了不一會兒,我們發現AppCompatActivity,裏面的:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { final AppCompatDelegate delegate = getDelegate(); //這裏installViewFactory() delegate.installViewFactory(); delegate.onCreate(savedInstanceState); //在onCreate之前設置Factory, 所以,你想根據自己的規則創建View,你需要在onCreate()的super之前設置就沒問題了。 super.onCreate(savedInstanceState); } @Override public void installViewFactory() { LayoutInflater layoutInflater = LayoutInflater.from(mContext); if (layoutInflater.getFactory() == null) { //爲空才設置,也就是我們自己可以創建自己的Factory,自己去創建View LayoutInflaterCompat.setFactory2(layoutInflater, this); } else { if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) { Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" + " so we can not install AppCompat's"); }}} public AppCompatDelegate getDelegate() { if (mDelegate == null) { //創建了 AppCompatDelegateImpl mDelegate = AppCompatDelegate.create(this, this); } return mDelegate; } //而這個AppCompatDelegateImpl實現了LayoutInflater.Factory2接口 /** * From {@link LayoutInflater.Factory2}. */ @Override public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) { return createView(parent, name, context, attrs); }
-
onCreateView中創建了一個類:AppCompatViewInflate,並且 mAppCompatViewInflater.createView(),來到這個createView看一看:
switch (name) { case "TextView": // new AppCompatTextView(context, attrs),new 出來了我們需要用的TextView view = createTextView(context, attrs); verifyNotNull(view, name); break; case "ImageView": view = createImageView(context, attrs); verifyNotNull(view, name); break; case "Button": view = createButton(context, attrs); verifyNotNull(view, name); break; case "EditText": view = createEditText(context, attrs); verifyNotNull(view, name); break; ........ //創建我們的自定View,所以自定義View需要寫上全路徑,因爲需要用到反射。 if (view == null && originalContext != context) { view = createViewFromTag(context, name, attrs); } if (view != null) { // If we have created a view, check its android:onClick checkOnClickListener(view, attrs); } return view;
那麼解釋我們經常使用LayoutInfalte.inflate(R.layout.xxx,root,true/faalse)第二個參數和第三個參數的意義下面這幾行代碼就差不多了:
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;}
到此,我們的setContView就差不多了,
- 分析了傳入佈局
- 生成DecorView
- 將佈局使用LayoutInflate.inflate添加進R.id.content的一些過程。
View顯示過程。假裝你已經知道Activity的生命週期,並且也知道代碼在哪裏執行。那我們直接來到應用入口點的那個類。
小明同學,請等等····是哪個類??嗚,那就告訴你吧,是ActivityThread
-
來到執行resume的方法:handleResumeActivity()
@Override public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { //onNewIntent(),以及調用Activity的resume()生命週期函數,都在下面這個方法執行 final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); ... //獲取到PhoneWindow, r.window = r.activity.getWindow(); //獲取DecorView View decor = r.window.getDecorView(); //decorView設置爲不可見 decor.setVisibility(View.INVISIBLE); //拿到windowManagerImpl ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; ... if (!a.mWindowAdded) { a.mWindowAdded = true; //WindowManagerImpl的addView方法,傳入參數爲decorView,和Window的屬性 //故事就從這裏開始了 wm.addView(decor, l); ... //調用Activity裏面的makeVisisble()使得視圖可見 if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } //你說了解Handler,那來說說IdleHandler吧。 Looper.myQueue().addIdleHandler(new Idler());
-
ok,可以看到上面一系列“熟悉”的流程。我們重點關注上面的wm.addView(DecorView,WindowManager.LayoutParams). 進入WindowManagerImpl.
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); //單例的WindowMagerGlobal. mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); }
-
WindowManagerGlobal
//創建ViewRootImpl 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 //調用ViewRootImpl的setView方法: root.setView(view, wparams, panelParentView);
-
來到ViewRootImpl的setView,很快我們看到了一個名爲requestLayout的方法
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } } //上面的checkThread void checkThread() { 還是那個熟悉的提示,原來這麼多年來,提示非主線程不能更新Ui的提示,都在這裏孤孤單單,今天我終於來看她了。 if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } //上面的scheduleTraversals() void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); }} 注意這個mTraversalRunnable final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); //調用了doThrversal() }}
doThaversal()中調用了performTraversals() 好的,我們的故事開始了~~
-
performTraversals中一直往下走,走啊走,你會看到一行代碼:
// Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
-
再走:
performLayout(lp, mWidth, mHeight);
-
再接再厲
performDraw();
-
完畢之後,我們看
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { if (mView == null) { return; } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { //這個mView就是DecorView mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
-
DecorView也是繼承自View,來到View中的measure(),我們看到:
調用了onMeasure,decorView的父類是FragmLayout onMeasure(widthMeasureSpec, heightMeasureSpec);
-
FrameLayout##onMeasure()
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); int maxHeight = 0; int maxWidth = 0; int childState = 0; //注意到,開始循環遍歷ViewGroup裏面包含的子View for (int i = 0; i < count; i++) { final View child = getChildAt(i); //不爲Gone的不去測算,爲InVISibility也需要測算的 if (mMeasureAllChildren || child.getVisibility() != GONE) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);} ....... setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
-
measureChildWithMargins()
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { //拿到孩子寫在佈局裏面的寬高屬性 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //測算子孩子的大小 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); //看到再次調用child.measure(),measure中會調用onMeasure() child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}
-
由父類和子類確定子類的模式和大小。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
-
父佈局是Exactly
- 子佈局如果爲AT_MOST(wrap_content), 那麼模式爲AT_MOST,其他都是Exactly
-
父佈局是AT_MOST
-
子View寫爲準確值(100dp),那麼模式是Exactly
-
子View是wrap_content或match_parent,都是AT_MOST模式。
-
上面兩句話讀起來有點晦澀難懂,記住Exactly是已經確定了,AT_MOST就是待計算。
-
父類確定(Exactly)了,子類確定(100dp和match_parent)那麼子類也是確定(Exactly)
-
父類不確定(AT_MOST), 子類確定(100dp)那麼是Exactly,否則,子類都不確定。
-
-
舉一個小栗子,就是ScrollView包裹ListView ,你會發現只顯示了一條數據。網上給出的解決方案很多都是: 計算ListView 的高度,然後更改List的LayoutParams。竟然把所有的ListView所有的孩子都拿來累加高度。
這個問題是因爲ScrollView,傳過來的模式是:UNSPECIFIED,ListView中的onMeasure,對於UNSPECIFIED的處理是:
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
所以正確的打開方式:
//繼承一下ListView,然後,重寫onMeasure()方法:
val height = MeasureSpec.makeMeasureSpec(heightMeasureSpec, MeasureSpec.AT_MOST)
super.onMeasure(widthMeasureSpec, height)
另外,我們還看到有performLayout()和performDraw()方法,調用也都是大同小異,完成各自的功能。
- performMeasure – > measure – > onMeasure
- performLayout – > layout – > layout
- performDraw – > draw – > drawbackground – > ondraw.