Android studio接入VirtualApk详细过程,对比Atlas、RePlugin

转载请标明出处:https://blog.csdn.net/u013254166/article/details/80431288
本文出自: rhino博客 

一、宿主工程接入

1.在宿主工程根目录的build.gradle添加依赖,注意:我这里用的gradle时3.0,其他版本可能会有问题。

dependencies {
	classpath 'com.android.tools.build:gradle:3.0.0'
	classpath 'com.didi.virtualapk:gradle:0.9.8.3'
}

2.在App的工程模块的build.gradle添加使用gradle插件

apply plugin: 'com.didi.virtualapk.host'
...
dependencies {
    ...
    implementation 'com.didi.virtualapk:core:0.9.5'
}

3.AppApplication.java类是继承了Application,覆写attachBaseContext函数,进行插件SDK初始化工作

package com.rhino.virtualapkdemo;

import android.app.Application;
import android.content.Context;

import com.didi.virtualapk.PluginManager;


/**
 * @author LuoLin
 * @since Create on 2018/5/23.
 */
public class AppApplication extends Application {

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

4.在使用插件之前加载插件,可以根据具体业务场景选择合适时机加载,demo在MainActivity的onCreate时加载,加载需要权限READ_EXTERNAL_STORAGE,这里直接贴我的MainActivity.java

package com.rhino.virtualapkdemo;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.didi.virtualapk.PluginManager;
import com.didi.virtualapk.internal.LoadedPlugin;

import java.io.File;

/**
 * @author LuoLin
 * @since Create on 2018/5/24.
 */
public class MainActivity extends AppCompatActivity {

    /** 插件文件路径 **/
    private static final String PLUGIN_FILE_PATH = Environment.getExternalStorageDirectory()
            + File.separator + "plugin_demo.apk";
    /** 插件apk包名 **/
    private static final String PLUGIN_PACKAGE_NAME = "com.rhino.virtualapkplugindemo";
    /** 插件apk入口activity **/
    private static final String PLUGIN_LAUNCHER_ACTIVITY = "com.rhino.virtualapkplugindemo.PluginMainActivity";

    /** 请求权限请求码 **/
    public static final int REQUEST_PERMISSION_CODE = 111;
    /** 权限 **/
    public static final String[] PERMISSIONS = new String[]{
            Manifest.permission.READ_EXTERNAL_STORAGE
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (lacksPermissions(PERMISSIONS)) {
            // 开始请求权限
            requestPermissions(PERMISSIONS);
        } else {
            loadPlugin();
        }

        findViewById(R.id.show_second_activity).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setClass(getApplicationContext(), SecondActivity.class);
                startActivity(intent);
            }
        });
        findViewById(R.id.run_plugin).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showPluginActivity();
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_PERMISSION_CODE) {
            loadPlugin();
        }
    }

    /** 判断是否缺少权限 **/
    private boolean lacksPermissions(String... permissions) {
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(getApplicationContext(), permission) == PackageManager.PERMISSION_DENIED) {
                return true;
            }
        }
        return false;
    }

    /** 请求权限 **/
    private void requestPermissions(String... permissions) {
        ActivityCompat.requestPermissions(this, permissions, REQUEST_PERMISSION_CODE);
    }

    /** 加载插件 **/
    private void loadPlugin() {
        PluginManager pluginManager = PluginManager.getInstance(this);
        //此处是当查看插件apk是否存在,如果存在就去加载(比如修改线上的bug,把插件apk下载,达到热修复)
        File pluginApk = new File(PLUGIN_FILE_PATH);
        if (pluginApk.exists()) {
            try {
                pluginManager.loadPlugin(pluginApk);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /** 运行插件中activity **/
    private void showPluginActivity() {
        if (PluginManager.getInstance(this).getLoadedPlugin(PLUGIN_PACKAGE_NAME) == null) {
            Toast.makeText(getApplicationContext(),"插件未加载,请尝试重启APP", Toast.LENGTH_SHORT).show();
            return;
        }
        Intent intent = new Intent();
        intent.setClassName(PLUGIN_PACKAGE_NAME, PLUGIN_LAUNCHER_ACTIVITY);
        startActivity(intent);
    }
}

至此,VirtualAPK插件功能就集成到宿主中了,接下来,我们看下插件工程是如何集成的。

二、插件工程接入

1.在插件工程根目录的build.gradle添加依赖,注意:我这里用的gradle时3.0,其他版本可能会有问题。

dependencies {
	classpath 'com.android.tools.build:gradle:3.0.0'
	classpath 'com.didi.virtualapk:gradle:0.9.8.3'
}

2.在App的工程模块的build.gradle添加使用gradle插件和插件配置信息,信息需要放在文件最下面。

apply plugin: 'com.didi.virtualapk.plugin'

...

// 插件配置信息,放在文件最下面
virtualApk {
    packageId = 0x6f // 插件资源id,避免资源id冲突
    targetHost='../../GitHub/VirtualApkDemo/app' // 宿主工程的路径
    applyHostMapping = true // 插件编译时是否启用应用宿主的apply mapping
}

       解释一下上面3个参数的作用

  • packageId用于定义每个插件的资源id,多个插件间的资源Id前缀要不同,避免资源合并时产生冲突
  • targetHost指明宿主工程的应用模块,插件编译时需要获取宿主的一些信息,比如mapping文件、依赖的SDK版本信息、R资源文件,一定不能填错,否则在编译插件时会提示找不到宿主工程。
  • applyHostMapping表示插件是否开启apply mapping功能。当宿主开启混淆时,一般情况下插件就要开启applyHostMapping功能。因为宿主混淆后函数名可能有fun()变为a(),插件使用宿主混淆后的mapping映射来编译插件包,这样插件调用fun()时实际调用的是a(),才能找到正确的函数调用。

3.生成插件,需要使用Gradle命令

gradle clean assemblePlugin 或者 gradlew clean assemblePlugin 

       执行成功后,会生成如下apk文件,即插件apk。

       

       注意:这里的apk不能通过其他方式生成,如:run

三、运行插件

1.将插件apk重命名为plugin_demo.apk(与宿主MainActiivty中加载apk名称保持一致),然后拷贝到sdcard根目录。

2.运行宿主apk,点击“进入插件中activity”,会发现成功启动了插件的activity。

四、测试实验

实验内容
现象
结论
宿主app加载插件apk文件,并启动插件中的Activity,以及activity是否需要在宿主manifest预注册?
插件apk不需要安装,宿主app可以直接在代码中加载指定的插件apk文件,可以成功启动插件中activity,该activity没有在宿主中预注册。
插件不需要安装,动态加载后可以启动插件activity,插件activity不需要在宿主中预注册。(这里只测试了activity,四大组件也支持)
修改插件ui界面,替换原来的apk文件,重启宿主app,观察修改能否及时生效?
重启宿主app后,显示了修改后的界面。
支持插件apk文件动态替换。
宿主和插件中创建相同activity,观察启动的时候会加载哪一个activity?
启动的是宿主中的activity。
插件中的class不能与宿主中class相同,加载的是宿主中class,不能做到替换。
宿主和插件中创建相同layout,观察宿主和插件分别会加载哪一个layout?
宿主和插件都加载宿主中layout。
Layout文件名称不能和宿主中重名,加载的是宿主中layout,不能做到替换,对于其他资源文件也是一样。

五、与其他插件化

Atlas:https://alibaba.github.io/atlas/

VirtualAPK:https://github.com/didi/VirtualAPK/wiki

RePlugin:https://github.com/Qihoo360/RePlugin/wiki

框架 Atlas RePlugin VirtualAPK
定义 Atlas是伴随着手机淘宝的不断发展而衍生出来的一个运行于Android系统上的一个容器化框架,我们也叫动态组件化(Dynamic Bundle)框架。 RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案。 VirtualAPK是滴滴出行自研的一款优秀的插件化框架。
功能

1.远程bundle

2.插件以so形式,放在sdcard,加载后会自动删除。

3.热修复,可以不升级apk就实现宿主和组件的更新。

1.远程bundle

2.插件以apk形式放在sdcard,加载后还在会自动备份apk到缓存目录。

1.远程bundle

2.插件以apk形式放在sdcard,加载后还在,删除会打不开。
更新插件方式 必须要和宿主一起,打差异补丁才能更新。 直接通过下载一个新的插件apk,然后调安装方法就能实现插件的更新。 直接通过下载一个新的插件apk,然后调安装方法就能实现插件的更新。
插件独立性 和宿主的依赖还是挺多,毕竟官方也强调是组件化,不是插件化。 插件不用声明和宿主的联系,所以你生成一个插件后,这个插件可以给其他宿主调用。 插件是一个独立的apk,但插件里面也定义和宿主的关联,就是说这个插件apk并不能给其他宿主用,只能给插件里面声明的那个宿主使用。

六、结论

       VirtualAPK不支持对宿主进行热修复。如果app需要热更新和插件的功能,推荐使用Atlas;如果插件无法很好地从宿主中拆出来,或者插件的运行和宿主有较多的交互,推荐使用VirtualAPK;如果同一个插件希望其他宿主也能用的话,那就只能RePlugin了,RePlugin就像一个应用市场,宿主仅仅是一个壳,然后把需要的插件下载加载使用就行,更新的话也无需更新宿主,直接更新插件就行。


最后附上我测试的demo源码下载链接,点击下载。

猜你喜欢

转载自blog.csdn.net/u013254166/article/details/80431288