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的更多相关文章

  1. Android平台免Root无侵入AOP框架Dexposed使用详解

    Dexposed是基于久负盛名的开源Xposed框架实现的一个Android平台上功能强大的无侵入式运行时AOP框架. Dexposed的AOP实现是完全非侵入式的,没有使用任何注解处理器,编织器或者 ...

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

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

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

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

  4. android免root hook框架legend

    一.前言 Android中hook框架已经非常多了,最优秀的当属Xposed和Substrate了,这两个框架我在之前的文章都详细介绍过了,不了解的同学,可以转战这里:http://www.wjdia ...

  5. Android 免Root实现Apk静默安装,覆盖兼容市场主流的98%的机型

    地址:http://blog.csdn.net/sk719887916/article/details/46746991 作者: skay 最近在做apk自我静默更新,在获取内置情况下,或者已root ...

  6. android免root兼容所有版本ui调试工具

    SwissArmyKnife是什么 SwissArmyKnife 是一款方便调试android UI的工具,可以兼容所有android版本,不需要root权限.可以直接在android手机屏幕上显示当 ...

  7. Android中免root的hook框架Legend原理解析

    一.前言 Android中hook框架已经非常多了,最优秀的当属Xposed和Substrate了,这两个框架我在之前的文章都详细介绍过了,不了解的同学,可以转战这里:http://www.wjdia ...

  8. [Android Pro] Android 4.1 使用 Accessibility实现免Root自动批量安装功能

    reference to  :  http://www.infoq.com/cn/articles/android-accessibility-installing?utm_campaign=info ...

  9. 免Root停用“Android键盘(AOSP)”

    一.效果:隐藏手机状态栏输入法选择图标: 二.手段:使用ADB免root 停用系统默认Android键盘(AOSP),这里参考了大神的方法,在此表示感谢: 三.实现过程: 上图 下面就是按照大神的方法 ...

随机推荐

  1. Dynamics CRM 不同的站点地图下设置默认不同的仪表板

    CRM的默认仪表板只能设置一个,也就是说每个引用仪表板的站点地图下点开仪表板后都是看到的默认仪表板,例如我下图中的"日常维修仪表板" 那如果我要在不同的站点地图下看到的默认仪表板不 ...

  2. Android Studio突然不显示logcat日志

    参考文章:http://blog.csdn.net/victor_e_n_01185/article/details/52818809 有时候,AS出现没有log的情况.一般您换了模拟器,或者使用真机 ...

  3. Android基础知识点-Manifest清单文件

    每个应用的根目录中都必须包含一个 AndroidManifest.xml 文件(且文件名精确无误). 清单文件向 Android 系统提供应用的必要信息,系统必须具有这些信息方可运行应用的任何代码. ...

  4. 用Python最原始的函数模拟eval函数的浮点数运算功能(2)

    这应该是我编程以来完成的难度最大的一个函数了.因为可能存在的情况非常多,需要设计合理的参数来控制解析流程.经验概要: 1.大胆假设一些子功能能够实现,看能否建立整个框架.如果在假设的基础上都无法建立, ...

  5. 关于JQuery中的ajax请求或者post请求的回调方法中的操作执行或者变量修改没反映的问题

    前段时间做一个项目,而项目中所有的请求都要用jquery 中的ajax请求或者post请求,但是开始处理一些简单操作还好,但是自己写了一些验证就出现问题了,比如表单提交的时候,要验证帐号的唯一性,所以 ...

  6. [Python]print vs sys.stdout.write

    之前只是在项目中看到过,没怎么注意,正好跟对象一起看python学习手册,看到了这个部分于是来研究下. python版本 2.7.x os  win7 print  一般就是执行脚本的时候,把信息直接 ...

  7. [error]configure: error: You need a C++ compiler for C++ support.

    安装pcre包的时候提示缺少c++编译器 解决办法 使用yum安装 yum -y install gcc-c++ 本文出自 "orangleliu笔记本"博客,转载请务必保留此出处 ...

  8. Unity3D核心技术详解

    在这里将多年游戏研发经验的积累写成一本书奉献给读者,目前已经开始预售,网址: http://www.broadview.com.cn/article/70 该书主要是将游戏中经常使用的技术给大家做了一 ...

  9. [ExtJS5学习笔记]第三十二节 sencha extjs 5与struts2的ajax交互配置

    本文地址:http://blog.csdn.net/sushengmiyan/article/details/43487751 本文作者:sushengmiyan ------------------ ...

  10. 没想到你是这样的UDP

    UDP是国际标准化组织为互联网设定的标准中的传输层中的一个协议.TCP/IP协议簇是一个很庞大的家族,但是今天我们就来看一看这个面向无连接的传输层在Java中是怎样通过编程实现的. 原理性知识 在Ja ...