插件框架Virtualapk使用要点记录

一、先记录一下使用过程中的要点:

1.宿主APP需要先运行一遍(编译APK或直接运行到手机),才能编译插件

2.宿主和插件的类名,资源名命名不要一样

3.插件apk如果放在SD卡,记得申请权限

4.插件打包必须签名

5.插件清单文件中有icon属性,插件中有application,不影响使用

6.插件的清单文件中可以带权限,宿主申请权限时同时要申请所有插件的权限

7.插件的样式都被宿主的application的样式取代了(有父子关系的样式好像可以生效)

   我的解决方法时,application都是用noactionbar样式,然后需要单独样式的使用toolbar增加标题

8.增加资源文件后需要将宿主和插件都重新打包,不然各种崩溃(不知道有其它解决方法没)

这个packageid 在我们打包的过程中 就是在aapt 执行的时候用到,他有他自己的规范

1.PackageId:是包的Id值,Android中如果是第三方应用的话,这个值默认就是0x7F,系统应用的话就是0x01,具体我们可以后面看aapt源码得知,他占用两个字节

2.TypeId:是资源的类型Id值,一般Android中有这几个类型:attr,drawable,layout,dimen,string,style等,而且这些类型的值是从1开始逐渐递增的,而且顺序不能改变,attr=0x01,drawable=0x02....他占用两个字节。

3.EntryId:是在具体的类型下资源实体的id值,从0开始,依次递增,他占用四个字节。

资源ID(packageId+typeId+ItemValue)

高两个字节是代表PackageId的值,而且第三方app的默认值是0x7F,那么我们能不能修改这个值呢?比如,插件1中的资源Id中的PackageId为0x30,插件2中的资源Id中的PackageId为0x31...这样每个插件的资源就被划分了一定的区域值,同时保证不要和主工程中的0x7F冲突即可,那么这些值就可以从0x02~0x7E了,这个区间值我们都是可以使用的,为什么0x01不能用呢?因为他是系统应用的呀,所以我们就有0x7E-0x02=124个区间,

 

二、配置如下:

1.宿主

宿主工程根目录的build.gradle

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
        classpath 'com.didi.virtualapk:gradle:0.9.8.6'  // 这个是需要加的

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

宿主app的build.gradle如下:

apply plugin: 'com.android.application'
apply plugin: 'com.didi.virtualapk.host' // 这个是主项目中添加的

android {
    compileSdkVersion 27

    defaultConfig {
        applicationId "com.example.lhy.mainproject"
        minSdkVersion 21
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    implementation 'com.didi.virtualapk:core:0.9.8'
}

宿主继承Application的代码

public class BaseApplication extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        PluginManager.getInstance(base).init();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        PluginManager pluginManager = PluginManager.getInstance(this);
        //此处是当查看插件apk是否存在,如果存在就去加载(比如修改线上的bug,把插件apk下载到sdcard的根目录下取名为Demo.apk)
        File apk = new File(Environment.getExternalStorageDirectory(), "plugin1.apk");
        if (apk.exists()) {
            try {
                pluginManager.loadPlugin(apk);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

宿主主页(只是为了测试使用,要根据实际需要修改)

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private Button toPlugin1Btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        toPlugin1Btn = findViewById(R.id.main_btn1);
        toPlugin1Btn.setOnClickListener(this);
        // 权限检查
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},1);
            return;
        }else {
            File apk = new File(Environment.getExternalStorageDirectory(), "plugin1.apk");
            PluginManager pluginManager = PluginManager.getInstance(this);
            if (apk.exists()) {
                try {
                    pluginManager.loadPlugin(apk);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.main_btn1:
                if (PluginManager.getInstance(MainActivity.this).getLoadedPlugin("com.example.lhy.pluginproject1") == null) {
                    Toast.makeText(MainActivity.this, "插件未加载", Toast.LENGTH_SHORT).show();
                } else {
                    Intent intent = new Intent();
                    intent.setClassName("com.example.lhy.pluginproject1", "com.example.lhy.pluginproject1.Plugin1MainActivity");
                    startActivity(intent);
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if(grantResults.length >0 && grantResults[0]  == PackageManager.PERMISSION_GRANTED) {
                    File apk = new File(Environment.getExternalStorageDirectory(), "plugin1.apk");
                    PluginManager pluginManager = PluginManager.getInstance(this);
                    if (apk.exists()) {
                        try {
                            pluginManager.loadPlugin(apk);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    } else {
                        Toast.makeText(MainActivity.this, "插件不存在", Toast.LENGTH_SHORT).show();
                    }
                }
                break;
            default:
                break;
        }
    }
}

 

2.插件工程:

根目录build.gradle

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
        classpath 'com.didi.virtualapk:gradle:0.9.8.6'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

app的build.gradle

apply plugin: 'com.android.application'
apply plugin: 'com.didi.virtualapk.plugin'//注意这个是plugin结尾,宿主是以host结尾的

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.example.lhy.pluginproject1"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    signingConfigs{
        release{
            storeFile file('../PluginProject1.jks')
            storePassword "android"
            keyAlias "pluginProdect1"
            keyPassword "android"
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

virtualApk {
    // 插件资源表中的packageId,需要确保不同插件有不同的packageId.
    packageId = 0x06

    // 宿主工程application模块的路径,插件的构建需要依赖这个路径,我这个宿主工程和插件工程在同一级目录下,所以下面这样写
    targetHost = '../MainProject/app'

    //默认为true,如果插件有引用宿主的类,那么这个选项可以使得插件和宿主保持混淆一致
    applyHostMapping = true
}

宿主编译一遍之后再编译插件

也可以使用命令编译,gradlew clean assemblePlugin

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值