在 Activity 中实现 getContentView 操作
Posted 我想我会记得你
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在 Activity 中实现 getContentView 操作相关的知识,希望对你有一定的参考价值。
2017/9/8 17:17:03
前言
最近接到个需要优化android原生系统设置APK的任务。这个任务里面有一个更换应用背景图片的需求。我手里的这个设备是一个平板设备,使用了一下这个原生设置APK,感觉它有点像是一个主Activity,通过更换Fragment的方式来切换不同的展示内容。这样一来就好办了,想着直接找到这个Activity,看看它是 set 了哪一个 layout 进去,然后再直接在这个 layout 中添加个背景图片就好了。但后来跟踪了一下源码,发现并没有这么简单。这个主Activity是继承自Android打包好的 PreferenceActivity 来实现的,而设置布局的工作,已经由这个父Activity完成了(如下图所示)。完全没有子Activity什么事,并且PreferenceActivity还没有暴露任何接口能让子类取得布局。而且像这种设置方式,想反射都不好反射。
./frameworks/base/core/java/android/preference/PreferenceActivity.java
![](https://image.cha138.com/20210510/a8f5aa20e8004d65a2335ccb33d3f775.jpg)
那这样可如何是好呢?如果能有一个 getContentView() 方法就好了。既然Android官方不提供,那我们干脆跟踪跟踪源码,看看能不能自己造一个出来。
开发环境
操作系统:Android4.4.2
硬件设备:智能音箱中的平板设备。
编译需求:有完整源代码,能够正常编译大包。
追本溯源-跟踪源代码
首先来看看Activity.java中的 setContentView() 是如何处理的。
./frameworks/base/core/java/android/app/Activity.java
![](https://image.cha138.com/20210510/b6c1f6e2100149949b81f1353aaeeb5b.jpg)
原来与 Window 有关啊。再看看 getWindow() 中是如何提供 Window 对象的。
![](https://image.cha138.com/20210510/7f3ccbe8108a4432ad8bc96d15f583fc.jpg)
![](https://image.cha138.com/20210510/c33bd01b9a754e87a2bc36f8d1b1a861.jpg)
到这里,我们就知道了,Activity中使用的 Window 就是 PhoneWindow 类的对象了。那么,我们直接去 PhoneWindow 类中看看它的 setContentView() 作了什么操作。
./frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
![](https://image.cha138.com/20210510/87ed305b73d74782ba00ce4f04fa57ed.jpg)
由上图所示代码第290行,我们知道了原来 set 进去的 layout 就是被加载到了这个 mContentParent 容器中去了啊。这个 mContentParent 是一个 ViewGroup 类的对象。那这个 mContentParent 又是怎么来的呢?
注意到上图第285行的条件判断语句里,似乎这个 mContentParent 对象还与 installDecor() 方法有关呢!去看看。
![](https://image.cha138.com/20210510/617d38bdf8ec453c97b80524dcea65e5.jpg)
看来刚才猜想的没错,这个 mContentParent 确实和这个 Decor 有着很密切的关系。先去 generateDecor() 方法里看看这个 mDecor 是个什么来头。
![](https://image.cha138.com/20210510/a1cc115a8ecc473b99022c9a72d65163.jpg)
简单粗暴,我喜欢!!!DecorView是一个定义在 PhoneWindow 内部的内部类,它是 FrameLayout 的子类,如下图所示。
![](https://image.cha138.com/20210510/48282c522e7c4d149765c11b668576c0.jpg)
然后我们再去看看 mContentParent 是如何由 mDecor 生成的。追踪 generateLayout() 方法。
generateLayout() 方法代码量较大,我们不需要看懂每一行代码的含义,只看我们需要的就好。
protected ViewGroup generateLayout(DecorView decor) {
// 为后续的加载作前期准备工作,读取属性值,选择布局文件等。
// ...
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);// ID_ANDROID_CONTENT = com.android.internal.R.id.content;
if (contentParent == null) {
throw new RuntimeException("Window couldn\'t find content container view");
}
if (getContainer() == null) {
Drawable drawable = mBackgroundDrawable;
if (mBackgroundResource != 0) {
drawable = getContext().getResources().getDrawable(mBackgroundResource);
}
mDecor.setWindowBackground(drawable);
drawable = null;
if (mFrameResource != 0) {
drawable = getContext().getResources().getDrawable(mFrameResource);
}
mDecor.setWindowFrame(drawable);
}
mDecor.finishChanging();
return contentParent;
}
由上述代码第7行可知,mDecor在这里添加了一个View,准确说,它添加的肯定是一个 ViewGroup 类对象。这次添加也是 mDecor 首次添加,即在 mDecor 的 child 中,它是第0个。
然后第9行的 ViewGroup contentParent 就是最终要返回的容器对象。这行代码后面的 findViewById() 方法是定义在 Window.java 中的方法。它其实就是通过 mDecor 来根据 ID 查找 View 。
./frameworks/base/core/java/android/view/Window.java
![](https://image.cha138.com/20210510/ef8d85c387134a8ba82d6980f96dbc80.jpg)
到这里,整个加载布局的流程就很清楚了。我们把它总结一下。
![](https://image.cha138.com/20210510/9bceb8dc29e3422da8f7b128445e8c30.jpg)
那么,借此设置流程图,创造 getContentView() 方法的步骤也清晰了。如下图所示:
![](https://image.cha138.com/20210510/e83dbfe9df844dde9bc672834d16cc70.jpg)
这里有一个很重要的、也是关键的一点,就是Activity提供了 getWindow() 方法用于取得 Window 对象,且这个 Window 对象又提供了取得 mDecorView 对象的接口。
代码实操
更改Android原生系统设置APK中的背景图片。
./packages/apps/Settings/src/.../Settings.java
![](https://image.cha138.com/20210510/00bc4d51e801404da3e7703d07740877.jpg)
至此,我们不仅完成了更换不是由自己设置布局的Activity的背景图片,还了解到了系统设置布局的流程的知识。
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">
以上是关于在 Activity 中实现 getContentView 操作的主要内容,如果未能解决你的问题,请参考以下文章
在 Activity 中实现 getContentView 操作