在开发Replugin插件时候遇到了很多的坑,当初都整理成了文档,现在年底正好有空闲时间,我都整理出来,看看小伙伴们都有遇到的没有:
一、接入插件存在问题
1、第一次启动插件过慢。
2、如果安装没有启动,就直接卸载,有时候会报 ANR(偶尔)
3、安装外部插件:必须为apk的绝对路径。
4、插件中使用SO库,需要在主程序中添加,对应的依赖库。
5、android7.0共享权限等,需要在主程序的Manifest中添加。
6、插件中用到的权限,需要在主程序Manifest中注册。
7、插件中主程的公共类需要以jar的形式在各插件中使用。
8、在插件中使用第三方SDK,会存在一些坑,(第三方的sdk与使用插件会存在冲突,需要在开发中注意)
9、当我们插件数量达到20多个时,会发现随着插件的增多,“资源分区ID”会越来越难管控,这对我们来说,也是一种挑战。
10、 插件中使用隐式Intent进行跳转。
这是坑位导致的。只能接受隐式intent,不接受显示intent
意图只能接受隐式调用。
二、360wiki中存在的问题
1.我们支持插件间,插件和宿主间的资源交互,但不支持(也不打算支持,2013年踩了无数坑,后述)“直接使用资源”的方式。即便支持这一特性会减少改动量。
这里所说的“直接使用资源”是指:插件A通过。“R.drawable.common_xxx”来使用插件B或主程序的资源。
2.插件中使用高德地图的经验分享?
关于高德地图的key值,请用宿主的包名创建一个key值在插件的AndroidManifest.xml中,否则会显示key值不正确。
解释:高德地图的key值,高德地图是用宿主的包名校验的,插件的包名没有校验。
3.请一定要确保符合Gradle开发规范,也即“必须将包名写在applicatonId”,而非AndroidManifest.xml中(通常从Eclipse迁移过来的项目可能出现此问题)。如果不这么写,则有可能导致运行时出现“Failed to find provider info for com.ss.android.auto.loader.p.main”的问题。具体可参见 #87 Issue的问答。
4.请将apply plugin: 'replugin-host-gradle'放在 android{} 块之后,防止出现无法读取applicationId,导致生成的坑位出现异常。
5.如果您的应用需要支持AppComat,则除了在主程序中引入AppComat-v7包以外,还需要在主程序的build.gradle中添加下面的代码若不支持AppComat则请不要设置此项:
repluginHostConfig {
useAppCompat = true
}
6.无法启动插件activity:log提示java.lang.NoClassDefFoundError: library.d 或 java.lang.NoClassDefFoundError: com.qihoo360.replugin.Entry$1
原因:插件过大,导致使用了mutidex的处理,而RePlugin的包恰好被分在class2.dex,然后就抛出找不到replugin相关类的异常,导致插件加载失败
解决方案:1.插件的代码尽量小,尽量保证在一个dex中;2.可以在gradle中,指定replugin插件库的类分配在主dex中。
7.还有一种可能,应该是插件被“分包”导致的问题,打包后虽然有主classes有找到com.qihoo360.replugin.Entry但还是报错,自己手动添加maindexlist.txt在里面添加com.qihoo360.replugin打包进去就好了,具体操作请参考http://blog.csdn.net/gaozhan_csdn/article/details/52024497
三、replugin插件不足之处
1、replugin插件中的api不全
2、不同的版本可能不兼容
3、v7包
插件布局加载后莫名其妙的多了一些外边距,无法全屏独立运行是正常
答:
插件不能共享资源宿主的v7包,注意如果你违反这条去尝试开发插件。会导致你的插件apk布局莫名其妙的多出一些边距。
注意:
即便插件中你没有依赖v7包,也是可以使用AppCompatActivity的。但是包里不会包含v7包的资源。
是因为SdkVersion版本过高所以可以直接使用。解决办法:
在插件中单独依赖一份v7包,而不是共享宿主的。
4、插件横屏
插件目前manifest配置了横屏是无效的。这也许是坑位导致的,可以动态在onCreate的时候改变一下
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
·····
}
5、热更新插件
wiki文档里已经讲过了,对于没有启动(加载过)的插件是可以安装后就运行的。但是若插件被启动过或正在运行时则不行。针对特殊的应用场景不得不重启app,而且还是主动重启。
那么发现如果开启了persistentEnable = true常驻应用内存。(就是一个服务)会导致即便重启了app,插件安装后还是没有被更新。这里迫使我关掉了persistentEnable选项,随后重启后就成功更新了。注意我这里说的重启是非root下,应用自己杀死自己,而非用户主动kill掉。
6、幽灵魔术
什么叫幽灵魔术问题?就和变魔术一样,我好像没有改什么逻辑性的代码,突然bug又好了。
多半是你动了rePlugin中的gradle里的配置项,可能留下了上一次的缓存在里面,你要做的就是每当改动了gradle里的配置最好搭配Clean Project一下。
四、RePlugin目前不支持以下功能
1、Application和主程序相关
·插件权限声明无效,只认主程序的
o建议:将真正需要用到的权限提前在“主程序”中声明好。
o原因:权限部分是系统在安装主程序时,会读取其AndroidManifest.xml中的权限部分,并放入系统的package.xml中,除了系统自己的核心应用(和Root)以外,外界根本无法修改。目前已知的插件化、热更新方案均不支持“动态权限的增删改”。
·插件声明Target SDK无效,只认主程序的
o建议:根据需要来调整主程序的Target SDK,同时插件和主程序最好同时调整。这样即便插件变成单品,也能做到“天然的适配”。
o原因同上,不赘述
·不支持“双开应用”功能。也即:“不经修改,直接将任意APK放入RePlugin进行‘免安装运行’”
o建议:RePlugin的核心诉求是在“稳定(1 Hook,0 Binder Hook)的前提下”,尽可能保持灵活,且以最小的成本,产生更多的插件,而非“免修改APK直接运行”。
o如确实需要开发双开、免安装应用,或公司接入的插件均“只提供APK(不含混淆后的AAR)”的,可使用我们360的另一套成熟的框架:DroidPlugin,或 @Lody 罗迪的 VirtualApp。
o原理:要想实现这种效果,必须先Hook AMS、PackageManager,甚至还要做很多Native Hook的工作。Hook点会很多,也会有不少的适配工作。但只要Hook齐了,就能让一个应用“直接运行起来”,但代价是“适配量太大”,对未来新增的机型充满了“未知性”。
·不支持Notification的Layout资源
o建议:可在主程序中预埋一些Layout模板,插件可以套用此模板。另外,“图片资源”以及文案等,是可以通过Drawable和String来透传的,是完美支持的。
o原因:RemoteViews的性质决定的。
2、交互
·不支持“宿主和主程序无缝使用资源”(共享资源)方案
o建议:采用更稳定的一些API来“迂回的”获取资源。顺便说一句,RePlugin支持“共享View”方案(例如公共UI库的使用等),这样“无缝获取资源”的场景,在其它方面也能得到满足。
o原因:这是RePlugin的性质所决定的。也即——尽可能的稳定,不要有适配代码。因篇幅所限,请点击此处阅读《FAQ》了解这块儿的更多内容。
·不支持“主程序退出后”的静态广播
o建议:请尽量避免过分依赖“主程序停止后”的“静态广播”(Android 8.0以上更是如此,因为原生就禁用了)。此外,只要常驻进程存在,则静态广播将一直生效。
o原因:毕竟是“模拟的”静态广播,本质上仍是“动态注册广播”。这和系统直接唤起应广播所在进程,然后发送的“静态广播”是不一样的。
3、Activity
·overridePendingTransition,仅支持使用宿主和系统的
o建议:将动画资源“预埋在主程序里”,且确保其ID为固定(利用public.xml),这样可以通过拿主程序的动画ID来传递给系统,实现相应效果。目前手机卫士等产品都是这样做的,效果不错。
o原因:此方法仅会传给AMS两个int值,且由AMS层进行资源解析并实现动画效果,根本到不了客户端。目前市面上所有插件化、热更新方案均无法实现此功能。
五、rePlugin插件使用注意事项:
1.权限和SO库, 必须在主宿,添加一份,程序才能正常运行。
2.7.0系统,拍照共享路径必须在androidManFist.xml中添加。
3.注意mavenLocal()必须放在jcenter()之前。
4.判断插件是否安装: 包名。
5.卸载插件,默认使用包名进行卸载,也可使用别名进行卸载。
6.定义的别名必须唯一,要不安装不上(两个相同的别名只能安装1个)。