Android插件开发 老生常谈问题
1 Android类加载
PathClassLoader
DexClassLoader
两者都继承自BaseDexClassLoader
但是两者又有区别
PathClassLoader 是加载apk包的路径 比如/data/data/包名/XXXX.dex
DexClassLoader 可以加载指定apk包路径和dex文件解压路径 比如/sdcard
本文热更插件原理 基于tinker 原理实现
TINKER原理
1.使用DexClassLoader加载补丁包的dex文件
2.通过反射获取DexClassLoader类的pathList,再次通过反射获得dexElements数组。
3.获取加载应用类的PathClassLoader,同样通过反射获取它的dexElements数组。
4.合并两个dexElements数组,且将补丁包的dex文件放在前面。
代码实现
1 我们建一个需要fix 的类 MainActivity.java
package com.example.fixtest;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends Activity {
public TextView fixText ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fixText = findViewById(R.id.fixtext);
testFix();
myadd(2,3);
}
public void testFix()
{
fixText.setText("修复之前 显示错误");
Log.e("mytag","fix before");
}
public int myadd(int a, int b)
{
return a+b;
}
}
接来下我们骚骚修改一下 把修改的java 制作打包成dex
package com.example.fixtest;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends Activity {
public TextView fixText ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fixText = findViewById(R.id.fixtext);
testFix();
myadd(2,3);
}
public void testFix()
{
fixText.setText("修复之后 显示正确");
Log.e("mytag","fix before");
}
public int myadd(int a, int b)
{
return a+b;
}
}
2 将java打包成dex
需要用到的工具 dx.bat
工具路径 \android-sdk-windows\build-tools\29.0.0
添加环境变量
使用
dx --dex --output=表示打包输出/hotfix.dex 表示原文件的路径名
将一下目录 打包为dex 也可以单独复制出来 我是单独复制出来的 删除了没有必要的类
单独复制出来
在 com 同级目录 执行一下命令既可以生成 dex
dx --dex --output=./hotfix.dex ./
由于我没有服务器 我直接复制到 项目的assets 下 然后拷贝到apk 的缓存目录 然后执行加载操作
public class MyAppliction extends Application {
protected void attachBaseContext(Context base) {
FixManeger.copyDex(base);
FixManeger.loadDex(base);
super.attachBaseContext(base);
}
}
public class FixManeger {
public static void copyDex(Context context) {
File filedex = new File(context.getCacheDir() + "/hotfix.dex");
if(filedex.exists()) {
filedex.delete();
}
try {
InputStream ins = context.getAssets().open("hotfix.dex");
byte[] buffer = new byte[ins.available()];
ins.read(buffer);
ins.close();
FileOutputStream outs = new FileOutputStream(filedex);
outs.write(buffer);
outs.close();
}
catch(Exception e) {
e.printStackTrace();
}
}
public static void loadDex(Context context) {
File filedex = new File(context.getCacheDir() + "/hotfix.dex");
if(filedex.exists()) {
try {
//通过反射获取dexElements 成员
ClassLoader classLoader = context.getClassLoader();
Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
pathListField.setAccessible(true);
Object pathListObject = pathListField.get(classLoader);
Field dexElementsField = pathListObject.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
Object dexElementsObject = dexElementsField.get(pathListObject);
Object newDexElementsObject = dexElementsField.get(pathListField.get(new PathClassLoader(filedex.getPath(), classLoader.getParent())));
int oldLength = Array.getLength(dexElementsObject);
int newLength = Array.getLength(newDexElementsObject);
//开始拼接dex
Object concatDexElementsObject = Array.newInstance(dexElementsObject.getClass().getComponentType(), oldLength + newLength);
//加载新的dex
int i;
for(i = 0; i < newLength; ++i) {
Array.set(concatDexElementsObject, i, Array.get(newDexElementsObject, i));
}
//加载原有的dex
int oldindex = 0;
while(oldindex < oldLength) {
Array.set(concatDexElementsObject, newLength + oldindex, Array.get(dexElementsObject, oldindex));
++oldindex;
classLoader = classLoader;
}
dexElementsField.set(pathListObject, concatDexElementsObject);
}
catch(NoSuchFieldException e) {
e.printStackTrace();
}
catch(IllegalAccessException e) {
e.printStackTrace();
}
return;
}
}
}