02布局原理与XML原理分析

Posted 清风百草

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了02布局原理与XML原理分析相关的知识,希望对你有一定的参考价值。

(1)使用插件化的方案为App换肤
(2)不需要重启App就能够换肤
(3)市场上所有的APP都可以当成自己的皮肤包来用。
(4)无闪烁
(5)便于扩展与维护,入侵性很小。
(6)只需要在Application初始化一次即可使用
(7)喜欢什么样的皮肤包,就可以将它的apk包拿过来就可以了。

【02】布局原理与XML原理分析

文章目录

1.布局原理

(1)因为要实现换肤功能,因此需要了解布局的原理
(2)换肤需要找到UI上的控件,对UI的结构是需要很清楚的。
(3)需要了解布局的层次结构,View的绘制流程。

2.UI是怎么出来的

2.1ActivityThread

(1)performLaunchActivity

  • Activity布局的入口是从performLaunchActivity开始的
  • 会初始化Activity上下文
  • 创建Activity
mInstrumentation.newActivity()
  • Application的初始化
  • activity getWindow()
  • activity.attach Activity绑定的过程对mWindow进行初始化

2.2PhoneWindow

(1)Activity中包含了一个PhoneWindow

2.2.1setContentView实现

@Override
    public void setContentView(int layoutResID) 
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) 
            installDecor();
         else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) 
            mContentParent.removeAllViews();
        

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) 
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
         else 
            mLayoutInflater.inflate(layoutResID, mContentParent);
        
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) 
            cb.onContentChanged();
        
        mContentParentExplicitlySet = true;
    

2.2.2installDecor()

(1)就是new了一个DecorView出来

 protected DecorView generateDecor(int featureId) 
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) 
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) 
                context = getContext();
             else 
                context = new DecorContext(applicationContext, this);
                if (mTheme != -1) 
                    context.setTheme(mTheme);
                
            
         else 
            context = getContext();
        
        return new DecorView(context, featureId, this, getAttributes());
    

2.2.3布局解析

mLayoutInflater.inflate(layoutResID, mContentParent);

2.2.4类之间的关系

(1)DecorView就是一个FrameLayout

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks 


(2)generateLayout()

protected ViewGroup generateLayout(DecorView decor) 
		// Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) 
            if (mIsFloating) 
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
             else 
                layoutResource = R.layout.screen_title_icons;
            
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
         else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) 
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = R.layout.screen_progress;
            // System.out.println("Progress!");
         else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) 
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) 
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
             else 
                layoutResource = R.layout.screen_custom_title;
            
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
         else if ((features & (1 << FEATURE_NO_TITLE)) == 0) 
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) 
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
             else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) 
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
             else 
                layoutResource = R.layout.screen_title;
            
            // System.out.println("Title!");
         else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) 
            layoutResource = R.layout.screen_simple_overlay_action_mode;
         else 
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        

  • 以上是返回窗口装饰相关的代码
  • 找到相关的layout文件 screen_simple.xml,系统自带的
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
  • 加载以上xml文件,通过addView的形式贴到DecorView上面。
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) 
        if (mBackdropFrameRenderer != null) 
            loadBackgroundDrawablesIfNeeded();
            mBackdropFrameRenderer.onResourcesLoaded(
                    this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                    getCurrentColor(mNavigationColorViewState));
        

        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) 
            if (mDecorCaptionView.getParent() == null) 
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
         else 

            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    

(3)mLayoutInflater.inflate(layoutResID, mContentParent);

  • mContentParent初始化是在
com.android.internal.policy.PhoneWindow#installDecor
mContentParent = generateLayout(mDecor);

(4)最终调用android.view.LayoutInflater#inflate(org.xmlpull.v1.XmlPullParser, android.view.ViewGroup, boolean)

  • root就是FrameLayout,mContentParent,也就是布局screen_simple.xml中的id为content的控件.

(5)创建Activity的View,即根布局视图
android.view.LayoutInflater#inflate(org.xmlpull.v1.XmlPullParser, android.view.ViewGroup, boolean)

android.view.LayoutInflater#createViewFromTag(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet, boolean)

(6)关于inflate的第三个参数attachToRoot

  • 如果是false父布局的LayoutParams参数填充进布局,就直接从布局中获取参数值。
  • 如果是true就父布局的LayoutParams参数就不会填充进去,就需要通过代码的方式填充进去。
  • 如果根布局产生的View不是root子布局的情况,就会进入到if条件中。
ViewGroup.LayoutParams params = null;

if (root != null) 
    if (DEBUG) 
        System.out.println("Creating params from 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);
    

(7)布局如何创建的
android.view.LayoutInflater#createViewFromTag(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet, boolean)

  • 创建
    android.view.LayoutInflater#createView(android.content.Context, java.lang.String, java.lang.String, android.util.AttributeSet)
constructor = clazz.getConstructor(mConstructorSignature);

所有的View在通过LayoutInflater创建时,都是通过带两个参数的构造方法完成的。
即任何一个自定义控件带两个参数的构造方法是绝对不能省略的。

3.自定义LayoutInflater统计View的执行过程

(1)View的生成都是通过反射生成出来的。
(2)我们是否可以自己重写一个LayoutInflater将它的代码全部拷贝进去,就可以统计到所有的View的执行过程了。
(3)在Activity中调用setContentView的时候,都会跑到PhoneWindow的setContentView里面来.
(4)进行布局解析的过程都会执行createViewFromTag()
(5)最终执行createView()
(6)做换肤就需要了解这些点。

4.工厂创建View

(1)android.view.LayoutInflater.Factory#onCreateView
不知道父亲是谁

(2)android.view.LayoutInflater.Factory2#onCreateView
带父控件绑定

(3)android.view.LayoutInflater.FactoryMerger#onCreateView(java.lang.String, android.content.Context, android.util.AttributeSet)

(4)android.view.LayoutInflater.FactoryMerger#onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet)

5.创建View的方案

(1)可以自己写一个LayoutInflater,将其创建的过程改掉
(2)自己写工厂,在工厂里面去抄源码的实现,去完成View创建。
(3)用自己写的工厂去干预到View的加载过程,在换肤的时候,所有的View的资源,都可以通过工厂提前设置进去,去修改控件的值。

6.换肤有两种方案

(1)重写LayoutInflater(这种方式有入侵性)
(2)自己实现Factory

7.代码阅读思维导图

(1)图片可以下载后放大看。

8.打赏鼓励

感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!

8.1微信打赏

8.2支付宝打赏

以上是关于02布局原理与XML原理分析的主要内容,如果未能解决你的问题,请参考以下文章

02布局原理与XML原理分析

03布局原理与XML原理分析二

03布局原理与XML原理分析二

03布局原理与XML原理分析二

Android-DataBinding原理分析

从源代码角度分析ViewStub 疑问与原理