Android 热加载 dex

Android 热加载 dex

参考文章

需要注意 Android 5.0 和 4.4 jvm 加载机制不同,需要测试兼容性

实践

定义接口

package com.zhouzhou.hunxiao.hotload;

public interface SignatureInterface {

    String getSignature();

}

实现接口

package com.zhouzhou.hunxiao.hotload;

import com.zhouzhou.hunxiao.SignatureInterface;

public class SignatureImpl implements SignatureInterface {
    @Override
    public String getSignature() {
        return "test";
    }
}

修改项目 app 目录下打包任务

网上教程比较老,2017 年的,
目前使用的是 Android studio 3.3.1
所以目录结构改变了

//打包任务
task makeJar(type: org.gradle.api.tasks.bundling.Jar) {
    //指定生成的jar名
    baseName 'signature'
    //从哪里打包class文件
    from('build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/zhouzhou/hunxiao/hotload/')
    //打包到jar后的目录结构
    into('com/zhouzhou/hunxiao/hotload/')
    //去掉不需要打包的目录和文件
    exclude('test/', 'SignatureInterface.class', 'BuildConfig.class', 'R.class')

    //去掉R$开头的文件
    exclude { it.name.startsWith('R$'); }
}
makeJar.dependsOn(clearJar, build)

Android studio 的 Terminal 中执行 gradle makeJar
然后在 build/libs 目录下就会存在 signature.jar 文件
可以使用反编译查看下这个 jar,在里面有需要的 SignatureImpl.class

将 java 的 class 转成 Android 加载的 class

在 sdk 路径的 \build-tools\23.0.1\lib 下,有 dx.jar 文件,进入该目录
执行命令:

.\dx.jar --dex --output=signature_dex.jar signature.jar

测试

通过测试,在 art 机型上,可以正确加载并执行,而 dalvik 虚拟机上,无法加载
目前手机基本都是 art 加载,只有模拟器还是 dalvik 加载,所以在虚拟机上的兼容性需要考虑

完整的代码文件:

MainActivity.java

package com.zhouzhou.hunxiao;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import com.zhouzhou.hunxiao.hotload.SignatureInterface;

import java.io.File;
import java.io.IOException;

import dalvik.system.DexClassLoader;

public class MainActivity extends AppCompatActivity {

    DexClassLoader dexClassLoader;

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

        tryStart();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    /**
     * 从资源文件读取打包的 dex 文件
     * 这个 dex 可以使用加密工具做加密等操作
     */
    private void hotLoadClass() {
        File cacheFile = FileUtils.getCacheDir(getApplicationContext());
        String internalPath = cacheFile.getAbsolutePath() + File.separator + "signature_dex.jar";
        File desFile = new File(internalPath);
        try {
            if (!desFile.exists()) {
                desFile.createNewFile();
                FileUtils.copyFiles(this, "signature_dex.jar", desFile);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        //下面开始加载dex class
        dexClassLoader = new DexClassLoader(internalPath, cacheFile.getAbsolutePath(), null, getClassLoader());

    }

    private SignatureInterface getLoadClass() {
        try {
            Class libClazz = dexClassLoader.loadClass("com.zhouzhou.hunxiao.hotload.SignatureImpl");
            return (SignatureInterface) libClazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private void tryStart() {
        long index = 0;
        SignatureInterface signature = getLoadClass();
        index++;
        if (signature != null) {
            Log.i("MainActivity", "Constant String:" + signature.getSignature() + "  index:" + index);
        } else {
            Log.i("MainActivity", "signature impl is null" + "  index:" + index);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

FileUtils.java

package com.zhouzhou.hunxiao;

import android.content.Context;
import android.os.Environment;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class FileUtils {

    public static void copyFiles(Context context, String fileName, File desFile) {
        InputStream in = null;
        OutputStream out = null;
        try {
            in = context.getApplicationContext().getAssets().open(fileName);
            out = new FileOutputStream(desFile.getAbsolutePath());
            byte[] bytes = new byte[1024];
            int i;
            while ((i = in.read(bytes)) != -1)
                out.write(bytes, 0, i);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null)
                    in.close();
                if (out != null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    public static boolean hasExternalStorage() {
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }

    /**
     * 获取缓存路径
     *
     * @param context
     * @return 返回缓存文件路径
     */
    public static File getCacheDir(Context context) {
        File cache;
        if (hasExternalStorage()) {
            cache = context.getExternalCacheDir();
        } else {
            cache = context.getCacheDir();
        }
        if (!cache.exists())
            cache.mkdirs();
        return cache;
    }


}

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://zwc365.com/2019/11/19/dex-hot-load

Buy me a cup of coffee ☕.