Android热修复技术——QQ空间补丁方案解析(2)

简介: Android热修复QQ空间补丁方案实战篇。本篇博客我们将介绍1)如何打包补丁包; 2)如何将通过ClassLoader加载补丁包;3)如何将补丁包的dex插入到`dexElements`的前面

接下来的几篇博客我会用一个真实的demo来介绍如何实现热修复。具体的内容包括:

  • 如何打包补丁包
  • 如何将通过ClassLoader加载补丁包

1. 创建Demo

demo很简单,创建一个只有一个Activity的demo:

package com.biyan.demo
public class MainActivity extends Activity {

    private Calculator mCal;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mCal = new Calculator();
    }
    public void click(View view) {
        Toast.makeText(this, String.valueOf(mCal.calculate()),Toast.LENGTH_SHORT).show();
    }
}
Public class Caculoator {
    public float calculate() {
        return 1 / 0;
    }
}

demo的代码很简单,运行会出什么bug也很清楚了,在此就不演示了。

2.创建补丁包

首先修复Calculator的bug。

package com.biyan.demo
Public class Caculoator {
    public float calculate() {
        return 1 / 1;
    }
}

重新编译项目,在build目录下找到Calculator.class文件,将其拷出来,准备打包。放置在于Calculator包名相同的路径下。
这里写图片描述
将其打成jar包:

jar -cvf patch.jar com

然后再将对应的jar包打成dex包:

dx --dex --output=patch_dex.jar patch.jar

dx是讲jar包打成dex包的工具,安装在path-android-sdk/build-tools/version(如24.0.0)/dx。
patch_dex.jar就是补丁包,接下来将其安装在sdCard中,接下来应用从sdCard上加载该补丁包。

3. 加载补丁

根据上一篇博客的介绍,加载补丁的思路如下:

  • 在Application的onCreate()方法中获取应用本身的BaseDexClassLoader,然后通过反射得到对应的dexElements
  • 创建一个新的DexClassLoader实例,然后加载sdCard上的补丁包,然后通过同样的方法得到对应的dexElements
  • 将两个dexElements合并,然后再利用反射将合并后的dexElements赋值给应用本身的BaseDexClassLoader

接下来看下具体代码:

package com.hotpatch.demo;

import android.app.Application;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import dalvik.system.DexClassLoader;

/**
 * Created by hp on 2016/4/6.
 */
public class HotPatchApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        // 获取补丁,如果存在就执行注入操作
        String dexPath = Environment.getExternalStorageDirectory().getAbsolutePath().concat("/patch_dex.jar");
        File file = new File(dexPath);
        if (file.exists()) {
            inject(dexPath);
        } else {
            Log.e("BugFixApplication", dexPath + "不存在");
        }
    }

    /**
     * 要注入的dex的路径
     *
     * @param path
     */
    private void inject(String path) {
        try {
            // 获取classes的dexElements
            Class<?> cl = Class.forName("dalvik.system.BaseDexClassLoader");
            Object pathList = getField(cl, "pathList", getClassLoader());
            Object baseElements = getField(pathList.getClass(), "dexElements", pathList);

            // 获取patch_dex的dexElements(需要先加载dex)
            String dexopt = getDir("dexopt", 0).getAbsolutePath();
            DexClassLoader dexClassLoader = new DexClassLoader(path, dexopt, dexopt, getClassLoader());
            Object obj = getField(cl, "pathList", dexClassLoader);
            Object dexElements = getField(obj.getClass(), "dexElements", obj);

            // 合并两个Elements
            Object combineElements = combineArray(dexElements, baseElements);

            // 将合并后的Element数组重新赋值给app的classLoader
            setField(pathList.getClass(), "dexElements", pathList, combineElements);

            //======== 以下是测试是否成功注入 =================
            Object object = getField(pathList.getClass(), "dexElements", pathList);
            int length = Array.getLength(object);
            Log.e("BugFixApplication", "length = " + length);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    /**
     * 通过反射获取对象的属性值
     */
    private Object getField(Class<?> cl, String fieldName, Object object) throws NoSuchFieldException, IllegalAccessException {
        Field field = cl.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(object);
    }

    /**
     * 通过反射设置对象的属性值
     */
    private void setField(Class<?> cl, String fieldName, Object object, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field = cl.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(object, value);
    }

    /**
     * 通过反射合并两个数组
     */
    private Object combineArray(Object firstArr, Object secondArr) {
        int firstLength = Array.getLength(firstArr);
        int secondLength = Array.getLength(secondArr);
        int length = firstLength + secondLength;

        Class<?> componentType = firstArr.getClass().getComponentType();
        Object newArr = Array.newInstance(componentType, length);
        for (int i = 0; i < length; i++) {
            if (i < firstLength) {
                Array.set(newArr, i, Array.get(firstArr, i));
            } else {
                Array.set(newArr, i, Array.get(secondArr, i - firstLength));
            }
        }
        return newArr;
    }

}

核心代码就这么多,接下来运行一下程序。程序还是Crash了。。。
DingTalk20170220205018
原因是类预校验问题引起的:

  • 在apk安装的时候系统会将dex文件优化成odex文件,在优化的过程中会涉及一个预校验的过程
  • 如果一个类的static方法,private方法,override方法以及构造函数中引用了其他类,而且这些类都属于同一个dex文件,此时该类就会被打上CLASS_ISPREVERIFIED
  • 如果在运行时被打上CLASS_ISPREVERIFIED的类引用了其他dex的类,就会报错
  • 所以MainActivityonCreate()方法中引用另一个dex的类就会出现上文中的问题
  • 正常的分包方案会保证相关类被打入同一个dex文件
  • 想要使得patch可以被正常加载,就必须保证类不会被打上CLASS_ISPREVERIFIED标记。而要实现这个目的就必须要在分完包后的class中植入对其他dex文件中类的引用
  • 要在已经编译完成后的类中植入对其他类的引用,就需要操作字节码,惯用的方案是插桩。常见的工具有javaassist,asm等。

其实QQ空间补丁方案的关键就在于字节码的注入而不是dex的注入。下一篇博客将会介绍字节码注入的相关细节。

相关文章
|
安全 虚拟化
在数字化时代,网络项目的重要性日益凸显。本文从前期准备、方案内容和注意事项三个方面,详细解析了如何撰写一个优质高效的网络项目实施方案,帮助企业和用户实现更好的体验和竞争力
在数字化时代,网络项目的重要性日益凸显。本文从前期准备、方案内容和注意事项三个方面,详细解析了如何撰写一个优质高效的网络项目实施方案,帮助企业和用户实现更好的体验和竞争力。通过具体案例,展示了方案的制定和实施过程,强调了目标明确、技术先进、计划周密、风险可控和预算合理的重要性。
527 5
|
8月前
|
数据采集 监控 API
告别手动埋点!Android 无侵入式数据采集方案深度解析
传统的Android应用监控方案需要开发者在代码中手动添加埋点,不仅侵入性强、工作量大,还难以维护。本文深入探讨了基于字节码插桩技术的无侵入式数据采集方案,通过Gradle插件 + AGP API + ASM的技术组合,实现对应用性能、用户行为、网络请求等全方位监控,真正做到零侵入、易集成、高稳定。
910 94
|
JavaScript 前端开发 Android开发
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
609 13
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
数据可视化 前端开发 测试技术
接口测试新选择:Postman替代方案全解析
在软件开发中,接口测试工具至关重要。Postman长期占据主导地位,但随着国产工具的崛起,越来越多开发者转向更适合中国市场的替代方案——Apifox。它不仅支持中英文切换、完全免费不限人数,还具备强大的可视化操作、自动生成文档和API调试功能,极大简化了开发流程。
|
数据采集 JSON 网络安全
移动端数据抓取:Android App的TLS流量解密方案
本文介绍了一种通过TLS流量解密技术抓取知乎App热榜数据的方法。利用Charles Proxy解密HTTPS流量,分析App与服务器通信内容;结合Python Requests库模拟请求,配置特定请求头以绕过反爬机制。同时使用代理IP隐藏真实IP地址,确保抓取稳定。最终成功提取热榜标题、内容简介、链接等信息,为分析热点话题和用户趋势提供数据支持。此方法也可应用于其他Android App的数据采集,但需注意选择可靠的代理服务。
636 11
移动端数据抓取:Android App的TLS流量解密方案
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
640 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
数据采集 JavaScript Android开发
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
686 7
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
存储 编解码 开发工具
Android平台毫秒级低延迟HTTP-FLV直播播放器技术探究与实现
本文详细探讨了在Android平台上实现HTTP-FLV播放器的过程。首先介绍了FLV格式的基础,包括文件头和标签结构。接着分析了HTTP-FLV传输原理,通过分块传输实现流畅播放。然后重点讲解了播放器的实现步骤,涵盖网络请求、数据解析、音视频解码与渲染,以及播放控制功能的设计。文章还讨论了性能优化和网络异常处理的方法,并总结了HTTP-FLV播放器的技术价值,尤其是在特定场景下的应用意义。
806 11
|
机器学习/深度学习 传感器 人工智能
穹彻智能-上交大最新Nature子刊速递:解析深度学习驱动的视触觉动态重建方案
上海交大研究团队在Nature子刊发表论文,提出基于深度学习的视触觉动态重建方案,结合高密度可拉伸触觉手套与视觉-触觉联合学习框架,实现手部与物体间力量型交互的实时捕捉和重建。该方案包含1152个触觉感知单元,通过应变干扰抑制方法提高测量准确性,平均重建误差仅1.8厘米。实验结果显示,其在物体重建的准确性和鲁棒性方面优于现有方法,为虚拟现实、远程医疗等领域带来新突破。
427 32
|
机器学习/深度学习 人工智能 自然语言处理
企业级API集成方案:基于阿里云函数计算调用DeepSeek全解析
DeepSeek R1 是一款先进的大规模深度学习模型,专为自然语言处理等复杂任务设计。它具备高效的架构、强大的泛化能力和优化的参数管理,适用于文本生成、智能问答、代码生成和数据分析等领域。阿里云平台提供了高性能计算资源、合规与数据安全、低延迟覆盖和成本效益等优势,支持用户便捷部署和调用 DeepSeek R1 模型,确保快速响应和稳定服务。通过阿里云百炼模型服务,用户可以轻松体验满血版 DeepSeek R1,并享受免费试用和灵活的API调用方式。
911 12

推荐镜像

更多