Android源码之DeskClock(三) Proxy/Delegate Application 框架应

<application android:name="cn.jesse.MyProxyApplication"android:label="@string/app_label"android:icon="@mipmap/ic_launcher_alarmclock"android:requiredForAllUsers="true"android:supportsRtl="true"><meta-dataandroid:name="DELEGATE_APPLICATION_CLASS_NAME"android:value="cn.jesse.MyApplication" ></meta-data></application>

定义一个抽象类,提供一个用于替换当前ProxyApplication 的ClassLoader成父类的ClassLoader的抽象方法(或者一些其他的个性化定制)

* Created by jesse on 15-7-17. */public abstract class ProxyApplication extends Application{protected abstract void initProxyApplication();} 当我们要替换当前ProxyApplication的ClassLoader为父类的ClassLoader,所以这个替换的动作要足够得早(要保证在app Context最早被构建的入口处替换ClassLoader),要不然就会出现替换不干净的情况,就会有程序中大部分使用的DelegateApplication的ClassLoader,而一小部分是使用的ProxyApplication的ClassLoader,这样可能会出现一些意想不到的bug.

通常来说在Application的OnCreate中来做替换就足够了,但是当app有注册ContentProvider的时候ContentProvider:OnCreate的调用是在Application:OnCreate之前的,所以我们必须保证替换ClassLoader的动作要在ContentProvider之前.

通过查看源码可以看到Application是继承自ContextWrapper,而在ContextWrapper中系统在构建完成完善的Context之后第一次回调是通过attachBaseContext方法,既然这样就通过在ProxyApplication中复写该方法来获取刚出炉热喷喷的Context来转换ClassLoader.

/*** Set the base context for this ContextWrapper. All calls will then be* delegated to the base context. Throws* IllegalStateException if a base context has already been set.** @param base The new base context for this wrapper.*/protected void attachBaseContext(Context base) {if (mBase != null) {throw new IllegalStateException("Base context already set");}mBase = base;} 转换ClassLoader的入口也确定之后就可以自定义一个MyProxyApplication,继承自ProxyApplication并且复写attachBaseContext方法,print相关信息

/** * Created by jesse on 15-7-17. */public class MyProxyApplication extends ProxyApplication {private final String TAG = MyProxyApplication.class.getSimpleName();private Context mContext;@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);Log.i(TAG + ", attachBaseContext");mContext = base;this.initProxyApplication();}@Overridepublic void onCreate() {super.onCreate();Log.i(TAG + ", onCreate" + this.getApplicationContext().getClass().getSimpleName());BootLoader.boot(mContext);}@Overrideprotected void initProxyApplication() {Log.i(TAG + ", initProxyApplication");BootLoader.resetClassLoader(mContext);}} Log运行的顺序,先进入attachBaseContext->initProxyApplication->onCreate->DeskClock:onCreate (这里DeskClock的onCreate获取到的ApplicationContext的名字是(MyProxyApplication)

入口的顺序没问题了之后,就可以在initProxyApplication方法中替换当前的ClassLoader到父类的ClassLoader,并且在MyProxyApplication的onCreate中将应用层所有的Application的引用全部从ProxyApplication替换成MyApplication(当前在DeskClock程序中没有替换ClassLoader的需求,只需要替换所有的Application的引用就能达到代理的效果,所以在initProxyApplication方法处就写了一个空方法带过).

先从AndroidManifest配置文件中的metadata拿到DelegateApplication的属性

String className = CLASS_NAME;ApplicationInfo appInfo = getPackageManager().getApplicationInfo(super.getPackageName(), PackageManager.GET_META_DATA);Bundle bundle = appInfo.metaData;if (bundle != null && bundle.containsKey(KEY)) {className = bundle.getString(KEY);if (className.startsWith("."))className = super.getPackageName() + className;} 根据className反射得到MyApplication,创建MyApplication实例并且取得MyProxyApplication的实例

Class delegateClass = Class.forName(className, true, getClassLoader());Application delegate = (Application) delegateClass.newInstance();Application proxyApplication = (Application)getApplicationContext(); 使用反射更换MyProxyApplication context成员中的mOuterContext属性

Class contextImplClass = Class.forName("android.app.ContextImpl");Field mOuterContext = contextImplClass.getDeclaredField("mOuterContext");mOuterContext.setAccessible(true);mOuterContext.set(mContext, delegate);

获取MyProxyApplication Context的PackageInfo对象,替换掉其中的mApplication属性

Field mPackageInfoField = contextImplClass.getDeclaredField("mPackageInfo");mPackageInfoField.setAccessible(true);Object mPackageInfo = mPackageInfoField.get(mContext);Class loadedApkClass = Class.forName("android.app.LoadedApk");Field mApplication = loadedApkClass.getDeclaredField("mApplication");mApplication.setAccessible(true);mApplication.set(mPackageInfo, delegate);大多数人想要改造这个世界,但却罕有人想改造自己。

Android源码之DeskClock(三) Proxy/Delegate Application 框架应

相关文章:

你感兴趣的文章:

标签云: