Dynamic loading on the study of the apk recently, in both Jane books and read many blogs on CSDN, but found that although a lot of bloggers is very detailed, but many of the articles are 14, 15 years, and some articles does not provide a demo or provide the demo not run up, make me a face of meng force, learning met a lot of resistance. But, god rewards those who work hard, and finally after a few days of hard work, the dynamic loading is a certain eyebrow, and listen to me below slowly.

First of all, there must be a host APK and a plug-in APK to dynamically load the apK. The so-called dynamic loading is nothing more than loading activities in the host APK plug-in APK, similar to alipay to open pages such as Feizhu and Taopiao. To achieve these two points, it involves the dynamic loading of the class and the dynamic loading of resources.

1. Dynamic loading of classes (if interested, you can go to the blog related to the loading mechanism of classes)

Here we need to use to DexClassLoader (String dexPath, String optimizedDirectory, String librarySearchPath, this parent) * dexPath * optimizedDirectory this is the directory where the dex will be cached after loading. In Android5.0, the Dalvik vm will generate a cache of “filenames. Dex” in the optimizedDirectory. In Android5.0, the oat folder will be generated in the same apk directory as the patch. Apk.cur. prof holds the * librarySearchPath C and C ++ libraries. Null in most cases * parent the parent loader of the currently executing class.

pluginDexClassLoader = DexClassLoader(
    apkPath,
    appCtx.getDir("dexOpt", Context.MODE_PRIVATE).absolutePath,
    null,
    appCtx.classLoader
)
Copy the code

2. Dynamic loading of RES

Since pluginApk does not go through the normal application initialization process when dynamically loading, and resource files are not loaded into the resouces of the host APK, we need to implement a resource ourselves. Before creating a resource, We instantiate an AssetManager object, and then reflection calls addAssetPath to set the address of our plug-in APK, Finally, through the Resources(AssetManager Assets, DisplayMetrics Metrics, Configuration Config) method, Create a new resource (this resource only contains res resources from the plug-in APK)

pluginAssets = AssetManager::class.java.newInstance() val addAssetPath: Method = AssetManager::class.java.getDeclaredMethod("addAssetPath", String::class.java) addAssetPath.invoke(pluginAssets, apkPath) val superResources: Resources? = ctx.resources pluginRes = Resources( pluginAssets, superResources? .displayMetrics, superResources? .configuration )Copy the code

3. ProxyActivity

At this point we have mastered the method of dynamically loading the class and RES resources in APK. However, since the activity in apK is not declared in the Androidmanifest of our host APK, it cannot jump to the activity in the traditional startActivity(Intent) way. So we need a ProxyActivity as a medium to control the activity in the plug-in APK. In the ProxyActivity onCreate(bundle) method, we need to reflect the pluginActivity instance we want to jump to, and execute the pluginActivity onCreate() in that method to activate the activity. In other life cycles of ProxyActivity, the corresponding life cycle methods of PluginActivity are transferred to make PluginActivity enter each life cycle process correctly and completely.

  • ProxyActivity resources, classLoader, and Assets should use our own instantiated objects
  • The startActivity method should be overridden with ProxyActivity to jump to the corresponding Activity
  • The PluginActivity lifecycle method should be called in the ProxyActivity lifecycle
class ProxyActivity : Activity() { companion object { private const val TAG = "ProxyActivity" } private lateinit var pluginInterface: PluginInterface override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d(TAG, "onCreate") try { val clsName = intent.getStringExtra("className") Log.d(TAG, "activity name:$clsName") val cls = PluginManager.pluginDexClassLoader.loadClass(clsName) val newInstance = cls.newInstance() if (newInstance is PluginInterface) { pluginInterface = newInstance pluginInterface.attach(this) val bundle = Bundle() bundle.putBoolean(PluginManager.TAG_IS_PLUGIN, true) pluginInterface.onCreate(bundle) } } catch (e: ClassNotFoundException) { e.printStackTrace() } catch (e: InstantiationException) { e.printStackTrace() } catch (e: IllegalAccessException) { e.printStackTrace() } } override fun startActivity(intent: Intent) { val newIntent = Intent(this, ProxyActivity::class.java) newIntent.putExtra("className", intent.component!! .className) super.startActivity(newIntent) } override fun getResources(): Resources? { return if (! PluginManager.is_plugin) super.getResources() else PluginManager.pluginRes } override fun getAssets(): AssetManager? { return if (! PluginManager.is_plugin) super.getAssets() else PluginManager.pluginAssets } override fun getClassLoader(): ClassLoader? { return if (! PluginManager.is_plugin) super.getClassLoader() else PluginManager.pluginDexClassLoader } override fun onStart() { Log.d(TAG, "onStart") pluginInterface.onStart() super.onStart() } override fun onResume() { Log.d(TAG, "onResume") pluginInterface.onResume() super.onResume() } override fun onRestart() { Log.d(TAG, "onRestart") pluginInterface.onRestart() super.onRestart() } override fun onPause() { Log.d(TAG, "onPause") pluginInterface.onPause() super.onPause() } override fun onStop() { Log.d(TAG, "onStop") pluginInterface.onStop() super.onStop() } override fun onDestroy() { Log.d(TAG, "onDestroy") pluginInterface.onDestroy() super.onDestroy() } override fun finish() { Log.d(TAG, "finish") pluginInterface.finish() super.finish() } override fun onBackPressed() { Log.d(TAG, "onBackPressed") pluginInterface.onBackPressed() super.onBackPressed() } override fun onNewIntent(intent: Intent?) { Log.d(TAG, "onNewIntent") pluginInterface.onNewIntent(intent) super.onNewIntent(intent) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { Log.d(TAG, "onActivityResult") pluginInterface.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { Log.d(TAG, "onRequestPermissionsResult") pluginInterface.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults) } override fun onConfigurationChanged(newConfig: Configuration) { Log.d(TAG, "onConfigurationChanged") pluginInterface.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig) } }Copy the code

4. PluginActivity (the base class for activities in plug-ins)

This class is the base class for the plug-in Activity (all activities in the plug-in should inherit this class)

  • As a plug-in: Our resources and class classes should be loaded using custom generated ClassLoaders and Resources, and methods to get app information should call instances of ProxyActivity to get it.
  • When not used as a plug-in (as a standalone APP) : To ensure that the app runs independently and works properly, we should call the parent method directly using super in the corresponding method.
abstract class PluginActivity : The Activity (), PluginInterface {private var TAG = this: : class. Java. The simpleName / * * * true as the plugin to use, The method should switch to mActivity * false as a standalone APP, ** / private var isPlugin = false ** / lateinit var mActivity: Activity override fun attach(activity: Activity) { mActivity = activity } override fun onCreate(bundle: Bundle?) { if (bundle ! = null) { isPlugin = bundle.getBoolean(PluginManager.TAG_IS_PLUGIN, false) } if (! isPlugin) { super.onCreate(bundle) mActivity = this } } override fun onStart() { Log.d(TAG, "onStart") if (! isPlugin) { super.onStart() } } override fun onResume() { Log.d(TAG, "onResume") if (! isPlugin) { super.onResume() } } override fun onPause() { Log.d(TAG, "onPause") if (! isPlugin) { super.onPause() } } override fun onStop() { Log.d(TAG, "onStop") if (! isPlugin) { super.onStop() } } override fun onRestart() { Log.d(TAG, "onRestart") if (! isPlugin) { super.onRestart() } } override fun onNewIntent(intent: Intent?) { if (! isPlugin) { super.onNewIntent(intent) } } override fun onDestroy() { Log.d(TAG, "onDestroy") if (! isPlugin) { super.onDestroy() } } override fun onBackPressed() { Log.d(TAG, "onBackPressed") if (! isPlugin) { super.onBackPressed() } } override fun finish() { Log.d(TAG, "finish") if (! isPlugin) { super.finish() } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (! isPlugin) { super.onActivityResult(requestCode, resultCode, data) } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { Log.d(TAG, "onRequestPermissionsResult") if (! isPlugin) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) } } override fun onConfigurationChanged(newConfig: Configuration) { if (! isPlugin) { super.onConfigurationChanged(newConfig) } } override fun startActivity(intent: Intent?) { if (! isPlugin) { super.startActivity(intent) } else { mActivity.startActivity(intent) } } override fun getLayoutInflater(): LayoutInflater { return if (! isPlugin) super.getLayoutInflater() else mActivity.layoutInflater } override fun getWindowManager(): WindowManager? { return if (! isPlugin) super.getWindowManager() else mActivity.windowManager } override fun getApplicationInfo(): ApplicationInfo? { return if (! isPlugin) super.getApplicationInfo() else mActivity.applicationInfo } override fun getResources(): Resources { return if (! isPlugin) super.getResources() else mActivity.resources } override fun getAssets(): AssetManager { return if (! isPlugin) super.getAssets() else mActivity.assets } override fun getClassLoader(): ClassLoader { return if (! isPlugin) super.getClassLoader() else mActivity.classLoader } override fun setContentView(layoutResID: Int) { if (! isPlugin) { super.setContentView(layoutResID) } else { mActivity.setContentView(layoutResID) } } override fun getWindow(): Window { return if (! isPlugin) { super.getWindow() } else { mActivity.window } } override fun setContentView(view: View?) { if (! isPlugin) { super.setContentView(view) } else { mActivity.setContentView(view) } } override fun setContentView(view: View? , params: ViewGroup.LayoutParams?) { if (! isPlugin) { super.setContentView(view, params) } else { mActivity.setContentView(view, params) } } override fun getPackageName(): String { return if(! isPlugin){ super.getPackageName() }else{ mActivity.packageName } } }Copy the code

The demo presentation

1. Create two app Modules:

  • App: host APK
  • Other: plug-in APK

We start the ProxyActivity in the MainActivity of the App Module and pass in the full class name of the Main1Activity of the Other Module so that we can start the activity in the plug-in smoothly in the host APK. The operation effect is shown as follows:

Ok, that’s all the content we share about dynamic loading APK. If you want to know more about the demo project, I have put the portal at the back. At the end of the demo project, if you find this article helpful and solve some of your doubts, please don’t be too generous with your enthusiasm. Click on the github demo to send your star!

Article Reference:

  • www.jianshu.com/p/a4ab102fa…