以下常见问题适用于SDK1.0,在升级SDK2.0后已解决绝大部分问题。SDK2.0详情参考 SDK2.0文档

4.1 安卓端兼容性怎么样?支持的机型有哪些?

当前通过我们测试,已确定包含(并不限于)的机型接近800款,包含了市面上绝大部分主流机器,可点击下载查看。(注意:不排除因刷机or系统升级导致机型不支持的情况,具体case请加入钉钉群和我们沟通)

4.2 HotFix的使用中不被允许的情况

  • 暂时不支持新增方法, 新增字段, 但是支持新增类, 所以需要新增方法/字段可以通过新增类来实现
  • 不支持资源修复, so修复
  • 三星note3,S4,S5的5.0设备以及X86设备不支持(点击查看具体支持的机型)
  • 参数包括long,double,float基本类型的方法不能被patch, 不包括基本类型封装类Long,Double,Float
  • 被反射调用的非静态方法不能被patch
  • 参数超过8的方法不能被patch
  • 构造方法不能被patch
  • 使用注解的方法视情况而定是否支持被patch(详细说明参考Demo工程BaseBug.md文件中关于注解的说明)
  • 泛型参数的方法如果patch存在兼容性问题
  • 在打包的时候偶尔会存在两次打包内部类的名字不一致问题,这种情况会导致打AndFix打包失败,暂时无解
  • 我们建议不要通过GooglePlay发布带热修复SDK的APP,存在政策风险
  • 暂不支持android7.0

违反上述规则, 则可能导致打补丁包或者加载补丁失败的情况, 代码实例请参考Demo工程下的BaseBug.md文件下的详细说明, 另一方面这些限制随着SDK版本的升级会逐步的减少, 敬请期待.

4.3 混淆过程中的问题

应用在上线打包APK时,往往会进行混淆操作,但是由于修复前后两个APK混淆结果不同会导致patch无效,无法修复bug。
所以,需要注意的是:应用打包APK的时候修复前后两个APK必须使用同一份mapping.txt,以保证两个APK混淆结果一致。

如果app应用了混淆配置, 那么需要做如下处理. 如果没有应用混淆配置, 则不需要如下处理

  • 修复前的项目, release包:生成的mapping.txt在当前模块目录下的/build/outputs/mapping/release路径下,debug包:mapping.txt在当前模块目录下的/build/outputs/mapping/debug路径下, 然后移动到当前模块目录下
    -printmapping mapping.txt命令发现在gradle升级到最新版本后不生效了, 所以mapping.txt文件移步build目录下查找
  • 修复后的项目, 修改proguard-rules.pro文件, 保证混淆结果一致

    -applymapping mapping.txt
    此时会在当前模块目录下查找mapping.txt文件, 所以务必确保mapping.txt文件移动到了当前模块的目录下

4.4 是否可以支持应用多dex的修复?

可以,比如谷歌MultiDex方案,最新的补丁工具打补丁包过程中会把多个dex整合为一个完整的dex, 然后分析前后dex的差异打出补丁包, 所以最后不管apk中有几个dex, 都是能正确打出补丁包.

PS: 正确打出补丁包的前提是apk未加固, 加固后将直接改变apk的dex结构, 会导致补丁工具生成补丁失败

4.5 应用加固支持情况

支持的, 但是必须使用加固前的apk生成补丁包, 加固后的apk能正常加载补丁,遇到具体case欢迎来钉钉群交流。

4.6 应用接入动态部署框架支持情况

动态部署框架一般属于多classloader实现, 暂时不支持.

4.7 项目依赖了其它阿里系sdk, 导致编译报utdid包冲突

去掉compile ‘com.alibaba.sdk.android.plugins.jar:alisdk-utdid:0.0.1’依赖即可

4.8 关于注解的支持

详细说明参考Demo工程BaseBug.md文件中关于注解的说明

4.9 补丁工具打补丁过程诡异错误

4.9.1 类Field修复的说明

  1. 不允许新增类Field
  2. 不允许修改构造函数
    第一个点很好理解, 第二点由于不允许修改构造函数所以导致了类Field是不允许直接修改的
//修改前
public class BaseBug {
    private String temp = "old apk...";
}
//修改后
public class BaseBug {
    private String temp = "new apk..." //情况1
    {
        temp = "new apk..." //情况2
    } 
    public BaseBug(){
        temp = "new apk..."; //情况3
    }
}

这样是不允许的, 因为类Field的直接修改会反应在构造函数中, 所以不允许

当然如果你是在除了构造函数之外任何方法中修改, 都是没问题的

public class BaseBug {
    private String temp = "old apk...";
    public void test(Context context) {
        temp =  "new apk...";
    }
}

¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨

4.9.2 代码明明没有新增全局field, 为什么补丁工具提示新增了field?

代码修复前BaseBug没有配置任何混淆配置, 那么temp域因为没有被任何代码块引用所以被移除

public class BaseBug {
    private String temp;
    public void test() {
        temp =  "new apk...";
    }
}

修复后, 因为temp域在test方法中被引用, 所以不会被混淆移除, 所以新apk中的BaseBug类就存在了新域temp

public class BaseBug {
    private String temp;
    public void test(Context context) {
        temp = "new apk";
        Toast.makeText(context.getApplicationContext(), temp, Toast.LENGTH_SHORT).show();
    }
}

hotfix不支持新增域, 打补丁工具检测到新增类field直接报错异常退出, 打补丁失败. 需要避免这种情况

¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨

4.9.3 代码明明没有新增method, 为什么补丁工具提示新增了method?

修复前, InnerClass作为内部类, 假设此时的私有变量s, 没有被任何类引用

public class BaseBug {
    public void test(Context context) {
        Toast.makeText(context.getApplicationContext(), "old apk...", Toast.LENGTH_SHORT).show();
    }
    class InnerClass {
        private String s;
        private InnerClass(String s) {
            this.s = s;
        }
    }
}

修复后test方法引用了内部类inner.s, 内部类会在编译期编译为跟外部类一样的顶级类. 所以外部类为了能访问private/protected修饰的内部类方法, 那么编译期间自动为内部类生成access$**相关方法, 此处修复后apk自动为InnerClass内部类生成access$100方法. 同样的如果此时匿名内部类需要访问外部类的private属性/方法, 内部类也会自动生成access$**相关方法.

public class BaseBug {
    public void test(Context context) {
        InnerClass inner = new InnerClass("old apk");
        Toast.makeText(context.getApplicationContext(), inner.s, Toast.LENGTH_SHORT).show();
    }
    class InnerClass {
        private String s;
        private InnerClass(String s) {
            this.s = s;
        }
    }
}

因为old apk没有access$100方法, 打补丁工具检测到新增了类方法直接报错异常退出, 打补丁失败. 需要避免这种情况

¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨

4.9.4 混淆配置导致的其它坑

其实除了4.9.2以及4.9.3节中说明可能导致的新增filed/method情况之外, 还有另一种情况. 我们知道打包编译的阶段可能会进行一系列的优化,包括方法的内敛/裁剪等等,这些东西平时我们是不关注的,但是在生成patch包的时候就会有影响. 如果你的应用混淆配置文件中没有加上-dontoptimize这一项, 那么很有可能导致方法被内敛/裁剪. 举例如下, 以下例子都是在gradle2.14.1版本的表现, 其它gradle版本表现是否一致, 有待考证.

实例1 方法的内敛

public class BaseBug {
    public static void test(Context context) {
        Toast.makeText(context.getApplicationContext(), "old apk...", Toast.LENGTH_SHORT).show();
        print("haha");
    }
    public static void print(String s) {
        Log.d("BaseBug", s);
    }
}

测试发现, 一个方法如果只被调用了一次那么该方法将会被内敛, 此时假如print方法只在test方法中被调用, 但是test方法被不止一个地方调用, 那么test方法没被内敛, print方法被内敛. 查看下生成的mapping.txt文件, 印证了这个结论, 没有print方法的映射

com.taobao.hotfix.demo.BaseBug -> com.taobao.hotfix.demo.a:
    void test(android.content.Context) -> a

如果恰好将要patch的一个方法调用了print方法, 那么print被调用了两次, 在新的apk中不会被内敛, 所以此时打补丁发现新增了print方法异常退出, 打补丁失败. 需要避免这种情况

实例2 方法的裁剪

这里先假设test方法没有被内敛掉, 修复前代码如下

public class BaseBug {
    public static void test(Context context) {
        Log.d("BaseBug", "test");
    }
}

查看下生成的mapping.txt文件

com.taobao.hotfix.demo.BaseBug -> com.taobao.hotfix.demo.a:
    void test$faab20d() -> a

此时test方法context参数没被使用, 所以test方法的context参数被裁剪, 混淆任务首先生成test$faab20d()裁剪过后的无参方法, 然后再混淆. 所以如果将要patch该test方法恰好用到了context参数, 那么新apk中新增了test(context)方法, 所以此时打补丁发现新增了方法异常退出, 打补丁失败.

那如何解决实例二的这种问题呢?当然是有办法的,参数引用住,不让编译器在优化的时候认为这是一个无用的参数就好了,可以采取的方法很多,这里介绍一种最有效的方法:

public static void test(Context context) {
        if (Boolean.FALSE.booleanValue()) {
            context.getApplicationContext();
        }
        Log.d("BaseBug", "test");
    }

注意这里不能用基本类型false,必须用包装类Boolean,因为如果写基本类型这个if语句也很可能会被优化掉的

实例3 android默认混淆配置文件

一般情况下项目的混淆配置都会使用到android sdk默认的混淆配置文件proguard-android-optimize.txt或者proguard-android.txt, 但是如果不了解这些原理的情况下, 强烈推荐不使用proguard-android-optimize.txt, 这个配置文件虽然会让最终的apk包变小, 但是也因为默认会在执行混淆任务的时候optimize代码从而导致实例1,2中举例的不可预料情况. 如下推荐使用proguard-android.txt这个配置文件有-dontoptimize这一项, 最后不会导致方法被裁剪/内敛

buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.pro'
        }
    }

4.10 HotFix与代码注入框架是否冲突?

方法如果只是被运行时注解是可以被patch的, 注意是被运行时注解的方法可以被patch, 而不是运行注解本身可以被patch.

但是如果是编译期注解就值得商榷, 因为无法确保编译期注解前后两次打包生成apk中生成的注解帮助类是否一致, 就算前后两次编译期间生成的注解类一致, 但如果生成的注解帮助类反射调用了方法, 那该方法也是不能被patch的.

另外一方面鉴于一般的开发者对注解框架原理不大熟悉, 注解框架可能既使用了编译期也使用运行期注解, 此时是不支持被patch的.

当然如果没有使用注解框架, 那么自定义运行时注解那么是可以被patch的. 或者使用了注解框架, 开发者能确保前后两次编译生成的注解编译类一致, 同时能自行辨认该方法不受注解的影响(该方法不被反射调用), 这样的情况下也是可以被patch的.

鉴于此, 所以hotfix统一标识:注解的类不支持被patch.

4.11 调试工具连接应用找不到包名的问题

因为部分手机禁用了跨进程aidl导致应用连接失败, 主要问题发生在魅族/小米手机上居多,只要手动设置开启即可

4.12 补丁加载特殊异常情况说明

UnsatisfiedLinkError... libandfix.so

找不到so文件, 参考part1节说明

IllegalArgumentException: Expected receiver of type com.taobao.hotfix.demo.BaseBug_CF_1476949855532, but got com.taobao.hotfix.demo.BaseBug

patch方法被反射调用了

java.io.InvalidClassException: com.demo.Temp; local class incompatible: stream classdesc serialVersionUID = -3445057096334719727, local class serialVersionUID = 4436690298739845368

反序列话失败, 我们知道实现了Serializable接口的一个类如果发生了任何变更(不包括方法内部的具体实现), 那么运行期间computeSerialVersionUID计算得到的SUID和反序列化文件得到的SUID不一致, 所以反序列号失败. 实际上类你可能没有手动修改它, 但是由于hotfix内部运行的需要可能会把这个类的一些method和field的访问符强制转为public, 那么这种情况下就会导致反序列失败. 这种情况下, 我们建议强制写死static final long serialVersionUID=value

FAQ

关于此文档暂时还没有FAQ
返回
顶部