美文网首页
VirtualAPK

VirtualAPK

作者: 小鱼你好 | 来源:发表于2022-04-17 10:02 被阅读0次

    https://github.com/didi/VirtualAPK

    功能完备

    • 支持几乎所有的Android特性;
    • 四大组件方面

    四大组件均不需要在宿主manifest中预注册,每个组件都有完整的生命周期。

    1. Activity:支持显示和隐式调用,支持Activity的themeLaunchMode,支持透明主题;
    2. Service:支持显示和隐式调用,支持Service的startstopbindunbind,并支持跨进程bind插件中的Service;
    3. Receiver:支持静态注册和动态注册的Receiver;
    4. ContentProvider:支持provider的所有操作,包括CRUDcall方法等,支持跨进程访问插件中的Provider。
    • 自定义View:支持自定义View,支持自定义属性和style,支持动画;
    • PendingIntent:支持PendingIntent以及和其相关的AlarmNotificationAppWidget
    • 支持插件Application以及插件manifest中的meta-data
    • 支持插件中的so

    优秀的兼容性

    • 兼容市面上几乎所有的Android手机,这一点已经在滴滴出行客户端中得到验证;
    • 资源方面适配小米、Vivo、Nubia等,对未知机型采用自适应适配方案;
    • 极少的Binder Hook,目前仅仅hook了两个Binder:AMSIContentProvider,hook过程做了充分的兼容性适配;
    • 插件运行逻辑和宿主隔离,确保框架的任何问题都不会影响宿主的正常运行。

    入侵性极低

    • 插件开发等同于原生开发,四大组件无需继承特定的基类;
    • 精简的插件包,插件可以依赖宿主中的代码和资源,也可以不依赖;
    • 插件的构建过程简单,通过Gradle插件来完成插件的构建,整个过程对开发者透明。

    VirtualAPK和主流开源框架的对比

    如下是VirtualAPK和主流的插件化框架之间的对比。

    特性 DynamicLoadApk DynamicAPK Small DroidPlugin VirtualAPK
    支持四大组件 只支持Activity 只支持Activity 只支持Activity 全支持 全支持
    组件无需在宿主manifest中预注册 ×
    插件可以依赖宿主 ×
    支持PendingIntent × × ×
    Android特性支持 大部分 大部分 大部分 几乎全部 几乎全部
    兼容性适配 一般 一般 中等
    插件构建 部署aapt Gradle插件 Gradle插件

    通俗易懂地说

    1. 如果你是要加载微信、支付宝等第三方APP,那么推荐选择DroidPlugin;
    2. 如果你是要加载一个内部业务模块,并且这个业务模块很难从主工程中解耦,那么VirtualAPK是最好的选择。

    抽象地说

    1. 如果你要加载一个插件,并且这个插件无需和宿主有任何耦合,也无需和宿主进行通信,并且你也不想对这个插件重新打包,那么推荐选择DroidPlugin;
    2. 除此之外,在同类的开源中,推荐大家选择VirtualAPK。

    VirtualAPK的工作过程

    VirtualAPK对插件没有额外的约束,原生的apk即可作为插件。插件工程编译生成apk后,即可通过宿主App加载,每个插件apk被加载后,都会在宿主中创建一个单独的LoadedPlugin对象。如下图所示,通过这些LoadedPlugin对象,VirtualAPK就可以管理插件并赋予插件新的意义,使其可以像手机中安装过的App一样运行。


    va1.png

    如何使用
    第一步: 初始化插件引擎

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        PluginManager.getInstance(base).init();
    }
    

    第二步:加载插件

    public class PluginManager {
        public void loadPlugin(File apk);
    }
    

    当插件入口被调用后,插件的后续逻辑均不需要宿主干预,均走原生的Android流程。 比如,在插件内部,如下代码将正确执行:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        LinearLayout holder = (LinearLayout)findViewById(R.id.holder);
        TextView imei = (TextView)findViewById(R.id.imei);
        imei.setText(IDUtil.getUUID(this));
         
        // bind service in plugin
        Intent service = new Intent(this, BookManagerService.class);
        bindService(service, mConnection, Context.BIND_AUTO_CREATE);
        
        // start activity in plugin
        Intent intent = new Intent(this, TCPClientActivity.class);
        startActivity(intent);
    }
    

    探究原理

    基本原理

    • 合并宿主和插件的ClassLoader 需要注意的是,插件中的类不可以和宿主重复
    • 合并插件和宿主的资源 重设插件资源的packageId,将插件资源和宿主资源合并
    • 去除插件包对宿主的引用 构建时通过Gradle插件去除插件对宿主的代码以及资源的引用

    四大组件的实现原理

    • Activity 采用宿主manifest中占坑的方式来绕过系统校验,然后再加载真正的activity;
    • Service 动态代理AMS,拦截service相关的请求,将其中转给Service Runtime去处理,Service Runtime会接管系统的所有操作;
    • Receiver 将插件中静态注册的receiver重新注册一遍;
    • ContentProvider 动态代理IContentProvider,拦截provider相关的请求,将其中转给Provider Runtime去处理,Provider Runtime会接管系统的所有操作。
      如下是VirtualAPK的整体架构图:
      va.png

    使用注意事项

    运行时获取资源需要通过packageId来映射apk中的资源文件,不同apk的packageId值不能相同,所以插件的packageId范围是介于系统应用(0x01,0x02,...具体占用多少值视系统而定)和宿主(0x7F)之间。
    宿主和插件同时依赖公共的本地jar文件或library module不支持自动去除,需要部署到maven或其它依赖管理服务器

    插件安装使用so的abi必须与宿主保持一致,而宿主的abi类型在app安装时根据apk配置确定,并不再改变。请按以下步骤检查项目配置:

    1. 宿主apk中未使用so,则按设备默认abi类型安装。如果插件中使用so并报此错误,则需要在宿主中放一个占位的同abi类型so。
    2. 插件中使用的liba.so中直接引用了libb.so导出的符号,需要在加载a.so前显式加载b.so,否则可能会报找不到b.so的错误。详见Android中的System.loadLibrary对于依赖so的加载分析,如:
    System.loadLibrary("b");
    System.loadLibrary("a");
    

    从Android 6.0开始,系统采用了新的权限机制,但是暂时不支持在插件中动态申请权限。
    构建插件请使用gradle assemblePlugin,而不能直接通过AndroidStudio run出来一个插件apk。
    资源id不能和宿主的资源重名,重名资源会在构建插件包时被自动剔除,导致插件内加载的是宿主资源而非自身资源

    参数说明

    virtualApk {
    
        // 插件资源表中的packageId,需要确保不同插件有不同的packageId.
        packageId = 0x6f
    
        // 宿主工程application模块的路径,插件的构建需要依赖这个路径
        targetHost = '../../VirtualAPK/app' 
    
        //默认为true,如果插件有引用宿主的类,那么这个选项可以使得插件和宿主保持混淆一致
        applyHostMapping = true 
    
    }
    

    如何开发插件?

    在VirtualAPK中,插件开发等同于原生Android开发,因此开发插件就和开发APP一样。

    插件如何和宿主交互?

    通过compile相同aar的方式来交互。 比如,宿主工程中compile了如下aar:

    compile 'com.didi.foundation:sdk:1.2.0'
    compile 'com.didi.virtualapk:core:[newest version]'
    compile 'com.android.support:appcompat-v7:22.2.0'
    
    

    但是插件工程需要访问宿主sdk中的类和资源,那么可以在插件工程中同样compile sdk的aar,如下:

    compile 'com.didi.foundation:sdk:1.2.0'
    
    

    这样一来,插件工程就可以正常地引用sdk了。并且,插件构建的时候会自动将这个aar从apk中剔除。

    上述就是VirtualAPK中插件和宿主通信的基本方式。

    然而,VirtualAPK仍然有一些小小的约束,如下注意事项,请务必仔细阅读。

    目前暂不支持的特性

    1. 暂不支持Activity的一些不常用特性(比如process、configChanges等属性),但是支持theme、launchMode和screenOrientation属性;
    2. overridePendingTransition(int enterAnim, int exitAnim)这种形式的转场动画,动画资源不能使用插件的(可以使用宿主或系统的);
    3. 插件中弹通知,需要统一处理,走宿主的逻辑,通知中的资源文件不能使用插件的(可以使用宿主或系统的)。
    4. 插件的Activity中不支持动态申请权限。

    插件中四大组件的已知约束

    Activity,支持LaunchMode和theme

    • 透明Activity,不能有启动模式,并且主题中必须含有android:windowIsTranslucent属性;
    <style name="AppTheme.Transparent">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>
    
    
    • 插件中调用宿主的四大组件,请注意Intent中的包名。

    VirtualAPK对Intent的处理遵循Android规范,插件之间乃至插件和宿主之间,包名是区分它们的唯一标识。

    为了兼容宿主与插件之间的activity互调的场景,我们弱化了插件的包名,在插件中通过context.getPackageName()取到的仍然是宿主的包名。因此在下面的例子中,假如宿主的包名是"com.didi.virtualapk",然后在插件中启动一个宿主Activity,仍然可正确的调用:

    // 兼容方式
    Intent intent = new Intent(this, HostActivity.class);
    startActivity(intent);
    
    // 显式指定包名的方式
    Intent intent = new Intent();
    intent.setClassName("com.didi.virtualapk", "com.didi.virtualapk.HostActivity");
    startActivity(intent);
    
    

    如果想在插件中去访问插件的四大组件,那么就没有任何要求了,下面的代码会在插件Activity中尝试启动另一个插件Activity:

    // 正确的用法,因为此时intent中的包名是插件的包名
    Intent intent = new Intent(this, PluginActivity.class);
    startActivity(intent);
    
    

    Service,支持跨进程bind service

    无约束

    BroadcastReceiver

    • 静态Receiver将被动态注册,当宿主停止运行时,外部广播将无法唤醒宿主;
    • 由于动态注册的缘故,插件中的Receiver必须通过隐式调用来唤起。

    ContentProvider,支持跨进程访问ContentProvider

    1)分情况,插件调用自己的ContentProvider,如果需要用到call方法,那么需要将provider的uri放到bundle中,否则调用不生效;

    Uri bookUri = Uri.parse("content://com.didi.virtualapk.demo.book.provider/book");
    Bundle bundle = PluginContentResolver.getBundleForCall(bookUri);
    getContentResolver().call(bookUri, "testCall", null, bundle);
    
    

    2)插件调用宿主和外部的ContentProvider,无约束;

    3)宿主调用插件的ContentProvider,需要将provider的uri包装一下,通过PluginContentResolver.wrapperUri方法,如果涉及到call方法,参考1)中所描述的;

    String pkg = "com.didi.virtualapk.demo";
    LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(pkg);
    Uri bookUri = Uri.parse("content://com.didi.virtualapk.demo.book.provider/book");
    bookUri = PluginContentResolver.wrapperUri(plugin, bookUri);
    Cursor bookCursor = getContentResolver().query(bookUri,
     new String[]{"_id", "name"}, null, null, null);
    
    

    Fragment

    推荐大家在Application启动的时候去加载插件,不然的话,请注意插件的加载时机。 考虑一种情况,如果在一个较晚的时机去加载插件并且去访问插件中的资源,请注意当前的Context。比如在宿主Activity(MainActivity)中去加载插件,接着在MainActivity去访问插件中的资源(比如Fragment),需要做一下显示的hook,否则部分4.x的手机会出现资源找不到的情况。

    String pkg = "com.didi.virtualapk.demo";
    PluginUtil.hookActivityResources(MainActivity.this, pkg);
    
    

    so文件的加载

    为了提升性能,VirtualAPK在加载一个插件时并不会主动去释放插件中的so,除非你在插件apk的manifest中显式地指定VA_IS_HAVE_LIB为true,如下所示:

    <application
        android:name=".VAApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/HostTheme">
    
        <meta-data
            android:name="VA_IS_HAVE_LIB"
            android:value="true" />
    
        ...
    
    </application>
    
    

    为了通用性,在armeabi路径下放置对应的so文件即可满足需求。如果考虑性能请做好各种so文件的适配。

    相关文章

      网友评论

          本文标题:VirtualAPK

          本文链接:https://www.haomeiwen.com/subject/kdmhertx.html