dexposed框架Android在线热修复

移动客户端应用相对于Webapp的最大一个问题每次出现bug,不能像web一样在服务器就完成修复,不需要发版本。紧急或者有安全漏洞的问题,

如果是Webapp你可能最多花个1,2个小时紧急发布上线,但是app呢,打包,跪求市场发布几百个渠道,周末还发不了,app配置升级,你还不能配置

强制升级, 就算配置提示升级,用户心里肯定想前两天刚升级最新版,怎么又要升,而且升级要流量,这时候会很反感甚至卸载应用。所以安卓是否

有能力做到在线打补丁?dexposed给我们解决了这个问题。

1、淘宝Dexposed框架

喜欢刷机的人应该对xposed不陌生,Xposed框架是一款可以在不修改APK的情况下影响程序运行(修改系统)的框架服务,基于它 可以制作出许多功

能强大的模块。其实Xposed安卓的一个开源框架,在github上的下载地址xposed,有兴趣的可以去研究下,dexPosed也是基于Xposed的。

他有几个典型的使用场景:

a. Classic AOP programming (aop编程)

b. Instrumentation (for testing, performance monitoring and etc.)  测试,性能监控

c. Online hot patch to fix critical, emergent or security bugs 线上打补丁,解决一些严重的,紧急的或者安全漏洞的bug。

d. SDK hooking for a better development experience

对于aop编程我这里就不多说了,deXposed提供的 是无侵入性的,而且AOP就是用的java代码,相比国外比较流行的Aspectj有几点优势:

a、无侵入性,

b、使用Java代码编写,Aspectj使用脚本需要一定的学习成本

c、Aspectj有自己的编译器,需要编译下Aspectj代码,会注入他自己的代码

下面来看看如果写一些AOP的代码:

Attach a piece of code before and after all occurrences of Activity.onCreate(Bundle).

beforeHookedMethod和afterHookedMethod方法做一些具体的操作。我们可以看到,用dexposed可以实现不改变原函

数的执行,但是在原函数执行前后去做一些其他的额外处理,例如改变入参和返回值等等的一些事情。

  Replace the original body of the target method.

    DexposedBridge.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodReplacement() {

        @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
            // Re-writing the method logic outside the original method context is a bit tricky but still viable.
            ...
        }

    });

则是可以将原有要执行的函数替换成一个我们需要的新的执行函数。

这个框架目前对android 5.0系统支持度不高,不过框架更新的时间可以看出,持续维护中,不久将对art完全支持。他提供一个函数来检测你的android

系统是否支持 Dexposed,DexposedBridge.canDexposed(context)。你需要把补丁包打包成一个apk, 然后通过下面的代码来加载补丁包:

// Run taobao.patch apk
    public void runPatchApk() {
        if (android.os.Build.VERSION.SDK_INT == 21) {
            return;
        }
        if (!isSupport) {
            Log.d("dexposed", "This device doesn't support dexposed!");
            return;
        }
        File cacheDir = getExternalCacheDir();
        if (cacheDir != null) {
            String fullpath = cacheDir.getAbsolutePath() + File.separator + "patch.apk";
            PatchResult result = PatchMain.load(this, fullpath, null);
            if (result.isSuccess()) {
                Log.e("Hotpatch", "patch success!");
            } else {
                Log.e("Hotpatch", "patch error is " + result.getErrorInfo());
            }
        }
    }

2.Dexposed原理

重点是搞清楚是怎样hook的。

1)首先通过ClassLoader把插件apk加载进入主工程

2)通过反射拿到具体的class类

3)DexposedBridge.findAndHookMethod定位到具体的方法中,对代码进行覆盖或者加入自定义功能

前面两点都是Java方法没什么可以说的,我这里重点分析下第三点:

a、DexposedBridge.findAndHookMethod()中调用了Xc_methdhook.hookMethos(),这个方法是通过传入的class,方法及签名去查找返回一个对应的Method。

然后把Method作为参数传入hookMethodNative(Method  ...);

b、hookMethodNative是进行hook的主要方法,分析源码发现,它里面做了这些事情

1、把Java层的变量,类型转换成c能识别的指针类型;

    // Save a copy of the original method and other hook info
    DexposedHookInfo* hookInfo = (DexposedHookInfo*) calloc(1, sizeof(DexposedHookInfo));
    memcpy(hookInfo, method, sizeof(hookInfo->originalMethodStruct));
    hookInfo->reflectedMethod = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(reflectedMethodIndirect));
    hookInfo->additionalInfo = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(additionalInfoIndirect));

2、用我们自己的code代替,看到下面的nativeFun了吗,这个方法被dexposedCallHandler代替,也就是说被我们自己的回调方法代替了,这样我们就可以

随意的操作了。然后把要hook的对象参数保存到insns

   // Replace method with our own code
    SET_METHOD_FLAG(method, ACC_NATIVE);
    method->nativeFunc = &dexposedCallHandler;
    method->insns = (const u2*) hookInfo;
    method->registersSize = method->insSize;
    method->outsSize = 0;

3、接下来我们去看看dexposedCallHandler做了哪些事情;

 //赋值
DexposedHookInfo* hookInfo = (DexposedHookInfo*) method->insns;
    Method* original = (Method*) hookInfo;
    Object* originalReflected = hookInfo->reflectedMethod;
    Object* additionalInfo = hookInfo->additionalInfo;
 // call the Java handler function
//对...这才是重点,dvmCallMethod方法回调了dexposedHandleHookedMethod方法,这个方法是在Java层实现的
 JValue result;
    dvmCallMethod(self, dexposedHandleHookedMethod, NULL, &result,
        originalReflected, (int) original, additionalInfo, thisObject, argsArray);

    dvmReleaseTrackedAlloc((Object *)argsArray, self);

4、我们在去看看handleHookMethod做了什么,一看你就明朗了;

private static Object handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj,
			Object thisObject, Object[] args) throws Throwable {
		AdditionalHookInfo additionalInfo = (AdditionalHookInfo) additionalInfoObj;

		if (disableHooks) {
			try {
				return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes,
						additionalInfo.returnType, thisObject, args);
			} catch (InvocationTargetException e) {
				throw e.getCause();
			}
		}

		Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot();
		final int callbacksLength = callbacksSnapshot.length;
		if (callbacksLength == 0) {
			try {
				return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes,
						additionalInfo.returnType, thisObject, args);
			} catch (InvocationTargetException e) {
				throw e.getCause();
			}
		}

		MethodHookParam param = new MethodHookParam();
		param.method  = method;
		param.thisObject = thisObject;
		param.args = args;

		// call "before method" callbacks
		int beforeIdx = 0;
		do {
			try {
				((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
			} catch (Throwable t) {
				DexposedBridge.log(t);

				// reset result (ignoring what the unexpectedly exiting callback did)
				param.setResult(null);
				param.returnEarly = false;
				continue;
			}

			if (param.returnEarly) {
				// skip remaining "before" callbacks and corresponding "after" callbacks
				beforeIdx++;
				break;
			}
		} while (++beforeIdx < callbacksLength);

		// call original method if not requested otherwise
		if (!param.returnEarly) {
			try {
				param.setResult(invokeOriginalMethodNative(method, originalMethodId,
						additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));
			} catch (InvocationTargetException e) {
				param.setThrowable(e.getCause());
			}
		}

		// call "after method" callbacks
		int afterIdx = beforeIdx - 1;
		do {
			Object lastResult =  param.getResult();
			Throwable lastThrowable = param.getThrowable();

			try {
				((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
			} catch (Throwable t) {
				DexposedBridge.log(t);

				// reset to last result (ignoring what the unexpectedly exiting callback did)
				if (lastThrowable == null)
					param.setResult(lastResult);
				else
					param.setThrowable(lastThrowable);
			}
		} while (--afterIdx >= 0);

		// return
		if (param.hasThrowable())
			throw param.getThrowable();
		else
			return param.getResult();
	}

判断是否hook成功,如果不成功执行invokeOriginalMethodNative,也就是执行原始函数;

成功则,这个函数查找被挂钩函数的挂钩 XC_MethodHook结构体,然后执行里边的 beforeHookedMethod函数,再通过 invokeOriginalMethodNative

执行挂钩前的原始函数,最后再执行 afterHookedMethod 函数。

findAndHookMethod 的实现就分析完了,

本质上仍然是寻找被挂钩函数的 Method 结构体,将Method属性改为native ,然后对其成员 nativeFunc,

registersize 等进行赋值,其中 insns 成员保存了挂钩的详细信息。所有被挂钩的函数,其nativeFunc都赋值为 dexposedCallHandler 函数,该函数最终执行 XposedBridge.class 里的 handleHookedMethod 。 handleHookedMethod 寻找dexposed模块及dexposed框架调用 findAndHookMethod 注册的 before,after

函数,如果有,就执行,再通过invokeOriginalMethodNative 执行挂钩前函数。

3、怎么使用dexposed

a、下载dexposed源码 dexposed

b、把下载的源码导入,只需要sample包,其中dexposedsample是有bug的主工程,patchsample为插件工程,(其他的两个为工具类源码,方便我们理解源码)

c、主工程配置gradle文件

apply plugin: 'com.android.application'
android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    defaultConfig {
        applicationId "com.taobao.dexposed"
        minSdkVersion 9
        targetSdkVersion 21
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    android {
        packagingOptions {
            exclude 'AndroidManifest.xml'
        }

    }

    buildTypes {
        //Debug打开,使用测试环境接口
        debug {
        }
    }
    lintOptions {
        abortOnError false
    }

    sourceSets {
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java']
            res.srcDirs = ['src/main/res']   //路径根据自己的对应修改
        }
    }
}

//我使用的是本地导入so包,
task copyNativeLibs(type: Copy) {
    from fileTree(dir: 'libs', include: '*/*.so' )  into  'build/native-libs'
}
tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn copyNativeLibs }

clean.dependsOn 'cleanCopyNativeLibs'

tasks.withType(com.android.build.gradle.tasks.PackageApplication) {
        pkgTask ->
        pkgTask.jniFolders = new HashSet<File>()
        pkgTask.jniFolders.add(new File(projectDir, 'libs'))
}
//---------------------------------
//这段配置是把主工程打成jar包,这个jar包需要导入到patch工程中
 task clearJar(type: Delete) {
    delete 'libs/sdk.jar'
}

task makeJar(type:org.gradle.api.tasks.bundling.Jar) {
    //指定生成的jar名
    baseName 'sdk'
    //从哪里打包class文件
    from('build/intermediates/classes/sample/debug/com')
    //打包到jar后的目录结构,根据自己工程需要写路径
    into('com')
    //去掉不需要打包的目录和文件
    exclude('test/', 'BuildConfig.class', 'R.class')
    //去掉R$开头的文件
    exclude{ it.name.startsWith('R$');}
}

makeJar.dependsOn(clearJar, build)
//---------------------------------------------

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile files('libs/dexposedbridge.jar')
}

patch工程gradle配置:

dependencies {
//        compile fileTree(dir: 'libs', include: ['*.jar'])
        provided files('libs/sdk.jar')
        provided files('libs/dexposedbridge.jar')
        provided files('libs/patchloader.jar')
    }

使用provided方式导入jar包,这样只会在编译时引用jar,不会把jar打包进patch.apk,防止与主工程引入包冲突;

用例代码就不贴了,自己去down一份

4、关于patch包的安全问题可以看Android Hotpatch

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-02 03:12:04

dexposed框架Android在线热修复的相关文章

Android RocooFix 热修复框架

这里我要讲述下android热补丁前世今生 Under the Hood: Rebuilding Facebook for Android 发布者:Frank Qixing DU · 发布时间:2012年12月14日上午 3:01 Over the last year, we've been retooling our mobile apps to make them faster, more reliable, and easier to use. Several months ago, we

Android的热修复-微信Tinker

作者:邓浩宸 11/9/2016 1:13:49 PM Android的热修复 前言: 随着时代的发展,由于公司的项目需要去求变化平凡计划总赶不上变化,H5的高灵活性,开发周期短,更新速度快H5以及一些混合开发越来越被看好,然而主要原因之一:这种混合开发的方式容错率大,更新和修复BUG快.不用发布版本就可以让用户不觉的情况下就更新对应的内容或者BUG,我们不能否认混合开发的快捷,正在此前提下热修复和热更新技术也得到了非常大的发展,不管热修复还是热更新,都是对app的内容或者逻辑变化做出像web页

Android中热修复框架Robust原理解析+并将框架代码从&quot;闭源&quot;变成&quot;开源&quot;(下篇)

一.回顾框架原理 本篇继续来看热修复框架Robust原理,在之前的一篇文章中已经详细讲解了:Robust框架原理,因为这个框架不是开源的,所以通过官方给出的原理介绍,咋们自己模拟了案例和框架逻辑的简单实践.最后在通过反编译美团app进行验证咋们的逻辑实现是否大致不差.最终确定实践的逻辑大同小异.但是在上一篇文章末尾多次强调了,这个框架吸引我研究的不是他热修复技术,而是他有一个技术点,就是如何在编译期给每个类每个方法都加上修复功能代码,对于上层开发代码是透明的.因为从之前案例可以看到,如果方法没有

Android Classloader热修复技术之百家齐放

大概在2015年10月底,QQ空间发了一篇叫<安卓App热补丁动态修复技术介绍>的文章,文章中提到为了能让Class进行热修复,其中一个条件就是防止类被打上CLASS_ISPREVERIFIED标记,具体的做法便是让一个Dex引用另一个Dex(hack.apk)中的空类(为了让业务无感知,需要在编译时动态注入字节码),并且在应用程序Application类起来的时候要加载这个hack.apk.也就是说最多需要进行两次反射,即加载hack.apk的时候需要进行一次反射操作,将hack.apk加入

Android中免Root实现Hook的Dexposed框架实现原理解析以及如何实现应用的热修复

一.前言 今天我们来看一下阿里的一个开源框架Dexposed,关于这个框架网上已经有很多解析了,但是都是讲解原理,而且讲的不是很清楚,这里因为工作中的需要就研究了一下,所以这里就先讲解一下这个框架的原理,然后在通过一个例子来看看他如何使用,最后在用它来实现应用的热修复问题. 二.知识点准备 首先在讲解这个框架的时候,我们先来了解几个知识点: 1.关于之前的Xposed框架 我们在很早就知道了这个框架,本来想整理一下顺便说一下这个框架的,但是这个框架网上说的很多,而且也很详细,所以就不做太多的解析

十分钟教会你使用安卓热修复框架AndFix

腾讯最近开发出一个Tinker,阿里也有一个Dexposed框架,当然还有一个就是今天的主角热修复框架AndFix.接下来,我会从它的概念.原理.使用方法等为你详细介绍. 1.什么是AndFix?AndFix是阿里巴巴出的一个专门针对Android的热修复框架,那什么是热修复呢?就是不需要重新安装APK而达到修复bugs的目的.看看阿里巴巴官方对AndFix的描述:英文好的可以看官网说明文档(有翻译不妥欢迎指出)1.AndFix是一个Android库,它是在线解决bugs,而不是重新安装app的

android产品研发(七)--&gt;Apk热修复

转载请标明出处:一片枫叶的专栏 去年一整年android社区中刮过了一阵热修复的风,各大厂商,逼格大牛纷纷开源了热修复框架,恩,产品过程中怎么可能没有bug呢?重新打包上线?成本太高用户体验也不好,咋办?上热修复呗. 好吧,既然要开始上热修复的功能,那么就得调研一下热修复的原理.下面我将分别讲述一下热修复的原理,各大热修复框架的比较,以及自身产品中热修复功能的实践. 热修复的原理 通过更改dex加载顺序实现热修复 最新github上开源了很多热补丁动态修复框架,大致有: HotFix      

Android热修复:Andfix和Hotfix,两种方案的比较与实现

Andfix和hotfix是两种android热修复框架. android的热修复技术我看的最早的应该是QQ空间团队的解决方案,后来真正需要了,才仔细调查,现在的方案中,阿里有两种Dexposed和Andfix框架,由于前一种不支持5.0以上android系统,所以阿里系的方案我们就看Andfix就好.Hotfix框架算是对上文提到的QQ空间团队理论实现.本文旨在写实现方案,捎带原理. Andfix 引入 框架官网:https://github.com/alibaba/AndFix 介绍是用英文

Android热修复技术选型——三大流派解析

2015年以来,Android开发领域里对热修复技术的讨论和分享越来越多,同时也出现了一些不同的解决方案,如QQ空间补丁方案.阿里AndFix以及微信Tinker,它们在原理各有不同,适用场景各异,到底采用哪种方案,是开发者比较头疼的问题.本文希望通过介绍QQ空间补丁.Tinker以及基于AndFix的阿里百川HotFix技术的原理分析和横向比较,帮助开发者更深入了解热修复方案. 技术背景 -----------------------------------------------------