一.概述
二.實例
/**
* Created by jesse on 15-7-17.
*/
public class MyApplication extends Application{
private final String TAG = MyApplication.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG + ", onCreate " + this.getApplicationContext().getClass().getSimpleName());
}
}
並且在DeskClock的入口Activity,DeskClock處也print出該程序目前ApplicationContext的名字用於後續Proxy後的對比. <application android:name="cn.jesse.MyApplication"
android:label="@string/app_label"
android:icon="@mipmap/ic_launcher_alarmclock"
android:requiredForAllUsers="true"
android:supportsRtl="true">
過濾後的運行Log: 簡單的流程就是先啓動自定義MyApplication 之後再launch DeskClock,同時都打印出來ApplicationContext的名字 <application android:name="cn.jesse.MyProxyApplication"
android:label="@string/app_label"
android:icon="@mipmap/ic_launcher_alarmclock"
android:requiredForAllUsers="true"
android:supportsRtl="true">
<meta-data
android:name="DELEGATE_APPLICATION_CLASS_NAME"
android:value="cn.jesse.MyApplication" >
</meta-data>
</application>
* 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. /**
* 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;
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Log.i(TAG + ", attachBaseContext");
mContext = base;
this.initProxyApplication();
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG + ", onCreate" + this.getApplicationContext().getClass().getSimpleName());
BootLoader.boot(mContext);
}
@Override
protected 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方法處就寫了一個空方法帶過).
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);
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);
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field mAcitivityThreadField = loadedApkClass.getDeclaredField("mActivityThread");
mAcitivityThreadField.setAccessible(true);
Object mActivityThread = mAcitivityThreadField.get(mPackageInfo);
Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");
mInitialApplicationField.setAccessible(true);
mInitialApplicationField.set(mActivityThread, delegate);
Field mAllApplicationsField = activityThreadClass.getDeclaredField("mAllApplications");
mAllApplicationsField.setAccessible(true);
ArrayList<Application> al = (ArrayList<Application>)mAllApplicationsField.get(mActivityThread);
al.add(delegate);
al.remove(proxyApplication);
Method attach = Application.class.getDeclaredMethod("attach", Context.class);
attach.setAccessible(true);
attach.invoke(delegate, mContext);
delegate.onCreate();
完成這些步驟之後再重新運行查看Log,觀察DeskClock處獲取的ApplicationContext的名字已經變成MyApplication.但是這樣還沒有完全結束,還記得開頭說的ContentProvider嗎?他的構造是在Application的onCreate之前的,那麼ContentProvider部分有沒有需要替換的Context引用呢?從framework/base/core/java/android/app下可以找到ActivityThread.java從其中裝載ContentProvider的部分可以看到,如果當前Context的包名和ProviderInfo的包名一樣的話,ContentProvider就會引用當前的MyProxyApplication的Context.由於當前的MyProxyApplication只是做代理啓動用的,所以在MyProxyApplication處複寫getPackageName並且返回空就可以避免ContentProvider複用當前Context了.
private IActivityManager.ContentProviderHolder installProvider(Context context,
IActivityManager.ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
if (holder == null || holder.provider == null) {
if (DEBUG_PROVIDER || noisy) {
Slog.d(TAG, "Loading provider " + info.authority + ": "
+ info.name);
}
Context c = null;
ApplicationInfo ai = info.applicationInfo;
if (context.getPackageName().equals(ai.packageName)) {
c = context;
} else if (mInitialApplication != null &&
mInitialApplication.getPackageName().equals(ai.packageName)) {
c = mInitialApplication;
} else {
try {
c = context.createPackageContext(ai.packageName,
Context.CONTEXT_INCLUDE_CODE);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
}
三.總結
轉載請註明出處:http://blog.csdn.net/l2show/article/details/46914881