Dexposed是基于久负盛名的开源Xposed框架实现的一个Android平台上功能强大的无侵入式运行时AOP框架。

Dexposed的AOP实现是完全非侵入式的,没有使用任何注解处理器,编织器或者字节码重写器。集成Dexposed框架很简单,只需要在应用初始化阶段加载一个很小的JNI库就可以,这个加载操作已经封装在DexposedBridge函数库里面的canDexposed函数中,源码如下所示:

/**
* Check device if can run dexposed, and load libs auto.
*/
public synchronized static boolean canDexposed(Context context) {
if (!DeviceCheck.isDeviceSupport(context)) {
return false;
}
//load xposed lib for hook.
return loadDexposedLib(context);
} private static boolean loadDexposedLib(Context context) {
// load xposed lib for hook.
try {
if (android.os.Build.VERSION.SDK_INT > 19){
System.loadLibrary("dexposed_l");
} else if (android.os.Build.VERSION.SDK_INT == 10
|| android.os.Build.VERSION.SDK_INT == 9 ||
android.os.Build.VERSION.SDK_INT > 14){
System.loadLibrary("dexposed");
}
return true;
} catch (Throwable e) {
return false;
}
}

Dexposed实现的hooking,不仅可以hook应用中的自定义函数,也可以hook应用中调用的Android框架的函数。Android开发者将从这一点得到很多好处,因为我们严重依赖于Android SDK的版本碎片化。

基于动态类加载技术,运行中的app可以加载一小段经过编译的Java AOP代码,在不需要重启app的前提下实现修改目标app的行为。

典型的使用场景

  • AOP编程
  • 插桩(例如测试,性能监控等)
  • 在线热更新,修复严重的,紧急的或者安全性的bug
  • SDK hooking以提供更好的开发体验

如何集成

集成方式很简单,只需要将一个jar包加入项目的libs文件夹中,同时将两个so文件添加到jniLibs中对应的ABI目录中即可。Gradle依赖如下所示:

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.10.+'
classpath 'com.nabilhachicha:android-native-dependencies:0.1'
}
} ... native_dependencies {
artifact 'com.taobao.dexposed:dexposed_l:0.2+:armeabi'
artifact 'com.taobao.dexposed:dexposed:0.2+:armeabi'
}
dependencies {
compile files('libs/dexposedbridge.jar')
}

其中,native_dependencies是一个第三方插件,使用方法可参考《如何在Android Gradle中添加原生so文件依赖》。当然,我们也可以直接把需要用到的so文件直接拷贝到jniLibs目录中,这样的话,可以把上面的native_dependencies代码段注释掉。

同时应该在应用初始化的地方(尽可能早的添加)添加初始化Dexposed的代码,例如在MyApplication中添加:

public class MyApplication extends Application {

    private boolean mIsSupported = false; // 设备是否支持dexposed

    private boolean mIsLDevice = false;  // 设备Android系统是否是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;
}
}

基本用法

对于某个函数而言,有三个注入点可供选择:函数执行前注入(before),函数执行后注入(after),替换函数执行的代码段(replace),分别对应于抽象类XC_MethodHook及其子类XC_MethodReplacement中的函数:

public abstract class XC_MethodHook extends XCallback {

    /**
* Called before the invocation of the method.
* <p>Can use {@link MethodHookParam#setResult(Object)} and {@link MethodHookParam#setThrowable(Throwable)}
* to prevent the original method from being called.
*/
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {} /**
* Called after the invocation of the method.
* <p>Can use {@link MethodHookParam#setResult(Object)} and {@link MethodHookParam#setThrowable(Throwable)}
* to modify the return value of the original method.
*/
protected void afterHookedMethod(MethodHookParam param) throws Throwable {}
}
public abstract class XC_MethodReplacement extends XC_MethodHook {

    @Override
protected final void beforeHookedMethod(MethodHookParam param) throws Throwable {
try {
Object result = replaceHookedMethod(param);
param.setResult(result);
} catch (Throwable t) {
param.setThrowable(t);
}
} protected final void afterHookedMethod(MethodHookParam param) throws Throwable {
} /**
* Shortcut for replacing a method completely. Whatever is returned/thrown here is taken
* instead of the result of the original method (which will not be called).
*/
protected abstract Object replaceHookedMethod(MethodHookParam param) throws Throwable;
}

可以看到这三个注入回调函数都有一个类型为MethodHookParam的参数,这个参数包含了一些很有用的信息:

  • MethodHookParam.thisObject:这个类的一个实例
  • MethodHookParam.args:用于传递被注入函数的所有参数
  • MethodHookParam.setResult:用于修改原函数调用的结果,如果在beforeHookedMethod回调函数中调用setResult,可以阻止对原函数的调用。但是如果有返回值的话仍然需要通过hook处理器进行return操作。

MethodHookParam代码如下所示:

public static class MethodHookParam extends XCallback.Param {
/** Description of the hooked method */
public Member method;
/** The <code>this</code> reference for an instance method, or null for static methods */
public Object thisObject;
/** Arguments to the method call */
public Object[] args; private Object result = null;
private Throwable throwable = null;
/* package */ boolean returnEarly = false; /** Returns the result of the method call */
public Object getResult() {
return result;
} /**
* Modify the result of the method call. In a "before-method-call"
* hook, prevents the call to the original method.
* You still need to "return" from the hook handler if required.
*/
public void setResult(Object result) {
this.result = result;
this.throwable = null;
this.returnEarly = true;
} /** Returns the <code>Throwable</code> thrown by the method, or null */
public Throwable getThrowable() {
return throwable;
} /** Returns true if an exception was thrown by the method */
public boolean hasThrowable() {
return throwable != null;
} /**
* Modify the exception thrown of the method call. In a "before-method-call"
* hook, prevents the call to the original method.
* You still need to "return" from the hook handler if required.
*/
public void setThrowable(Throwable throwable) {
this.throwable = throwable;
this.result = null;
this.returnEarly = true;
} /** Returns the result of the method call, or throws the Throwable caused by it */
public Object getResultOrThrowable() throws Throwable {
if (throwable != null)
throw throwable;
return result;
}
}

例子一:AOP编程

AOP(Aspect Oriented Programming),也就是面向方面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP一般应用在日志记录,性能统计,安全控制,事务处理,异常处理等方面,它的主要意图是将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

例如我们可以在应用中所有的Activity.onCreate(Bundle)函数调用之前和之后增加一些相同的处理:

// Target class, method with parameter types, followed by the hook callback (XC_MethodHook).
DexposedBridge.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodHook() { // To be invoked before Activity.onCreate().
@Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
// "thisObject" keeps the reference to the instance of target class.
Activity instance = (Activity) param.thisObject; // The array args include all the parameters.
Bundle bundle = (Bundle) param.args[0];
Intent intent = new Intent();
// XposedHelpers provide useful utility methods.
XposedHelpers.setObjectField(param.thisObject, "mIntent", intent); // Calling setResult() will bypass the original method body use the result as method return value directly.
if (bundle.containsKey("return"))
param.setResult(null);
} // To be invoked after Activity.onCreate()
@Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedHelpers.callMethod(param.thisObject, "sampleMethod", 2);
}
});

当然也可以替换目标函数原来执行的代码段:

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.
...
} });

例子二:在线热更新

在线热更新一般用于修复线上严重的,紧急的或者安全性的bug,这里会涉及到两个apk文件,一个我们称为宿主apk,也就是发布到应用市场的apk,一个称为补丁apk。宿主apk出现bug时,通过在线下载的方式从服务器下载到补丁apk,使用补丁apk中的函数替换原来的函数,从而实现在线修复bug的功能。

为了实现这个功能,需要再引入一个名为patchloader的jar包,这个函数库实现了一个热更新框架,宿主apk在发布时会将这个jar包一起打包进apk中,而补丁apk只是在编译时需要这个jar包,但打包成apk时不包含这个jar包,以免补丁apk集成到宿主apk中时发生冲突。因此,补丁apk将会以provided的形式依赖dexposedbridge.jar和patchloader.jar,补丁apk的build.gradle文件中依赖部分脚本如下所示:

dependencies {
provided files('libs/dexposedbridge.jar')
provided files('libs/patchloader.jar')
}

这里我们假设宿主apk的MainActivity.showDialog函数出现问题,需要打补丁,宿主代码如下所示:(类完整路径是com.taobao.dexposed.MainActivity)

package com.taobao.dexposed;

public class MainActivity extends Activity {

    private void showDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Dexposed sample")
.setMessage(
"Please clone patchsample project to generate apk, and copy it to \"/Android/data/com.taobao.dexposed/cache/patch.apk\"")
.setPositiveButton("ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
}).create().show();
}
}

补丁apk只有一个名为DialogPatch的类,实现了patchloader函数库中的IPatch接口,IPatch接口代码如下所示:

/**
* The interface implemented by hotpatch classes.
*/
public interface IPatch { void handlePatch(PatchParam lpparam) throws Throwable; }

DialogPatch类实现IPatch的handlePatch函数,在该函数中通过反射得到宿主APK中com.taobao.dexposed.MainActivity类实例,然后调用dexposedbridge函数库中的DexposedBridge.findAndHookMethod函数,对MainActivity中的showDialog函数进行Hook操作,替换宿主apk中的相应代码,DialogPatch代码如下所示:

public class DialogPatch implements IPatch {

    @Override
public void handlePatch(final PatchParam arg0) throws Throwable {
Class<?> cls = null;
try {
cls= arg0.context.getClassLoader()
.loadClass("com.taobao.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("Dexposed sample")
.setMessage("The dialog is shown from patch apk!")
.setPositiveButton("ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
}).create().show();
return null;
}
});
} }

最后宿主apk通过调用patchloader函数库提供的PatchMain.load函数来动态加载下载到的补丁apk,加载代码如下所示:

// Run patch 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){
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();
}

为便于理解,这里也把load函数体贴出来,更详细内容大家可以看源码:

/**
* Load a runnable patch apk.
*
* @param context the application or activity context.
* @param apkPath the path of patch apk file.
* @param contentMap the object maps that will be used by patch classes.
* @return PatchResult include if success or error detail.
*/
public static PatchResult load(Context context, String apkPath, HashMap<String, Object> contentMap) { if (!new File(apkPath).exists()) {
return new PatchResult(false, PatchResult.FILE_NOT_FOUND, "FILE not found on " + apkPath);
} PatchResult result = loadAllCallbacks(context, apkPath,context.getClassLoader());
if (!result.isSuccess()) {
return result;
} if (loadedPatchCallbacks.getSize() == 0) {
return new PatchResult(false, PatchResult.NO_PATCH_CLASS_HANDLE, "No patch class to be handle");
} PatchParam lpparam = new PatchParam(loadedPatchCallbacks);
lpparam.context = context;
lpparam.contentMap = contentMap; return PatchCallback.callAll(lpparam);
}

支持的系统版本

Dexposed支持从Android2.3到4.4(除了3.0)的所有dalvid运行时arm架构的设备,稳定性已经经过实践检验。

支持的系统版本:

  • Dalvik 2.3
  • Dalvik 4.0~4.4

不支持的系统版本:

  • Dalvik 3.0
  • ART 5.1
  • ART M

测试中的系统版本:

  • ART 5.0

未经测试的系统版本:

  • Dalvik 2.2

使用Dexposed的项目

目前阿里系主流app例如手机淘宝,支付宝,天猫都使用了Dexposed支持在线热更新,而开源项目中,在Github上面能搜到的只有一个XLog项目,它的主要功能是方便的打印函数调用和耗时日志,这也是一个了解Dexposed如何使用的很好的例子。

参考资料

文/asce1885(简书作者)
原文链接:http://www.jianshu.com/p/14edcb444c51
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

Android平台免Root无侵入AOP框架Dexposed使用详解的更多相关文章

  1. Android免Root无侵入AOP框架Dexposed

    Dexposed框架是阿里巴巴无线事业部近期开源的一款在Android平台下的免Root无侵入运行期AOP框架,该框架基于AOP思想,支持经典的AOP使用场景,可应用于日志记录,性能统计,安全控制,事 ...

  2. Android新技术学习——阿里巴巴免Root无侵入AOP框架Dexposed

    阿里巴巴无线事业部近期开源的Android平台下的无侵入运行期AOP框架Dexposed,该框架基于AOP思想,支持经典的AOP使用场景.可应用于日志记录,性能统计,安全控制.事务处理.异常处理等方面 ...

  3. Dexposed:android免Root无侵入Aop框架

    在网上看到了阿里推出的一个android开源项目,名为Dexposed, 是一个Android平台下的无侵入运行期AOP框架.旨在解决像性能监控.在线热补丁等移动开发常见难题,典型使用场景为: AOP ...

  4. Android开发之最火的开源框架之一Xutils2详解(摘自开源作者官方介绍详解)

    此框架说实话还是挺不错的,挺好用的,功能多,所以我也用过. 由于CSDN博客写的字数有限制,所以全文的用法打包成了markdown 文件,因为markdown真的太还用了. 全文下载地址为: http ...

  5. Android平台dalvik模式下java Hook框架ddi的分析(2)--dex文件的注入和调用

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/77942585 前面的博客<Android平台dalvik模式下java Ho ...

  6. java的集合框架最全详解

    java的集合框架最全详解(图) 前言:数据结构对程序设计有着深远的影响,在面向过程的C语言中,数据库结构用struct来描述,而在面向对象的编程中,数据结构是用类来描述的,并且包含有对该数据结构操作 ...

  7. Android四大组件之——Activity的生命周期(图文详解)

        转载请在文章开头处注明本博客网址:http://www.cnblogs.com/JohnTsai       联系方式:JohnTsai.Work@gmail.com       [Andro ...

  8. Django框架 之 querySet详解

    Django框架 之 querySet详解 浏览目录 可切片 可迭代 惰性查询 缓存机制 exists()与iterator()方法 QuerySet 可切片 使用Python 的切片语法来限制查询集 ...

  9. TP框架I方法详解

    TP框架I方法详解   I方法是ThinkPHP众多单字母函数中的新成员,其命名来自于英文Input(输入),主要用于更加方便和安全的获取系统输入变量,可以用于任何地方,用法格式如下:I('变量类型. ...

随机推荐

  1. git常用命令1

    git clone   #克隆远程仓库的项目到本地 git config --global user.name ""    #查看或设置贡献者的名字 git config --gl ...

  2. DIV布局-高度不同DIV,自动换行并对齐

    最近弄了一个动态添加div框,每个div框内容有多有少,要支持div高度自适应,还要添加的div自动追加,并且换行还要保持每行对齐. 刚开始的效果: 要改啊,搞不定,问了UI高手,终于给出了完美解决方 ...

  3. JavaI/O系统

    I/O:(输入/输出)指的是计算机与外部世界,或者一个与计算机其余部分的接口.它对任何计算机系统都非常关键. Java类库中有大量的类,帮助我们从不同的设备读取数据并保存或输出到不同的设备中. 这些类 ...

  4. 【洛谷P1941】飞扬的小鸟

    f [ i ] [ j ] 表示横坐标为 i ,高度为 j 时的最小点击次数 分别dp处理: 1.上升,(1)<m (2)>=m 2.下降 3.管道 #include<cstdio& ...

  5. tesseract api C++使用例子

    转自:https://code.google.com/p/tesseract-ocr/wiki/APIExample APIExample API examples Updated Aug 12, 2 ...

  6. 修改默认MYSQL数据库data存放位置

    随着业务量的增长,mysql默认安装所在分区大小出现瓶颈,通常需要将datadir换到较大的分区 示例原目录:/usr/local/mysql/data/示例新目录:/data/mysqldata/ ...

  7. step by step 之餐饮管理系统三

    1.说明 表名的长度最长为18个字符 茶色的字段为主键或联合主键 浅黄色的字段为索引 浅灰底色的字段为临时表中比正式表多出的字段 数据库系统:Sqlserver2008 脚本工具:使用CodeGene ...

  8. Eclipse4.6(Neon) + Tomcat8 + MAVEN3.3.9 + SVN项目完整环境搭建

    软件清单 jdk-8u102-windows-x64.exe eclipse-inst-win64.exe (Eclipse4.6 Neon) apache-tomcat-8.5.5-windows- ...

  9. linux SMP启动

    SMP简介 1,硬件上,CPU没有主次之分 2,软件上,每个CPU平等动态地从进程就绪队列中调度进程加以执行,中断请求也是等概率动态的分布给某个CPU SMP启动 1,SMP结构中的CPU都是平等的, ...

  10. log4net简单配置内容

    首先将log4net.dll下载来,添加到项目引用中: 在assembly文件最后面加(其实没关系的): [assembly: log4net.Config.XmlConfigurator(Confi ...