Anroid插件化一:jar包动态加载

Android中常用的两种类加载器:PathClassLoaderDexClassLoader,它们都继承于BaseDexClassLoader
PathClassLoader 可以加载已经安装到Android系统中的apk文件,在ART虚拟机上可以加载未安装的apkdex,在Dalvik则不行;DexClassLoader可以加载jardex和未安装的apk

考虑到需要兼容4.0版本的Android系统,以下选用DexClassLoader实现动态加载。

动态加载jar

一、新建项目PluginDemo

此时会默认生成Project的主module app
gradle版本选择使用3.1.4
二、新建module PluginJarDemo
新建类PluginJarDemo,以作演示。

package com.don.pluginjardemo;

import android.util.Log;

/**
 * Created by don on 2019-09-09
 */
public class PluginJarDemo {
    private final String TAG = "PluginJarDemo";
    private String content;

    public void writeTest(String content) {
        Log.i(TAG, "writeTest " + content + "  " + this);
        this.content = content;
    }

    public String readTest() {
        Log.i(TAG, "readTest " + this);
        return content;
    }

}

接下来通过DexClassLoader动态加载PluginJarDemo

1、编译module PluginJarDemo,生成jar

task makeJar(type: Jar, dependsOn: [':PluginJarDemo:assembleRelease' ]) {

    archiveName = "pluginDemo.jar"
    destinationDir = file('build/libs')

    def where = "build/intermediates/classes/release"
    from(where)
    from fileTree(dir: 'src/main', includes: ['assets/**'])

    exclude('**/R.class')
    exclude('**/R\$*.class')
    include "**/*.*"
}

在命令行中执行
gradlew :PluginJarDemo:makeJar
PluginJarDemobuild/libs/目录下会生成pluginDemo.jar文件。
这种直接生成的jar包是不能被DexClassLoader加载的,因为其中没有dex文件。

2、生成包含dex文件的新jar

task makeDex(type: Exec, description: 'for PluginJarDemo dex') {
    doFirst {
        println("convert jar to dex start")

        // 拼接的dex转换工具路径
        Properties properties = new Properties()
        //local.properites也放在posdevice目录下
        File propertyFile = new File(rootDir.getAbsolutePath() + "/local.properties")
        properties.load(propertyFile.newDataInputStream())
        def dexToolPath = properties.getProperty('sdk.dir') + "/build-tools/27.0.3"
        // dex转换工具参数
        def dexToolArgs = "--dex --output="
        def releaseJar = "build/libs/pluginDemo.jar"
        def dexJar = "build/libs/pluginDex.jar"

        def finalCmd
        def dxToolName
        if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
            // Windows
            dxToolName = "dx.bat"
            finalCmd = dexToolPath + "/$dxToolName " + dexToolArgs + dexJar + " " + releaseJar
            finalCmd = finalCmd.replaceAll("/", "\\\\")
            commandLine "cmd", "/c", finalCmd
        } else {
            // linux
            dxToolName = "dx"
            finalCmd = dexToolPath + "/$dxToolName " + dexToolArgs + dexJar + " " + releaseJar
            finalCmd = finalCmd.replaceAll("\\\\", "/")
            commandLine "/bin/sh", "-c", finalCmd
        }
        println("convert jar to dex end, command: " + finalCmd)
    }
}

在命令行中运行
gradlew :PluginJarDemo:makeDex
PluginJarDemobuild/libs/目录下会生成pluginDex.jar文件,这个文件包含class.dex文件,可被DexClassLoader动态加载。

三、动态加载pluginDex.jar

在主module app中新建类JarLoad用于动态加载pluginDex.jar

package com.don.plugindemo;

import android.content.Context;
import android.util.Log;

import java.io.File;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

/**
 * Created by don on 2019-09-09
 */
public class JarLoader {
    private final static String TAG = "JarLoader";
    private final String DEMO_CLASS = "com.don.pluginjardemo.PluginJarDemo";
    private Class mDemoClass;
    private Object mDemoInstance;

    public void load(Context context, String pluginPath) {
        File file = new File(pluginPath);
        if (!file.exists()) {
            Log.i(TAG, "load ignore, invalid file: " + pluginPath);
            return;
        }
        String dataFile = context.getFilesDir().getAbsolutePath();
        try {
            DexClassLoader dexClassLoader = new DexClassLoader(pluginPath, dataFile, null, getClass().getClassLoader());
            mDemoClass = dexClassLoader.loadClass(DEMO_CLASS);
            mDemoInstance = mDemoClass.newInstance();
        } catch (Exception e) {
            Log.w(TAG, e);
        }
    }

    public void writeTest(String content) {
        try {
            Method method = mDemoClass.getDeclaredMethod("writeTest", String.class);
            method.invoke(mDemoInstance, content);
        } catch (Exception e) {
            Log.w(TAG, e);
        }
    }

    public String readTest() {
        try {
            Method method = mDemoClass.getDeclaredMethod("readTest");
            return method.invoke(mDemoInstance).toString();
        } catch (Exception e) {
            Log.w(TAG, e);
        }
        return null;
    }

}

DexClassLoader的创建需要填4个参数:

  • 第一个是jar包的地址
  • 第二个是jar包中dex文件被加载之后的路径,需要可执行,这里使用了app的安* 装根目录
  • 第三个是so文件目录,如果该jar包需要加载so,则需要传入so文件所在目录,同样的,此目录需要有可执行权限
  • 第四个是父类构造器,使用当前app的构造器即可

jar包动态加载成功之后可以通过反射机制创建jar包中的PluginJarDemo对象并操作。

在主界面添加一个按钮,点击之后读取PluginJarDemo实例中的content变量。

package com.don.plugindemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private JarLoader mJarLoader;
    private Button mJarBtn;
    private TextView mResultTxt;
    private View.OnClickListener mClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.jarBtn:
                    mJarLoader.writeTest("jar load test");
                    mResultTxt.setText(mJarLoader.readTest());
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        mJarLoader = new JarLoader();
        mJarLoader.load(this,"sdcard/pluginDex.jar");
    }

    private void initView() {
        mJarBtn = (Button) findViewById(R.id.jarBtn);
        mJarBtn.setOnClickListener(mClickListener);
        mResultTxt = (TextView) findViewById(R.id.resultTxt);
    }
}

运行,点击按钮 JAR
如下图,可以从pluginDex.jar中成功读取content变量

image.png

动态加载dex与动态加载jar类似,唯一的区别是需要将打包的jar转换为dex文件,以上述实现为例,通过下面的命令可以得到一个可用于动态加载的dex文件
dx --dex --output=pluginDex.dex pluginDemo.jar
接下来通过加载pluginDex.dex的实现,与动态加载pluginDex.jar文件一样。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269