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

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

Freedom框架,我个人手写的0反射插件化框架

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

1.首先我们需要从github上获取项目代码:
项目地址: https://github.com/singwhatiwanna/dynamic-load-apk
解压后,项目目录如下
这里写图片描述
lib目录就是DL的插件库,sample目录是对应的demo

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

compile fileTree(dir: 'libs', include: ['*.jar'])

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

provided files('external-jars/dl-lib.jar')

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

provided files('external-jars/android-support-v4.jar')

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

继承DLBasePluginActivity

1.	public class MainActivity extends DLBasePluginActivity

继承DLBasePluginFragmentActivity

1.	TestFragmentActivity extends DLBasePluginFragmentActivity

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

DLIntent intent = new DLIntent(getPackageName(), ListActivity.class);
intent.putExtra(TYPE, item.getNavigationInfo());
startPluginActivity(intent);

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

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
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<dexPath,pluginPackage>的方式保存PluginPackage实例。

DL插件开发注意事项

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

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>

必须这样:

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>

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

1.	android:theme="@style/AppTheme"

只能这样

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与我们服务器预置的数值不同,就认为插件被改动过,弃用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的代码家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值