publicstaticvoidmonkeyPatchApplication(@Nullable Context context, @Nullable Application bootstrap, @Nullable Application realApplication, @Nullable String externalResourceFile){try {// Find the ActivityThread instance for the current thread Class<?> activityThread = Class.forName("android.app.ActivityThread"); Object currentActivityThread = getActivityThread(context, activityThread);// Find the mInitialApplication field of the ActivityThread to the real application Field mInitialApplication = activityThread.getDeclaredField("mInitialApplication"); mInitialApplication.setAccessible(true); Application initialApplication = (Application) mInitialApplication.get(currentActivityThread);if (realApplication != null && initialApplication == bootstrap) {//**2.替换掉ActivityThread.mInitialApplication** mInitialApplication.set(currentActivityThread, realApplication); }// Replace all instance of the stub application in ActivityThread#mAllApplications with the// real oneif (realApplication != null) { Field mAllApplications = activityThread.getDeclaredField("mAllApplications"); mAllApplications.setAccessible(true); List<Application> allApplications = (List<Application>) mAllApplications .get(currentActivityThread);for (int i = 0; i < allApplications.size(); i++) {if (allApplications.get(i) == bootstrap) {//**1.替换掉ActivityThread.mAllApplications** allApplications.set(i, realApplication); } } }// Figure out how loaded APKs are stored.// API version 8 has PackageInfo, 10 has LoadedApk. 9, I don't know. Class<?> loadedApkClass;try { loadedApkClass = Class.forName("android.app.LoadedApk"); } catch (ClassNotFoundException e) { loadedApkClass = Class.forName("android.app.ActivityThread$PackageInfo"); } Field mApplication = loadedApkClass.getDeclaredField("mApplication"); mApplication.setAccessible(true); Field mResDir = loadedApkClass.getDeclaredField("mResDir"); mResDir.setAccessible(true);// 10 doesn't have this field, 14 does. Fortunately, there are not many Honeycomb devices// floating around. Field mLoadedApk = null;try { mLoadedApk = Application.class.getDeclaredField("mLoadedApk"); } catch (NoSuchFieldException e) {// According to testing, it's okay to ignore this. }// Enumerate all LoadedApk (or PackageInfo) fields in ActivityThread#mPackages and// ActivityThread#mResourcePackages and do two things:// - Replace the Application instance in its mApplication field with the real one// - Replace mResDir to point to the external resource file instead of the .apk. This is// used as the asset path for new Resources objects.// - Set Application#mLoadedApk to the found LoadedApk instancefor (String fieldName : new String[]{"mPackages", "mResourcePackages"}) { Field field = activityThread.getDeclaredField(fieldName); field.setAccessible(true); Object value = field.get(currentActivityThread);for (Map.Entry<String, WeakReference<?>> entry : ((Map<String, WeakReference<?>>) value).entrySet()) { Object loadedApk = entry.getValue().get();if (loadedApk == null) {continue; }if (mApplication.get(loadedApk) == bootstrap) {if (realApplication != null) {//**3.替换掉mApplication** mApplication.set(loadedApk, realApplication); }if (externalResourceFile != null) {//替换掉资源目录 mResDir.set(loadedApk, externalResourceFile); }if (realApplication != null && mLoadedApk != null) {//**4.替换掉mLoadedApk** mLoadedApk.set(realApplication, loadedApk); } } } } } catch (Throwable e) {thrownew IllegalStateException(e); } }
@Overridepublicbooleanload(){try {//遍历已记录的所有修改的类for (String className : getPatchedClasses()) { ClassLoader cl = getClass().getClassLoader();//我们刚才说的修改的类名后面都有$override Class<?> aClass = cl.loadClass(className + "$override"); Object o = aClass.newInstance();//1.**反射修改原类中的$change字段为修改后的值** Class<?> originalClass = cl.loadClass(className); Field changeField = originalClass.getDeclaredField("$change");// force the field accessibility as the class might not be "visible"// from this package. changeField.setAccessible(true);// If there was a previous change set, mark it as obsolete: Object previous = changeField.get(null);if (previous != null) { Field isObsolete = previous.getClass().getDeclaredField("$obsolete");if (isObsolete != null) { isObsolete.set(null, true); } } changeField.set(null, o); } } catch (Exception e) {returnfalse; }returntrue; }