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. 用Netty解析Redis网络协议

    用Netty解析Redis网络协议 根据Redis官方文档的介绍,学习了一下Redis网络通信协议.然后偶然在GitHub上发现了个用Netty实现的Redis服务器,很有趣,于是就动手实现了一下! ...

  2. ubuntu垃圾清理命令

    ubuntu的空间莫名不够用了 通过系统自带的工具磁盘使用分析器,发现var文件下面的log100多个g,这个日志文件是可以删除的,然后tmp文件也是可以删除的. 1.sudo rm -rf /tmp ...

  3. Android的Spinner控件用法解析

    微调框 微调框提供一种方法,让用户可以从值集中快速选择一个值.默认状态下,微调框显示其当前所选的值. 触摸微调框可显示下拉菜单,其中列有所有其他可用值,用户可从中选择一个新值. 您可以使用 Spinn ...

  4. 国内外主流BI工具介绍和点评

    商业智能的应用在国外已广为普及,并且开始不断探索大数据和云技术.而国内,商业智能BI工具在这几年才开始慢慢被接受,企业开始有意识地建立一体化数据分析平台,为经营决策提供分析. 从国内企业使用情况来看, ...

  5. YYModel V1.0.4源码解析

    YYKit出现了很长时间了,一直想要详细解析一下它的源码,都是各种缘由推迟了. 最近稍微闲了一点,决定先从最简单的YYModel开始吧. 首先,我也先去搜索了一下YYModel相关的文章,解析主要AP ...

  6. EBS并发程序监控

    SELECT s.* FROM fnd_concurrent_requests r, v$session v, v$sql s WHERE r.oracle_session_id = v.audsid ...

  7. Android自定义View(三、深入解析控件测量onMeasure)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51490283 本文出自:[openXu的博客] 目录: onMeasure什么时候会被调用 ...

  8. listener.ora--sqlnet.ora--tnsnames.ora的关系以及手工配置举例(转载:http://blog.chinaunix.net/uid-83572-id-5510.ht)

    listener.ora--sqlnet.ora--tnsnames.ora的关系以及手工配置举例 ====================最近看到好多人说到tns或者数据库不能登录等问题,就索性总结 ...

  9. GitHub无法访问或访问缓慢解决办法

    缘由 由于众所周知的原因,Github最近无法访问或访问很慢.由于Github支持https,因此此次屏蔽Github采用的方法是dns污染,用户访问github会返回一个错误的IPFQ当然是一种解决 ...

  10. 使用std::vector优化点云动画显示一例

    1. 准备 使用std::vector应该知道几点: (1)内存连续的容器,有点像数组 (2)与std::list相比,插入和删除元素比较慢- 因为数据迁移 (3)添加元素可能会引发内存分配和数据迁移 ...