Android热更新全认识
热更新应用场景刚发布的应用出现了比较严重的bug有一些小的功能想即时的推送给用户去使用热更新流程1、线上检测到严重的crash2、拉出bugfix分支并在分支上修复问题3、jenkins构建和补丁生成4、app通过推送或者主动拉取补丁文件5、将bugfix代码合到master分支上热更新原理BaseDexClassLoaderPathClassLoaderDex...
热更新
应用场景
刚发布的应用出现了比较严重的bug
有一些小的功能想即时的推送给用户去使用
热更新流程
1、线上检测到严重的crash
2、拉出bugfix分支并在分支上修复问题
3、jenkins构建和补丁生成
4、app通过推送或者主动拉取补丁文件
5、将bugfix代码合到master分支上
热更新原理
BaseDexClassLoader
PathClassLoader
DexClassLoader
Android中使用PathClassLoader类作为Android的默认的类加载器,其功能是简单的从文件系统中加载类文件
源码分析
//BaseDexClassLoader重写了findClass()
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
//通过pathList对象的findClass()获取Class对象。pathList是在BaseDexClassLoader的构造函数中new出来的
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
//pathList的findClass()
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
//遍历dexElements列表,调用loadClassBinaryName()来加载类
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
//dexElements前面出现的dex会被优先加载,一旦Class被加载成功
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
具体实现
案例见本文件夹
构造一个DexClassLoader对象来加载新的dex文件,调用一次dexClassLoader.loadClass(dummyClassName)让dexClassLoader.pathList.dexElements中,包含新的dex
通过把dexClassLoader.pathList.dexElements**(整个列表)**插入到系统默认的classLoader.pathList.dexElements列表前面,就可以让系统优先加载新的dex中的类
优点
Dexposed的AOP实现是完全非侵入式的,没有使用任何注解处理器,编织器或者字节码重写器
Dexposed实现的hooking,不仅可以hook应用中的自定义函数,也可以hook应用中调用的Android框架的函数
使用场景
AOP编程
插桩(例如测试,性能监控等)
在线热更新,修复严重的,紧急的或者安全性的bug
SDK hooking以提供更好的开发体验
集成
//添加依赖
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.10.+'
classpath 'com.nabilhachicha:android-native-dependencies:0.1'
}
}
...
//导入.so包,有本地文件可直接拷贝,此处可省略
native_dependencies {
artifact 'com.taobao.dexposed:dexposed_l:0.2+:armeabi'
artifact 'com.taobao.dexposed:dexposed:0.2+:armeabi'
}
//导入jar包
dependencies {
compile files('libs/dexposedbridge.jar')
}
//初始化
public class MyApplication extends Application {
private boolean mIsSupported = false; // 设备是否支持dexposed
private boolean mIsLDevice = false; // 是否是Android 5.0及以上
@Override
public void onCreate() {
super.onCreate();
// check device if support and auto load libs
mIsSupported = DexposedBridge.canDexposed(this);
mIsLDevice = Build.VERSION.SDK_INT >= 21;
}
public boolean isSupported() {
return mIsSupported;
}
public boolean isLDevice() {
return mIsLDevice;
}
}
在线热更新案例
//添加依赖,见上述
//宿主apk,类路径:com.dexposed.MainActivity
public class MainActivity extends Activity {
private void showDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("宿主的标题")
.setMessage("宿主的信息")
.setPositiveButton("宿主的按钮", newDialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {}
}).create().show();
}
}
//补丁apk,替换
//添加依赖
dependencies {
provided files('libs/dexposedbridge.jar') //使用provided防止冲突
provided files('libs/patchloader.jar')
}
public class DialogPatch implements IPatch {
@Override public void handlePatch(final PatchParam arg0) throws Throwable {
Class<?> cls = null;
try {
cls= arg0.context.getClassLoader()
.loadClass("com.dexposed.MainActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
return;
}
DexposedBridge.findAndHookMethod(cls, "showDialog",
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
Activity mainActivity = (Activity) param.thisObject;
AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity);
builder.setTitle("补丁的标题")
.setMessage("补丁的信息")
.setPositiveButton("ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {}
}).create().show();
return null;
}
});
}
}
//宿主apk
public void runPatchApk(View view) {
Log.d("dexposed", "runPatchApk button clicked.");
if (isLDevice) {
showLog("dexposed", "It doesn't support this function on L device.");
return;
}
if (!isSupport) {
Log.d("dexposed", "This device doesn't support dexposed!");
return;
}
File cacheDir = getExternalCacheDir();
if(cacheDir != null){
//fullpath: /data/user/0/com.example.myhotfix/cache/patch.apk
String fullpath = cacheDir.getAbsolutePath() + File.separator + "patch.apk";
PatchResult result = PatchMain.load(this, fullpath, null);
if (result.isSuccess()) {
Log.e("Hotpatch", "patch success!");
} else {
Log.e("Hotpatch", "patch error is " + result.getErrorInfo());
}
}
showDialog();
}
更多推荐
所有评论(0)