android setContentView()源码解析
Posted 史大拿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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主题 |
---|---|
1.setContentView() 如何解析View
这段源码在View生命周期中就有提到过,但是还不够细致,本篇带你完全理解!
从入口开始:
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_custom_parse)
代码块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;
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.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();
这段代码有很多博主直接进入LayoutInflater#inflate中解释,直接略过了最精彩的 ensureSubDecor()方法,现在来看看吧~
代码块1.4
# AppCompatDelegateImpl.java
private void ensureSubDecor()
if (!mSubDecorInstalled)
mSubDecor = createSubDecor();
...
代码块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;
这段代码前半部分主要是解析样式
期间会创建一个ViewGroup,这个ViewGroup的布局为:
这个东西是什么不重要,重要的是往**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);
...
这里要看仔细了, 传过来的是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.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;
来看看 R.layout.screen_simple 布局是什么样子的:
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();
在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;
这里通过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);
代码块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;
代码块2.4:
那我们只要看看SYSTEM_SERVICE_FETCHERS是什么就OK了
# SystemServiceRegistry.java
private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new ArrayMap<String, ServiceFetcher<?>>();
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());
可以看到所有的服务都在这里注册的.
那么就可以知道: 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();
LayoutInflater#.inflate(id, ViewGroup);
代码块1.3.1
# LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
return inflate(resource, root, root != null);
- @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.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;
这里都好理解,传过来一个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则不是全类名
以上是关于android setContentView()源码解析的主要内容,如果未能解决你的问题,请参考以下文章
Android 9 (API 28) 活动在启动时崩溃 (setContentView)