一、背景:
插件化的第一代目,任玉刚大神的dynamic-load-apk。目前插件化的方案主要有以Dynamic-load-apk为代表的的静态代理方案,以及以张勇的DroidPlugin为代表的动态代理hook系统AMS和PM的方案
-
第一种方案,使用静态代理插件的方案,来代理插件apk中Activity的生命周期管理。
-
第二种方案,使用动态代理hook系统AMS的方式,来拦截AMS启动Activity和Activity生命周期的逻辑,通过使用选坑位Activity来骗过AMS,回调到Client端再脱坑脱壳,启动targetActivity来实现。
二、解析:
这里先逐步解析一下Dynamic-load-apk的原理。
插件化涉及到几个重要的问题:
- 插件中资源和host宿主资源的管理(资源ID冲突为题等)
- 插件中四大组件的生命周期管理
-
插件中资源的管理
我们查看Dynamic-load-apk的工程代码,可以从DLPluginManager这个类开始。
运行插件的第一步,肯定是加载插件apk文件。public DLPluginPackage loadApk(final String dexPath, boolean hasSoLib) { //标识来自外部,即从宿主调用 mFrom = DLConstants.FROM_EXTERNAL; //解析插件apk的packageInfo PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES); if (packageInfo == null) { return null; } //1. 准备插件运行的环境 DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath); ///2. 如果有so文件,则需要拷贝so文件 if (hasSoLib) { copySoLib(dexPath); } return pluginPackage; }
我们先看注释1:准备插件运行环境
/** * prepare plugin runtime env, has DexClassLoader, Resources, and so on. * * @param packageInfo * @param dexPath * @return */ private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) { //从缓存中拿PluginPackage对象 DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName); if (pluginPackage != null) { return pluginPackage; } //为当前插件创建独立的DexClassLoader DexClassLoader dexClassLoader = createDexClassLoader(dexPath); //为当前插件创建资源管理AssetManager AssetManager assetManager = createAssetManager(dexPath); //为当前插件创建资源Resource Resources resources = createResources(assetManager); // create pluginPackage 创建新的pluginPackage对象。PluginPackage对象持有dexClassLoader、Resource、插件自己的PackageInfo pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo); //放进缓存 mPackagesHolder.put(packageInfo.packageName, pluginPackage); return pluginPackage; }
我们再分别看
cerateDexClassLoader()
、createAssetManager()
、createResource()
- createDexClassLoder(dexpath) 为当前插件创建ClassLoader。此处提一下:pathClassLoader和dexClassLoader的区别:
- 首先,二者都是继承自BaseDexClassLoder
- pathClassLoader只支持安装的apk,dexClassLoader可以支持dex、未安装的apk、aar、jar等
private DexClassLoader createDexClassLoader(String dexPath) { File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE); dexOutputPath = dexOutputDir.getAbsolutePath(); //插件所在目录,dexopt后所在目录,插件目录下放so的路径、父classloader DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, mNativeLibDir, mContext.getClassLoader()); return loader; }
- createAssetManager(dexpath) 将插件的资源加入到宿主中
private AssetManager createAssetManager(String dexPath) { try { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class); //将插件的资源一起加入到AssetManager中 addAssetPath.invoke(assetManager, dexPath); return assetManager; } catch (Exception e) { e.printStackTrace(); return null; } }
- createResources(assetManager) 为当前插件创建资源对象
private Resources createResources(AssetManager assetManager) { Resources superRes = mContext.getResources(); Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); return resources; }
- createDexClassLoder(dexpath) 为当前插件创建ClassLoader。此处提一下:pathClassLoader和dexClassLoader的区别:
-
再看注释2:将so文件copy到插件目录下
private void copySoLib(String dexPath) { // TODO: copy so lib async will lead to bugs maybe, waiting for // resolved later. // TODO : use wait and signal is ok ? that means when copying the // .so files, the main thread will enter waiting status, when the // copy is done, send a signal to the main thread. // new Thread(new CopySoRunnable(dexPath)).start(); SoLibManager.getSoLoader().copyPluginSoLib(mContext, dexPath, mNativeLibDir); } /** * copy so lib to specify directory(/data/data/host_pack_name/pluginlib) * * @param dexPath plugin path * @param nativeLibDir nativeLibDir */ public void copyPluginSoLib(Context context, String dexPath, String nativeLibDir) { String cpuName = getCpuName(); String cpuArchitect = getCpuArch(cpuName); sNativeLibDir = nativeLibDir; Log.d(TAG, "cpuArchitect: " + cpuArchitect); long start = System.currentTimeMillis(); try { ZipFile zipFile = new ZipFile(dexPath); Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry zipEntry = (ZipEntry) entries.nextElement(); if (zipEntry.isDirectory()) { continue; } String zipEntryName = zipEntry.getName(); if (zipEntryName.endsWith(".so") && zipEntryName.contains(cpuArchitect)) { final long lastModify = zipEntry.getTime(); if (lastModify == DLConfigs.getSoLastModifiedTime(context, zipEntryName)) { // exist and no change Log.d(TAG, "skip copying, the so lib is exist and not change: " + zipEntryName); continue; } mSoExecutor.execute(new CopySoTask(context, zipFile, zipEntry, lastModify)); } } } catch (IOException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); Log.d(TAG, "### copy so time : " + (end - start) + " ms"); } private class CopySoTask implements Runnable { private String mSoFileName; private ZipFile mZipFile; private ZipEntry mZipEntry; private Context mContext; private long mLastModityTime; CopySoTask(Context context, ZipFile zipFile, ZipEntry zipEntry, long lastModify) { mZipFile = zipFile; mContext = context; mZipEntry = zipEntry; mSoFileName = parseSoFileName(zipEntry.getName()); mLastModityTime = lastModify; } private final String parseSoFileName(String zipEntryName) { return zipEntryName.substring(zipEntryName.lastIndexOf("/") + 1); } private void writeSoFile2LibDir() throws IOException { InputStream is = null; FileOutputStream fos = null; is = mZipFile.getInputStream(mZipEntry); fos = new FileOutputStream(new File(sNativeLibDir, mSoFileName)); copy(is, fos); mZipFile.close(); } /** * 输入输出流拷贝 * * @param is * @param os */ public void copy(InputStream is, OutputStream os) throws IOException { if (is == null || os == null) return; BufferedInputStream bis = new BufferedInputStream(is); BufferedOutputStream bos = new BufferedOutputStream(os); int size = getAvailableSize(bis); byte[] buf = new byte[size]; int i = 0; while ((i = bis.read(buf, 0, size)) != -1) { bos.write(buf, 0, i); } bos.flush(); bos.close(); bis.close(); } private int getAvailableSize(InputStream is) throws IOException { if (is == null) return 0; int available = is.available(); return available <= 0 ? 1024 : available; } @Override public void run() { try { writeSoFile2LibDir(); DLConfigs.setSoLastModifiedTime(mContext, mZipEntry.getName(), mLastModityTime); Log.d(TAG, "copy so lib success: " + mZipEntry.getName()); } catch (IOException e) { Log.e(TAG, "copy so lib failed: " + e.toString()); e.printStackTrace(); } } }
到此,整个插件的load过程全部完成,主要包括:
- 为插件准备运行环境
- copy出插件的so文件 整个过程通过loadApk加载插件会得到PluginPackager对象,
该对象持有该插件的classLoader、该插件的资源resources、该插件的packageInfo。每个插件加载完之后,PluginPackager都会存储中缓存mPackagesHolder中,为后期启动插件做准备。
-
启动插件(以Activity为例子)
DlPluginManager.startPluginActivity(Context context, DLIntent dlIntent)
会调用到startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode)
/** * @param context * @param dlIntent * @param requestCode * @return One of below: {@link #START_RESULT_SUCCESS} * {@link #START_RESULT_NO_PKG} {@link #START_RESULT_NO_CLASS} * {@link #START_RESULT_TYPE_ERROR} */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) { //如果是插件内部调用,则不用代理中转,直接打开 if (mFrom == DLConstants.FROM_INTERNAL) { dlIntent.setClassName(context, dlIntent.getPluginClass()); performStartActivityForResult(context, dlIntent, requestCode); return DLPluginManager.START_RESULT_SUCCESS; } //如果是外部打开插件Activity,则需要一系列操作 //1、获取插件包名 String packageName = dlIntent.getPluginPackage(); if (TextUtils.isEmpty(packageName)) { throw new NullPointerException("disallow null packageName."); } //根据插件包名,从缓存中获取之前加载加载解析到的DLPluginPackager对象 DLPluginPackage pluginPackage = mPackagesHolder.get(packageName); if (pluginPackage == null) { return START_RESULT_NO_PKG; } //获取Activity完整路径的名称:包名+ActivityClass类名 final String className = getPluginActivityFullPath(dlIntent, pluginPackage); //用该插件自己的DexClassLoader去加载这个Activity类 Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className); if (clazz == null) { return START_RESULT_NO_CLASS; } // get the proxy activity class, the proxy activity will launch the // plugin activity. //根据插件的Activity去拿到插件Activity代理的Activity Class<? extends Activity> activityClass = getProxyActivityClass(clazz); if (activityClass == null) { return START_RESULT_TYPE_ERROR; } // put extra data 往dlIntent中塞数据,给ProxyActivity使用 dlIntent.putExtra(DLConstants.EXTRA_CLASS, className); dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName); dlIntent.setClass(mContext, activityClass); //打开Activity performStartActivityForResult(context, dlIntent, requestCode); return START_RESULT_SUCCESS; } public int startPluginService(final Context context, final DLIntent dlIntent) { if (mFrom == DLConstants.FROM_INTERNAL) { dlIntent.setClassName(context, dlIntent.getPluginClass()); context.startService(dlIntent); return DLPluginManager.START_RESULT_SUCCESS; } fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() { @Override public void onFetch(int result, Class<? extends Service> proxyServiceClass) { // TODO Auto-generated method stub if (result == START_RESULT_SUCCESS) { dlIntent.setClass(context, proxyServiceClass); // start代理Service context.startService(dlIntent); } mResult = result; } }); return mResult; }
看看如何找到代理Activity的getProxyActivityClass(clazz)
/** * get the proxy activity class, the proxy activity will delegate the plugin * activity * * @param clazz * target activity's class * @return */ private Class<? extends Activity> getProxyActivityClass(Class<?> clazz) { Class<? extends Activity> activityClass = null; //如果插件的Activity是继承自DLBasePluginActivity,则它在宿主host中的代理Activity是DLProxyActivity if (DLBasePluginActivity.class.isAssignableFrom(clazz)) { activityClass = DLProxyActivity.class; //同上 } else if (DLBasePluginFragmentActivity.class.isAssignableFrom(clazz)) { activityClass = DLProxyFragmentActivity.class; } return activityClass; }
为插件Activty找到在host中的代理Activity后,所以生命周期都交给代理Activity来实现。
// put extra data 往dlIntent中塞数据,给ProxyActivity使用 dlIntent.putExtra(DLConstants.EXTRA_CLASS, className); dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName); dlIntent.setClass(mContext, activityClass); //打开Activity 这里先打开activityClass,也就是host中的代理Activity performStartActivityForResult(context, dlIntent, requestCode);
宿主host中的代理Activity有两种类型:DLProxyActivity和DLProxyFragmentActvity。
我们拿DlProxyActivity来看,另一个同理/** * HOST中的代理Activity,所有插件的生命周期都是通过它来代理的 */ public class DLProxyActivity extends Activity implements DLAttachable { protected DLPlugin mRemoteActivity; //关键类DLPrpxyImpl是真正的实现代理类 private DLProxyImpl impl = new DLProxyImpl(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //获取塞过来的DlIntent来代理插件Activity的OnCreate impl.onCreate(getIntent()); } @Override public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) { //将插件Activity与当前代理Activity绑定 mRemoteActivity = remoteActivity; } //AssetManager的代理 @Override public AssetManager getAssets() { return impl.getAssets() == null ? super.getAssets() : impl.getAssets(); } //Resources的代理 @Override public Resources getResources() { return impl.getResources() == null ? super.getResources() : impl.getResources(); } //getTheme的代理 @Override public Theme getTheme() { return impl.getTheme() == null ? super.getTheme() : impl.getTheme(); } @Override public ClassLoader getClassLoader() { return impl.getClassLoader(); } //以下是其他生命周期回调和其他一些回调方法的代理 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { mRemoteActivity.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data); } @Override protected void onStart() { mRemoteActivity.onStart(); super.onStart(); } @Override protected void onRestart() { mRemoteActivity.onRestart(); super.onRestart(); } ... }
关键类ProxyImpl(代理的真正实现)
/** * This is a plugin activity proxy, the proxy will create the plugin activity * with reflect, and then call the plugin activity's attach、onCreate method, at * this time, the plugin activity is running. * * @author mrsimple */ public class DLProxyImpl { private static final String TAG = "DLProxyImpl"; private String mClass; private String mPackageName; private DLPluginPackage mPluginPackage; private DLPluginManager mPluginManager; private AssetManager mAssetManager; private Resources mResources; private Theme mTheme; private ActivityInfo mActivityInfo; private Activity mProxyActivity; protected DLPlugin mPluginActivity; public ClassLoader mPluginClassLoader; public DLProxyImpl(Activity activity) { mProxyActivity = activity; } /** * 初始化ActivityInfo 主要是主题 */ private void initializeActivityInfo() { PackageInfo packageInfo = mPluginPackage.packageInfo; if ((packageInfo.activities != null) && (packageInfo.activities.length > 0)) { if (mClass == null) { mClass = packageInfo.activities[0].name; } //Finals 修复主题BUG int defaultTheme = packageInfo.applicationInfo.theme; for (ActivityInfo a : packageInfo.activities) { if (a.name.equals(mClass)) { mActivityInfo = a; // Finals ADD 修复主题没有配置的时候插件异常 if (mActivityInfo.theme == 0) { if (defaultTheme != 0) { mActivityInfo.theme = defaultTheme; } else { if (Build.VERSION.SDK_INT >= 14) { mActivityInfo.theme = android.R.style.Theme_DeviceDefault; } else { mActivityInfo.theme = android.R.style.Theme; } } } } } } } //为Activity设置主题 private void handleActivityInfo() { Log.d(TAG, "handleActivityInfo, theme=" + mActivityInfo.theme); if (mActivityInfo.theme > 0) { mProxyActivity.setTheme(mActivityInfo.theme); } Theme superTheme = mProxyActivity.getTheme(); mTheme = mResources.newTheme(); mTheme.setTo(superTheme); // Finals适配三星以及部分加载XML出现异常BUG try { mTheme.applyStyle(mActivityInfo.theme, true); } catch (Exception e) { e.printStackTrace(); } // TODO: handle mActivityInfo.launchMode here in the future. } /** * 最关键的方法你,外部启动插件内Activity会跑到这里 * * @param intent */ public void onCreate(Intent intent) { // set the extra's class loader intent.setExtrasClassLoader(DLConfigs.sPluginClassloader); //获取插件包名 mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE); //插件Activity类名 mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS); Log.d(TAG, "mClass=" + mClass + " mPackageName=" + mPackageName); mPluginManager = DLPluginManager.getInstance(mProxyActivity); mPluginPackage = mPluginManager.getPackage(mPackageName); mAssetManager = mPluginPackage.assetManager; mResources = mPluginPackage.resources; //初始化ActivityInfo initializeActivityInfo(); //为activity设置主题 handleActivityInfo(); //启动插件Activity launchTargetActivity(); } /** * 启动插件内的activity: * 1、先反射创建Activity实例 * 2、调用插件Activity的onCreate * 3、由于ProxyActivity其实是一个空Activity,所以ProxyActivity相当于一个壳子,只是负责代理回调方法 */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) protected void launchTargetActivity() { try { Class<?> localClass = getClassLoader().loadClass(mClass); Constructor<?> localConstructor = localClass.getConstructor(new Class[]{}); Object instance = localConstructor.newInstance(new Object[]{}); mPluginActivity = (DLPlugin) instance; ((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager); Log.d(TAG, "instance = " + instance); // attach the proxy activity and plugin package to the mPluginActivity mPluginActivity.attach(mProxyActivity, mPluginPackage); Bundle bundle = new Bundle(); bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL); mPluginActivity.onCreate(bundle); } catch (Exception e) { e.printStackTrace(); } } public ClassLoader getClassLoader() { return mPluginPackage.classLoader; } public AssetManager getAssets() { return mAssetManager; } public Resources getResources() { return mResources; } public Theme getTheme() { return mTheme; } public DLPlugin getRemoteActivity() { return mPluginActivity; } }
插件中Acitivity的例子
public class MainActivity extends DLBasePluginActivity { private static final String TAG = "MainActivity"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initView(savedInstanceState); } private void initView(Bundle savedInstanceState) { that.setContentView(generateContentView(that)); } private View generateContentView(final Context context) { LinearLayout layout = new LinearLayout(context); layout.setOrientation(LinearLayout.VERTICAL); layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); Button button = new Button(context); button.setText("Invoke host method"); layout.addView(button, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { TestHostClass testHostClass = new TestHostClass(); testHostClass.testMethod(that); } }); TextView textView = new TextView(context); textView.setText("Hello, I'm Plugin B."); textView.setTextSize(30); layout.addView(textView, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); return layout; } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { Log.d(TAG, "onActivityResult resultCode=" + resultCode); if (resultCode == RESULT_FIRST_USER) { that.finish(); } super.onActivityResult(requestCode, resultCode, data); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
到此整个启动过程解析完成,核心是:
- 插件中Activity必须都继承自DLBasePluginActivity或DLBaseFragmentActvity
- DLProxyActivity或DlPrxoyFragment作为空壳代理了插件Activity的所有回调方法
-
Service的启动也类似。宿主也是有个代理Service叫DLProxyService,插件中的Service必须继承DLBasePluginService