Android source code-embedded clock (3) Proxy/Delegate Application framework Application, android‑clock
I. overview when a project requires shell, plug-in, or Hotfix, you can use the Proxy/Delegate Application framework. In normal mode, a program generally has only one Application entry, in the Proxy/Delegate mode, two applications are required. The Application of the original program is changed to the Delegate Application, and a new Proxy Application is added. The Proxy Application provides a series of personalized customization, then convert all context and context references to Delegate Application instances, so that the Application entry of the App is Delegate Application.
II. instance 1. before Proxy/Delegate, the Proxy/Delegate framework is applied to the mongoclock program of Android 4.4. For example, the native mongoclock program does not have a custom Application. Here, we first define one, and print the current ApplicationContext name of the program (the Log used in commit clock is customized)
/** * 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()); }}
In addition, at the entry Activity of commit clock, commit clock also prints the current ApplicationContext name of the program for subsequent Proxy comparison. The Manifest configuration of the Application is
<application android:name="cn.jesse.MyApplication" android:label="@string/app_label" android:icon="@mipmap/ic_launcher_alarmclock" android:requiredForAllUsers="true" android:supportsRtl="true">
Run Log after filtering: A simple process is to start the custom MyApplication before launch implements clock, and print the ApplicationContext name at the same time.
2. after using the Proxy/Delegate framework and then using the Proxy/Delegate framework, you need to re-build a new ProxyApplication for Proxy Application. The original MyApplication function is DelegateApplication, so the configuration of Manifest needs to be changed, the main portal of the app is changed to MyProxyApplication, And the DelegateApplication information is stored in the form of meta-data sub-elements (of course, other methods can be used)
<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>
Define an abstract class and provide an abstract method (or some other customization) for replacing the ClassLoader of the current ProxyApplication with the ClassLoader of the parent class)
* Created by jesse on 15-7-17. */public abstract class ProxyApplication extends Application{ protected abstract void initProxyApplication();}
When we want to replace the ClassLoader of the current ProxyApplication with the ClassLoader of the parent class, the replacement action should be early enough (ensure that the ClassLoader is replaced at the entrance where the app Context was first constructed ), otherwise, the ClassLoader of DelegateApplication will be used in most programs, while a small part is the ClassLoader of ProxyApplication, this may cause unexpected bugs. generally, it is enough to replace in OnCreate of Application. However, when the app registers ContentProvider, the call of ContentProvider: OnCreate is prior to Application: OnCreate, therefore, we must ensure that the replacement of ClassLoader is before ContentProvider. by viewing the source code, we can see that the Application is inherited from ContextWrapper. In ContextWrapper, the first callback after the complete Context is constructed is through the attachBaseContext method, in this case, the ClassLoader can be converted by re-writing the method in ProxyApplication to obtain the Context of the Hot Spray.
/** * 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; }
After the entry for converting ClassLoader is determined, you can define a MyProxyApplication, inherit from ProxyApplication, rewrite attachBaseContext method, and print related information.
/** * 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); }}
In the Log running order, first go to attachBaseContext-> initProxyApplication-> onCreate-> export clock: onCreate (here, the ApplicationContext name obtained by onCreate in export clock is (MyProxyApplication)
After the entry sequence is correct, you can replace the current ClassLoader with the ClassLoader of the parent class in the initProxyApplication method, in addition, in onCreate of MyProxyApplication, replace all Application references in the Application layer from ProxyApplication to MyApplication (ClassLoader is not required in the javasclock program currently, you only need to replace all Application references to achieve the proxy effect, so an empty method is written in the initProxyApplication method ). obtain the DelegateApplication attributes from metadata in the AndroidManifest configuration file.
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; }
Obtain the MyApplication according to the className reflection, create the MyApplication instance, and obtain the MyProxyApplication instance.
Class delegateClass = Class.forName(className, true, getClassLoader()); Application delegate = (Application) delegateClass.newInstance(); Application proxyApplication = (Application)getApplicationContext();
Use reflection to replace the mOuterContext attribute in the MyProxyApplication context Member
Class contextImplClass = Class.forName("android.app.ContextImpl"); Field mOuterContext = contextImplClass.getDeclaredField("mOuterContext"); mOuterContext.setAccessible(true); mOuterContext.set(mContext, delegate);
Obtain the PackageInfo object of MyProxyApplication Context and replace the mApplication attribute.
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);
Then obtain the mActivityThread attribute based on the previously reflected packageInfo object and replace the mInitialApplication attribute.
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);
Get the mAllApplications attribute with the previous mActivityThread object. Note that this attribute is a list. Here, remove MyProxyApplication and add DelegateApplication. At this point, all references to the Context of MyProxyApplication at the application layer are replaced with references of MyApplication.
Field mAllApplicationsField = activityThreadClass.getDeclaredField("mAllApplications"); mAllApplicationsField.setAccessible(true); ArrayList<Application> al = (ArrayList<Application>)mAllApplicationsField.get(mActivityThread); al.add(delegate); al.remove(proxyApplication);
Set baseContext for MyApplication through reflection and attach, and call the onCreate method of MyApplication to initialize DelegateApplication.
Method attach = Application.class.getDeclaredMethod("attach", Context.class); attach.setAccessible(true); attach.invoke(delegate, mContext); delegate.onCreate();
After completing these steps, run the command again to view the Log. observe that the name of the ApplicationContext obtained at commit clock has changed to MyApplication.
But this is not completely over yet. Do you still remember the ContentProvider mentioned at the beginning? Its construction is prior to the onCreate of the Application. Is there any Context reference to be replaced in the ContentProvider section? ActivityThread can be found in framework/base/core/java/android/app. java shows that if the package name of the current Context is the same as the package name of ProviderInfo, ContentProvider will reference the Context of the current MyProxyApplication. because the current MyProxyApplication is only used for proxy startup, rewrite getPackageName at MyProxyApplication and return NULL to avoid reusing the current Context by ContentProvider.
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 } }
III. to sum up this article, I simply followed the process of the Proxy/Delegate framework. This framework is actually used in many scenarios, such as multi-dex dynamic loading and plug-ins, online Program hotfix bugs can be used flexibly with a lot of interesting technologies. If you have time, you will also post a blog about hotfix bugs for online programs implemented by Proxy/Delegate.
Reprinted please indicate the source: http://blog.csdn.net/l2show/article/details/46914881