Android插件化技术,你值得一看
相信大家在工作中会遇到关于Android插件化开发出现的问题,随着对这种技术的了解,我发现在插件开发的一些基础问题上下需要花费更多的时间。就比如在主工程Context和插件Context的区别所在,权限必然要在主工程申明,本着学习的心态,对插件的历史花了些时间进行了解,随后便写了两个Demo进行总结。这篇文章主要是通过对插件的实现原理进行直观的说明,以此来对插件内开发进行更进一步的加深和了解,所以这篇文章也是主要对核心逻辑进行更多的探讨,所以也就不会对其背景和原理进行一个深入的探讨.
原理和背景
首先从android插件化的技术起源开始,插件化最初是从免安装运行apk这个方向的一个初步想法从而发展出来的技术,插件也可以称之为一个免安装的apk,app在运行过程中因为其支持插件化,从而可以将aap中一些平常不适应的模块做成我们所需要的插件,以此来实现app中的动态扩展功能,更是进一步减少了其安装包的大小,当然需要实现其插件化,主要是要如何解决这三个问题: 载类与主程序如何进行相互调动 资源的加载如何与主工程进行相互访问 组件的生命周期如何管理
我将相对来说比较有名的开源插件化框架进行了时间排序,以此来了解它们的原理,如何实现,这样我们就可以看出插件化技术的发展历程,我将其插件化框架进行了分层,根据其实现原理划分很了三代
第一代:dynamic-load-apk相较于其他插件框架,它最先使用了ProxyActivity静态代理技术,由ProxyActivity进行控制插件PluginActivity生命周期。但是这种方式对的缺点很明显,插件里activity就必须要继承PluginActivity,所以如果要进行开发就必须要仔细处理context。相对而言DroidPlugin就是通过Hook系统服务进行启动该插件中的Activity,这样我们在开发插件的过程与普通的app开发的是一样的,区别不大,但hook会过多的进行系统服务,会显得复杂而且相对而说就不够稳定。
第二代:VirtualAPK为了跟普通app一样开发插件并且让插件开发达到低侵入性,同时也让其拥有框架的稳定性,所以在实现原理上都会尽量选择比较少的hook,在通过manifest中实现对四大组件的插件化进行一个预埋。而且各个框架都从设计的思想进行了一定程度的扩展,其中更是将Small做成了一个具有跨平台,组件化功能的开发框架。
第三代:VirtualApp相对于前面两代就比较厉害了,不仅能够app的运行环境进行完全模拟,还能够讲app的免安装运行和双开技术进行实现。Atlas是阿里19年开源出来的一个具有热修复技术和结合组件化的一个app的基础框架,至今仍然应用于阿里系的app中,因此也将其号称为一个容器化框架。
下面详细介绍插件化框架的原理,分别对应着实现插件化的三个核心问题。 类加载
从外部apk中类的加载来看,通常在Android中常用的共有两种类加载器,PathClassLoader和DexClassLoader,这两种类加载器都继承于BaseDexClassLoader。
Android对于外部的dex文件,主要通过 DexClassLoader 类加载,因此,只需要给定插件的路径,就可以构造对应的类加载器: // DexClassLoaderpublic class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), libraryPath, parent); } } // PathClassLoader public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) { super(dexPath, null, libraryPath, parent); } }
这种区别就是在调用父类构造器时,DexClassLoader会多传了一个optimizedDirectory参数,并且这个目录的存储路径必须是内部存储路径,具体是用来缓存系统创建的Dex文件。而PathClassLoader的参数是为null,所以就只能从内部存储目录中加载Dex文件。
那么我们就可以使用DexClassLoader拿去加载外部的apk,所使用的方法如下
//第一个参数为apk的文件目录//第二个参数为内部存储目录//第三个为库文件的存储目录//第四个参数为父加载器new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent) 资源加载
Android系统是通过Resource对象进行加载资源,下面代码的生成过程展示了该对象
所以也就说明现在插件Activity中就不会有所限制: //创建AssetManager对象 AssetManager assets = new AssetManager(); //将apk路径添加到AssetManager中 if (assets.addAssetPath(resDir) == 0){ return null; } //创建Resource对象 r = new Resources(assets, metrics, getConfiguration(), compInfo);
所以,我们只要将插件apk中的路径加入到AssetManager之后,就可以实现访问其插件资源。
在具体实现时,因为AssetManager跟public并不是一类,所以就需要通过反射让其去创建,而且部分Rom会对其创建的Resource类进行修改,那么我们就有必要考虑不同Rom的兼容性。 生命周期
在Android进行开发中会有一些特殊的类,全是才系统创建而来,并且会让系统来管理生命周期。
对生命周期的管理在插件化中是较为复杂的,就如同常用的四大组件,Service,Activity,ContentProvider和BroadcastReceiver。其中插件化中Activity是最为繁琐的,其中早期的dynamic-load-apk所采用的基本上是用代理的方式,主要是通过使用一个空壳的Activity让其成为代理(Proxy),系统会将Activity的回调都一一映射到这个Activity插件,这样的话就可以将插件的生命周期实现通过系统来进行管理,不得不说这种方式及其直观,但其所拥有的插件Activity需要继承这样代理的PluginActivity (Demo中的命名),不仅拥有侵入性强的特点,更可以结合我们后面的例子并加以理解。 代理实现
首先我们将建立一个 PluginManager 类从而来实现插件的加载: public class PluginManager { static class PluginMgrHolder { static PluginManager sManager = new PluginManager(); } private static Context mContext; Map sMap = new HashMap<>(); public static PluginManager getInstance() { return PluginMgrHolder.sManager; } public PluginApk getPluginApk(String packageName) { return sMap.get(packageName); } public static void init(Context context) { mContext = context.getApplicationContext(); } public final void loadApk(String apkPath) { PackageInfo packageInfo = queryPackageInfo(apkPath); if (packageInfo == null || TextUtils.isEmpty(packageInfo.packageName)) { return; } // check cache PluginApk pluginApk = sMap.get(packageInfo.packageName); if (pluginApk == null) { pluginApk = createApk(apkPath); if (pluginApk != null) { pluginApk.packageInfo = packageInfo; sMap.put(packageInfo.packageName, pluginApk); } else { throw new NullPointerException("PluginApk is null"); } } } ``` ``` private PluginApk createApk(String apkPath) { String addAssetPathMethod = "addAssetPath"; PluginApk pluginApk = null; try { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod, String.class); addAssetPath.invoke(assetManager, apkPath); Resources pluginRes = new Resources(assetManager, mContext.getResources().getDisplayMetrics(), mContext.getResources().getConfiguration()); pluginApk = new PluginApk(pluginRes); pluginApk.classLoader = createDexClassLoader(apkPath); } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } return pluginApk; } ``` ``` private PackageInfo queryPackageInfo(String apkPath) { PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES); if (packageInfo == null) { return null; } return packageInfo; } ``` private DexClassLoader createDexClassLoader(String apkPath) { File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE); DexClassLoader loader = new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(), null, mContext.getClassLoader()); return loader; } ``` ``` public void startActivity(Intent intent) { Intent pluginIntent = new Intent(mContext, ProxyActivity.class); Bundle extra = intent.getExtras(); // complicate if statement if (extra == null || !extra.containsKey(Constants.PLUGIN_CLASS_NAME) && !extra.containsKey(Constants.PACKAGE_NAME)) { try { throw new IllegalAccessException("lack class of plugin and package name"); } catch (IllegalAccessException e) { e.printStackTrace(); } } pluginIntent.putExtras(intent); pluginIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(pluginIntent); } } ```
PluginApk 表示一个Apk文件: public class PluginApk { public PackageInfo packageInfo; public DexClassLoader classLoader; public Resources pluginRes; public PluginApk(Resources pluginRes) { this.pluginRes = pluginRes; } } ```
插件Activity的所以都必须继承一个父类 PluginActivity : public abstract class PluginActivity extends Activity implements Pluginable, Attachable { public final static String TAG = PluginActivity.class.getSimpleName(); protected Activity mProxyActivity; private Resources mResources; PluginApk mPluginApk; @Override public void attach(Activity proxy, PluginApk apk) { mProxyActivity = proxy; mPluginApk = apk; mResources = apk.pluginRes; } @Override public void setContentView(int layoutResID) { mProxyActivity.setContentView(layoutResID); } @Override public void setContentView(View view) { mProxyActivity.setContentView(view); } @Override public void setContentView(View view, ViewGroup.LayoutParams params) { mProxyActivity.setContentView(view, params); } @Override public View findViewById(int id) { return mProxyActivity.findViewById(id); } @Override public Resources getResources() { return mResources; } @Override public WindowManager getWindowManager() { return mProxyActivity.getWindowManager(); } @Override public ClassLoader getClassLoader() { return mProxyActivity.getClassLoader(); } @Override public Context getApplicationContext() { return mProxyActivity.getApplicationContext(); } @Override public MenuInflater getMenuInflater() { return mProxyActivity.getMenuInflater(); } @Override public Window getWindow() { return mProxyActivity.getWindow(); } @Override public Intent getIntent() { return mProxyActivity.getIntent(); } @Override public LayoutInflater getLayoutInflater() { return mProxyActivity.getLayoutInflater(); } @Override public String getPackageName() { return mPluginApk.packageInfo.packageName; } @Override public void onCreate(Bundle bundle) { // DO NOT CALL super.onCreate(bundle) // following same VLog.log(TAG + ": onCreate"); } @Override public void onStart() { } @Override public void onResume() { } @Override public void onStop() { } @Override public void onPause() { } @Override public void onDestroy() { } } ```
这个类相当于一个壳,系统将通过 ProxyActivity 能够触发对应的方法进行具体的实现: public class ProxyActivity extends Activity { LifeCircleController mPluginController = new LifeCircleController(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPluginController.onCreate(getIntent().getExtras()); } @Override public Resources getResources() { // construct when loading apk Resources resources = mPluginController.getResources(); return resources == null ? super.getResources() : resources; } @Override public Resources.Theme getTheme() { Resources.Theme theme = mPluginController.getTheme(); return theme == null ? super.getTheme() : theme; } @Override public AssetManager getAssets() { return mPluginController.getAssets(); } @Override protected void onStart() { super.onStart(); mPluginController.onStart(); } @Override protected void onResume() { super.onResume(); mPluginController.onResume(); } @Override protected void onStop() { super.onStop(); mPluginController.onStop(); } @Override protected void onPause() { super.onPause(); mPluginController.onPause(); } @Override protected void onDestroy() { super.onDestroy(); mPluginController.onDestroy(); } } ```
这个类是系统能够实际启动的类,其中最主要逻辑由 LifeCircleController对其负责: public class LifeCircleController implements Pluginable { Activity mProxy; PluginActivity mPlugin; Resources mResources; Resources.Theme mTheme; PluginApk mPluginApk; String mPluginClazz; public LifeCircleController(Activity activity) { mProxy = activity; } public void onCreate(Bundle bundle) { mPluginClazz = bundle.getString(Constants.PLUGIN_CLASS_NAME); String packageName = bundle.getString(Constants.PACKAGE_NAME); mPluginApk = PluginManager.getInstance().getPluginApk(packageName); try { mPlugin = (PluginActivity) loadPluginable(mPluginApk.classLoader, mPluginClazz); mPlugin.attach(mProxy, mPluginApk); mResources = mPluginApk.pluginRes; mPlugin.onCreate(bundle); } catch (Exception e) { VLog.log("Fail in LifeCircleController onCreate"); VLog.log(e.getMessage()); e.printStackTrace(); } } private Object loadPluginable(ClassLoader classLoader, String pluginActivityClass) throws Exception { Class<?> pluginClz = classLoader.loadClass(pluginActivityClass); Constructor<?> constructor = pluginClz.getConstructor(new Class[] {}); constructor.setAccessible(true); return constructor.newInstance(new Object[] {}); } @Override public void onStart() { if (mPlugin != null) { mPlugin.onStart(); } } ``` ``` @Override public void onResume() { if (mPlugin != null) { mPlugin.onResume(); } } @Override public void onStop() { mPlugin.onStop(); } @Override public void onPause() { mPlugin.onPause(); } @Override public void onDestroy() { mPlugin.onDestroy(); } public Resources getResources() { return mResources; } public Resources.Theme getTheme() { return mTheme; } public AssetManager getAssets() { return mResources.getAssets(); } } ```
这个看起来有点像Activity源码所拥有的的外观模式,而且内部的职责和分工划分是使用者中不可见的。
我们在最后在主工程进行启动插件: Intent intent = new Intent(); intent.putExtra(Constants.PACKAGE_NAME, PLUGIN_PACKAGE_NAME); intent.putExtra(Constants.PLUGIN_CLASS_NAME, PLUGIN_CLAZZ_NAME); mPluginManager.startActivity(intent);
通过插件Activity如下: public class MainActivity extends PluginActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setTitle("Plugin App"); ((ImageView) findViewById(R.id.iv_logo)).setImageResource(R.drawable.android); } } ```效果
可以看出Hook已经实现
当然Hook的方式只需要了解系统启动一个基本Activity的过程,但对于系统来说会先检查Activity是否注册,所以再去生成Activity,这样的话我们只需要在检查的过程中使用一个已经注册过的Activity(桩,通常表示为StubActivity)从而让系统进行一个检查,检查通过之后再生成的时候再可以替换成插件,这样可以了。
所以要通过自己实现一个 Instrumentation ,要在里面使用一些替换工作,然后再去Hook掉系统所持有的对象: public class HookedInstrumentation extends Instrumentation implements Handler.Callback { public static final String TAG = "HookedInstrumentation"; protected Instrumentation mBase; private PluginManager mPluginManager; public HookedInstrumentation(Instrumentation base, PluginManager pluginManager) { mBase = base; mPluginManager = pluginManager; } /** * 覆盖掉原始Instrumentation类的对应方法,用于插件内部跳转Activity时适配 * * @Override */ public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { if (Constants.DEBUG) Log.e(TAG, "execStartActivity"); mPluginManager.hookToStubActivity(intent); try { Method execStartActivity = Instrumentation.class.getDeclaredMethod( "execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class); execStartActivity.setAccessible(true); return (ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token, target, intent, requestCode, options); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("do not support!!!" + e.getMessage()); } } ``` @Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { if (Constants.DEBUG) Log.e(TAG, "newActivity"); if (mPluginManager.hookToPluginActivity(intent)) { String targetClassName = intent.getComponent().getClassName(); PluginApp pluginApp = mPluginManager.getLoadedPluginApk(); Activity activity = mBase.newActivity(pluginApp.mClassLoader, targetClassName, intent); activity.setIntent(intent); ReflectUtil.setField(ContextThemeWrapper.class, activity, Constants.FIELD_RESOURCES, pluginApp.mResources); return activity; } if (Constants.DEBUG) Log.e(TAG, "super.newActivity(...)"); return super.newActivity(cl, className, intent); } @Override public boolean handleMessage(Message message) { if (Constants.DEBUG) Log.e(TAG, "handleMessage"); return false; } @Override public void callActivityOnCreate(Activity activity, Bundle icicle) { if (Constants.DEBUG) Log.e(TAG, "callActivityOnCreate"); super.callActivityOnCreate(activity, icicle); } } ```
启动过程中负责的 execStartActivity 让其设置成启动已注册的Activity,然后在 newActivity的设置中为其实际过程要启动的插件的Activity。最后去Hook系统持有该字段: public class ReflectUtil { public static final String METHOD_currentActivityThread = "currentActivityThread"; public static final String CLASS_ActivityThread = "android.app.ActivityThread"; public static final String FIELD_mInstrumentation = "mInstrumentation"; public static final String TAG = "ReflectUtil"; private static Instrumentation sInstrumentation; private static Instrumentation sActivityInstrumentation; private static Field sActivityThreadInstrumentationField; private static Field sActivityInstrumentationField; private static Object sActivityThread; public static boolean init() { //获取当前的ActivityThread对象 Class<?> activityThreadClass = null; try { activityThreadClass = Class.forName(CLASS_ActivityThread); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(METHOD_currentActivityThread); currentActivityThreadMethod.setAccessible(true); Object currentActivityThread = currentActivityThreadMethod.invoke(null); //拿到在ActivityThread类里面的原始mInstrumentation对象 Field instrumentationField = activityThreadClass.getDeclaredField(FIELD_mInstrumentation); instrumentationField.setAccessible(true); sActivityThreadInstrumentationField = instrumentationField; sInstrumentation = (Instrumentation) instrumentationField.get(currentActivityThread); sActivityThread = currentActivityThread; sActivityInstrumentationField = Activity.class.getDeclaredField(FIELD_mInstrumentation); sActivityInstrumentationField.setAccessible(true); return true; } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { e.printStackTrace(); } return false; } public static Instrumentation getInstrumentation() { return sInstrumentation; } public static Object getActivityThread() { return sActivityThread; } public static void setInstrumentation(Object activityThread, HookedInstrumentation hookedInstrumentation) { try { sActivityThreadInstrumentationField.set(activityThread, hookedInstrumentation); if (Constants.DEBUG) Log.e(TAG, "set hooked instrumentation"); } catch (IllegalAccessException e) { e.printStackTrace(); } } public static void setActivityInstrumentation(Activity activity, PluginManager manager) { try { sActivityInstrumentation = (Instrumentation) sActivityInstrumentationField.get(activity); HookedInstrumentation instrumentation = new HookedInstrumentation(sActivityInstrumentation, manager); sActivityInstrumentationField.set(activity, instrumentation); if (Constants.DEBUG) Log.e(TAG, "set activity hooked instrumentation"); } catch (IllegalAccessException e) { e.printStackTrace(); } } public static void setField(Class clazz, Object target, String field, Object object) { try { Field f = clazz.getDeclaredField(field); f.setAccessible(true); f.set(target, object); } catch (Exception e) { e.printStackTrace(); } } } ```
PluginManager 也是负责加载插件的资源和类这一块 public class PluginManager { private final static String TAG = "PluginManager"; private static PluginManager sInstance; private Context mContext; private PluginApp mPluginApp; public static PluginManager getInstance(Context context) { if (sInstance == null && context != null) { sInstance = new PluginManager(context); } return sInstance; } private PluginManager(Context context) { mContext = context; } public void hookInstrumentation() { try { Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(); final HookedInstrumentation instrumentation = new HookedInstrumentation(baseInstrumentation, this); Object activityThread = ReflectUtil.getActivityThread(); ReflectUtil.setInstrumentation(activityThread, instrumentation); } catch (Exception e) { e.printStackTrace(); } } ``` ``` public void hookCurrentActivityInstrumentation(Activity activity) { ReflectUtil.setActivityInstrumentation(activity, sInstance); } public void hookToStubActivity(Intent intent) { if (Constants.DEBUG) Log.e(TAG, "hookToStubActivity"); if (intent == null || intent.getComponent() == null) { return; } String targetPackageName = intent.getComponent().getPackageName(); String targetClassName = intent.getComponent().getClassName(); if (mContext != null && !mContext.getPackageName().equals(targetPackageName) && isPluginLoaded(targetPackageName)) { if (Constants.DEBUG) Log.e(TAG, "hook " + targetClassName + " to " + Constants.STUB_ACTIVITY); intent.setClassName(Constants.STUB_PACKAGE, Constants.STUB_ACTIVITY); intent.putExtra(Constants.KEY_IS_PLUGIN, true); intent.putExtra(Constants.KEY_PACKAGE, targetPackageName); intent.putExtra(Constants.KEY_ACTIVITY, targetClassName); } } ``` ``` public boolean hookToPluginActivity(Intent intent) { if (Constants.DEBUG) Log.e(TAG, "hookToPluginActivity"); if (intent.getBooleanExtra(Constants.KEY_IS_PLUGIN, false)) { String pkg = intent.getStringExtra(Constants.KEY_PACKAGE); String activity = intent.getStringExtra(Constants.KEY_ACTIVITY); if (Constants.DEBUG) Log.e(TAG, "hook " + intent.getComponent().getClassName() + " to " + activity); intent.setClassName(pkg, activity); return true; } return false; } ``` ``` private boolean isPluginLoaded(String packageName) { // TODO 检查packageNmae是否匹配 return mPluginApp != null; } ``` ``` public PluginApp loadPluginApk(String apkPath) { String addAssetPathMethod = "addAssetPath"; PluginApp pluginApp = null; try { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod, String.class); addAssetPath.invoke(assetManager, apkPath); Resources pluginRes = new Resources(assetManager, mContext.getResources().getDisplayMetrics(), mContext.getResources().getConfiguration()); pluginApp = new PluginApp(pluginRes); pluginApp.mClassLoader = createDexClassLoader(apkPath); } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } return pluginApp; } ``` private DexClassLoader createDexClassLoader(String apkPath) { File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE); return new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(), null, mContext.getClassLoader()); } public boolean loadPlugin(String apkPath) { File apk = new File(apkPath); if (!apk.exists()) { return false; } mPluginApp = loadPluginApk(apkPath); return mPluginApp != null; } public PluginApp getLoadedPluginApk() { return mPluginApp; } } ```
在MainActivity中进行初始化,并且注意Hook的时机: public class MainActivity extends Activity implements View.OnClickListener { // https://zhuanlan.zhihu.com/p/33017826 public static final boolean DEBUG = true; public static final String TAG = "MainActivity"; private String mPluginPackageName = "top.vimerzhao.image"; private String mPluginClassName = "top.vimerzhao.image.MainActivity"; //读写权限 private static String[] PERMISSIONS_STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; //请求状态码 private static int REQUEST_PERMISSION_CODE = 1; private PluginManager mPluginManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); initView(); initPlugin(); } private void initPlugin() { // !! must first ReflectUtil.init(); mPluginManager = PluginManager.getInstance(getApplicationContext()); mPluginManager.hookInstrumentation(); mPluginManager.hookCurrentActivityInstrumentation(this); } private void initData() { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_PERMISSION_CODE); } } } private void initView() { (findViewById(R.id.tv_launch)).setOnClickListener(this); } @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); // !!! 不要在此Hook,看源码发现mInstrumentaion会在此方法后初始化 } @Override public void onClick(View view) { if (Constants.DEBUG) Log.e(TAG, "click view id: " + view.getId()); if (view.getId() == R.id.tv_launch) { // TODO launch plugin app if (mPluginManager.loadPlugin(Constants.PLUGIN_PATH)) { Intent intent = new Intent(); intent.setClassName(mPluginPackageName, mPluginClassName); startActivity(intent); } } } } ```
证明插件Activity现在将不会有任何的限制: public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onResume() { super.onResume(); } } ```
效果和上图类似。
参考 1.Android插件化:从入门到放弃(http://www.infoq.com/cn/articles/android-plug-ins-from-entry-to-give-up)
2.Android apk动态加载机制的研究 (http://blog.csdn.net/singwhatiwanna/article/details/22597587)
3.Android插件化原理解析系列文章 (http://weishu.me/2016/01/28/understand-plugin-framework-overview/)
4.深度 | 滴滴插件化方案 VirtualApk 源码解析
5.VirtualAPK资源加载原理解析 (https://www.notion.so/VirtualAPK-1fce1a910c424937acde9528d2acd537)
如果您觉得我们的内容还不错,可以在评论区下方留言或者私信我