Android免Root无侵入AOP框架Dexposed
Dexposed框架是阿里巴巴无线事业部近期开源的一款在Android平台下的免Root无侵入运行期AOP框架,该框架基于AOP思想,支持经典的AOP使用场景,可应用于日志记录,性能统计,安全控制,事务处理,异常处理等方面。
针对Android平台,Dexposed只支持函数级别的在线热更新,如对已经发布在应用市场上的宿主APK,当我们从crash统计平台上发现某个函数调用有bug,导致经常性crash,这时,可以在本地开发一个补丁APK,并发布到服务器中,宿主APK下载这个补丁APK并使用Dexposed框架集成后,就可以很容易修复这个crash
如何在Android Studio集成Dexposed
关于在Android Studio中集成Dexposed,很简单,只需要导入两个jar包和两个so文件,jar包和so文件都可以在github上下载传送门,关于jar的导入大家肯定很清楚,只需要把对应的jar包放入libs目录下,然后添加项目依赖即可,而关于so文件的导入,不同的AS版本便不一样了,较早期的版本可能更麻烦些,而比较新的版本则非常方便,我用的是AS1.4Beta2版的,所以只需要在src/main目录下新建一个jniLibs目录,然后把so文件放入进去即可,当我们打包成apk的时候自到会将so文件添加进去。所以在Android Studio中集成Dexposed的配置如下:
1、添加so文件:
2、添加jar依赖:
dependencies {
//...
compile files('libs/dexposedbridge.jar')
compile files('libs/patchloader.jar')
}
Dexposed支持的SDK版本
Dexposed目前只支持从Android2.3到4.4(除了3.0)的所有Dalvid运行时arm架构的设备,而Android 5.0以后摒弃了Dalvid而使用ART模式,所以说在api19以上的系统目前来说都不支持,不过阿里的团队也正在测试Android5.0加入对ART的支持。
—–支持的系统版本:
Dalvik 2.3
Dalvik 4.0~4.4
—–不支持的系统版本:
Dalvik 3.0
ART 5.1
ART M
ART 6.0
Dexposed应用场景
Dexposed实现的hooking,不仅可以hook应用中的自定义函数,也可以hook应用中调用的Android框架的函数。不过Dexposed只能hook函数和构造器,我们从源码中就可以看出:
应用场景:
1、 AOP编程
2、插桩(例如测试,性能监控等)
3、在线热更新,修复严重的,紧急的或者安全性的bug
4、SDK hooking以提供更好的开发体验
Dexposed框架是基于动态类加载技术,运行中的app可以加载一小段经过编译的Java AOP代码,在不需要重启app的前提下实现修改目标app的行为。
Dexposed的用法
1、检查当前系统是否支持Dexposed
首先上面我们讲了由于Dexposed框架目前并不是支持所有的系统,所以我们在使用Dexposed框架的时候应该对当前系统的版本进行检查是否支持Dexposed,不过这段检查代码Dexposed内部已经封装好了,所以我们只需要调用DexposedBridge.canDexposed(this)方法即可,我们应该尽可能的在程序一启动时候完成检查,如:
public class MyApplication extends Application {
private static boolean canDexPosed = false;
@Override
public void onCreate() {
super.onCreate();
canDexPosed = DexposedBridge.canDexposed(this);
if(canDexPosed){
//do something
}
}
public static boolean isCanDexPosed() {
return canDexPosed;
}
}
我们可以看看Dexposed源码
里面是怎么完成对系统的检查的:
public static synchronized boolean canDexposed(Context context) {
return !DeviceCheck.isDeviceSupport(context)?false:loadDexposedLib(context);
}
private static boolean loadDexposedLib(Context context) {
try {
if(VERSION.SDK_INT != 10 && VERSION.SDK_INT != 9) {
if(VERSION.SDK_INT > 19) {
System.loadLibrary("dexposed_l");
} else {
System.loadLibrary("dexposed");
}
} else {
System.loadLibrary("dexposed2.3");
}
return true;
} catch (Throwable var2) {
return false;
}
}
public static synchronized boolean isDeviceSupport(Context context) {
boolean var2;
try {
if(!isCheckedDeviceSupport) {
if(isDalvikMode() && isSupportSDKVersion() && !isX86CPU() && !isYunOS()) {
isDeviceSupportable = true;
return isDeviceSupportable;
}
isDeviceSupportable = false;
return isDeviceSupportable;
}
var2 = isDeviceSupportable;
} finally {
Log.d("hotpatch", "device support is " + isDeviceSupportable + "checked" + isCheckedDeviceSupport);
isCheckedDeviceSupport = true;
}
return var2;
}
2、Dexposed基本功能
Dexposed框架有三个注入点可供选择:函数执行前注入(before),函数执行后注入(after),替换函数执行的代码段(replace)。
其中函数执行前注入和函数执行后注入用的是同一个抽象类:XC_MethodHook,而替换函数执行代码段的抽象类为:XC_MethodReplacement。
这三点注入点对应的抽象类中的方法名分别为:
1、XC_MethodHook抽象类中函数执行前注入和函数执行后注入的两个对应方法为:
protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
}
protected void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
}
2、XC_MethodReplacement抽象类中替换执行的代码段的方法为:
protected abstract Object replaceHookedMethod(MethodHookParam var1) throws Throwable;
所以我们只要实现上述中相应的方法,便可以在某个函数执行前、函数执行后或者在要替换的函数中执行相应的代码。那么怎么使用呢?就是通过DexposedBridge.findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback)
该方法的参数意思为:
1、clazz - 就是hook的方法所在的类的字节码对象
2、methodName - 就是hook的方法名
3、parameterTypesAndCallback - 可变参数,如果hook的方法中有参数的话,需要在此传入参数类型,如String则传入String.class,这和反射一样的,如果没有参数则不传。在最后还需要传入XC_MethodHook或者XC_MethodReplacement对象
如:
1、在getMsg方法执行前或者执行后做相应的操作:
DexposedBridge.findAndHookMethod(clazz, "getMsg", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
//do something
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
//do something
}
});
2、替换getMsg方法中的代码:
DexposedBridge.findAndHookMethod(clazz, "getMsg", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
//replace code
return null;
}
});
我们可以看到在上面三个方法中,都会传入一个MethodHookParam
类型的参数,这个参数包含了一些很重要的信息:
- MethodHookParam.thisObject:被hook方法所在类的一个实例(相当于clazz类的一个实例)
- MethodHookParam.args:用于传递被hook方法的所有参数,它返回的是一个Object[]对象,通过它如果hook方法有参数传入的话,用这个可以获取到这些参数的值
- MethodHookParam.setResult:用于修改hook方法调用的结果,如果在beforeHookedMethod回调函数中调用setResult,可以阻止对hook方法的调用。但是如果有返回值的话仍然需要通过hook处理器进行return操作
Dexposed的应用示例
AOP编程
AOP(Aspect Oriented Programming)也就是面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,AOP编程最大的特点就是低耦合,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP一般应用在日志记录,性能统计,安全控制,事务处理,异常处理等方面,它的主要意图是将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
比如我们可以在应用的某些Activity中onCreate(Bundle)方法调用之前和之后做一些处理:
在MainActivity中onCreate方法调用之前预分配一些值或者调用一些方法:
public class MyApplication extends Application {
private static boolean canDexPosed = false;
@Override
public void onCreate() {
super.onCreate();
canDexPosed = DexposedBridge.canDexposed(this);
if(canDexPosed){
Class<?> clazz = null;
try {
clazz = getClassLoader().loadClass("com.sunzxyong.dexposeddemo.MainActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
DexposedBridge.findAndHookMethod(clazz, "onCreate", Bundle.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Log.v("zxy", "before onCreate ");
//为Activity预先分配一些值
XposedHelpers.setObjectField(param.thisObject,"content","sunzxyong");
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Log.v("zxy", "after onCreate ");
}
});
}
}
public static boolean isCanDexPosed() {
return canDexPosed;
}
}
然后在MainActivity:
public class MainActivity extends Activity {
private String content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.v("zxy", "in onCreate ");
Log.v("zxy","content="+content);
}
运行后可以看到打印的Log为:
09-10 13:16:34.950 28373-28373/? V/zxy﹕ before onCreate
09-10 13:16:35.265 28373-28373/? V/zxy﹕ in onCreate
09-10 13:16:35.265 28373-28373/? V/zxy﹕ content=sunzxyong
09-10 13:16:35.265 28373-28373/? V/zxy﹕ after onCreate
可以看到预分配值的方法beforeHookedMethod()确实是在onCreate方法执行前执行,而onCreate方法执行后又执行了afterHookedMethod()方法。
其中XposedHelpers
这个帮助类提供了很多方法,比如调用某个类中的某个方法,设置变量的值等等。
当然也可以使用如下代码替换onCreate方法中的代码:
DexposedBridge.findAndHookMethod(clazz, "onCreate", Bundle.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
//relpace code
return null;
}
});
在线热更新
在线热更新一般用于修复已经上线app中严重的,紧急的或者安全性的bug,这里会涉及到两个apk文件,一个我们称为宿主apk,也就是已经上线的apk,一个称为补丁apk。宿主apk出现bug时,通过在线下载的方式从服务器下载补丁apk,然后使用补丁apk中的函数替换掉宿主apk的函数,从而实现在线修复bug的功能。
我们假设刚刚上线的apk中的MainActivity类中的getMsg(String str)方法出现了bug,然后我们采用在线热更新的方法去修复这个bug。
getMsg()方法:
public void getMsg(String str){
new AlertDialog.Builder(this,AlertDialog.THEME_HOLO_LIGHT).setTitle("提示").setMessage(str).setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).create().show();
}
1、首先我们需要一个补丁apk
我们创建一个叫PatchApk的Module作为补丁apk的工程,然后为它添加jar包依赖:
dependencies {
provided fileTree(include: ['*.jar'], dir: 'libs')
provided files('libs/patchloader.jar')
provided files('libs/dexposedbridge.jar')
}
这里需要注意的是,由于补丁apk也添加了jar包依赖,而我们的宿主apk中也有jar包依赖,如果补丁apk中添加的jar包依赖是以compile
的方式添加的话,则会报重复包引用错误,错误如下:
Caused by: java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
所以我们在补丁apk中需要以provided
提供的方式添加jar包依赖。
然后我们创建一个PatchMsg
类,意思就是getMsg
方法的补丁类,此时我们需要将PatchMsg
补丁类实现IPatch
接口,然后实现该接口的handlePatch
方法,顾名思义,这个方法就是处理补丁的方法。然后通过DexposedBridge.findAndHookMethod()
方法来hook在MainActivity中的getMsg方法,然后实现replaceHookedMethod
方法进而替换掉宿主apk中getMsg()
中的代码。其中MainActivity的字节码对象可以由包名+类名反射得到。
整个PatchMsg类实现如下:
PatchMsg.java
public class MsgPatch implements IPatch {
@Override
public void handlePatch(PatchParam patchParam) throws Throwable {
Class<?> clazz = patchParam.context.getClassLoader().loadClass("com.sunzxyong.dexposeddemo.MainActivity");
DexposedBridge.findAndHookMethod(clazz, "getMsg", String.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
Activity activity = (Activity) methodHookParam.thisObject;
new AlertDialog.Builder(activity, AlertDialog.THEME_HOLO_LIGHT).setTitle("提示").setMessage("这是补丁信息--->使用的是无侵入AOP插件Dexposed!").setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).create().show();
String msg = (String) methodHookParam.args[0];//得到宿主apk中getMsg方法传递过来的参数值
Log.v("zxy", msg);//Dexposed hello!
//由于getMsg方法没有返回值则返回null
return null;
}
});
}
}
好了,补丁类已经创建好了,此时我们可以把补丁apk中的无关类删了,比如MainActivity。
2、宿主apk加载补丁apk从而修复bug
我们假如宿主apk已经在线下下载好了补丁apk,并将补丁apk放在了cache目录中。
最后我们在宿主apk中通过调用PatchMain.load()
方法来加载补丁apk,代码如下:
public void click(View view){
if(!MyApplication.isCanDexPosed()){
Log.v("zxy","can not support DexPosed!");
return;
}
String patchPath = this.getCacheDir().getPath()+ File.separator+"patch.apk";
PatchResult result = PatchMain.load(this, patchPath, null);
if(result.isSuccess()){
new AlertDialog.Builder(this,AlertDialog.THEME_HOLO_LIGHT).setTitle("提示").setMessage("加载MsgPatch补丁成功!").setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).create().show();
}
}
这样,当加载补丁成功后,即用补丁中的代码替换了宿主apk中getMsg中的代码,从而修复了bug。
效果图:
这个demo用genymotion演示不了,只能用真机,然后360录屏太渣渣,录的有点不好,鼠标点击顺序依次是:展示信息->加载补丁->展示信息->退出->再次打开,展示信息。这个效果充分说明了在线热更新修复bug非常方便
Demo源码下载
参考资料:
Android免Root无侵入AOP框架Dexposed的更多相关文章
- Android平台免Root无侵入AOP框架Dexposed使用详解
Dexposed是基于久负盛名的开源Xposed框架实现的一个Android平台上功能强大的无侵入式运行时AOP框架. Dexposed的AOP实现是完全非侵入式的,没有使用任何注解处理器,编织器或者 ...
- Android新技术学习——阿里巴巴免Root无侵入AOP框架Dexposed
阿里巴巴无线事业部近期开源的Android平台下的无侵入运行期AOP框架Dexposed,该框架基于AOP思想,支持经典的AOP使用场景.可应用于日志记录,性能统计,安全控制.事务处理.异常处理等方面 ...
- Dexposed:android免Root无侵入Aop框架
在网上看到了阿里推出的一个android开源项目,名为Dexposed, 是一个Android平台下的无侵入运行期AOP框架.旨在解决像性能监控.在线热补丁等移动开发常见难题,典型使用场景为: AOP ...
- android免root hook框架legend
一.前言 Android中hook框架已经非常多了,最优秀的当属Xposed和Substrate了,这两个框架我在之前的文章都详细介绍过了,不了解的同学,可以转战这里:http://www.wjdia ...
- Android 免Root实现Apk静默安装,覆盖兼容市场主流的98%的机型
地址:http://blog.csdn.net/sk719887916/article/details/46746991 作者: skay 最近在做apk自我静默更新,在获取内置情况下,或者已root ...
- android免root兼容所有版本ui调试工具
SwissArmyKnife是什么 SwissArmyKnife 是一款方便调试android UI的工具,可以兼容所有android版本,不需要root权限.可以直接在android手机屏幕上显示当 ...
- Android中免root的hook框架Legend原理解析
一.前言 Android中hook框架已经非常多了,最优秀的当属Xposed和Substrate了,这两个框架我在之前的文章都详细介绍过了,不了解的同学,可以转战这里:http://www.wjdia ...
- [Android Pro] Android 4.1 使用 Accessibility实现免Root自动批量安装功能
reference to : http://www.infoq.com/cn/articles/android-accessibility-installing?utm_campaign=info ...
- 免Root停用“Android键盘(AOSP)”
一.效果:隐藏手机状态栏输入法选择图标: 二.手段:使用ADB免root 停用系统默认Android键盘(AOSP),这里参考了大神的方法,在此表示感谢: 三.实现过程: 上图 下面就是按照大神的方法 ...
随机推荐
- 周口网视界易付点卡销售平台招商中 www.zkpay.cn 欢迎各界朋友加盟合作。
周口网视界易付点卡销售平台针对全国各地网吧及游戏点卡代理招商中. http://www.zkpay.cn 腾讯新的游戏点卡销售平台,平台价优稳定,这个是老家朋友开的公司,欢迎全国各地网吧客户及游戏 ...
- 短文本分析----基于python的TF-IDF特征词标签自动化提取
绪论 最近做课题,需要分析短文本的标签,在短时间内学习了自然语言处理,社会标签推荐等非常时髦的技术.我们的需求非常类似于从大量短文本中获取关键词(融合社会标签和时间属性)进行用户画像.这一切的基础就是 ...
- Swift完成fizz buzz test
看到一篇文章上说,很多貌似看过很多本编程书的童鞋连简单的fizz buzz测试都完不成. 不知道fizz buzz test为何物的,建议自行搜之. 测试要求是,编写满足以下条件的代码: Write ...
- Python 表示无穷大的数
我之前只知道设置初始值0.今天偶然在Python算法书上看到这个片段,从100个随机数里面找2个最靠近的自然数(不相等): from random import randrange seq = [ra ...
- Dynamics CRM 视图显示列的拷贝—view layout replicator
在视图设置的时候很多人会遇到这样的问题,要设置多张视图,而这多张视图可能除了筛选条件不同外其他的均相同,手动去设置是件重复的令人非常头痛的事情,如果能够拷贝那就相当完美了. 本篇即介绍视图显示列的拷贝 ...
- FFmpeg源代码简单分析:内存的分配和释放(av_malloc()、av_free()等)
===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...
- 【移动开发】Service类onStartCommand()返回值和参数
Android开发的过程中,每次调用startService(Intent)的时候,都会调用该Service对象的onStartCommand(Intent,int,int)方法,然后在onStart ...
- JS 遍历对象 jQuery遍历对象
jquery for 循环遍历对象的属性: //对象的定义如下: var person={id:"1",name:"springok",age:25}; for ...
- Java-IO之字符输入输出流(Reader和Writer)
以字符为单位的输入流的公共父类是Reader: 以字符为单位的输出流的超类是Writer: 基于JDK8的Reader的源码: public abstract class Reader impleme ...
- UNIX环境高级编程——计算机体系结构基础知识
无论是在CPU外部接总线的设备还是在CPU内部接总线的设备都有各自的地址范围,都可以像访问内存一样访问,很多体系结构(比如ARM)采用这种方式操作设备,称为等都会产生异常. 通常操作系统把虚拟地址空间 ...