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

针对Android平台。Dexposed支持函数级别的在线热更新,比如对已经公布在应用市场上的宿主APK,当我们从crash统计平台上发现某个函数调用有bug,导致常常性crash,这时。能够在本地开发一个补丁APK,并公布到server中。宿主APK下载这个补丁APK并集成后,就能够非常easy修复这个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">/**
* 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">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时,通过在线下载的方式从server下载到补丁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"> 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">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新技术学习——阿里巴巴免Root无侵入AOP框架Dexposed的更多相关文章

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

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

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

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

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

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

  4. 借助ADB冻结与卸载Android系统应用(免ROOT)

    背景: 我妈的手机饱受系统应用广告推送之苦,每天都能在通知栏里收到好几条广告.为了给她个清净,本篇博文应运而生. 目标: 卸载安卓系统应用 所用工具: 硬件:我妈的手机(魅蓝5) PC端:Minima ...

  5. Android驱动学习-app调用内核驱动过程(驱动框架回顾)

    考研已经过去了,android驱动的学习也断了半年多了,现在重新捡起来学习,回顾一下Android驱动的大体框架. Android系统的核心是java,其有一个David虚拟机.Android-app ...

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

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

  7. android免root hook框架legend

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

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

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

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

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

随机推荐

  1. wordcloud的安装报错 error: Microsoft Visual C++ 10.0 is required. Get it with "Microsoft Windows SDK 7.1"解决办法

    cmd中使用pip install wordcloud失败,没看懂报错的原因…… 而在pycharm中添加也报错 解决方法: 1. 下载wordcloud-1.4.1.tar.gz,解压缩 cmd c ...

  2. 关于priority_queue的一些说明

    参考GGBeng 相关定义 优先队列容器与队列一样,只能从队尾插入元素,从队首删除元素.但是它有一个特性,就是队列中最大的元素总是位于队首,所以出队时,并非按照先进先出的原则进行,而是将当前队列中最大 ...

  3. cf 1016D

    D. Vasya And The Matrix time limit per test 2 seconds memory limit per test 256 megabytes input stan ...

  4. JavaScript正则表达式-或字符

    或字符是一个单竖线“|”,表示字符串只要匹配“|”连接的多个表达式中的任意一个. /ab|ac|bc/表示字符串匹配ab,或者匹配ac,或者bc

  5. 笔记-python-字符串格式化-format()

    笔记-python-字符串格式化-format() 1.      简介 本文介绍了python 字符串格式化方法format()的常规使用方式. 2.      使用 2.1.    Accessi ...

  6. slave_net_timeout 问题一则

    [背景]   对一套数据库集群进行5.5升级到5.6之后,alter.log 报warning异常. 2015-02-03 15:44:51 19633 [Warning] Storing MySQL ...

  7. POJ 1651 区间DP Multiplication Puzzle

    此题可以转化为最优矩阵链乘的形式,d(i, j)表示区间[i, j]所能得到的最小权值. 枚举最后一个拿走的数a[k],状态转移方程为d(i, j) = min{ d(i, k) + d(k, j) ...

  8. 【转】使用信号监控 Django 模型对象字段值的变化

    转载出处:http://blog.csdn.net/pushiqiang/article/details/74949465 Django 信号 (Signals) 的功能类似于 WordPress 的 ...

  9. loj2141 「SHOI2017」期末考试

    我们枚举每一个时间点,使得所有科目的时间都小于等于这个时间点,计算安排老师的代价和学生们的不满意度更新答案. 但是枚举太慢了,可以发现,时间点越早,学生们不满意度越小,安排老师的代价越高.即安排老师的 ...

  10. c++ - 在终端中,cout不显示任何内容

    g++ 是一个编译器,它将源代码转换成可以执行程序,但不运行它. 你必须亲自运行程序. g++ 生成的程序的默认名称是 a.out ( 因为历史原因),因此你将运行它作为 $./a.out   如果要 ...