diff --git a/README.md b/README.md index 93b18ca51c3e831c64d8bce4a1771910d865cdf9..1f7f7cb7a25e52510460e9be1ae2c35ab16ab512 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,156 @@ -# virtualApk 多模块插件化实践 +# VirtualAPK的特性 +VirtualAPK是滴滴出行自研的一款优秀的插件化框架,主要有如下几个特性。 +##1.功能完备 +**·**支持几乎所有的Android特性; +**·**四大组件方面 +####四大组件均不需要在宿主manifest中预注册,每个组件都有完整的生命周期。 +1.Activity:支持显示和隐式调用,支持Activity的theme和LaunchMode,支持透明主题; +2.Service:支持显示和隐式调用,支持Service的start、stop、bind和unbind,并支持跨进程bind插件中的Service; +3.Receiver:支持静态注册和动态注册的Receiver; +4.ContentProvider:支持provider的所有操作,包括CRUD和call方法等,支持跨进程访问插件中的Provider。 +5.自定义View:支持自定义View,支持自定义属性和style,支持动画; +6.PendingIntent:支持PendingIntent以及和其相关的Alarm、Notification和AppWidget; +7.支持插件Application以及插件manifest中的meta-data; +8.支持插件中的so。 +####优秀的兼容性 + 1.兼容市面上几乎所有的Android手机,这一点已经在滴滴出行客户端中得到验证; + 2.资源方面适配小米、Vivo、Nubia等,对未知机型采用自适应适配方案; + 3.极少的Binder Hook,目前仅仅hook了两个Binder:AMS和IContentProvider,hook过程做了充分的兼容性适配; + 4.插件运行逻辑和宿主隔离,确保框架的任何问题都不会影响宿主的正常运行。 +####入侵性极低 +1.插件开发等同于原生开发,四大组件无需继承特定的基类; +2.精简的插件包,插件可以依赖宿主中的代码和资源,也可以不依赖; +3.插件的构建过程简单,通过Gradle插件来完成插件的构建,整个过程对开发者透明。 +``` +以上内容均引用(https://github.com/didi/VirtualAPK/wiki) +``` + # 集成 +####先列一下项目的结构。![项目结构.png](https://upload-images.jianshu.io/upload_images/10881059-79344da540e0be92.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +####开始集成 +1.项目级的build.gradle文件中 +``` + classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.didi.virtualapk:gradle:0.9.8.6' + 这是第一个坑,VirtualAPK目前只能在gradle3.0一下的版本使用 +``` +2.宿主app的build.gradle +``` +apply plugin: 'com.didi.virtualapk.host' +implementation 'com.didi.virtualapk:core:0.9.8' +``` +3.插件plugin_one和plugin_two的build.gradle中 +``` +apply plugin: 'com.didi.virtualapk.plugin' +virtualApk { + // 插件资源表中的packageId,需要确保不同插件有不同的packageId这个值的范围在系统和宿主的之间即大于0x02,小于0x7f + packageId = 0x5f + // 宿主App模块的路径,可以填写绝对路径 + targetHost = '../PluginDemo/app' + //默认为true,如果插件有引用宿主的类,那么这个选项可以使得插件和宿主保持混淆一致 + applyHostMapping = true +``` +4.在自定义的Application中 +``` + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + PluginManager.getInstance(base).init(); + } +``` +5.在需要加载插件的Activity中 +``` + //加载插件需要文件读写权限,这里我图方便没有写,但是是必须要的 + //lugin_one.apk 和 plugin_two.apk 是打好插件后手动push到sd卡根目录的,实际开发中,应该根据实际情况作处理 + private fun loadPlugin(){ + if (Environment.MEDIA_MOUNTED != Environment.getExternalStorageState()) { + Toast.makeText(this, "sdcard was NOT MOUNTED!", Toast.LENGTH_SHORT).show() + } + val pluginManager = PluginManager.getInstance(this) + val pluginOne = File(Environment.getExternalStorageDirectory(), "plugin_one.apk") + val pluginTwo = File(Environment.getExternalStorageDirectory(), "plugin_two.apk") + try { + if (pluginOne.exists()){ + pluginManager.loadPlugin(pluginOne) + Log.e("MainActivity--->","load succss $pluginOne") + } + if (pluginTwo.exists()){ + pluginManager.loadPlugin(pluginTwo) + Log.e("MainActivity--->","load succss $pluginTwo") + } + }catch (e:Exception){ + Log.e("MainActivity--->",e.toString()) + } + } + //跳转插件Activity(BundleUrl位于common模块,用于管理所有插件Activity的地址, + //插件和宿主都依赖该模块,我的理解是用该模块作一下常规通信) + findViewById