• android setContentView()源码解析


    系统:mac

    android studio: 4.1.3

    kotlin version:1.5.0

    gradle: gradle-6.5-bin.zip

    看完本篇你讲学会什么?

    • setContentView() 如何解析View
    • LayoutInflater 什么时候初始化,在什么地方?
    • LayoutInflater 如何加载View
    • Factory2和Factory的作用
    • Factory2什么时候初始化?
    • appcompat1.2 和 appcompat1.3的区别
    • AppCompatViewInflater 如何改变View
    • AppCompat主题和Material主题对普通View的区别
    • 如何自己解析View(Activity / Fragment)
    • onCreate中不调用super.onCreate()为什么会报错

    高温预警!

    AppCompat主题material主题
    image-20220824143621732image-20220824143742512

    1.setContentView() 如何解析View

    这段源码在View生命周期中就有提到过,但是还不够细致,本篇带你完全理解!

    从入口开始:

    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_custom_parse)
        }
    
    • 1
    • 2
    • 3
    • 4

    代码块1.1:

    #AppCompatActivity.java
    
    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }
    
    @NonNull
    public AppCompatDelegate getDelegate() {
      if (mDelegate == null) {
        // 执行到了这里,创建AppCompatDelegate
        mDelegate = AppCompatDelegate.create(this, this);
      }
      return mDelegate;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    tips: 其实不会执行AppCompatDelegate.create() 因为此时mDelegate已经!=null了,后面会说在什么时候初始化的,这里就先以他会null来说

    代码块1.2:

    #abstract AppCompatDelegate.java
    @NonNull
    public static AppCompatDelegate create(@NonNull Activity activity,
            @Nullable AppCompatCallback callback) {
         // AppCompateDelegate 是一个抽象类
      	 // 实现类为 AppCompateDelegateImpl
        return new AppCompatDelegateImpl(activity, callback);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接着执行代码块1.1的setContentView(),

    就会执行到AppCompateDelegateImpl.setContentView()方法

    代码块1.3

    # AppCompatDelegateImpl.java
    @Override
    public void setContentView(int resId) {
        // 解析主题属性等 并且调用Window#setContentView()方法
        ensureSubDecor();
      
        // android.R.id.content为screen_simple.xml中的id
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
      
        contentParent.removeAllViews();
      
    	  // 解析View
        LayoutInflater.from(mContext).inflate(resId, contentParent);
      
        // 空方法代表解析View完成
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这段代码有很多博主直接进入LayoutInflater#inflate中解释,直接略过了最精彩的 ensureSubDecor()方法,现在来看看吧~

    代码块1.4

    # AppCompatDelegateImpl.java
    private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();
    				...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    代码块1.5

    # AppCompatDelegateImpl.java
    Window mWindow;
    
    private ViewGroup createSubDecor() {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
    
        // 解析主题,设置样式
        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false))
          else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) 
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false))
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) 
        a.recycle();
         .... 
    
        // 初始化DecorView
        mWindow.getDecorView();
        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;
    	  if (!mWindowNoTitle) {
          if (mIsFloating) {
            ...
          } else if (mHasActionBar) {
             // 会走这里.. 可以自行打断点看看.
             subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                            .inflate(R.layout.abc_screen_toolbar, null);
          }
        }
      
    	  .... 省略了大量代码 ...
    		// 初始化主界面
        mWindow.setContentView(subDecor);
    
        ...
    
        return subDecor;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    这段代码前半部分主要是解析样式

    期间会创建一个ViewGroup,这个ViewGroup的布局为:

    image-20220824155214439

    这个东西是什么不重要,重要的是往**Window#setContentView()**中传的布局不是自己的布局

    而是系用的布局!

    接下来 主要代码是调用**Window#setContentView()**方法,来初始化主界面

    众说周知,Window是PhoneWindow,在 Activity#attatch中初始化的

    那么直接走到了PhoneWindow#setContentView()

    代码块1.6

    # PhoneWindow.java
      
    @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) {
            // szj加载DecorView 初始化 mContentParent
            installDecor();
        } 
      ...
        mContentParent.addView(view, params);
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里要看仔细了, 传过来的是View,所以执行的是setContentView(View)

    千万别跑到setContentView(id)上!

    代码块1.7

    # PhoneWindow.java
      
    private void installDecor() {
        mForceDecorInstall = false;
        // 在代码块1.5中已经通过 mWindow.getDecorView();方法初始化过了,所以这里不执行
        if (mDecor == null) {
            // szj初始化DecorView
            mDecor = generateDecor(-1);
            ...
        } else {
            mDecor.setWindow(this);
        }
        
    	  // szj初始化主界面布局
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    代码块1.8

    # PhoneWindow.java
      
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
    
    // 初始化主界面布局
    protected ViewGroup generateLayout(DecorView decor) {
        TypedArray a = getWindowStyle();
        // 初始化Window样式等
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
          requestFeature(FEATURE_NO_TITLE);
        } 
        // 这里有很多初始化的方法..
      
        // 用来区分主界面布局
        int layoutResource;
      	if(...){}else if(...){
        }else {
          // 一般没有设置的情况下,主界面都是这一个
          layoutResource = R.layout.screen_simple;
        }
        // 吧布局解析添加到DecorView上 
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
     	  return contentParent;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    来看看 R.layout.screen_simple 布局是什么样子的:

    image-20220824160823728

    Tips: R.layout.screen_simple需要下载android源码,我是下载android11的源码

    那么终于可以找到了id为content的了

    到此时,系统界面就初始化好了

    接下来退回到起点,再来看看

    代码块1.3

    # AppCompatDelegateImpl.java
    @Override
    public void setContentView(int resId) {
        // 解析主题属性等 并且调用Window#setContentView()方法
        ensureSubDecor();
      
        // android.R.id.content为screen_simple.xml中的id
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
      
        contentParent.removeAllViews();
      
    	  // 解析View
        LayoutInflater.from(mContext).inflate(resId, contentParent);
      
        // 空方法代表解析View完成
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在ensureSubDecor() 方法中,我们会初始化系统的布局

    初始化完系统的布局后,我们获取到 R.id.content 也就是screen_simple.xml 中的FrameLayout

    在由FrameLayout作为ViewGroup初始化我们的布局

    开始执行 LayoutInflater.from(mContext).inflate(resId, contentParent);

    走到这里先停一下, 我们先看LayoutInflater在什么时候初始化,然后在进行布局解析!

    LayoutInflater 什么时候初始化,在什么地方?

    代码块2.1:

    // 通过form实例一个LayoutInflater
    public static LayoutInflater from(Context context) {
        // szj 获取LayoutInflater实例
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里通过context.getSystemService() 初始化LayoutInflater,

    我们知道 context实现类为ContextImpl, 在ActivityThread.java 中初始化(这里就不展开了)

    那么我们直接到ContextImpl去找getSystemService() 方法

    代码块2.2:

    # ContextImpl.java
    @Override
    public Object getSystemService(String name) {
        if (vmIncorrectContextUseEnabled()) {
            
        //szj走到了这里。。
        return SystemServiceRegistry.getSystemService(this, name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    代码块2.3:

    # SystemServiceRegistry.java
    public static Object getSystemService(ContextImpl ctx, String name) {
        if (name == null) {
            return null;
        }
        // 在 SYSTEM_SERVICE_FETCHERS中找
        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        if (fetcher == null) {return null;}
      
        // 然后在ServiceFetcher中去找服务
        final Object ret = fetcher.getService(ctx);
        if (ret == null) {return null;}
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    代码块2.4:

    那我们只要看看SYSTEM_SERVICE_FETCHERS是什么就OK了

    # SystemServiceRegistry.java
    private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new ArrayMap<String, ServiceFetcher<?>>();
    
    • 1
    • 2
    • 3

    SYSTEM_SERVICE_FETCHERS是一个全局静态map,并且是不可改变的

    那么直接在静态代码块中找即可

    # SystemServiceRegistry.java
      
     static {
      // 注册LayoutInflater服务
      registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                      new CachedServiceFetcher<LayoutInflater>() {
                        @Override
                        public LayoutInflater createService(ContextImpl ctx) {
                          return new PhoneLayoutInflater(ctx.getOuterContext());
                        }});
       
        // 注册window服务
       registerService(Context.WINDOW_SERVICE, WindowManager.class,
                       new CachedServiceFetcher<WindowManager>() {
                         @Override
                         public WindowManager createService(ContextImpl ctx) {
                           return new WindowManagerImpl(ctx);
                         }});
      ......
     }
    
    private static <T> void registerService(@NonNull String serviceName,
                @NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
            SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    可以看到所有的服务都在这里注册的.

    那么就可以知道: LayoutInflater是在SystemServiceRegistry静态代码块初始化的

    好了,对LayoutInflater有一个基本的了解后,就接着代码块1.3开始解析布局吧

    代码块1.3

    # AppCompatDelegateImpl.java
    @Override
    public void setContentView(int resId) {
        // 解析主题属性等 并且调用Window#setContentView()方法
        ensureSubDecor();
      
        // android.R.id.content为screen_simple.xml中的id
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
      
        contentParent.removeAllViews();
      
    	  // 解析View
        LayoutInflater.from(mContext).inflate(resId, contentParent);
      
        // 空方法代表解析View完成
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    LayoutInflater#.inflate(id, ViewGroup);

    代码块1.3.1

    # LayoutInflater.java
      
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • @param resource: 自己布局的id(R.layout.activity_main)
    • @param root: 系统的FrameLayout

    代码块1.3.2

    # LayoutInflater.java
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
       
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        // XmlResourceParser看名字就知道用来解析XML文件
        XmlResourceParser parser = res.getLayout(resource);
        try {
          	// 接着执行这里
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    代码块1.3.3

    # LayoutInflater.java
      
    private static final String TAG_MERGE = "merge";
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...
            // 保存传进来的view
            View result = root;
    
            try {
                // 找到root标签 如果找不到就直接报错
                advanceToRootNode(parser);
    
                // 获取到这个root标签name
                final String name = parser.getName();
    
                // 判断是否是 merge标签
                if (TAG_MERGE.equals(name)) {
                    // 直接加载View,忽略merge标签
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // 通过标签来解析View
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
                    ViewGroup.LayoutParams params = null;
    
                    if (root != null) {
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                           // 设置ViewLayoutParams
                            temp.setLayoutParams(params);
                        }
                    }
    									
                    // 解析childView
                    rInflateChildren(parser, temp, attrs, true);
    
                    // 最终解析完成后添加到root中
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                }
    
            }  catch (Exception e) {
              ...
            } 
    
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    这里都好理解,传过来一个view,通过 createViewFromTag()解析,

    解析完成之后添加到View上就完成了

    那么来看看createViewFromTag() 解析View的方法

    代码块1.3.4

    # LayoutInflater.java
      
    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }
    
    
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                               boolean ignoreThemeAttr) {
            
            try {
                // 尝试创建View  
                View view = tryCreateView(parent, name, context, attrs);
    
                if (view == null) 
                  // 判断是不是自定义View 
                  // 1.自定义View在布局文件中是全类名
                  // 2.而系统的View则不是全类名
                  if (-1 == name.indexOf('.')) {
                    // 自定义View
                    view = onCreateView(context, parent, name, attrs);
                  } else {
                    // 系统View
                    view = createView(context, name, null, attrs);
                  }
                }
                return view;
            } catch (InflateException e) {
                ...
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    这段代码很好理解:

    先尝试获取View,如果获取不到,

    就判断当前View

    • 是系统的
    • 还是自己定义的

    然后在创建View

    来看看尝试创建View的方法

    代码块1.3.5

    # LayoutInflater.java
      
    public final View tryCreateView(@Nullable View parent, @NonNull String name,
                                    @NonNull Context context,
                                    @NonNull AttributeSet attrs) {
        ...
        View view;
      	
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
    		...
        return view;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这段代码很关键!

    先说结果:

    • 如果自身Activity extends AppCompatActivity 那么 mFactory2 !=null 就会执行

    mFactory2.onCreateView(parent, name, context, attrs);

    • 如果自身Activity extends Activity 那么view = null ,表示尝试获取view失败

    ​ 那么再去判断是系统的View还是自定义的View,再去处理各自的逻辑操作

    现在咋们假设view返回的是null,来看看view是创建的 回到代码块1.3.4,接着看

    # LayoutInflater#createViewFromTag()方法内代码
      
    if (view == null) {
      // 判断是不是自定义View 
      // 1.自定义View在布局文件中是全类名
      // 2.而系统的View则不是全类名
      if (-1 == name.indexOf('.')) {
        // 自定义View
        view = onCreateView(context, parent, name, attrs);
      } else {
        // 系统View
        view = createView(context, name, null, attrs);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 自定义View:

      代码块1.3.6

    # LayoutInflater.java
      
    public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
                             @NonNull String name, @Nullable AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(parent, name, attrs);
    }
    
     protected View onCreateView(View parent, String name, AttributeSet attrs)
                throws ClassNotFoundException {
            return onCreateView(name, attrs);
     }
    
    protected View onCreateView(String name, AttributeSet attrs)
                throws ClassNotFoundException {
            return createView(name, "android.view.", attrs);
    }
    
    // 会把咋们自定义的view前缀加上android.view.
     public final View createView(String name, String prefix, AttributeSet attrs)
                throws ClassNotFoundException, InflateException {
            ...
            return createView(context, name, prefix, attrs);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    最终都会走到createView()方法,那么系统创建view也是走的这个方法,

    来看看createView()方法吧:

    • 系统创建View:

    代码块1.3.7

    public final View createView(@NonNull Context viewContext, @NonNull String name,
                                 @Nullable String prefix, @Nullable AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
         ...
    
        // 缓存
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;
    
        try {
            // 先从缓存中拿 如果缓存中没有,那么反射创建View
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                // 反射构建View
                clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);
    
                ... 
                // 加入缓存中
                sConstructorMap.put(name, constructor);
            } 
          ...
            try {
                // 反射创建系统View
                final View view = constructor.newInstance(args);
                ... 
                return view;
            }
        } catch (NoSuchMethodException e) {
          ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    这里通过反射创建完成之后,就会一直返回,先返回到代码块1.3.4createViewFromTag() 方法上

    然后添加到屏幕上就完成了

    哦~ 怪不得很多同学说setContentView() 的时候就是通过反射来创建view, 布局复杂的情况下会很影响性能,原来说的就是这里啊

    but!

    如果说尝试创建view成功了,那么他不就不会反射了么…

    代码块1.3.4

    # LayoutInflater.java
      
    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }
    
    
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                               boolean ignoreThemeAttr) {
            
            try {
                // 尝试创建View 
                View view = tryCreateView(parent, name, context, attrs);
    
                // TODO 如果创建View成功,那么就不会反射!!!!!!!!!!!!!!
                if (view == null) 
                  // 判断是不是自定义View 
                  if (-1 == name.indexOf('.')) {
                    // 自定义View
                    view = onCreateView(context, parent, name, attrs);
                  } else {
                    // 系统View
                    view = createView(context, name, null, attrs);
                  }
                }
                return view;
            } catch (InflateException e) {
                ...
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    那么再来看一下尝试创建View的代码:

    代码块1.3.8

    # LayoutInflater.java
    
    private Factory mFactory;
    private Factory2 mFactory2;
    
    public final View tryCreateView(@Nullable View parent, @NonNull String name,
                                    @NonNull Context context,
    
        View view;
        // 如果mFactory2 != null 就创建View
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
           // 如果mFactory != null 就创建View
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
    
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }
    
        return view;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    那么先来看看mFactory2和mFactory是什么东西

    image-20220824175007276

    可以看出Factory2 和 Factory都是接口 , 并且Factory2继承Factory

    而Factory2和Factory的区别为:

    Factory2比Factory多一个parent参数而已

    刚才我也说过, 如果你的Activity 继承自 AppCompatActivity() 那么mFactory2 != null

    现在要做的就是看一下mFactory2在什么地方初始化的

    mFactory2在什么地方初始化?

    这里我们需要注意的是:

    • appcompat1.2 和 appcompat1.3稍微有一点不一样

    现在最新版是appcompat1.4, appcompat1.4的代码基本和appcompat1.3是一样的,

    所以我们就先来看

    appcompat1.2中的mFactory2是在什么地方初始化吧

    要看他的源码需要注意的是,自身的Activity继承自AppCompatActivity

    现在已知的条件是:

    在setContentView()中,我们去解析布局,然后用mFactory2去尝试创建view(代码块1.3.4),防止通过反射创建

    那么在setContentView() 就已经初始化好了mFactory2,

    所以mFactory2一定是在setContentView() 之前就初始化的!

    那么我们就从AppCompatActivity.onCreate()方法看起

    代码块3.1:

    # AppCompatActivity.java (appcompat1.2)
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate(); // 代码1
        delegate.installViewFactory(); // 代码2
        delegate.onCreate(savedInstanceState); //代码3
        super.onCreate(savedInstanceState); // 代码4
      
        // 正常情况下在这里加载布局.. 
        // setContentView();  // 代码5
        // findViewById()... 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以看出,在setContentView()之前还有一段代码

    • 代码1在setContentView() 的时候聊的很清晰

    那么就来看看代码2干了什么事情

    delegate.installViewFactory(); // 代码2

    他的实现类为AppCompatDelegateImpl,直接跳过去看看

    代码块3.2:

    # AppCompatDelegateImpl.java
      
    @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        // 如果factory == null
        if (layoutInflater.getFactory() == null) {
          // 就设置factory2
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else {
            ... 
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    代码块3.3:

    # AppCompatDelegateImpl.java
      
    public static void setFactory2(
            @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
        inflater.setFactory2(factory);
    
        if (Build.VERSION.SDK_INT < 21) {
             // 这里代码不用看,现在android版本98% 都 > 21
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以看出,这里就一行代码, 设置factory2

    刚才也看过factory2,他其实就是一个接口,

    代码块3.4:

    public interface Factory2 extends Factory {
        @Nullable
        View onCreateView(@Nullable View parent, @NonNull String name,
                          @NonNull Context context, @NonNull AttributeSet attrs);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    咋们先不管创建的代码,先假设这里是可以创建成功的!

    在重新梳理一遍现在的流程:

    1. 当 当前activity继承自AppCompatActivity时

    2. 首先会执行onCreate()方法中的 getDelegate() 创建一个AppCompatDelegate,AppCompatDelegate的实现类为AppCompatDelegateImpl

    3. 紧接着就会调用 AppCompatDelegateImpl#installViewFactory(), 来初始化LayoutInflater.Factory2

    4. LayoutInflater.Factory2最本质的作用就是创建view,(先假设能创建成功)

    5. 然后就会执行**AppCompatDelegateImpl#onCreate()**方法,在这个方法中都是一些判断,版本适配的东西,这个方法对于我们来说不重要,略过即可

    6. 最后才到了setContentView() 方法来加载整个界面布局

      • setContentView() 还是会执行到AppCompatDelegateImpl中的 setContentView()方法

      • 先执行ensureSubDecor() 方法来创建mSubDecor

        • 在ensureSubDecor()中通过createSubDecor() 创建mSubDecor, 期间最重要的就是调用了PhoneWindow#setContentView(View)方法

        • 在PhoneWindow#setContentView(View)中又会通过installDecor()->generateLayout() 初始化主界面的布局

      • 走到这里,界面的布局就已经初始化完成了

      • 界面的布局初始化完成之后就会初始化我们自己的布局,通过 LayoutInflater.from(mContext).inflate(resId, contentParent);

        • LayoutInflater在ContextImpl.getSystemService() 中初始化
        • 接着走到了SystemServiceRegistry.getSystemService()
        • 最终在SystemServiceRegistry的静态代码块中注册了所有的服务,所以 LayoutInflater也是在这里初始化的
      • 初始化完成LayoutInflater 后就调用 inflate来初始化咋们自己的View

      • 最终在LayoutInflater#createViewFromTag()方法中初始化view,然后添加到系统view上即完成了初始化

        • 在createViewFromTag()中我们会通过 tryCreateView()尝试创建View,并且在这里就调用了 mFactory2.onCreateView(parent, name, context, attrs)方法, mFactory2在AppCompatDelegateImpl#installViewFactory()方法中初始化的,他在AppCompatActivity#onCreate()中调用的!

        • 如果tryCreateView() 创建view不成功,我们在通过反射的办法创建出所有view即可!

        • 走到这里的时候,createViewFromTag() 一定会返回一个view,无论是通过mFactory2.onCreateView()创建还是反射创建,都会返回一个view

    7. 最终添加到系统的view上即可!

      走到这里,appcompat1.2中setContentView()的源码就差不多了!

      那么我们先不管LayoutInflater.Factory2是如何创建View的,先来看看appcompat1.3中有什么变化吧!

      appcompat1.3中的mFactory2是在什么地方初始化吧

      更新到appcompat1.3:

      image-20220825101902985

    当更新到appcompat1.3后你就会惊奇的发现,在AppCompatActivity中已经没有onCreate()方法了,

    当在搜索AppCompatDelegate#installViewFactory()等方法的时候就变成了这样:

    代码块4.1:

    # AppCompatActivity.java (appcompat1.3)
    private void initDelegate() {
       ...
        addOnContextAvailableListener(new OnContextAvailableListener() {
            @Override
            public void onContextAvailable(@NonNull Context context) {
                final AppCompatDelegate delegate = getDelegate();
                delegate.installViewFactory();
                delegate.onCreate(getSavedStateRegistry()
                        .consumeRestoredStateForKey(DELEGATE_TAG));
            }
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    那么我们看看这个 addOnContextAvailableListener()方法是什么:

    代码块4.2:

    # ComponentActivity.java
      
    public final void addOnContextAvailableListener(
            @NonNull OnContextAvailableListener listener) {
        mContextAwareHelper.addOnContextAvailableListener(listener);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里会执行到ComponentActivity中的方法,ComponentActivity又是什么鬼,首先我们先来捋清楚他的继承关系

    MainActivity -> AppCompatActivity -> FragmentActivity -> ComponentActivity -> androidx.core.app.ComponentActivity -> Activity

    在AppCompatActivity中调用addOnContextAvailableListener() 就是调用到他爷爷类的ComponentActivity()

    那么这很显然是一个观察者设计模式, 那就来看看他是什么时机分发的吧

    代码块4.3:

    # ComponentActivity.java
      
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        // 分发
        mContextAwareHelper.dispatchOnContextAvailable(this);
        super.onCreate(savedInstanceState);
       ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    代码块4.4:

    # ContextAwareHelper.java
      
     private final Set<OnContextAvailableListener> mListeners = new CopyOnWriteArraySet<>();
    
     // 监听
     public void addOnContextAvailableListener(@NonNull OnContextAvailableListener listener) {
        if (mContext != null) {
          listener.onContextAvailable(mContext);
        }
        mListeners.add(listener);
    }
    
    // 分发
    public void dispatchOnContextAvailable(@NonNull Context context) {
        mContext = context;
        for (OnContextAvailableListener listener : mListeners) {
            listener.onContextAvailable(context);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    既然他可以这样,那么我们来试试这么写代码会不会报错:

    image-20220825103607206

    可以看出,在appcompat1.3中,还是会在onCreate() 中调用,AppCompatDelegate的那些方法!只不过他用了观察者设计模式,统一管理了起来!

    AppCompatViewInflater 如何改变View

    那么接下来就趁热打铁,来看看Factory2.onCreateView() 方法是如何创建View的!

    Factory2在AppCompatDelegateImpl中实现的,所以就去找 AppCompatDelegateImpl#onCreateView() 方法

    代码块5.1:

    # AppCompatDelegateImpl.java
      
    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        return createView(parent, name, context, attrs);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    代码块5.2:

    @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            // 获取当前主题样式
            TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
            String viewInflaterClassName =
                    a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
          
            if (viewInflaterClassName == null) {
                mAppCompatViewInflater = new AppCompatViewInflater();
            } else {
                try {
                  // 通过主题样式反射创建AppCompatViewInflater
                    Class<?> viewInflaterClass = Class.forName(viewInflaterClassName);
                    mAppCompatViewInflater =
                            (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
                                    .newInstance();
                } catch (Throwable t) {
                    // 如果报错就直接new AppCompatViewInflater
                    mAppCompatViewInflater = new AppCompatViewInflater();
                }
            }
        }
    
    		// 最终在这里创建view
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, 
                true, 
                VectorEnabledTintResources.shouldBeUsed() 
        );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    tips: 当前主题为 Theme.AppCompat.Light.DarkActionBar

    代码块5.3:

    final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;
        ....
    
        View view = null;
    
        switch (name) {
            case "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;
            case "Spinner":
                view = createSpinner(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageButton":
                view = createImageButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckBox":
                view = createCheckBox(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RadioButton":
                view = createRadioButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckedTextView":
                view = createCheckedTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "AutoCompleteTextView":
                view = createAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "MultiAutoCompleteTextView":
                view = createMultiAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RatingBar":
                view = createRatingBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "SeekBar":
                view = createSeekBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ToggleButton":
                view = createToggleButton(context, attrs);
                verifyNotNull(view, name);
                break;
            default:
                view = createView(context, name, attrs);
        }
    
        if (view == null && originalContext != context) {
            // 反射创建view
            view = createViewFromTag(context, name, attrs);
        }
    	 ...
    
        return view;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    可以看出,这里做了很多判断,最终都会调用到 createXXX上

    image-20220825104936692

    然后在对应的createXXX() 中就会创建出来对应的AppCompatXXX

    那么,是不是就会恍然大悟!

    原来普通系统的View

    • TextView
    • Button
    • ImageView 等

    如果继承自AppCompatActivity那么就不会反射创建,而是 直接new出来

    那么也就解答了开头的一个问题

    image-20220824143621732

    在xml布局中明明是TextView,但是用的时候他就给转换了!!

    恍然大悟, 醍醐灌顶!

    但是问题又来了,这里只能将TextView转换成对应的AppComatTextView

    如果切换成Material风格,为啥会转换成MaterialTextView呢?

    image-20220824143742512

    并且这里为啥只有

    • TextView转换为了MaterialTextView
    • Button转换为了MaterialButton
    • RadioButton转换成了MaterialRadioButton

    但是ImageView为什么是转换成了AppComatImageView呢?

    首先将主题风格切换成Material风格,在打个断点看一看!

    还记得刚调用AppCompatDelegateImpl$createView()的时候有一个主题判断,那么就在哪里打断点!

    image-20220825110326931

    可以看出,在反射创建的时候, 这里获取到的其实是MaterialComponentsViewInflater!

    那么就好理解了,来看看MaterialComponentsViewInflater的代码:

    image-20220825110533971

    可以看出,在MaterialComponentsViewInflater中就有4个view的方法,那么现在应该没问题恍然大悟了吧?

    如何自己解析View(Activity / Fragment)

    自己解析View-Activity

    既然系统代码中可以通过Factory2拦截代码并且自己创建,那么我们是不是也可以呢…来尝试一下吧

    image-20220825130614123

    可以看出,如果就这么直接new AppCompatViewInflater() 调用createView方法的话是不行的

    因为AppCompatViewInflater.createView没有修饰符 (public)

    所以我们只能吧AppCompatViewInflater类复制出来一份

    image-20220825131040372

    可以看出,我们复制出来一份,然后不让他去自己创建,而是就用系统原来的

    事实证明确实可以!

    自己解析View-Fragment

    代码块6.1

    supportFragmentManager.beginTransaction()
        // 替换系统的content来作为容器
        .replace(android.R.id.content, CustomParseFragment())
        .commit()
    
    • 1
    • 2
    • 3
    • 4

    既然我们已经阅读了源码,知道系统是如何布局的,那么我们就用系统布局的id来替换Fragment即可

    接着还是老套路,在framgnet中设置Factory2

    image-20220825131621327

    结果发现 报错了!!

    image-20220825131745842

    在设置Factory2的时候,报错

    A factory has already been set on this LayoutInflater

    表示已经设置过工厂了!

    那么我们点进去源码看一看他是怎么写的

    代码块6.2

    # LayoutInflaterCompat.java
      
    public static void setFactory2(
            @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
    		inflater.setFactory2(factory);
    	  ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接着看:

    代码块6.3

    public void setFactory2(Factory2 factory) {
        // mFactorySet默认是false
        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");
        }
        // 如果执行过就设置为true
        mFactorySet = true;
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    原来如此,Factory2只能设置一个,那么在Fragment中如何自己解析View呢?

    既然不能设置多个Factory2那么只能看看LayoutInflater有没有提供一个“自我拷贝”的方法

    在全局查找clone关键之后,找到了一个 cloneInContext,看着还可以,那么就试试吧~

    image-20220825142717570

    可以看出,这样也可以自己创建View, 而不是通过反射的方式!

    onCreate中不调用super.onCreate()为什么会报错

    既然说我们可以自己解析View,那么我们是不是就不用调用super.onCreate()方法了呢?

    那是不行的,先来看报什么错

    image-20220825143123739

    意思很清楚,就是说你没有调用super.onCreate()方法,并且as也会提示你

    那么找到分发onCreate事件的地方 ActivityThread#performLaunchActivity

    image-20220825143523109

    所有生命周期都是通过Instrumentation来分发的,这部分源码有时间再了解,

    这里的重点是调用了Activity#mCalled属性

    分发事件之前为false,如果分发完属性还为false那么就报错

    image-20220825144011968

    很明显,在Activity#onCreate()中将这个标记改为了true,表示调用了super.onCreate()

    其实他的所有生命周期都有这个判断, 我猜他可能处理了其他东西吧.

    你以为本篇只是为了聊源码嘛? 那你就大错特错了,敬请期待下一篇,看看自己解析View有什么好处

    tips:下一篇非源码, 纯粹实战,本篇源码就是为了引出下一篇, 你以为我在第一层,其实我在臭氧层 hhhh

    部分思路参考地址

    原创不易,您的点赞就是对我最大的支持!

    热门源码文章:

  • 相关阅读:
    解决nexus3登录x509: certificate has expired or is not yet valid
    简单讲解Android Fragment(三)
    mybatis基本构成&mybatis与hibernate的区别&添加mybatis支持
    k个相等的子集
    基于Docker的JMeter分布式压测实战讲解
    中国棉纺织行业市场深度分析及发展规划咨询综合研究报告
    【正点原子FPGA连载】 第三章 硬件资源详解 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0
    01-JavaScript-数据类型
    情态动词习题
    Redis〔篇〕
  • 原文地址:https://blog.csdn.net/weixin_44819566/article/details/126525702