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
 
在 Activity 中实现 getContentView 操作
    那这样可如何是好呢?如果能有一个 getContentView() 方法就好了。既然Android官方不提供,那我们干脆跟踪跟踪源码,看看能不能自己造一个出来。
 

开发环境

    操作系统:Android4.4.2
    硬件设备:智能音箱中的平板设备。
    编译需求:有完整源代码,能够正常编译大包。
 
 

追本溯源-跟踪源代码

    首先来看看Activity.java中的 setContentView() 是如何处理的。
./frameworks/base/core/java/android/app/Activity.java
 
在 Activity 中实现 getContentView 操作
原来与 Window 有关啊。再看看 getWindow() 中是如何提供 Window 对象的。
在 Activity 中实现 getContentView 操作
在 Activity 中实现 getContentView 操作
到这里,我们就知道了,Activity中使用的 Window 就是 PhoneWindow 类的对象了。那么,我们直接去 PhoneWindow 类中看看它的 setContentView() 作了什么操作。
 
./frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
 
在 Activity 中实现 getContentView 操作
由上图所示代码第290行,我们知道了原来 set 进去的 layout 就是被加载到了这个 mContentParent 容器中去了啊。这个 mContentParent 是一个 ViewGroup 类的对象。那这个 mContentParent 又是怎么来的呢?
注意到上图第285行的条件判断语句里,似乎这个 mContentParent 对象还与 installDecor() 方法有关呢!去看看。
 
在 Activity 中实现 getContentView 操作
看来刚才猜想的没错,这个 mContentParent 确实和这个 Decor 有着很密切的关系。先去 generateDecor() 方法里看看这个 mDecor 是个什么来头。
在 Activity 中实现 getContentView 操作
 简单粗暴,我喜欢!!!DecorView是一个定义在 PhoneWindow 内部的内部类,它是 FrameLayout 的子类,如下图所示。
在 Activity 中实现 getContentView 操作
 
然后我们再去看看 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
 
在 Activity 中实现 getContentView 操作
 
到这里,整个加载布局的流程就很清楚了。我们把它总结一下。
在 Activity 中实现 getContentView 操作
 
那么,借此设置流程图,创造 getContentView() 方法的步骤也清晰了。如下图所示:
在 Activity 中实现 getContentView 操作
这里有一个很重要的、也是关键的一点,就是Activity提供了 getWindow() 方法用于取得 Window 对象,且这个 Window 对象又提供了取得 mDecorView 对象的接口。
 
 

代码实操

更改Android原生系统设置APK中的背景图片。
./packages/apps/Settings/src/.../Settings.java
 
在 Activity 中实现 getContentView 操作
 
至此,我们不仅完成了更换不是由自己设置布局的Activity的背景图片,还了解到了系统设置布局的流程的知识。
 

<wiz_tmp_tag >

 
 
 
 

相关文章:

  • 2022-12-23
  • 2021-08-22
  • 2022-12-23
  • 2023-02-13
  • 2022-12-23
  • 2021-07-23
  • 2022-01-14
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2021-12-19
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-06-19
相关资源
相似解决方案