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


    阿里巴巴无线事业部近期开源的Android平台下的无侵入运行期AOP框架Dexposed,该框架基于AOP思想,支持经典的AOP使用场景。可应用于日志记录,性能统计,安全控制。事务处理。异常处理等方面。
    针对Android平台。Dexposed支持函数级别的在线热更新,比如对已经公布在应用市场上的宿主APK,当我们从crash统计平台上发现某个函数调用有bug,导致常常性crash,这时。能够在本地开发一个补丁APK,并公布到server中。宿主APK下载这个补丁APK并集成后,就能够非常easy修复这个crash。

    Dexposed是基于久负盛名的开源Xposed框架实现的一个Android平台上功能强大的无侵入式运行时AOP框架。Dexposed的AOP实现是全然非侵入式的。没有使用不论什么注解处理器。编织器或者字节码重写器

    先来讲讲集成方法,从项目地址下载整个项目dexposed。将..dexposedsamplepatchsampleapplibs文件夹下的两个jar拷出来备用,以及将..dexposedsampledexposedexamplesappsrcmainjniLibs文件夹下的native库拷出来备用。

    打开Android Studio,新建项目。将前面拷出来的jar拷到libs文件夹下,在main文件夹下新建jniLibs文件夹。将native库拷进去。终于项目结构会成这样
    这里写图片描写叙述

    为了应用热更新。所以我们还要建一个module用于编写热更新的代码。用Android studio新建一个module,这里让其名叫patch。将之前的jar拷入到patch下的libs文件夹。

    而native库不须要拷。之后进行同步。点击如图图标
    这里写图片描写叙述

    我们在Application中检查是否支持Dexposed,编写一个子类继承Application类,并在Manifest文件里指定该类。

    /**
     * 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中的函数:

    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中有一个初始化数据的方法

        public List<String> initData() {
            return null;
        }

    可是我们手误返回了null,可是在MainActivity中调用了这个返回值的内容

        public void showTextView(){
            tv.setText(initData().get(0) + "");
        }

    于是就产生了常见的异常。即空指针异常。我们使用patch来修复这个bug。

    我们须要实现IPatch接口

    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

  • 相关阅读:
    xiaopiu产品原型设计与团队实时协作平台
    asp.net webform过滤器(注意我们可以在拦截请求的同时设置回调函数)
    wdScrollTab
    pageoffice实现网页打开编辑保存word文档(基于SSM框架)
    ESB企业服务总线
    JRebel for IntelliJ
    dtcms 手机浏览
    maven仓库添加jar架包
    shell脚本实现FTP自动上传文件
    mysql创建数据库指定字符集
  • 原文地址:https://www.cnblogs.com/gccbuaa/p/7211786.html
Copyright © 2020-2023  润新知