Android的Proxy/Delegate Application框架
来源:互联网 发布:php后缀的文件怎么打开 编辑:程序博客网 时间:2024/06/01 19:27
有的时候,为了实现一些特殊需求,如界面换肤、插件化等,我们希望改变应用的运行环境(surrounding)。例如,我们希望某个应用在运行时,所有Class(包括自定义Application,下面假设它叫MyApplication)都被一个自定义的ClassLoader加载。
要实现这个需求,需要在MyApplication被加载之前,先替换掉API层的默认ClassLoader,否则MyApplication就会被默认ClassLoader加载。但这会产生一个悖论,MyApplication被加载之前,没有任何应用代码可以运行,替换ClassLoader无法办到。Proxy/Delegate Application框架就是用来解决这类问题的。
Proxy/Delegate Application简介
在Proxy/Delegate Application框架里,应用一共有两个Application对象,一个称为ProxyApplication,另一个称为DelegateApplication:
(1) ProxyApplication:框架会提供一个ProxyApplication抽象基类(abstract class),使用者需要继承这个类,并重载其initProxyApplication()方法,在其中改变surrounding,如替换ClassLoader等。
(2) DelegateApplication:即应用原有的Application,应用从getApplicationContext()等方法中取到的都是DelegateApplication。注意DelegateApplication只是一个称谓,并没有一个叫DelegateApplication的基类存在。
使用Proxy/Delegate Application框架,使用者可以在对原有Application类不做任何修改的情况下,改变整个应用的运行环境。所需要做的只是添加一个新的Application类,并相应的修改AndroidManifest.xml。
老的AndroidManifest.xml:
1
<
application
2
android:name
=
".MyApplication"
3
android:icon
=
"@drawable/icon"
4
android:label
=
"@string/app_name"
>
添加的Application类:
1
public
class
MyProxyApplication
extends
ProxyApplication {
2
@Override
3
protected
void
initProxyApplication() {
4
// 在这里替换运行环境,如将ClassLoader替换为自定义的
5
// ......
6
}
7
}
新的AndroidManifest.xml:
1
<
application
2
android:name
=
".MyProxyApplication"
3
android:icon
=
"@drawable/icon"
4
android:label
=
"@string/app_name"
>
5
<
meta-data
6
android:name
=
"DELEGATE_APPLICATION_CLASS_NAME"
7
android:value
=
".MyApplication"
>
8
</
meta-data
>
MyProxyApplication(ProxyApplication)对象对应用是不可见的,应用看到的Application是MyApplication(DelegateApplication),也就是以前的Application对象。这样对于应用而已,似乎一切都没有改变;但它的运行环境已经改变,例如所有的类已经被新的ClassLoader加载了。整个实现是非侵入式的,已有代码无须任何修改,只有AndroidManifest.xml略有改动。
下面开始探讨ProxyApplication本身如何实现。核心问题是两个,一是什么时机调用子类的initProxyApplication()方法,让子类改变surrounding;二是如何加载DelegateApplication并让应用认为它就是真实的Application。另外Android四大组件之一的ContentProvider会给我们带来不少麻烦,需要妥善处理。
ProxyApplication实现:时机
理论上ProxyApplication对任何能够访问到的变量,包括Java层和Native层,都是可以替换(或者HOOK,类似的含义)的;比较有意义的除了ClassLoader外,还有Resources和各路Binder对象。通过这些手段可以实现非常多有意思的功能。具体如何替换ClassLoader、Resources等这里不深入讨论,如有兴趣,在网上可以找到很多相关资料。本文的重点是介绍框架本身,替换ClassLoader仅作为一个例子。
现在的问题是改变surrounding的时机必须足够早,特别是对于ClassLoader来说尤为重要。是否可以在Application:onCreate()里做?我们通常认为,Application是一个Android应用最早被加载的组件;但当应用注册有ContentProvider的时候,这并不正确的。ContentProvider:onCreate()调用优先于Application:onCreate()。
幸好,我们还有另一个方法:attachBaseContext()。Android的几个主要顶级组件(Application、Activity、Service)都是ContextWrapper的子类。ContextWrapper一方面继承(inherit)了Context,一方面又包含(composite)了一个Context对象(称为mBase),对Context的实现为转发给mBase对象处理。这一个听起来很绕的设计,是为了对这些顶级组件中的Context功能做延迟初始化(delay init)的处理。这里不展开讨论了,仅贴一些Android源代码片段做参考。
01
// android.app.Application
02
public
class
Application
extends
ContextWrapper {
03
// ...
04
public
application() {
05
super
(
null
);
06
}
07
// ...
08
}
09
// android.content.ContextWrapper
10
public
class
ContextWrapper
extends
Context {
11
Context mBase;
12
// ...
13
public
ContextWrapper(Context base) {
14
mBase = base;
15
}
16
protected
void
attachBaseContext(Context base) {
17
if
(mBase !=
null
) {
18
throw
new
IllegalStateException(
"Base context already set"
);
19
}
20
mBase = base;
21
}
22
// ...
23
@Override
24
public
AssetManager getAssets() {
25
return
mBase.getAssets();
26
}
27
@Override
28
public
Resources getResources()
29
{
30
return
mBase.getResources();
31
}
32
// ...
33
}
ContextWrapper完成这个delay init语义的方法就是attachBaseContext()。可以这样说,Application对象在刚刚构造完成时是“残废”的,访问所有Context的方法都会抛出NullPointerException。只有attachBaseContext()执行完后,它的功能才完整。
在ContentProvider:onCreate()中,我们知道Application:onCreate()还没有运行,但已经可以使用getContext().getApplicationContext()函数获取Application对象,并访问其Context方法。显然,Android的API设计者不能允许此时获取的Application是“残废”的。结论是Application:attachBaseContext()必须要发生在ContentProvider:onCreate()之前,否则API将出现BUG;无论Android的系统版本如何变化,这一点也不能改变。
于是,Application与ContentProvider的初始化次序是这样的:Application:attachBaseContext()最早执行,然后是ContentProvider:onCreate(),然后是Application:onCreate()。我们的解决方案也就很简单了:
1
public
abstract
class
ProxyApplication
extends
Application {
2
protected
abstract
void
initProxyApplication();
3
@Override
4
protected
void
attachBaseContext (Context context) {
5
super
.attachBaseContext(context);
6
initProxyApplication();
7
}
8
// ……
9
}
ProxyApplication实现:加载DelegateApplication
当子类的initProxyApplication()返回后,ProxyApplication就要加载DelegateApplication,完成自己的历史使命。这一部分在onCreate()中完成,基本是些体力活,但也有些需要注意的地方,下面分步骤简述一下。
(1) 获取DelegateApplication的Class Name
即从AndroidManifest.xml中获取DELEGATE_APPLICAION的metadata值,若不存在,则使用android.app.Application作为默认。这一步比较简单。
01
String className =
"android.app.Application"
;
02
String key =
"DELEGATE_APPLICATION_CLASS_NAME"
;
03
ApplicationInfo appInfo = getPackageManager().getApplicationInfo(
04
super
.getPackageName(), PackageManager.GET_META_DATA);
05
Bundle bundle = appInfo.metaData;
06
if
(bundle !=
null
&& bundle.containsKey(key)) {
07
className = bundle.getString(key);
08
if
(className.startsWith(
"."
))
09
className =
super
.getPackageName() + className;
10
}
(2) 加载DelegateApplication并生成对象
这里要注意的是使用哪个ClassLoader?答案是应该用getClassLoader()(即Context:getClassLoader()),而不是getClass().getClassLoader()。要仔细揣摩这两者之间的差别。
1
Class delegateClass = Class.forName(className,
true
, getClassLoader());
2
Application delegate = (Application) delegateClass.newInstance();
(3) 替换API层的所有Application引用
即把API层所有保存的ProxyApplication对象,都替换为新生成的DelegateApplication对象。以ProxyApplication的baseContext作为起点顺藤摸瓜,可以找到所有的位置,使用反射一一换掉。注意最后一个mAllApplications是List,要换掉其内部的内容。
1
baseContext.mOuterContext
2
baseContext.mPackageInfo.mApplication
3
baseContext.mPackageInfo.mActivityThread.mInitialApplication
4
baseContext.mPackageInfo.mActivityThread.mAllApplications
(4) 设置baseContext并调用onCreate
将控制权交给DelegateApplication。当然,后者会认为自己就是“正牌”的Application,后续的其它组件也都会这么认为。这正是我们要的效果。
1
Method attach = Application.
class
.getDeclaredMethod(
"attach"
, Context.
class
);
2
attach.setAccessible(
true
);
3
attach.invoke(delegate, base);
4
delegate.onCreate();
再次对付ContentProvider
前面提到过,Android的顶级组件Application、Activity、Service都是ContextWrapper,这个列表中并没有ContentProvider。ContentProvider不是ContextWrapper,甚至不是Context,而是内部有一个mContext变量,通过getContext()函数获取这个Context。
那么,ContentProvider:getContext()获取到的是哪一个Context?实验证明,ContentProvider:getContext()获取的Context是Application;准确的说,在Proxy/Delegate Application框架里,是ProxyApplication。这就不符合框架的语义了。那么,我们需要像其它处理其它ProxyApplication引用一样,把它换成DelegateApplication吗?这是可行的:遍历API层的ContentProvider列表,将每一个ContentProvider中的mContext都替换为DelegateApplication。
但这种处理方式,会进一步增加对Android API层源代码依赖,是否必要?毕竟Android的API文档中,并没有规定ContentProvider:getContext()返回的必须是Application;如果要取得Application,正确的方式是getContext().getApplicationContext()。那么为什么getContext()就直接返回了Application对象?我们可以从源代码中找到答案:
01
// in ActivityThread:installProvider()
02
if
(context.getPackageName().equals(ai.packageName)) {
03
c = context;
04
}
else
if
(mInitialApplication !=
null
&&
05
mInitialApplication.getPackageName().equals(ai.packageName)) {
06
c = mInitialApplication;
07
}
else
{
08
try
{
09
c = context.createPackageContext(ai.packageName,
10
Context.CONTEXT_INCLUDE_CODE);
11
}
catch
(PackageManager.NameNotFoundException e) {
12
// Ignore
13
}
14
}
容易看出,因为ProxyApplication对象的getPackageName()函数与ContentProvider对应的包名相同,就会复用ProxyApplication对象作为Context,而不会再创建一个新的packageContext。于是解决方案也很简单了:
1
@Override
2
public
String getPackageName() {
3
return
""
;
4
}
由于ProxyApplication不是最终的Application,这并不会产生什么副作用。
使用注意事项
不要保留ProxyApplication子类对象的引用,也不要在任何系统回调(包括onCreate)中做事情。onCreate()被基类用于加载DelegateApplication,而其它回调都不会再收到。
在ProxyApplication:onCreate()执行完成之后,虚拟机中所有的线程栈和所有的JAVA对象,都不会再有ProxyApplication对象的引用。ProxyApplication对象将在下一次GC运行时被回收,这也意味着从ProxyApplication到DelegateApplication的替换进行得非常彻底。自然地,ProxyApplication也收不到其它回调了。DelegateApplication会正常的接收所有的回调。
另外,在ProxyApplication子类中,如果需要获取当前APK的包名,需要使用getBaseContext().getPackageName(),而不能简单调用getPackageName()。原因在上面“再次对付ContentProvider”中有说明。
- Android的Proxy/Delegate Application框架
- Android的Proxy/Delegate Application框架
- Android的Proxy/Delegate Application框架
- Android的Proxy/Delegate Application框架
- Android源码之DeskClock (三) Proxy/Delegate Application 框架应用
- 基于Proxy思想的Android插件框架
- 基于Proxy思想的Android插件框架
- 基于Proxy思想的Android插件框架
- 基于Proxy思想的Android插件框架
- 基于Proxy思想的Android插件框架
- Android 基于Proxy/Delegate 实现bug热修复
- Android 基于Proxy/Delegate 实现bug热修复
- ”Android Application Framework FAQ“关于Android框架的常见问题
- iOS Application Delegate Lifecycle
- Android应用框架之 Application
- 关于File's Owner, First Responder, Application Delegate的解释
- 编写Java application时,如何设置proxy的信息
- Android应用框架之Android Application
- kindeditor判断输入为空
- c++ explicit关键字详解
- XML学习笔记(二)
- Python中的yield
- AWK简明教程
- Android的Proxy/Delegate Application框架
- spring(基础七) spring事务回滚详解
- contains uncommitted changes解决办法
- 图片url
- jsp 开发中的 json
- 深入浅出的讲解傅里叶变换
- 关于iOS中地图定位新问题
- OpenSymphony——SiteMesh——Decorators(2)
- shell编程