深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

Posted 刘镓旗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解View知识系列一- setContentView和LayoutInflater源码原理分析相关的知识,希望对你有一定的参考价值。

这个系列的来由来:

在工作中经常有小伙伴问我,到底怎么自定义控件,什么时候重写这onMeasure啊,onMeasure中的两个参数怎么来的,View的三大流程是怎么回事,等等一系列的问题,后来我发现其实是因为他们对View的知识不是特别的了解,或者说知道但是都是零零碎碎的,没有系统的串下来,所以就有了这一系列的文章在组内每周进行分享,先将每周整理的文档发布出来希望可以帮助更多的朋友。 其实自定义控件说难确实难,说简单又特别简单,难是因为自定义控件是和业务需求相关联的,没有什么统一的标准,所以可能有点摸不着头脑,但是这并不是最主要的难点,最重要的是对View的相关知识掌握不够深,所以才有无从下手的感觉,其实就是只要掌握的View的相关知识,剩下的基本就是按照业务逻辑、相关计算、api调用等了,但是View所设计的知识点太多了,如果想完全弄明白,须要一个系统的学习,所以我整理了这些文章给团队分享。

深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

深入理解View知识系列二- View底层工作原理以及View的绘制流程

深入理解View知识系列三-Window机制、Canvas的由来、Android事件的由来

深入理解View知识系列四-View的测量规则以及三大方法流程


那么我们就要从Activity的setContentView作为我们应用第一次涉及View的地方,所以非常有必要知道它背后做了什么,而且也是我们了解View知识的起点.本文注重的是setContentView和LayoutInflater中的逻辑,期间可能会设计到其他的知识,会在以后深入讲解。

本篇源码基于android7.0

1. 先来看一下Activity中的setContentView()方法,一共有三个重载的方法,我们去看一下Activity中的这个方法。

源码路径:
/frameworks/base/core/java/android/app/Activity.java

    //传入布局id的
public void setContentView(@LayoutRes int layoutResID)
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();

//传入一个View的
public void setContentView(View view)
getWindow().setContentView(view);
initWindowDecorActionBar();

//传入View和LayoutParams的
public void setContentView(View view, ViewGroup.LayoutParams params)
getWindow().setContentView(view, params);
initWindowDecorActionBar();

2.上面全部调用了getWindow().setContentView()方法,那么这个Window是谁呢?其实它是PhoneWindow,这个Window再Activity中的attach里被赋值,这个attach方法是在Activity被创建以后在ActivityThread中被调用的,这里我们只分析View的流程,至于Activity和Window以后再说,暂时知道Window的具体实现是PhoneWindow就好了,先看一下这个Window被创建的过程。

  final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback)
....
//创建PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
//设置回调接口
mWindow.setWindowControllerCallback(this);
//给Window设置回调监听Callback
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
....
//设置WindowManger
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null)
mWindow.setContainer(mParent.getWindow());

mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);

3. 上边主要逻辑就是创建PhoneWindow,给Window设置一些回调监听和WindowManger,我们知道了Window的实现类是PhoneWindow,下边我们去看一下PhoneWindow的setContentView方法,设置布局的setContentView,PhoneWindow中同样有三个重载的方法,这里我们选择最常用的资源id来分析,至于其他两个看一下就好了,逻辑基本相同。

这里简单说一下WindowManger,我们知道平时可以通过WindowManger的可以在屏幕上添、更新、删除一个View,其实这几个方法并不是WindowManger的,WindowManger实现了ViewManger接口,这几个方法是ViewManger接口中的,至于Window、WindowManger会在以后分享中单独讲说。

源码路径:
/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

    @Override
public void setContentView(int layoutResID)
//1.mContentParent就是一个ViewGroup,第一次进入时肯定为null
if (mContentParent == null)
//创建DecorView
installDecor();
else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS))
//如果不为空会清空所有的View,
mContentParent.removeAllViews();

//是否有过场动画.略过
if (hasFeature(FEATURE_CONTENT_TRANSITIONS))
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
else
//2.加载我们的布局id后添加到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);

mContentParent.requestApplyInsets();
//3.回调Activity
final Callback cb = getCallback();
if (cb != null && !isDestroyed())
cb.onContentChanged();

mContentParentExplicitlySet = true;

4. PhoneWindow的setContentView中主要做了三件事,

  • 判断mContentParent是否为空,如果为空则执行installDecor方法,否则清空所有的子View
  • 通过LayoutInflater将我们设置的布局添加到mContentParent
  • 通过Window拿到Callback监听对象,也就是Activity,在attach中设置过,并回调它的onContentChanged

5.接着看上边先来看一下installDecor()方法

源码位置:
/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

    private void installDecor() 
mForceDecorInstall = false;
if (mDecor == null)
///第一次进来肯定为空,会走到这里,创建DecorView
mDecor = generateDecor(-1);
...
else
mDecor.setWindow(this);

if (mContentParent == null)
//给mContentParent赋值,mContentParent其实就是展示我们设置布局的父View
mContentParent = generateLayout(mDecor);
...


6.installDecor方法中执行了两个逻辑,一个是给mDecor赋值,一个是给mContentParent赋值,mContentParent其实就是展示我们设置布局的父View,我们先来看一下generateDecor方法,

源码位置:
/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

    protected DecorView generateDecor(int featureId) 
Context context;
//根据不同情况创建Context上下文
if (mUseDecorContext)
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null)
context = getContext();
else
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1)
context.setTheme(mTheme);


else
context = getContext();

//直接new一个DecorView,其实就是一个FrameLayout
return new DecorView(context, featureId, this, getAttributes());

//看一下DecorView类的声明
//继承自FrameLayout
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
....
深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

深入理解View知识系列四-View的测量规则以及三大方法流程

深入理解View知识系列四-View的测量规则以及三大方法流程

深入理解View知识系列三-Window机制Canvas的由来Android事件的由来

深入理解View知识系列三-Window机制Canvas的由来Android事件的由来