本文来自网易云社区

作者:王晨彦

Application 处理

上面我们已经对所有 class 文件插入了 Hack 的引用,而插入 dex 是在 Application 中,Application 启动前肯定要先加载 Application.class,但这时 dex 还没被插入,因此肯定会引起 ClassNotFoundException ,因此我们不能使 Application 引用 Hack。

那么修改 class 文件时如何知道哪个是 Application 呢,有人可能会说直接特判不就行了,但是我觉得要作为一个插件的话就要做到兼容,并且尽量减少使用者的手动配置。

那么如何让插件找到 Application 的名字呢,这时就要用到上面的 processDebugManifest Task 了。

我们都知道,Application需要在 Manifest 中注册,因此只要找到 Manifest 文件就能得到 Application 的名字了。

没错,Manifest 文件就在 processDebugManifest 的 outputs.files 中,找到 Manifest 后解析 application 标签即可。

  • 开启混淆会怎样?

我们正式上线的应用都是会混淆的,我们刚才测试的使用 debug 未混淆模式,如果我们开启混淆的话 Task 还会和上面的完全一样吗?

我们把 release 的混淆打开,然后执行 assembleRelease,观察 Gradle Console 输出

:app:preBuild UP-TO-DATE// 省略部分Task:app:processReleaseJavaRes NO-SOURCE
:app:transformResourcesWithMergeJavaResForRelease
:app:transformClassesAndResourcesWithProguardForRelease
:app:transformClassesWithDexForRelease
:app:mergeReleaseJniLibFolders
:app:transformNativeLibsWithMergeJniLibsForRelease
:app:validateSigningRelease
:app:packageRelease
:app:assembleRelease

可以看到相比较未开启混淆多了一个 transformClassesAndResourcesWithProguardForRelease, 那么这个Proguard Task有用吗?

有用!

为了保证打包 APK 和 patch 时 class 混淆后的名字不变,我们需要在 Proguard Task 前插入混淆逻辑

使用 Proguard 的 -applymapping 即可实现。

因此,我们还要对打包APK后生成的 mapping 文件进行保存。

插件中代码实现

static applymapping(TransformTask proguardTask, File mappingFile) {    if (proguardTask) {
        ProGuardTransform transform = (ProGuardTransform) proguardTask.getTransform()        if (mappingFile.exists()) {
            transform.applyTestedMapping(mappingFile)
        } else {
            CFixLogger.i("${mappingFile} does not exist")
        }
    }
}
  • 补丁签名

为了安全,上线时我们最好对补丁加上签名验证,保证补丁签名和 APK 签名一致。

签名使用 JDK 中的 jarsigner

List<String> command = [JavaEnvUtils.getJdkExecutable('jarsigner'),                        '-verbose',                        '-sigalg', 'MD5withRSA',                        '-digestalg', 'SHA1',                        '-keystore', extension.storeFile.absolutePath,                        '-keypass', extension.keyPassword,                        '-storepass', extension.storePassword,
                        patchFile.absolutePath,
                        extension.keyAlias]Process proc = command.execute()

校验签名的代码我就不贴了,对应的是源码中的 SignChecker 类。

检验成果

上面我们已经把制作补丁,导入补丁的过程大致梳理了一遍,接下来就需要把上面的代码整理一下。

为了方便使用,我们将其制作为一个 Gradle 插件。如果还不了解如何制作 Gradle 插件的话快点去学习啦

我已将插件和依赖库上传至 JCenter,在 app 中引入插件。

// root build.gradlebuildscript {
    repositories {
        jcenter()
    }     dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath 'me.wcy:cfix-gradle:1.1'
    }
}// app build.gradleapply plugin: 'com.android.application'apply plugin: 'me.wcy.cfix'cfix {
    includePackage = ['me/wcy/cfix/sample'] // 需要插入补丁的包名,一般为应用的包名
    excludeClass = [] // 不需要插入补丁的类
    debugOn = true // debug 模式是否插入补丁     sign = true // 是否添加签名
    storeFile = file("release.jks")
    storePassword = 'android'
    keyAlias = 'cfix'
    keyPassword = 'android'}// 省略部分代码dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'me.wcy:cfix:1.0'}

在 Application 中插入 Hack dex 和 patch

@Overrideprotected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    CFix.init(this);
    CFix.loadPatch(Environment.getExternalStorageDirectory().getPath().concat("/patch.jar"), !BuildConfig.DEBUG);
}

首先不对项目做任何修改,直接运行

熟悉的 Hello World

检查下 class 文件是否已经引入 Hack 类,编译后的 class 位于 app/build/intermediates/classes

 

可以看到,Application 没有引入 Hack 类,Activity 已经成功引入 Hack 类。

然后我们添加一个对话框类,并在Activity中调用该类显示对话框

public class FixDialog {    public void show(Context context) {        new AlertDialog.Builder(context)
                .setTitle("Congratulations")
                .setMessage("Patch Success!")
                .setPositiveButton("OK", null)
                .show();
    }
}// MainActivity@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);     FixDialog dialog = new FixDialog();
    dialog.show(this);
}

保存生成的 Hash 文件,制作补丁包

打开终端,执行以下命令

gradlew clean cfixXiaomiDebugPatch -P cfixDir=D:\Android\AndroidStudioProjects\CFix\app\cfix

Xiaomi 表示 productFlavor,Debug 表示 buildType

将生成的 patch.jar push 到手机 SD 根目录

adb push D:\Users\wcy\Desktop\patch.jar /sdcard/

重启应用

注意,因为我们只是测试,所以把补丁包放在了SD中,因此需要添加读取SD权限,还需要把 targetSdk 改为小于 23 或者手动给予权限。

源码

该框架可以说是对 Nuwa 的优化升级,几乎支持了目前所有的 Gradle 版本 1.5.0-3.0.1(1.5之前的版本由于太旧未适配)。

再次对 Nuwa 作者表示感谢,给我们提供了很好的例子。

该框架在网易七鱼 Android 客服工作台中已经验证通过。

框架使用方法请参考 README

声明:该框架未进行兼容性测试,因此不保证兼容所有机型。如果要在商业项目中使用,建议进行兼容性测试。

总结

今天我们主要对 QQ 空间的热修复方案进行了可实行性探讨,对整个流程进行梳理,并最终实现了整套方案,验证通过。

其实我在这期间也踩了不少坑,如 QQ 空间博客中提到的使用 javassist 对 class 进行修改,我使用 javassist 后,一开始在 demo 中可以正常修改 class,但是到了大量代码的线上项目中一直报找不到 v4 包中的类,导致无法修改 class 文件引入 Hack 类。打 log 又发现类已经正常被加载,而且有时能找到有时找不到,每次找不到的类还不一样,WTF。

最后参考了 Nuwa 的实现,替换为 ASM,问题解决。

近两年涌现了很多热修复框架,关于热修复的文章也有很多,相信大家也看了不少,但是看的再多,终究不如动手实践来的深刻。

网易云免费体验馆,0成本体验20+款云产品!

更多网易研发、产品、运营经验分享请访问网易云社区

相关文章:
【推荐】 机器学习、深度学习、和AI算法可以在网络安全中做什么?
【推荐】 关于网易云验证码V1.0版本的服务介绍

手把手带你打造一个 Android 热修复框架的更多相关文章

  1. 手把手带你打造一个 Android 热修复框架(上篇)

    本文来自网易云社区 作者:王晨彦 前言 热修复和插件化是目前 Android 领域很火热的两门技术,也是 Android 开发工程师必备的技能. 目前比较流行的热修复方案有微信的 Tinker,手淘的 ...

  2. Android热修复框架汇总整理(Hotfix)

      Android平台出现了一些优秀的热更新方案,主要可以分为两类:一类是基于multidex的热更新框架,包括Nuwa.Tinker等:另一类就是native hook方案,如阿里开源的Andfix ...

  3. [Android]热修复框架AndFix测试说明

    AndFix,全称是Android hot-fix.是阿里开源的一个热补丁框架,允许APP在不重新发布版本的情况下修复线上的bug.支持Android 2.3 到 6.0,并且支持arm 与 X86系 ...

  4. Android热修复之微信Tinker使用初探

      文章地址:Android热修复之微信Tinker使用初探 前几天,万众期待的微信团队的Android热修复框架tinker终于在GitHub上开源了. 地址:https://github.com/ ...

  5. Android热修复之AndFix使用教程

    AndFix的github地址 AndFix 全称Android hot-fix,是alibaba的Android热修复框架,支持Android 2.3到6.0的版本,支持arm与X86系统架构,支持 ...

  6. 手把手带你做一个超炫酷loading成功动画view Android自定义view

    写在前面: 本篇可能是手把手自定义view系列最后一篇了,实际上我也是一周前才开始真正接触自定义view,通过这一周的练习,基本上已经熟练自定义view,能够应对一般的view需要,那么就以本篇来结尾 ...

  7. Android 热修复方案Tinker(一) Application改造

    基于Tinker V1.7.5 Android 热修复方案Tinker(一) Application改造    Android 热修复方案Tinker(二) 补丁加载流程    Android 热修复 ...

  8. Android 热修复使用Gradle Plugin1.5改造Nuwa插件

    随着谷歌的Gradle插件版本号的不断升级,Gradle插件如今最新的已经到了2.1.0-beta1,相应的依赖为com.android.tools.build:gradle:2.0.0-beta6, ...

  9. 深入探索Android热修复技术原理读书笔记 —— 资源热修复技术

    该系列文章: 深入探索Android热修复技术原理读书笔记 -- 热修复技术介绍 深入探索Android热修复技术原理读书笔记 -- 代码热修复技术 1 普遍的实现方式 Android资源的热修复,就 ...

随机推荐

  1. Git(四):Git远程操作详解

    转: http://www.ruanyifeng.com/blog/2014/06/git_remote.html Git是目前最流行的版本管理系统,学会Git几乎成了开发者的必备技能. Git有很多 ...

  2. 关于StringUtils类isEmpty、isNotEmpty、isBlank、isNotBlank针对null、空字符串和空白字符(如空格、制表符)的区别

    isEmpty | null | 空字符串("")|空白字符(空格.制表符)| | isEmpty | true | true | false | | isNotEmpty | f ...

  3. Unity牛逼的2D纹理功能

    [Unity牛逼的2D纹理功能] 1.可直接将贴图生成成为Cubemap. 2.自动生成Mipmap. 3.查看纹理被当前场景哪些对象引用.在Project窗口中,右击图像,选择 参考:file:// ...

  4. AttributeUsage

    [AttributeUsage] System.AttributeUsage声明一个Attribute的使用范围与使用原则. AllowMultiple 和 Inherited 参数是可选的,所以此代 ...

  5. 【祥哥带你玩HoloLens开发】了解如何实现远程主机为HoloLens实时渲染

    今天有一个兄弟在群里讲到他们的项目模型比较大,单用HoloLens运行设备的性能无法满足需要,问道如何将渲染工作交给服务器来做,讲渲染结果传给HoloLens.正好刚刚看官方github的时候发现一个 ...

  6. Hibernate其它API

    ----------------siwuxie095 (一)Query 1.使用 Query 对象执行查询操作,不需要写 sql 语句,但是要写 hql 语句 (1)hql:即 Hibernate Q ...

  7. 1.spark的wordcount解析

    一.Eclipse(scala IDE)开发local和cluster (一). 配置开发环境 要在本地安装好java和scala.  由于spark1.6需要scala 2.10.X版本的.推荐 2 ...

  8. code1043 方格取数

    第一次走用dfs枚举每种情况,第二次走用dp求剩下的最大值 设一个点集q用来保存有价值的点,排序,在最后加一个终点:x=m+1,y=m+1,v=0  //m是矩阵长宽 因为v=0的点是没有意义的,所以 ...

  9. 1118 Lining Up

    题目链接: http://poj.org/problem?id=1118 题意: 给定n个点, 求在同一直线上的点最多的直线上点的数目. 解法: 简单题目, 规模比较小,  暴力搜索. #includ ...

  10. UVa 10766 Organising the Organisation (生成树计数)

    题意:给定一个公司的人数,然后还有一个boss,然后再给定一些人,他们不能成为直属上下级关系,问你有多少种安排方式(树). 析:就是一个生成树计数,由于有些人不能成为上下级关系,也就是说他们之间没有边 ...