android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束。关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好。在这篇文章中,我们将不仅止于原理,对源代码的具体实现进行分析。文章中涉及的代码可从https://github.com/kissazi2/AndroidDemo/tree/master/PlugLoadDemo下载,基于Android Studio 1.2编译。
在正式开始分析源代码之前,我们首先需要有一些动态加载Apk的基础知识。
从这两篇文章中,我们大致了解了插件动态加载的流程。我们将要分析的android_plugmgr的思路与上面文章中的思路完全不同,它通过将继承关系反转,也就是说宿主Activity中的ProxyActivity去继承插件中的Activity。在startActivity(...)启动这个Activity之前,通过自己实现的ClassLoader去加载这个临时被构造出来的Activity。这个框架不像上面文章介绍的插件加载框架有那么多的限制。
这个框架只需要在如下面截图中的指定文件夹(一般是sdcard中的Download文件夹)中放入需要动态加载的APK文件,选择对应的图标就可以实现动态加载了.
1、加载插件信息
- @Override
- public void onClick(View v) {
- final String dirText = pluginDirTxt.getText().toString().trim();
- //省略不必要的代码
- new Thread(new Runnable() {
- @Override
- public void run() {
- Collection<PlugInfo> plugs = plugMgr
- .loadPlugin(new File(dirText));
- setPlugins(plugs);
- }
- }).start();
- }
- private void setPlugins(final Collection<PlugInfo> plugs) {
- if (plugs == null || plugs.isEmpty()) {
- return;
- }
- final ListAdapter adapter = new PlugListViewAdapter(this, plugs);
- runOnUiThread(new Runnable() {
- public void run() {
- pluglistView.setAdapter(adapter);
- }
- });
- }
从上面我们看到插件的信息通过文件地址dirText传入,然后由 plugMgr.loadPlugin(new File(dirText))进行apk信息的加载,最终将信息保存在plugInfo的集合。plugMgr是一个PluginManager类型的对象,它定义在android_plugmgr类库中。PlugListViewAdapter(继承自BaseAdapter)就是一个带有PlugInfo的Adatpter而已。
2、点击每一项,加载对应的插件
- private void plugItemClick(int position) {
- PlugInfo plug = (PlugInfo) pluglistView.getItemAtPosition(position);
- plugMgr.startMainActivity(this, plug.getPackageName());
- }
在了解基本的使用之后,我们看看android_plugmgr中包含什么具体的类。截图中的类所在的包与原作者的类库中的包略有不同,因为要在Android Studio中编译做的小修改。下面罗列出这些类,只为给大家一个模糊的关于这个类库结构,可以尝试着理清他们之间的关系,看不懂就跳过。
从前面的分析中,我们知道要加载插件apk中的信息,我们需要加载插件apk中的组件(Activity、service、brocastreceiver)、用DexClassLoader加载Dex文件中信息、修改Resource类的资源指向。要完成以上操作,我们需要读取插件apk包中的AndroidManifest信息,取出一些必要的内容(不单单是从AndroidManifest.xml,还有插件apk本身)。
之前我们忽略了PluginManager的初始化。它的初始化是在宿主APK的MainActivity中进行的。初始化通过PluginManager.getInstance()→PluginManager.init()进行调用。getInstance()所做的就是获取ApplicationContext然后传给init().
- private void init(Context ctx) {
- Log.i(tag, "init()...");
- context = ctx;
- //plugsout是让DexClassLoader存放它Dex文件的地方,可以先放过
- File optimizedDexPath = ctx.getDir("plugsout", Context.MODE_PRIVATE);
- if (!optimizedDexPath.exists()) {
- optimizedDexPath.mkdirs();
- }
- dexOutputPath = optimizedDexPath.getAbsolutePath();
- dexInternalStoragePath = context
- .getDir("plugins", Context.MODE_PRIVATE);
- //创建plugins用来存放插件apk,以及插件apk中的lib包
- dexInternalStoragePath.mkdirs();
- // 改变classLoader,以便根据是插件apk、还是宿主apk中的类进行特殊处理
- try {
- Object mPackageInfo = ReflectionUtils.getFieldValue(ctx,
- "mBase.mPackageInfo", true);
- frameworkClassLoader = new FrameworkClassLoader(
- ctx.getClassLoader());
- // set Application's classLoader to FrameworkClassLoader
- ReflectionUtils.setFieldValue(mPackageInfo, "mClassLoader",
- frameworkClassLoader, true);
- } catch (Exception e) {
- e.printStackTrace();
- }
- hasInit = true;
- }
我们来模拟计算机一步步地执行看看。从"先用起来"那一节,我们知道加载类库会调用androidx.pluginmgr.PluginManager.loadPlugin(...)方法.在这一步里面主要对后面用的到的信息进行加载.从代码里面不管单个还是多个插件都进入PluginManager.buildPlugInfo(...)。
- public Collection<PlugInfo> loadPlugin(final File pluginSrcDirFile)
- throws Exception {
- checkInit();
- if (pluginSrcDirFile == null || !pluginSrcDirFile.exists()) {
- Log.e(tag, "invalidate plugin file or Directory :"
- + pluginSrcDirFile);
- return null;
- }
- if (pluginSrcDirFile.isFile()) {
- // 如果是文件则尝试加载单个插件,暂不检查文件类型,除apk外,以后可能支持加载其他类型文件,如jar
- PlugInfo one = loadPluginWithId(pluginSrcDirFile, null, null);
- return Collections.singletonList(one);
- }
- // clear all first
- synchronized (this) {
- pluginPkgToInfoMap.clear();
- pluginIdToInfoMap.clear();
- }
- File[] pluginApks = pluginSrcDirFile.listFiles(this);
- if (pluginApks == null || pluginApks.length < 1) {
- throw new FileNotFoundException("could not find plugins in:"
- + pluginSrcDirFile);
- }
- for (File pluginApk : pluginApks) {
- PlugInfo plugInfo = buildPlugInfo(pluginApk, null, null);
- if (plugInfo != null) {
- savePluginToMap(plugInfo);
- }
- }
- return pluginIdToInfoMap.values();
- }
- /**
- * 初始化插件apk的必要信息
- * @param pluginApk 要加载的APK路径
- * @param pluginId 插件apk的名字(一般是xxx.apk),nullable
- * @param targetFileName 插件apk的名称,nullable
- * @return
- * @throws Exception
- */
- private PlugInfo buildPlugInfo(File pluginApk, String pluginId,
- String targetFileName) throws Exception {
- PlugInfo info = new PlugInfo();
- info.setId(pluginId == null ? pluginApk.getName() : pluginId);
- File privateFile = new File(dexInternalStoragePath,
- targetFileName == null ? pluginApk.getName() : targetFileName);
- info.setFilePath(privateFile.getAbsolutePath());
- //1.把插件apk复制到app_plugins目录下,防止因为意外情况被破坏
- if (!pluginApk.getAbsolutePath().equals(privateFile.getAbsolutePath())) {
- copyApkToPrivatePath(pluginApk, privateFile);
- }
- //2.读取AndroidManifest中的信息
- String dexPath = privateFile.getAbsolutePath();
- PluginManifestUtil.setManifestInfo(context, dexPath, info);
- //3.设置插件加载器
- PluginClassLoader loader = new PluginClassLoader(dexPath,
- dexOutputPath, frameworkClassLoader, info);
- info.setClassLoader(loader);
- //4.重定向Resource对象的资源指向
- try {
- AssetManager am = (AssetManager) AssetManager.class.newInstance();
- am.getClass().getMethod("addAssetPath", String.class)
- .invoke(am, dexPath);
- info.setAssetManager(am);
- Resources ctxres = context.getResources();
- Resources res = new Resources(am, ctxres.getDisplayMetrics(),
- ctxres.getConfiguration());
- info.setResources(res);
- } catch (Exception e) {
- e.printStackTrace();
- }
- //5.初始化插件的application,调用它的onCreate()
- if (actFrom != null) {
- initPluginApplication(info, actFrom, true);
- }
- // createPluginActivityProxyDexes(info);
- Log.i(tag, "buildPlugInfo: " + info);
- return info;
- }
这段函数的思路还是很清晰的,其中我们最为感兴趣的是,加载插件apk究竟需要AndroidManifest什么信息?可以预想到Android四大组件肯定是必须的,权限不是必须的。插件apk的所拥有的权限只能宿主apk中AndroidManifest中的,这是Android的安全机制所保证的。进入PluginManifestUtil.setManifestInfo(...)。
- /**
- * 设置跟Manifest有关的信息
- * @param context MainActivity的实例
- * @param apkPath 插件apk的路径
- * @param info 设置了插件apk的Id、FilePath的PlugInfo
- * @throws XmlPullParserException
- * @throws IOException
- */
- static void setManifestInfo(Context context, String apkPath, PlugInfo info)
- throws XmlPullParserException, IOException {
- //1.读取压缩包里面的信息,将AndroidManifest的值读取出来
- ZipFile zipFile = new ZipFile(new File(apkPath), ZipFile.OPEN_READ);
- ZipEntry manifestXmlEntry = zipFile.getEntry(XmlManifestReader.DEFAULT_XML);
- String manifestXML = XmlManifestReader.getManifestXMLFromAPK(zipFile,
- manifestXmlEntry);
- PackageInfo pkgInfo = context.getPackageManager()
- .getPackageArchiveInfo(
- apkPath,
- PackageManager.GET_ACTIVITIES
- | PackageManager.GET_RECEIVERS//
- | PackageManager.GET_PROVIDERS//
- | PackageManager.GET_META_DATA//
- | PackageManager.GET_SHARED_LIBRARY_FILES//
- );
- //2.将插件apk中libs目录下的所有文件复制到app_plugins/插件Id-dir-lib目录下
- File libdir = ActivityOverider.getPluginLibDir(info.getId());
- try {
- if(extractLibFile(zipFile, libdir)){
- pkgInfo.applicationInfo.nativeLibraryDir=libdir.getAbsolutePath();
- }
- } finally {
- zipFile.close();
- }
- //3.设置插件中activity、receiver、service、application等信息
- setAttrs(info, manifestXML);
- }
从这个函数中,我们就完成了对插件apk所需要的信息的加载,需要注意的是,android-plugmgr这版的源代码暂时只支持Activity,Service、Receiver都还是不支持的。
这部分是这个类库的核心。这个类库之所以能够无约束地调用插件apk中的Activity,依赖的就是依赖倒置,让宿主apk中的ProxyActivity去继承插件apk中特定的Activity。它的做法是利用dexmaker在运行时动态生成代码实现继承,然后将这个子类输出成Dex文件放到/data/data/androidx.plmgrdemo/app_plugsout。最后它就利用DexClassLoader进行加载。
从点击主界面点击每一个插件开始,
- private void plugItemClick(int position) {
- PlugInfo plug = (PlugInfo) pluglistView.getItemAtPosition(position);
- plugMgr.startMainActivity(this, plug.getPackageName());
- }
- public boolean startMainActivity(Context context, String pkgOrId) {
- Log.d(tag, "startMainActivity by:" + pkgOrId);
- //1.根据包名或Apk命,获取plugInfo
- PlugInfo plug = preparePlugForStartActivity(context, pkgOrId);
- if (frameworkClassLoader == null) {
- Log.e(tag, "startMainActivity: frameworkClassLoader == null!");
- return false;
- }
- if (plug.getMainActivity() == null) {
- Log.e(tag, "startMainActivity: plug.getMainActivity() == null!");
- return false;
- }
- if (plug.getMainActivity().activityInfo == null) {
- Log.e(tag,
- "startMainActivity: plug.getMainActivity().activityInfo == null!");
- return false;
- }
- //2.这里很关键,三个参数分别是插件的包名、将要加载的插件Activity的名称,这是为了后面能正确地根据
- //Activity或其他普通类是宿主的还是插件的,进行分别处理
- String className = frameworkClassLoader.newActivityClassName(
- plug.getId(), plug.getMainActivity().activityInfo.name);
- //还记得之前贴的代码中(3.1中buildPlugInfo函数),我们提到的application中的classLoader被替换
- //成FrameworkClassloader
- context.startActivity(new Intent().setComponent(new ComponentName(
- context, className)));
- return true;
- }
上面代码就是插件加载Activity的整个过程,然而我们漏掉了插件Activity的加载流程,以及特定的Activity怎么被ProxyActivity继承的过程。接下来分析这个类库中自定义的FrameworkClassloader加载特定类的过程。在上面代码context.startActivity(...)中会对将要start的Activity进行加载,也就是调用ClassLoader进行加载,由于我们已经将宿主apk中application的默认Classloader替换成了FrameworkClassloader,所以在context.startActivity(...)的过程中会调用FrameworkClassLoader.loadClass(...)。上一个流程中调用了FrameworkClassloader.newActivityClassName(...),主要为现在插件apk中Activity的加载埋下伏笔。
- /**
- * 1.用actName记录真正要加载的插件apk中的Activity
- * 2.返回ActivityOverider.targetClassName来指示后面的处理流程,要加载的是插件Apk中的Activity
- * @param plugId
- * @param actName
- * @return
- */
- String newActivityClassName(String plugId, String actName) {
- this.plugId = plugId;
- this.actName = actName;
- return ActivityOverider.targetClassName;
- }
- protected Class < ?>loadClass(String className, boolean resolv) throws ClassNotFoundException {
- Log.i("cl", "loadClass: " + className);
- if (plugId != null) {
- String pluginId = plugId;
- PlugInfo plugin = PluginManager.getInstance().getPluginById(pluginId);
- Log.i("cl", "plugin = " + plugin);
- if (plugin != null) {
- try {
- //这里判断要加载的Activity是否插件apk中的Activity
- if (className.equals(ActivityOverider.targetClassName)) {
- // Thread.dumpStack();
- String actClassName = actName;
- //这里的Classloader是PluginClassLoader
- return plugin.getClassLoader().loadActivityClass(actClassName);
- } else {
- return plugin.getClassLoader().loadClass(className);
- }
- } catch(ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- }
- return super.loadClass(className, resolv);
- }
- }
看来frameworkClassloader也就是对类的加载进行预处理,真正的处理还在PluginClassLoader.loadActivityClass(...)。
- /**
- * 加载Activity类
- * @param actClassName 将要加载的特定Activity(带包名)
- * @return
- * @throws ClassNotFoundException
- */
- Class < ?>loadActivityClass(final String actClassName) throws ClassNotFoundException {
- Log.d(tag, "loadActivityClass: " + actClassName);
- // 在类加载之前检查创建代理的Activity dex文件,以免调用者忘记生成此文件
- File dexSavePath = ActivityOverider.createProxyDex(thisPlugin, actClassName, true);
- ClassLoader actLoader = proxyActivityLoaderMap.get(actClassName);
- if (actLoader == null) {
- actLoader = new DexClassLoader(dexSavePath.getAbsolutePath(), optimizedDirectory, libraryPath, this) {@Override protected Class < ?>loadClass(String name, boolean resolve) throws ClassNotFoundException {
- Log.d("PlugActClassLoader(" + actClassName + ")", "loadClass: " + name);
- if (ActivityOverider.targetClassName.equals(name)) {
- Class < ?>c = findLoadedClass(name);
- if (c == null) {
- Log.d("PlugActClassLoader(" + actClassName + ")", "findClass");
- c = findClass(name);
- }
- if (resolve) {
- resolveClass(c);
- }
- return c;
- }
- return super.loadClass(name, resolve);
- }
- };
- proxyActivityLoaderMap.put(actClassName, actLoader);
- }
- return actLoader.loadClass(ActivityOverider.targetClassName);
- }
这段函数里面重点就是ActivityOverider.createProxyDex(...)函数。另外,关于为什么 new DexClassLoader(...)的时候要将this(也就是PluginClassLoader)作为parent传入,参考Java ClassLoader基础及加载不同依赖 Jar 中的公共类。
- static File createProxyDex(PlugInfo plugin, String activity, boolean lazy) {
- //这里savePath = /data/data/androidx.plmgrdemo/app_plugins/app名称.apk-dir/activities/app包名.XXXXActivity.dex
- File savePath = getPorxyActivityDexPath(plugin.getId(), activity);
- createProxyDex(plugin, activity, savePath, lazy);
- return savePath;
- }
- private static void createProxyDex(PlugInfo plugin, String activity, File saveDir,
- boolean lazy) {
- // Log.d(tag + ":createProxyDex", "plugin=" + plugin + "\n, activity="
- // + activity);
- if (lazy && saveDir.exists()) {
- // Log.d(tag, "dex alreay exists: " + saveDir);
- // 已经存在就不创建了,直接返回
- return;
- }
- // Log.d(tag, "actName=" + actName + ", saveDir=" + saveDir);
- try {
- String pkgName = plugin.getPackageName();
- //这里是ProxyActivity继承插件中特定Activity的具体处理
- ActivityClassGenerator.createActivityDex(activity, targetClassName,
- saveDir, plugin.getId(), pkgName);
- } catch (Throwable e) {
- Log.e(tag, Log.getStackTraceString(e));
- }
- }
- public static void createActivityDex(String superClassName,
- String targetClassName, File saveTo, String pluginId, String pkgName)
- throws IOException {
- byte[] dex = createActivityDex(superClassName, targetClassName,
- pluginId, pkgName);
- if (saveTo.getName().endsWith(".dex")) {
- FileUtil.writeToFile(dex, saveTo);
- } else {
- JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(
- saveTo));
- jarOut.putNextEntry(new JarEntry(DexFormat.DEX_IN_JAR_NAME));
- jarOut.write(dex);
- jarOut.closeEntry();
- jarOut.close();
- }
- }
可以看出ActivityClassGenerator.createActivityDex(...)是重点,它利用dexmaker在运行时动态生成代码让ProxyActivity继承插件apk中特定的Activity,当然这个Activity中的生命周期函数肯定要进行一些修改。在看接下来的代码前,你可能需要先了解dexmarker的语法.
- /**
- * 生成Activity的Dex文件
- * @param superClassName
- * @param targetClassName
- * @param pluginId
- * @param pkgName
- * @return
- */
- public static < S,
- D extends S > byte[] createActivityDex(final String superClassName, final String targetClassName, final String pluginId, String pkgName) {
- DexMaker dexMaker = new DexMaker();
- TypeId < D > generatedType = TypeId.get('L' + targetClassName.replace('.', '/') + ';');
- TypeId < S > superType = TypeId.get('L' + superClassName.replace('.', '/') + ';');
- // 声明类
- dexMaker.declare(generatedType, "", PUBLIC | FINAL, superType);
- // 定义字段
- //private static final String _pluginId = @param{pluginId};
- // private AssetManager asm;
- // private Resources res;
- declareFields(dexMaker, generatedType, superType, pluginId, pkgName);
- // 声明 默认构造方法
- declare_constructor(dexMaker, generatedType, superType);
- // 声明 方法:onCreate
- declareMethod_onCreate(dexMaker, generatedType, superType);
- // 声明 方法:public AssetManager getAssets()
- declareMethod_getAssets(dexMaker, generatedType, superType);
- // 声明 方法:public Resources getResources()
- declareMethod_getResources(dexMaker, generatedType, superType);
- /*
- * 声明 方法:startActivityForResult(Intent intent, int requestCode, Bundle
- * options)
- */
- declareMethod_startActivityForResult(dexMaker, generatedType, superType);
- // 声明 方法:public void onBackPressed()
- declareMethod_onBackPressed(dexMaker, generatedType, superType);
- declareMethod_startService(dexMaker, generatedType, superType);
- declareMethod_bindService(dexMaker, generatedType, superType);
- declareMethod_unbindService(dexMaker, generatedType, superType);
- declareMethod_stopService(dexMaker, generatedType, superType);
- // Create life Cycle methods
- declareLifeCyleMethod(dexMaker, generatedType, superType, "onResume");
- declareLifeCyleMethod(dexMaker, generatedType, superType, "onStart");
- declareLifeCyleMethod(dexMaker, generatedType, superType, "onRestart");
- declareLifeCyleMethod(dexMaker, generatedType, superType, "onPause");
- declareLifeCyleMethod(dexMaker, generatedType, superType, "onStop");
- declareLifeCyleMethod(dexMaker, generatedType, superType, "onDestroy");
- declareMethod_attachBaseContext(dexMaker, generatedType, superType);
- declareMethod_getComponentName(dexMaker, generatedType, superType, superClassName);
- declareMethod_getPackageName(dexMaker, generatedType, pkgName);
- declareMethod_getIntent(dexMaker, generatedType, superType);
- declareMethod_setTheme(dexMaker, generatedType, superType);
- // Create the dex Content
- byte[] dex = dexMaker.generate();
- return dex;
- }
传入这个函数的值(以Android-PullToRefresh说明)
别看这段函数这么长,它也就做了下面的事情(你可以看一下原作者博客看这段生成的代码究竟是怎么样的):
我们以declareMethod_onCreate(dexMaker, generatedType, superType)进行分析
- private static < S,
- D extends S > void declareMethod_onCreate(DexMaker dexMaker, TypeId < D > generatedType, TypeId < S > superType) {
- //
- // 声明 方法:onCreate
- TypeId < Bundle > Bundle = TypeId.get(Bundle.class);
- TypeId < ActivityOverider > ActivityOverider = TypeId.get(ActivityOverider.class);
- MethodId < D,
- Void > method = generatedType.getMethod(TypeId.VOID, "onCreate", Bundle);
- Code methodCode = dexMaker.declare(method, PROTECTED);
- // locals
- Local < D > localThis = methodCode.getThis(generatedType);
- Local < Bundle > lcoalBundle = methodCode.getParameter(0, Bundle);
- Local < Boolean > lcoalCreated = methodCode.newLocal(TypeId.BOOLEAN);
- Local < String > pluginId = get_pluginId(generatedType, methodCode);
- // this.mOnCreated = true;
- FieldId < D,
- Boolean > beforeOnCreate = generatedType.getField(TypeId.BOOLEAN, FIELD_mOnCreated);
- methodCode.loadConstant(lcoalCreated, true);
- methodCode.iput(beforeOnCreate, localThis, lcoalCreated);
- //ActivityOverider.callback_onCreate(str, this);
- MethodId < ActivityOverider,
- Void > method_call_onCreate = ActivityOverider.getMethod(TypeId.VOID, "callback_onCreate", TypeId.STRING, TypeId.get(Activity.class));
- methodCode.invokeStatic(method_call_onCreate, null, pluginId, localThis);
- // super.onCreate()
- MethodId < S,
- Void > superMethod = superType.getMethod(TypeId.VOID, "onCreate", Bundle);
- methodCode.invokeSuper(superMethod, null, localThis, lcoalBundle);
- methodCode.returnVoid();
- }
这段函数所做的事情可以用下面的java代码表示
- protected void onCreate(Bundle paramBundle)
- {
- String str = _pluginId;
- this.mOnCreated = true;
- ActivityOverider.callback_onCreate(str, this);
- super.onCreate(paramBundle);
- }
其他函数的声明与此相似,这个类库关于生命周期函数的处理就不如表面看的如此简单,它在实现原有Activity功能的时候,同时提供了用ActivityOverride的回调函数。细细地考虑一下,这点给了我们一个拓展的接口。另外如果什么东西都用dexmark写,那就太烦了。
我们的分析就到这里。这个类库目前也就支持Activity、receiver(最新的实验分支支持)而已,对于Service还是不支持的。
来源: http://click.aliyun.com/m/35311/