需要注意 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;
}
}