Android新技术学习——阿里巴巴免Root无侵入AOP框架Dexposed
2015-08-07 15:46
609 查看
阿里巴巴无线事业部最近开源的Android平台下的无侵入运行期AOP框架Dexposed,该框架基于AOP思想,支持经典的AOP使用场景,可应用于日志记录,性能统计,安全控制,事务处理,异常处理等方面。
针对Android平台,Dexposed支持函数级别的在线热更新,例如对已经发布在应用市场上的宿主APK,当我们从crash统计平台上发现某个函数调用有bug,导致经常性crash,这时,可以在本地开发一个补丁APK,并发布到服务器中,宿主APK下载这个补丁APK并集成后,就可以很容易修复这个crash。
Dexposed是基于久负盛名的开源Xposed框架实现的一个Android平台上功能强大的无侵入式运行时AOP框架。Dexposed的AOP实现是完全非侵入式的,没有使用任何注解处理器,编织器或者字节码重写器
先来讲讲集成方法,从项目地址下载整个项目dexposed,将..\dexposed\sample\patchsample\app\libs目录下的两个jar拷出来备用,以及将..\dexposed\sample\dexposedexamples\app\src\main\jniLibs目录下的native库拷出来备用。
打开Android Studio,新建项目,将前面拷出来的jar拷到libs目录下,在main目录下新建jniLibs目录,将native库拷进去。最终项目结构会成这样
![](http://img.blog.csdn.net/20150807154447176)
为了应用热更新,所以我们还要建一个module用于编写热更新的代码,用Android studio新建一个module,这里让其名叫patch,将之前的jar拷入到patch下的libs目录。而native库不需要拷。之后进行同步,点击如图图标
![](http://img.blog.csdn.net/20150807154455168)
我们在Application中检查是否支持Dexposed,编写一个子类继承Application类,并在Manifest文件中指定该类。
我们调用了 DexposedBridge.canDexposed函数用于判断是否支持Dexposed,如果支持,我们则做下一步动作。
现在有这么一个需求,需要给每个Activity的onCreate调用前增加日志,调用完成后增加日志,调用完成后需要调用另外一个统计用的方法。同时,需要直接替换掉MainActivity中的一个叫replaceMethod的方法。我们来编写代码。
代码中调用了 XposedHelpers.callMethod进行反射调用统计方法statics。
replcaeMethod方法的原型
statics方法原型
然后我们调用hook函数
同时我们的MainActivity中进行了调用replaceMethod
这时候观察一下日志输出
![](http://img.blog.csdn.net/20150807154509446)
我们发现在onCreate方法前后都有日志输出,并且onCreate后有统计方法的调用,而replaceMethod方法的内容以及完全被替换了。
我们开到前面使用了XposedHelpers类,这个类是一个辅助类,里面全是跟反射相关的。使用了DexposedBridge.findAndHookMethod进行注入。
对于某个函数而言,有三个注入点可供选择:函数执行前注入(before),函数执行后注入(after),替换函数执行的代码段(replace),分别对应于抽象类XC_MethodHook及其子类XC_MethodReplacement中的函数:
可以看到这三个注入回调函数都有一个类型为MethodHookParam的参数,这个参数包含了一些很有用的信息:
MethodHookParam.thisObject:这个类的一个实例
MethodHookParam.args:用于传递被注入函数的所有参数
MethodHookParam.setResult:用于修改原函数调用的结果,如果在beforeHookedMethod回调函数中调用setResult,可以阻止对原函数的调用。但是如果有返回值的话仍然需要通过hook处理器进行return操作。
下面我们来应用一下在线热更新
在线热更新一般用于修复线上严重的,紧急的或者安全性的bug,这里会涉及到两个apk文件,一个我们称为宿主apk,也就是发布到应用市场的apk,一个称为补丁apk。宿主apk出现bug时,通过在线下载的方式从服务器下载到补丁apk,使用补丁apk中的函数替换原来的函数,从而实现在线修复bug的功能。
为了实现这个功能,需要再引入一个名为patchloader的jar包,我们已将将它拷到libs目录下,这个函数库实现了一个热更新框架,宿主apk在发布时会将这个jar包一起打包进apk中,而补丁apk只是在编译时需要这个jar包,但打包成apk时不包含这个jar包,以免补丁apk集成到宿主apk中时发生冲突。因此,补丁apk将会以provided的形式依赖dexposedbridge.jar和patchloader.jar,补丁apk也就是patch的build.gradle文件中依赖部分脚本如下所示:
现在假设我们MainActivity中有一个初始化数据的方法
但是我们手误返回了null,但是在MainActivity中调用了这个返回值的内容
于是就产生了常见的异常,即空指针异常。我们使用patch来修复这个bug。我们需要实现IPatch接口
我们的补丁程序返回了几个数据,完成后我们生成module为patch的apk。然后将该apk拷到对应的目录下,这里简单的将其拷到Android/data/your package/cache/pacth.apk目录下。然后我们要应用该补丁
之后你会发现不再报空指针了。
对于热更新,我们可以在手机淘宝中找到它的影子。如下图
![](http://img.blog.csdn.net/20150807154544903)
总之,该库前途无量,但是目前支持的系统有限,也希望它能不断发展,造福开发者。
源码下载
http://download.csdn.net/detail/sbsujjbcy/8973895
针对Android平台,Dexposed支持函数级别的在线热更新,例如对已经发布在应用市场上的宿主APK,当我们从crash统计平台上发现某个函数调用有bug,导致经常性crash,这时,可以在本地开发一个补丁APK,并发布到服务器中,宿主APK下载这个补丁APK并集成后,就可以很容易修复这个crash。
Dexposed是基于久负盛名的开源Xposed框架实现的一个Android平台上功能强大的无侵入式运行时AOP框架。Dexposed的AOP实现是完全非侵入式的,没有使用任何注解处理器,编织器或者字节码重写器
先来讲讲集成方法,从项目地址下载整个项目dexposed,将..\dexposed\sample\patchsample\app\libs目录下的两个jar拷出来备用,以及将..\dexposed\sample\dexposedexamples\app\src\main\jniLibs目录下的native库拷出来备用。
打开Android Studio,新建项目,将前面拷出来的jar拷到libs目录下,在main目录下新建jniLibs目录,将native库拷进去。最终项目结构会成这样
为了应用热更新,所以我们还要建一个module用于编写热更新的代码,用Android studio新建一个module,这里让其名叫patch,将之前的jar拷入到patch下的libs目录。而native库不需要拷。之后进行同步,点击如图图标
我们在Application中检查是否支持Dexposed,编写一个子类继承Application类,并在Manifest文件中指定该类。
=21; if (mIsSupported) { //do something } public boolean isSupported(){ return mIsSupported; } public boolean isLDevice(){ return mIsLDevice; } } " data-snippet-id="ext.cbac1920fe2b159e8d09527399afb014" data-snippet-saved="false" data-csrftoken="ghze0m0H-iLDfP_9FkxsQ8VHeDWDVMNpzddQ">[code]/** * User:lizhangqu(513163535@qq.com) * Date:2015-08-06 * Time: 13:46 */ public class App extends Application { private boolean mIsSupported = false; private boolean mIsLDevice = false; @Override public void onCreate() { super.onCreate(); mIsSupported= DexposedBridge.canDexposed(this); mIsLDevice= Build.VERSION.SDK_INT>=21; if (mIsSupported) { //do something } public boolean isSupported(){ return mIsSupported; } public boolean isLDevice(){ return mIsLDevice; } }
我们调用了 DexposedBridge.canDexposed函数用于判断是否支持Dexposed,如果支持,我们则做下一步动作。
现在有这么一个需求,需要给每个Activity的onCreate调用前增加日志,调用完成后增加日志,调用完成后需要调用另外一个统计用的方法。同时,需要直接替换掉MainActivity中的一个叫replaceMethod的方法。我们来编写代码。
private void hook() { DexposedBridge.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { Log.e("TAG", "onCreate:" + param.thisObject.getClass().getSimpleName() + "start"); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Log.e("TAG", "onCreate:" + param.thisObject.getClass().getSimpleName() + "end"); XposedHelpers.callMethod(param.thisObject, "statics", new Class[]{long.class}, System.currentTimeMillis()); } }); DexposedBridge.findAndHookMethod(MainActivity.class, "replaceMethod", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { Log.e("TAG", "the method has replaced by DexposedBridge!"); return "has replaced"; } }); }
代码中调用了 XposedHelpers.callMethod进行反射调用统计方法statics。
replcaeMethod方法的原型
public String replaceMethod(){ Log.e("TAG","replaceMethod"); return "replaceMethod"; }
statics方法原型
public void statics(long a){ Log.e("TAG","==now:"+a+"=="); //do something }
然后我们调用hook函数
if (mIsSupported) { hook(); }
同时我们的MainActivity中进行了调用replaceMethod
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String result=replaceMethod(); Log.d("TAG","result:"+result); }
这时候观察一下日志输出
我们发现在onCreate方法前后都有日志输出,并且onCreate后有统计方法的调用,而replaceMethod方法的内容以及完全被替换了。
我们开到前面使用了XposedHelpers类,这个类是一个辅助类,里面全是跟反射相关的。使用了DexposedBridge.findAndHookMethod进行注入。
对于某个函数而言,有三个注入点可供选择:函数执行前注入(before),函数执行后注入(after),替换函数执行的代码段(replace),分别对应于抽象类XC_MethodHook及其子类XC_MethodReplacement中的函数:
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. * 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 {} }" data-snippet-id="ext.ec5a90af279f1546a0cfe7416996bf4e" data-snippet-saved="false" data-csrftoken="b2P6Twyq-u0J4zOnMUmMfyyRhCgBEcDJQTtI">[code]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操作。
下面我们来应用一下在线热更新
在线热更新一般用于修复线上严重的,紧急的或者安全性的bug,这里会涉及到两个apk文件,一个我们称为宿主apk,也就是发布到应用市场的apk,一个称为补丁apk。宿主apk出现bug时,通过在线下载的方式从服务器下载到补丁apk,使用补丁apk中的函数替换原来的函数,从而实现在线修复bug的功能。
为了实现这个功能,需要再引入一个名为patchloader的jar包,我们已将将它拷到libs目录下,这个函数库实现了一个热更新框架,宿主apk在发布时会将这个jar包一起打包进apk中,而补丁apk只是在编译时需要这个jar包,但打包成apk时不包含这个jar包,以免补丁apk集成到宿主apk中时发生冲突。因此,补丁apk将会以provided的形式依赖dexposedbridge.jar和patchloader.jar,补丁apk也就是patch的build.gradle文件中依赖部分脚本如下所示:
dependencies { provided fileTree(dir: 'libs', include: ['*.jar']) }
现在假设我们MainActivity中有一个初始化数据的方法
initData() { return null; }" data-snippet-id="ext.2dc67706508631effc47c5ba54945db9" data-snippet-saved="false" data-csrftoken="ay8clLgw-3zWBDDQ4Qtfkbewh78MiJEcqyNY">[code] public List<String> initData() { return null; }
但是我们手误返回了null,但是在MainActivity中调用了这个返回值的内容
public void showTextView(){ tv.setText(initData().get(0) + ""); }
于是就产生了常见的异常,即空指针异常。我们使用patch来修复这个bug。我们需要实现IPatch接口
cls = null; try { cls= patchParam.context.getClassLoader() .loadClass("zafu.edu.cn.dexposed.MainActivity"); } catch (ClassNotFoundException e) { e.printStackTrace(); return; } DexposedBridge.findAndHookMethod(cls, "initData", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { List list=new ArrayList(); list.add("1111111111"); list.add("2222222222"); list.add("3333333333"); list.add("4444444444"); //param.setResult(list); return list; } }); } } " data-snippet-id="ext.13ad004ed45b795660ffb1f9033b55f8" data-snippet-saved="false" data-csrftoken="9PVVDQhi-7eOu3OY4RrpxiF4WZdMKBoIpBw8">[code]public class TestPatch implements IPatch { @Override public void handlePatch(PatchParam patchParam) throws Throwable { Log.e("TAG","handlePatch"); Class<?> cls = null; try { cls= patchParam.context.getClassLoader() .loadClass("zafu.edu.cn.dexposed.MainActivity"); } catch (ClassNotFoundException e) { e.printStackTrace(); return; } DexposedBridge.findAndHookMethod(cls, "initData", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { List<String> list=new ArrayList<String>(); list.add("1111111111"); list.add("2222222222"); list.add("3333333333"); list.add("4444444444"); //param.setResult(list); return list; } }); } }
我们的补丁程序返回了几个数据,完成后我们生成module为patch的apk。然后将该apk拷到对应的目录下,这里简单的将其拷到Android/data/your package/cache/pacth.apk目录下。然后我们要应用该补丁
public void runPatchApk(){ if (isLDevice()){ Log.e("TAG","it does not support L"); return; } if (!isSupported()){ Log.e("TAG","It does not support!"); return ; } File cacheDir = getExternalCacheDir(); if(cacheDir != null){ String fullpath = cacheDir.getAbsolutePath() + File.separator + "patch.apk"; Log.e("PATH",fullpath); PatchResult result = PatchMain.load(this, fullpath, null); if (result.isSuccess()) { Log.e("Hotpatch", "patch success!"); } else { Log.e("Hotpatch", "patch error is " + result.getErrorInfo()); } } }
if (mIsSupported) { hook(); runPatchApk(); }
之后你会发现不再报空指针了。
对于热更新,我们可以在手机淘宝中找到它的影子。如下图
总之,该库前途无量,但是目前支持的系统有限,也希望它能不断发展,造福开发者。
源码下载
http://download.csdn.net/detail/sbsujjbcy/8973895
相关文章推荐
- Android 开发环境下载连接
- Android初学习 - 在Java code(布局容器)中动态添加控件
- android内存泄露及OOM介绍
- android sdk下载更新不了问题解决
- 彻底解决android中Textview控件文本自动换行问题
- Android EditText 修改提示字体的大小
- 【android控件学习笔记】GridView表格形式显示多张图片
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- 我的Android进阶之旅------>Android关于dp(dip)、sp转px的工具类
- 关于Android GridLayou 的孩子控件宽度设置,超出屏幕
- android自定义button边框,颜色渐变,圆角button快速生成
- 史上最全的Android的Tab与TabHost讲解
- Android 一个代码教你学会运用服务相关知识
- 动态设置TextView的android:drawableLeft属性
- Android中级联列表ExpandableListView使用
- Android 属性动画(Property Animation) 完全解析 (上)
- Android:利用SharedPreferences实现自动登录
- Android学习笔记——文件路径(/mnt/sdcard/...)、Uri(content://media/external/...)学习
- android 官网访问地址
- android客户端和php服务简单交互