前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android插件化学习之路(七)之DL插件开发该注意的坑

Android插件化学习之路(七)之DL插件开发该注意的坑

作者头像
老马的编程之旅
发布2022-06-22 10:18:03
4850
发布2022-06-22 10:18:03
举报
文章被收录于专栏:深入理解Android

随着前面几篇博客的学习,相信大家对插件化已经有了比较清楚的认识,然而如何将插件化应用到项目中?网上已经有一些优秀的开源框架,这里要向大家推荐一个开源的动态加载框架DL, 该项目由任玉刚大神发起的,项目地址: https://github.com/singwhatiwanna/dynamic-load-apk,该项目结构图如下:

本篇博客,我主要向大家介绍一下利用DL框架进行开发的具体步骤,还有一些注意事项:

利用DL框架进行开发的步骤

1.首先我们需要从github上获取项目代码: 项目地址: https://github.com/singwhatiwanna/dynamic-load-apk 解压后,项目目录如下

lib目录就是DL的插件库,sample目录是对应的demo

2.引入DL库 宿主工程中只要将dl-lib.jar加入libs即可,然后在gradle中引用

代码语言:javascript
复制
compile fileTree(dir: 'libs', include: ['*.jar'])

但是插件中则不同,因为DL插件需要用到DL库的类(),所以需要引入DL库,但是插件是最终要加载到宿主程序中的,宿主程序中也是引入了DL库的,如果常规办法导入DL库,则会有两份DL的拷贝,为了解决这个问题,我们让插件中的DL只是编译的时候用,但是不打包进apk。如何让它参与编译却不被打包进apk呢?在Android-studio中 只需要在插件工程中创建一个目录,比如external-jars,然后把dl-lib.jar和放进去,同时在gradle中追加如下代码即可:

代码语言:javascript
复制
provided files('external-jars/dl-lib.jar')

同样的如果宿主程序中用了support-v4.jar,那么插件中原有的support-v4.jar也不能被打包进去,也需要将support-v4.jar放到external-jars同时追加

代码语言:javascript
复制
provided files('external-jars/android-support-v4.jar')

3.插件的java代码修改 插件中的所有Activity 必须是继承自DLBasePluginActivity或者是DLBasePluginFragmentActivity。如果原有的为Activity,这里需要改为继承DLBasePluginActivity,如果原来为FragmentActivity,那么需要继承DLBasePluginFragmentActivity。

继承DLBasePluginActivity

代码语言:javascript
复制
1.  public class MainActivity extends DLBasePluginActivity

继承DLBasePluginFragmentActivity

代码语言:javascript
复制
1.  TestFragmentActivity extends DLBasePluginFragmentActivity

另外原有activity中所有代表context引用的this都必须改写为that 如果要调用另外一个activity,不能使用startActivity(),而是使用startPluginActivity,并且intent也要变为DLIntent:

代码语言:javascript
复制
DLIntent intent = new DLIntent(getPackageName(), ListActivity.class);
intent.putExtra(TYPE, item.getNavigationInfo());
startPluginActivity(intent);

4.调用的插件apk 在宿主工程中,首先我们需要获取要调用的插件apk对应的MainActivity,DL的demo中插件路径为 sd卡上的DynamicLoadHost目录,没有的话需要创建,或者根据自己需求进行修改.

代码语言:javascript
复制
1.  String pluginFolder = Environment.getExternalStorageDirectory() + "/DynamicLoadHost";  
2.  File file = new File(pluginFolder);  
3.  File[] plugins = file.listFiles();  
4.  if (plugins == null || plugins.length == 0) {  
5.      mNoPluginTextView.setVisibility(View.VISIBLE);  
6.      return;  
7.  }  
8.    
9.  for (File plugin : plugins) {  
10.     PluginItem item = new PluginItem();  
11.     item.pluginPath = plugin.getAbsolutePath();  
12.     item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);  
13.     if (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {  
14.         item.launcherActivityName = item.packageInfo.activities[0].name;  
15.     }  
16.     mPluginItems.add(item);  
17. }  

接着是调起响应的apk,这时需要使用dl-lib.jar: 1) 通过Class.forName的方式获取我们需要调用的插件apk中MainActivity的class对象 2) 就上面提到的,我们需要判断该对象继承自DLBasePluginActivity还是DLBasePluginFragmentActivity,得到对应的代理class对象 3) 使用对应的代理class对象调起插件apk

代码语言:javascript
复制
1.  PluginItem item = mPluginItems.get(position);  
2.         Class<?> proxyCls = null;  
3.    
4.         try {  
5.             Class<?> cls = Class.forName(item.launcherActivityName, false,  
6.                     DLClassLoader.getClassLoader(item.pluginPath, getApplicationContext(), getClassLoader()));  
7.             if (cls.asSubclass(DLBasePluginActivity.class) != null) {  
8.                 proxyCls = DLProxyActivity.class;  
9.             }  
10.        } catch (ClassNotFoundException e) {  
11.            e.printStackTrace();  
12.            Toast.makeText(this,  
13.                    "load plugin apk failed, load class " + item.launcherActivityName + " failed.",  
14.                    Toast.LENGTH_SHORT).show();  
15.        } catch (ClassCastException e) {  
16.            // ignored  
17.        } finally {  
18.            if (proxyCls == null) {  
19.                proxyCls = DLProxyFragmentActivity.class;  
20.            }  
21.            Intent intent = new Intent(this, proxyCls);  
22.            intent.putExtra(DLConstants.EXTRA_DEX_PATH,  
23.                    mPluginItems.get(position).pluginPath);  
24.            startActivity(intent);  
25.        }  

DL框架优秀之处

dynamic-load-apk向我们展示了许多优秀的处理方法,比如: 1. 把Activity关键的生命周期方法抽象成DLPlugin接口,ProxyActivity通过DLPlugin代理调用插件Activity的生命周期; 2. 设计一个基础的BasePluginActivity类,插件项目里使用这些基类进行开发,可以以接近常规Android开发的方式开发插件项目; 3. 以类似的方式处理Service的问题; 4. 处理了大量常见的兼容性问题(比如使用Theme资源时出现的问题); 5. 处理了插件项目里的so库的加载问题; 6. 使用PluginPackage管理插件APK,从而可以方便地管理多个插件项目

处理插件项目里的so库的加载 这里需要把插件APK里面的SO库文件解压释放出来,在根据当前设备CPU的型号选择对应的SO库,并使用System.load方法加载到当前内存中来 多插件APK的管理 动态加载一个插件APK需要三个对应的DexClassLoader、AssetManager、Resources实例,可以用组合的方式创建一个PluginPackage类存放这三个变量,再创建一个管理类PluginManager,用成员变量HashMap

DL插件开发注意事项

1.主题 dl的插件必须每个activity都单独设置主题(插件的作者说的是也可以在application上设置主题),但我实际测试,即使application设置了主题也必须每个activity都单独设置主题。 也就是说这样是不行的:

代码语言:javascript
复制
1.  <application
2.      android:allowBackup="true"
3.      android:theme="@android:style/Theme.Holo.Light"
4.      android:icon="@drawable/ic_launcher"
5.      android:label="@string/app_name" >
6.      <activity
7.          android:name=".SampleActivity"

9.          android:label="@string/app_name" >
10.         <intent-filter>
11.             <action android:name="android.intent.action.MAIN" />
12.             <category android:name="android.intent.category.LAUNCHER" />
13.         </intent-filter>
14.     </activity>
15. </application>

必须这样:

代码语言:javascript
复制
1.  <application
2.      android:allowBackup="true"
3.      android:icon="@drawable/ic_launcher"
4.      android:label="@string/app_name" >
5.      <activity
6.          android:name=".SampleActivity"
7.          android:theme="@android:style/Theme.Holo.Light.DarkActionBar" 
8.          android:label="@string/app_name" >
9.          <intent-filter>
10.             <action android:name="android.intent.action.MAIN" />
11.             <category android:name="android.intent.category.LAUNCHER" />
12.         </intent-filter>
13.     </activity>
14. </application>

注意的是 插件只能用系统主题 不能直接定义主题 不能这样

代码语言:javascript
复制
1.  android:theme="@style/AppTheme"

只能这样

代码语言:javascript
复制
1.  android:theme="@android:style/Theme.Light"

虽然在某些插件上可能不按照此规则也可以正确运行 ,但是我试过绝大多数多需要满足此条件。

2.插件所需要权限需要在宿主工程中声明 3. 使用DL进行插件apk的开发规范

1) 慎用this(接口除外):因为this指向的是当前对象,即apk中的activity,但是由于activity已经不是常规意义上的activity,所以this是没有意义的,但是如果this表示的是一个接口而不是context,比如activity实现了而一个接口,那么this继续有效。

2) 使用that:既然this不能用,那就用that,that是apk中activity的基类BaseActivity中的一个成员,它在apk安装运行的时候指向this,而在未安装的时候指向宿主程序中的代理activity,anyway,that is better than this。

3) activity的成员方法调用问题:原则来说,需要通过that来调用成员方法,但是由于大部分常用的api已经被重写,所以仅仅是针对部分api才需要通过that去调用用。同时,apk安装以后仍然可以正常运行。

4) 启动新activity的约束:启动外部activity不受限制,启动apk内部的activity有限制,首先由于apk中的activity没注册,所以不支持隐式调用,其次必须通过BaseActivity中定义的新方法startActivityByProxy和startActivityForResultByProxy,还有就是不支持LaunchMode。

5) 目前暂不支持Service、BroadcastReceiver等需要注册才能使用的组件,但广播可以采用代码动态注册

4.插件APK的管理后台 使用动态加载的目的,就是希望可以绕过APK的安装过程升级应用的功能,如果插件APK是打包在主项目内部的那动态加载纯粹是多次一举。更多的时候我们希望可以在线下载插件APK,并且在插件APK有新版本的时候,主项目要从服务器下载最新的插件替换本地已经存在的旧插件。为此,我们应该有一个管理后台,它大概有以下功能:

  1. 上传不同版本的插件APK,并向APP主项目提供插件APK信息查询功能和下载功能;
  2. 管理在线的插件APK,并能向不同版本号的APP主项目提供最合适的插件APK;
  3. 万一最新的插件APK出现紧急BUG,要提供旧版本回滚功能;
  4. 出于安全考虑应该对APP项目的请求信息做一些安全性校验;

5.插件APK合法性校验 加载外部的可执行代码,一个逃不开的问题就是要确保外部代码的安全性,我们可不希望加载一些来历不明的插件APK,因为这些插件有的时候能访问主项目的关键数据。 最简单可靠的做法就是校验插件APK的MD5值,如果插件APK的MD5与我们服务器预置的数值不同,就认为插件被改动过,弃用。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016-11-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 利用DL框架进行开发的步骤
  • DL框架优秀之处
  • DL插件开发注意事项
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档