dexposed框架Android在线热修复
移动client应用相对于Webapp的最大一个问题每次出现bug,不能像web一样在server就完毕修复,不须要发版本号。紧急或者有安全漏洞的问题,
假设是Webapp你可能最多花个1,2个小时紧急公布上线。可是app呢,打包,跪求市场公布几百个渠道,周末还发不了。app配置升级。你还不能配置
强制升级。 就算配置提示升级,用户心里肯定想前两天刚升级最新版,怎么又要升。并且升级要流量。这时候会非常反感甚至卸载应用。所以安卓是否
有能力做到在线打补丁?dexposed给我们攻克了这个问题。
1、淘宝Dexposed框架
喜欢刷机的人应该对xposed不陌生,Xposed框架是一款能够在不改动APK的情况下影响程序执行(改动系统)的框架服务。基于它 能够制作出很多功
能强大的模块。
事实上Xposed安卓的一个开源框架,在github上的下载地址xposed,有兴趣的能够去研究下,dexPosed也是基于Xposed的。
他有几个典型的使用场景:
a. Classic AOP programming (aop编程)
b. Instrumentation (for testing, performance monitoring and etc.) 測试,性能监控
c. Online hot patch to fix critical, emergent or security bugs 线上打补丁,解决一些严重的,紧急的或者安全漏洞的bug。
d. SDK hooking for a better development experience
对于aop编程我这里就不多说了,deXposed提供的 是无侵入性的,并且AOP就是用的java代码。相比国外比較流行的Aspectj有几点优势:
a、无侵入性。
b、使用Java代码编写,Aspectj使用脚本须要一定的学习成本
c、Aspectj有自己的编译器,须要编译下Aspectj代码。会注入他自己的代码
以下来看看假设写一些AOP的代码:
Attach a piece of code before and after all occurrences of Activity.onCreate(Bundle).
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
beforeHookedMethod和afterHookedMethod方法做一些详细的操作。我们能够看到,用dexposed能够实现不改变原函
数的运行。可是在原函数运行前后去做一些其它的额外处理。比如改变入參和返回值等等的一些事情。
Replace the original body of the target method.
DexposedBridge.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodReplacement() {
@Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
// Re-writing the method logic outside the original method context is a bit tricky but still viable.
...
}
});
则是能够将原有要运行的函数替换成一个我们须要的新的运行函数。
这个框架眼下对android 5.0系统支持度不高。只是框架更新的时间能够看出,持续维护中,不久将对art全然支持。
他提供一个函数来检測你的android
系统是否支持 Dexposed,DexposedBridge.canDexposed(context)。
你须要把补丁包打包成一个apk。 然后通过以下的代码来载入补丁包:
// Run taobao.patch apk
public void runPatchApk() {
if (android.os.Build.VERSION.SDK_INT == 21) {
return;
}
if (!isSupport) {
Log.d("dexposed", "This device doesn't support dexposed!");
return;
}
File cacheDir = getExternalCacheDir();
if (cacheDir != null) {
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());
}
}
}
2.Dexposed原理
重点是搞清楚是如何hook的。
1)首先通过ClassLoader把插件apk载入进入主project
2)通过反射拿到详细的class类
3)DexposedBridge.findAndHookMethod定位到详细的方法中,对代码进行覆盖或者增加自己定义功能
前面两点都是Java方法没什么能够说的,我这里重点分析下第三点:
a、DexposedBridge.findAndHookMethod()中调用了Xc_methdhook.hookMethos(),这种方法是通过传入的class。方法及签名去查找返回一个相应的Method。
然后把Method作为參数传入hookMethodNative(Method ...);
b、hookMethodNative是进行hook的主要方法。分析源代码发现,它里面做了这些事情
1、把Java层的变量,类型转换成c能识别的指针类型。
// Save a copy of the original method and other hook info
DexposedHookInfo* hookInfo = (DexposedHookInfo*) calloc(1, sizeof(DexposedHookInfo));
memcpy(hookInfo, method, sizeof(hookInfo->originalMethodStruct));
hookInfo->reflectedMethod = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(reflectedMethodIndirect));
hookInfo->additionalInfo = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(additionalInfoIndirect));
2、用我们自己的code取代,看到以下的nativeFun了吗。这种方法被dexposedCallHandler取代。也就是说被我们自己的回调方法取代了。这样我们就能够
任意的操作了。然后把要hook的对象參数保存到insns
// Replace method with our own code
SET_METHOD_FLAG(method, ACC_NATIVE);
method->nativeFunc = &dexposedCallHandler;
method->insns = (const u2*) hookInfo;
method->registersSize = method->insSize;
method->outsSize = 0;
3、接下来我们去看看dexposedCallHandler做了哪些事情;
//赋值
DexposedHookInfo* hookInfo = (DexposedHookInfo*) method->insns;
Method* original = (Method*) hookInfo;
Object* originalReflected = hookInfo->reflectedMethod;
Object* additionalInfo = hookInfo->additionalInfo;
// call the Java handler function
//对...这才是重点。dvmCallMethod方法回调了dexposedHandleHookedMethod方法,这种方法是在Java层实现的
JValue result;
dvmCallMethod(self, dexposedHandleHookedMethod, NULL, &result,
originalReflected, (int) original, additionalInfo, thisObject, argsArray); dvmReleaseTrackedAlloc((Object *)argsArray, self);
4、我们在去看看handleHookMethod做了什么。一看你就明朗了;
private static Object handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj,
Object thisObject, Object[] args) throws Throwable {
AdditionalHookInfo additionalInfo = (AdditionalHookInfo) additionalInfoObj; if (disableHooks) {
try {
return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes,
additionalInfo.returnType, thisObject, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
} Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot();
final int callbacksLength = callbacksSnapshot.length;
if (callbacksLength == 0) {
try {
return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes,
additionalInfo.returnType, thisObject, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
} MethodHookParam param = new MethodHookParam();
param.method = method;
param.thisObject = thisObject;
param.args = args; // call "before method" callbacks
int beforeIdx = 0;
do {
try {
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
} catch (Throwable t) {
DexposedBridge.log(t); // reset result (ignoring what the unexpectedly exiting callback did)
param.setResult(null);
param.returnEarly = false;
continue;
} if (param.returnEarly) {
// skip remaining "before" callbacks and corresponding "after" callbacks
beforeIdx++;
break;
}
} while (++beforeIdx < callbacksLength); // call original method if not requested otherwise
if (!param.returnEarly) {
try {
param.setResult(invokeOriginalMethodNative(method, originalMethodId,
additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));
} catch (InvocationTargetException e) {
param.setThrowable(e.getCause());
}
} // call "after method" callbacks
int afterIdx = beforeIdx - 1;
do {
Object lastResult = param.getResult();
Throwable lastThrowable = param.getThrowable(); try {
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
} catch (Throwable t) {
DexposedBridge.log(t); // reset to last result (ignoring what the unexpectedly exiting callback did)
if (lastThrowable == null)
param.setResult(lastResult);
else
param.setThrowable(lastThrowable);
}
} while (--afterIdx >= 0); // return
if (param.hasThrowable())
throw param.getThrowable();
else
return param.getResult();
}
推断是否hook成功。假设不成功运行invokeOriginalMethodNative,也就是运行原始函数;
成功则,这个函数查找被挂钩函数的挂钩 XC_MethodHook结构体,然后运行里边的 beforeHookedMethod函数。再通过 invokeOriginalMethodNative
运行挂钩前的原始函数。最后再运行 afterHookedMethod 函数。
findAndHookMethod 的实现就分析完了,
本质上仍然是寻找被挂钩函数的 Method 结构体,将Method属性改为native ,然后对其成员 nativeFunc,
registersize 等进行赋值,当中 insns 成员保存了挂钩的具体信息。全部被挂钩的函数。其nativeFunc都赋值为 dexposedCallHandler 函数,该函数终于运行 XposedBridge.class 里的 handleHookedMethod 。 handleHookedMethod 寻找dexposed模块及dexposed框架调用 findAndHookMethod 注冊的 before,after
函数,假设有,就运行,再通过invokeOriginalMethodNative 运行挂钩前函数。
3、怎么使用dexposed
a、下载dexposed源代码 dexposed
b、把下载的源代码导入,仅仅须要sample包。当中dexposedsample是有bug的主project,patchsample为插件project,(其它的两个为工具类源代码,方便我们理解源代码)
c、主project配置gradle文件
apply plugin: 'com.android.application'
android {
compileSdkVersion 21
buildToolsVersion "21.1.2" defaultConfig {
applicationId "com.taobao.dexposed"
minSdkVersion 9
targetSdkVersion 21
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
} android {
packagingOptions {
exclude 'AndroidManifest.xml'
} } buildTypes {
//Debug打开,使用測试环境接口
debug {
}
}
lintOptions {
abortOnError false
} sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java']
res.srcDirs = ['src/main/res'] //路径依据自己的相应改动
}
}
} //我使用的是本地导入so包。
task copyNativeLibs(type: Copy) {
from fileTree(dir: 'libs', include: '*/*.so' ) into 'build/native-libs'
}
tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn copyNativeLibs } clean.dependsOn 'cleanCopyNativeLibs' tasks.withType(com.android.build.gradle.tasks.PackageApplication) {
pkgTask ->
pkgTask.jniFolders = new HashSet<File>()
pkgTask.jniFolders.add(new File(projectDir, 'libs'))
}
//---------------------------------
//这段配置是把主project打成jar包。这个jar包须要导入到patchproject中
task clearJar(type: Delete) {
delete 'libs/sdk.jar'
} task makeJar(type:org.gradle.api.tasks.bundling.Jar) {
//指定生成的jar名
baseName 'sdk'
//从哪里打包class文件
from('build/intermediates/classes/sample/debug/com')
//打包到jar后的文件夹结构,依据自己project须要写路径
into('com')
//去掉不须要打包的文件夹和文件
exclude('test/', 'BuildConfig.class', 'R.class')
//去掉R$开头的文件
exclude{ it.name.startsWith('R$');}
} makeJar.dependsOn(clearJar, build)
//--------------------------------------------- dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile files('libs/dexposedbridge.jar')
}
patchprojectgradle配置:
dependencies {
// compile fileTree(dir: 'libs', include: ['*.jar'])
provided files('libs/sdk.jar')
provided files('libs/dexposedbridge.jar')
provided files('libs/patchloader.jar')
}
使用provided方式导入jar包,这样仅仅会在编译时引用jar,不会把jar打包进patch.apk。防止与主project引入包冲突;
用例代码就不贴了,自己去down一份
4、关于patch包的安全问题能够看Android Hotpatch
dexposed框架Android在线热修复的更多相关文章
- Android 模块化/热修复/插件化 框架选用
概念汇总 动态加载:在程序运行的时候,加载一些程序自身原本不存在的文件并运行这些文件里的代码逻辑.动态加载是热修复与插件化实现的基础. 热修复:修改部分代码,不用重新发包,在用户不知情的情况下,给ap ...
- 包建强的培训课程(15):Android App热修复技术
@import url(/css/cuteeditor.css); Normal 0 10 pt 0 2 false false false EN-US ZH-CN X-NONE $([{£¥·‘“〈 ...
- Android RocooFix热修复动态加载框架介绍
RocooFix Another hotfix framework 之前的HotFix项目太过简单,也有很多同学用Nuwa遇到很多问题,作者也不再修复,所以重新构建了一套工具. Bugfix 2016 ...
- 手把手带你打造一个 Android 热修复框架
本文来自网易云社区 作者:王晨彦 Application 处理 上面我们已经对所有 class 文件插入了 Hack 的引用,而插入 dex 是在 Application 中,Application ...
- Android 热补丁和热修复
参考: 各大热补丁方案分析和比较 Android App 线上热修复方案 1. Xposed Github地址:https://github.com/rovo89/Xposed 项目描述:Xposed ...
- 十分钟教会你使用安卓热修复框架AndFix
腾讯最近开发出一个Tinker,阿里也有一个Dexposed框架,当然还有一个就是今天的主角热修复框架AndFix.接下来,我会从它的概念.原理.使用方法等为你详细介绍. 1.什么是AndFix? A ...
- Android热修复技术原理详解(最新最全版本)
本文框架 什么是热修复? 热修复框架分类 技术原理及特点 Tinker框架解析 各框架对比图 总结 通过阅读本文,你会对热修复技术有更深的认知,本文会列出各类框架的优缺点以及技术原理,文章末尾简单 ...
- 热修复干货| AndFix热补丁动态修复框架使用教程
本篇文章会与大家一起学习使用阿里的AndFix热修复框架,可以说AndFix是国内热修复技术的开山始祖,尽管现在阿里已经放弃了对这个项目的维护,但是后来很多的热修复技术都借鉴了这一框架的实现思路. 1 ...
- 探索安卓热修复框架AndFix的奥秘
虽然阿里的AndFix框架已经出来很长时间了,但是还不了解它的同学依然挺多,接下来就跟着我一起来到AndFix的世界里一起看看,如何达到不用重新安装app就可以修复bug. 1.什么是AndFix? ...
随机推荐
- 网络抓包工具 Fiddler
网络抓包工具 Fiddler 下载网址 http://www.telerik.com/fiddler 简单介绍 Fiddler是一个http协议调试代理工具,它能够记录并检查全部你的电脑和互联网之间的 ...
- 一步一步跟我学习hadoop(5)----hadoop Map/Reduce教程(2)
Map/Reduce用户界面 本节为用户採用框架要面对的各个环节提供了具体的描写叙述,旨在与帮助用户对实现.配置和调优进行具体的设置.然而,开发时候还是要相应着API进行相关操作. 首先我们须要了解M ...
- 网页爬虫框架jsoup介绍
序言:在不知道jsoup框架前,因为项目需求.须要定时抓取其它站点上的内容.便想到用HttpClient方式获取指定站点的内容.这样的方法比較笨,就是通过url请求指定站点.依据指定站点返回文本解析. ...
- 编译Redis系统提示缺少gcc,可以使用yum进行安装:
yum -y install gcc yum -y install gcc-c++ yum install make -- 或者 yum groupinstall "Development ...
- redis.conf 配置项说明
redis.conf 配置项说明如下: Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程 daemonize no 当Redis以守护进程方式运行时,Redis ...
- php global GLOBALS[]变量
$a=123; function aa() { Global $a; //如果不把$a定义为global变量,函数体内是不能访问函数体外部的$a的,但是可以定义一个相同的名字$a,此时这个变量是局部变 ...
- go并发设计模式 --资源生成器模式
1.简单的生成器 package main import ( "fmt" "math/rand" ) func GenerateIntA()chan int { ...
- SpringBoot(十一) Dubbo分布式与Zookeeper
Dubbo简介 1.Dubbo简介 1. Dubbo是什么? dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上 ...
- OpenGL编程(八)3D数学与坐标变换
笛卡尔坐标 一维坐标系 以一个点为原点,选定一个方向为正方向(相反的方向为反方向),以一定的距离为标尺建立一维坐标系.一维坐标系一般应用于描述在一维空间中的距离. 举个例子:一维坐标系好比一条拉直的电 ...
- 02 C#高级
第九天 面向过程--à面向对象 面向过程:面向的是完成这件事儿的过程,强调的是完成这件事儿的动作. 把大象塞进冰箱 1. 打开冰箱门 2. 把大象塞进去,亲下大象的屁股 3. 关闭冰箱门 孙全 瘦小 ...