Plug-in system implementation

The process of starting an Activity with a plug-in

  1. Register an empty activity in the host’s AndroidManifest.xml

  2. From the start of execStartActivity to the end of the Activity object new, The system layer validates the validity of the activity to be launched (that is, whether it has been registered in an application’s AndroidManifest.xml) and creates the activity object as required. With this in mind, we are able to bypass the constraints of the system and achieve our goal: components in the plug-in have a real life cycle and are completely managed by the system, non-reflective agents. In a nutshell, there are two steps: Step1, replace the plug-in components that need to be started with the host’s pre-declared number when starting startActivity.

    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent Intent, int requestCode, Bundle options) {// If the activity component of the plug-in is started, It will be replaced with the host in advance statement PluginIntentResolver. ResolveActivity (intent); return hackInstrumentation.execStartActivity(who, contextThread, token, target, intent, requestCode, ptions); }Copy the code

Step2. Change back to the plug-in component when you finally create the activity object.

@Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { ClassLoader orignalCl = cl; String orginalClassName = className; String orignalIntent = intent.toString(); If (ProcessUtil isPluginProcess ()) {/ / will PluginStubActivity replace plug-in for the activity if (PluginManagerHelper. IsStub (className)) { String action = intent.getAction(); if (action ! = null && action.contains(PluginIntentResolver.CLASS_SEPARATOR)) { String[] targetClassName = action.split(PluginIntentResolver.CLASS_SEPARATOR); String pluginClassName = targetClassName[0]; final String pid = intent.getStringExtra(PluginIntentResolver.INTENT_EXTRA_PID).trim(); PluginDescriptor pluginDescriptor = TextUtils.isEmpty(pid) ? PluginManagerHelper.getPluginDescriptorByClassName(pluginClassName) : PluginManagerHelper.getPluginDescriptorByPluginId(pid); Class<? > clazz = PluginLoader.loadPluginClassByName(pluginDescriptor, pluginClassName); if (clazz ! = null) { className = pluginClassName; cl = clazz.getClassLoader(); intent.setExtrasClassLoader(cl); If (targetClassName.length > 1) {// Before passing classNae, Intent.setaction (targetClassName[1]); intent.setAction(targetClassName[1]); } else { intent.setAction(null); } // Add an intent.addCategory(RELAUNCH_FLAG + className); } else { throw new ClassNotFoundException("pluginClassName : " + pluginClassName, new Throwable()); } } else if (PluginManagerHelper.isExact(className, PluginDescriptor. ACTIVITY)) {/ / this logic is to support the external app arouse configuration stub_exact plugin ACTIVITY PluginDescriptor PluginDescriptor = PluginManagerHelper.getPluginDescriptorByClassName(className); if (pluginDescriptor ! = null) { boolean isRunning = PluginLauncher.instance().isRunning(pluginDescriptor.getPackageName()); if (! isRunning) { return waitForLoading(pluginDescriptor); } } Class<? > clazz = PluginLoader.loadPluginClassByName(pluginDescriptor, className); if (clazz ! = null) { cl = clazz.getClassLoader(); } else { throw new ClassNotFoundException("className : " + className, new Throwable()); }} else {// Enter this branch because the activity has restarted. ClassName Boolean found = false; className Boolean found = false; className Boolean found = false; Set<String> category = intent.getCategories(); if (category ! = null) { Iterator<String> itr = category.iterator(); while (itr.hasNext()) { String cate = itr.next(); if (cate.startsWith(RELAUNCH_FLAG)) { className = cate.replace(RELAUNCH_FLAG, ""); PluginDescriptor pluginDescriptor = PluginManagerHelper.getPluginDescriptorByClassName(className); if (pluginDescriptor ! = null) { boolean isRunning = PluginLauncher.instance().isRunning( pluginDescriptor.getPackageName()); if (! isRunning) { return waitForLoading(pluginDescriptor); } } Class<? > clazz = PluginLoader.loadPluginClassByName(pluginDescriptor, className); cl = clazz.getClassLoader(); found = true; break; } } } if (! found) { throw new ClassNotFoundException( "className : " + className + ", intent : " + intent.toString(), new Throwable()); } } } else { if (cl instanceof PluginClassLoader) { PluginIntentResolver.resolveActivity(intent); } else { // Do Nothing } } } try { Activity activity = super.newActivity(cl, className, intent); if (activity instanceof PluginContainer) { ((PluginContainer) activity).setPluginId(intent.getStringExtra(PluginContainer.FRAGMENT_PLUGIN_ID)); } return activity; } catch (ClassNotFoundException e) {// Throw new ClassNotFoundException(" orignalCl: " + orignalCl.toString() + ", orginalClassName : " + orginalClassName + ", orignalIntent : " + orignalIntent + ", currentCl : " + cl.toString() + ", currentClassName : " + className + ", currentIntent : " + intent.toString() + ", process : " + ProcessUtil.isPluginProcess() + ", isStubActivity : " + PluginManagerHelper.isStub(orginalClassName) + ", isExact : " + PluginManagerHelper.isExact(orginalClassName, PluginDescriptor.ACTIVITY), e); }}Copy the code

The solution is simple enough, but there is still a bit of finishing touches, which involves doing the necessary init operations on the created [plug-in] components, such as: Before onCreate statement cycle in the context to replace operation, such as these are inside the plugin framework provides PluginInstrumentionWrapper, look at the code snippet:

@Override public void callActivityOnCreate(Activity activity, Bundle icicle) { PluginInjector.injectActivityContext(activity); Intent intent = activity.getIntent(); if (intent ! = null) { intent.setExtrasClassLoader(activity.getClassLoader()); } if (icicle ! = null) { icicle.setClassLoader(activity.getClassLoader()); } if (ProcessUtil.isPluginProcess()) { installPluginViewFactory(activity); if (activity instanceof WaitForLoadingPluginActivity) { // NOTHING } else { } if (activity.isChild()) { // Fix ContextImpl for Activity in TabActivity with packageName Context Base = Activity.getBaseconText (); while (base instanceof ContextWrapper) { base = ((ContextWrapper) base).getBaseContext(); } if (HackContextImpl.instanceOf(base)) { HackContextImpl impl = new HackContextImpl(base); String packageName = PluginLoader.getApplication().getPackageName(); // String packageName1 = activity.getPackageName(); impl.setBasePackageName(packageName); impl.setOpPackageName(packageName); } } } super.callActivityOnCreate(activity, icicle); monitor.onActivityCreate(activity); }Copy the code

At this point the plug-in activity components are started sequentially, and the system is maintained for a full lifecycle. The same is true for the service and Receiver components, except that the intercepting point is in the Callback to the Handler member of the ActivityThread. The Application and provider load the plug-in when it starts.

Solution to resource conflicts

Resources. Arsc Resource descriptors

  1. PackageId: the package id
  2. The resource type id: string, drawable, layout, color
  3. Offset: Offset value of a certain type

Conflict resolution

Because the package name of each plug-in is inconsistent, you can specify that the value of packageId of a plug-in is fixed in advance, and then change aAPT to compile it to be fixed, so that each plug-in can be assigned different values.