Android hotfix 方案

http://blog.csdn.net/watermusicyes/article/details/50529461


项目以飞快的速度迭代,2周进行一次迭代升级。每次开发完功能跑通,在现有机型上测试没问题的话,就提交市场。

在以极快的速度迭代的时候,避免不了出现各种问题,传说中的bug

或,重大bug,需要紧急修复 
或,可以下次迭代修复的bug 
或,影响用户体验的行为

出现bug后,我们的一贯做法:

发布紧急版本,等待用户下载更新。

可是,并不是所有的用户都会下载更新。那该怎么办?

强制更新,这是不太友好的行为,如果人现在用的是流量,强制别人更新,会耗费别人流量,还是挺贵的!这确实有点说不过去。

有没有一种方法,在用户不下载新App的前提下修复这些bug。当然有,以下是我找到的两种解决方案。

有两种解决方案:

  1. Dexposed
  2. AndFix

Dexposed

Dexposed是基于开源Xposed框架实现的一个Android平台上功能强大的无侵入式运行时AOP框架。

Dexposed的AOP实现是完全无侵入式的,没有使用任何注解处理器,编织器或者字节码重写器。只需要在应用初始化阶段加载一个很小的JNI库就可以完成Dexposed框架的集成。

Dexposed不仅可以hook应用中的自定义函数,也可以hook你的应用进程中调用的Android框架的函数。这个特性对于Android开发者来说有很多好处,因为我们严重依赖于Android SDK的版本碎片化。

借助动态类加载技术,运行中的app可以加载一小段经过编译的Java AOP代码,在不需要重启app的前提下实现修改目标app的行为。

典型的应用场景

AOP编程 
插桩(例如测试,性能监控等) 
在线热更新,修复严重的,紧急的或者安全性的bug 
SDK hooking以提供更好的开发体验

在这里,我只关心Dexposed在Android平台上的在线热更新Hotfix的使用:

在Android平台,Dexposed支持函数级别的在线热更新,例如对已经发布在应用市场上的APK,当我们从crash统计平台上发现某个函数调用有bug,导致经常性crash,而这个时候离下次迭代的预定发布日期还有一段时间。这种情况下,可以在本地开发一个补丁APK,并发布到服务器中,应用市场上的APK下载这个补丁APK并集成后,就可以修复这个crash。

上述涉及到两个角色:

  1. 宿主apk——已经发布在应用市场上,有bug并且需要修复的apk;
  2. 补丁apk——在本地开发的,修复了bug的apk;

HotFix是什么

HotFix用来修复线上严重的,紧急的或者安全性的bug,这里会涉及到两个apk文件:一个我们称为宿主apk,也就是发布到应用市场的apk,一个称为补丁apk宿主apk出现bug时,通过在线下载的方式从服务器下载补丁apk,使用补丁apk中的函数替换原来的函数,从而实现在线修复bug的功能。

如何借助Dexposed实现HotFix

  1. 需要再引入一个名为patchloader的jar包,这个函数库实现了一个热更新框架,宿主apk在发布时会将这个jar包一起打包进apk中
  2. 补丁apk只是在编译时需要这个jar包,但打包成apk时不包含这个jar包,以免补丁apk集成到宿主apk中时发生冲突
  3. 补丁apk将会以provided的形式依赖dexposedbridge.jar和patchloader.jar,补丁apk的build.gradle文件中依赖部分脚本如下所示
<code class="hljs livecodeserver has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">dependencies {
    provided <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">files</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'libs/dexposedbridge.jar'</span>)
    provided <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">files</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'libs/patchloader.jar'</span>)
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>
支持状态:

Dexposed支持从Android2.3到4.4(除了3.0)的所有dalvid运行时arm架构的设备,稳定性已经经过实践检验。

缺点:

我的设备是5.0 或者更高系统的使用起来就有点问题了。

因此,我并没打算仔细的学习Dexposed,只是做一个简要的了解。

具体使用可以参见这个博客,写的很棒:

使用示例:Android平台免Root无侵入AOP框架Dexposed使用详解

AndFix

AndFix is What ?

经过综合对比,发现AndFix可用性比较强,因此决定学习AndFix的使用,从官方文档入手。

AndFix是一个Android App的在线热补丁框架。使用此框架,我们能够在不重复发版的情况下,在线修改App中的Bug。AndFix就是 “Android Hot-Fix”的缩写。 
它支持Android 2.3到6.0版本,并且支持arm与X86系统架构的设备。完美支持Dalvik与ART的Runtime。AndFix 的补丁文件是以 .apatch 结尾的文件。它从你的服务器分发到你的客户端来修复你App的bug 。

原理:

AndFix执行的原理是实现方法体的替换,如下图所示(该图片来自AndFix的github介绍): 
这里写图片描述 
方法替换:

AndFix通过Java的自定义注解来判断一个方法是否应该被替换,如果可以就会hook该方法并进行替换。AndFix在ART架构上的Native方法是art_replaceMethod 、在X86架构上的Native方法是dalvik_replaceMethod。他们的实现方式是不同的。对于Dalvik,它将改变目标方法的类型为Native同时hook方法的实现至AndFix自己的Native方法,这个方法称为 dalvik_dispatcher,这个方法将会唤醒已经注册的回调,这就是我们通常说的hooked(挂钩)。对于ART来说,我们仅仅改变目标方法的属性来替代它。

更多信息,见这里:AndFix/jni/

Fix 流程,大致如下:

  1. 发现bug(友盟、TestIn 、用户反馈)
  2. 分析bug产生原因
  3. 创建并发布补丁
  4. App打补丁

在App中引入AndFix

如何来获得AndFix ?

将AndFix aar 作为library编译入你的工程:

  • 如果你用的是maven依赖,添加如下代码:
<code class="hljs xml has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">dependency</span>></span>
    <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">groupId</span>></span>com.alipay.euler<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">groupId</span>></span>
    <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">artifactId</span>></span>andfix<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">artifactId</span>></span>
    <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">version</span>></span>0.3.1<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">version</span>></span>
    <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">type</span>></span>aar<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">type</span>></span>
<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">dependency</span>></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>
  • 如果你用的是gradle依赖,添加如下代码:
<code class="hljs nginx has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">dependencies</span> {
    <span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">compile</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'com.alipay.euler:andfix:0.3.1<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">@aar</span>'</span>
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>
如何使用AndFix ?
  1. 初始化 PatchManager

    <code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    patchManager = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> PatchManager(context);
        patchManager.init(appversion);<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//current version</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>
  2. Load patch 加载补丁

    <code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    patchManager<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.loadPatch</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

    你应该尽可能早的加载补丁,通常都是在Application的onCreate()方法中进行初始化。

  3. Add patch 添加补丁

    <code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    patchManager.addPatch(path);<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//path:补丁文件下载到本地的路径。</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

    当一个新的补丁文件被下载后,调用addPatch(path)就会立即生效。

开发工具

AndFix 提供了一个补丁创建工具 apkpatch.

如何使用这个工具

1.准备两个应用apk: 一个是线上的apk,另一个是修复了bug的apk. 
2.通过提供的两个apk生成补丁文件.

运行如下命令:

<code class="hljs haml has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">usage: apkpatch -f <new> -t <old> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
 -<span class="ruby" style="box-sizing: border-box;">a,--<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">alias</span> <<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">alias</span>>     keystore entry <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">alias</span>.
</span> -<span class="ruby" style="box-sizing: border-box;">e,--epassword <***>   keystore entry password.
</span> -<span class="ruby" style="box-sizing: border-box;">f,--from <loc>        new <span class="hljs-constant" style="box-sizing: border-box;">Apk</span> file path.
</span> -<span class="ruby" style="box-sizing: border-box;">k,--keystore <loc>    keystore path.
</span> -<span class="ruby" style="box-sizing: border-box;">n,--name <name>       patch name.
</span> -<span class="ruby" style="box-sizing: border-box;">o,--out <dir>         output dir.
</span> -<span class="ruby" style="box-sizing: border-box;">p,--kpassword <***>   keystore password.
</span> -<span class="ruby" style="box-sizing: border-box;">t,--to <loc>          old <span class="hljs-constant" style="box-sizing: border-box;">Apk</span> file path.</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>

这个时候,你就有了你的应用的救世主——补丁文件。接下来你就可以通过某种方式将该补丁文件分发给你的客户端。

  • 或,adb push 到sdk
  • 或,发布到服务器,客户端下载该apatch

有时候,你的团队成员可能会fix各自的bug,这个时候就会有不止一个补丁文件.apatch。这种情况下,你可以用这个工具merge这些.apatch文件:

<code class="hljs haml has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">usage: apkpatch -m <apatch_path...> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
 -<span class="ruby" style="box-sizing: border-box;">a,--<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">alias</span> <<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">alias</span>>     keystore entry <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">alias</span>.
</span> -<span class="ruby" style="box-sizing: border-box;">e,--epassword <***>   keystore entry password.
</span> -<span class="ruby" style="box-sizing: border-box;">k,--keystore <loc>    keystore path.
</span> -<span class="ruby" style="box-sizing: border-box;">m,--merge <loc...>    path of .apatch files.
</span> -<span class="ruby" style="box-sizing: border-box;">n,--name <name>       patch name.
</span> -<span class="ruby" style="box-sizing: border-box;">o,--out <dir>         output dir.
</span> -<span class="ruby" style="box-sizing: border-box;">p,--kpassword <***>   keystore password.</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>
运行示例
  1. 导入samplesI/AndFixDemo进你的IDE,给AndFixDemo添加AndFix(library project 或者 aar)的依赖。
  2. build 工程,保存应用为1.apk,然后将该apk安装到手机或者模拟器上。
  3. 修改com.euler.test.A,作为Fix后的包。
  4. build 工程,保存应用为2.apk
  5. Use apkpatch tool to make a patch. 使用apkpatch 工具制作一个补丁文件。
  6. Rename the patch file to out.apatch, and then copy it to sdcard.重命名补丁文件为out.apatch ,然后将它拷贝到sdcard上。
  7. 运行1.apk然后查看log
注意

混淆

如果你使用混淆,你要保存mapping.txt,这样的话你在新版本的构建是就可以借助 “”-applymapping” 来使用它了。

混淆时需要保留下面的内容:

  • Native方法 ,比如:com.alipay.euler.andfix.AndFix
  • Annotation,比如:com.alipay.euler.andfix.annotation.MethodReplace

为了确保这些类在运行了ProGuard后,可以被找见,在ProGuard配置中添加如下代码:

<code class="hljs scala has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">-keep <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> * <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">extends</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">java</span>.<span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">lang</span>.<span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">annotation</span>.<span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">Annotation</span></span>
-keepclasseswithmembernames <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> * {</span>
    native <methods>;
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>
局限性

如果你使用了,软件加固比如 Bangcle,为了生成补丁文件,你最好使用为经过加固的apk.使用这些加固,很可能使热补丁失效。

安全性

验证apatch文件的签名是否就是在使用apkpatch工具时使用的签名(如果不验证那么任何人都可以制作自己的apatch文件来对你的APP进行修改)

验证optimize file的指纹(防止有人替换掉本地保存的补丁文件,所以要验证MD5码)


以下是我在用demo时碰到的问题:

  • 运行apkpatch报nullPointException

    关于该问题的github issue : 运行apkpatch报nullPointException

    解决方法:

    运行命令行时 alias 填错了,应该填alias名字,我填成了alias路径。修改后,解决了问题。

    <code class="hljs haml has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">zhanggeng:apkpatch-1.0.3$
    ./apkpatch.sh 
    -<span class="ruby" style="box-sizing: border-box;">f /<span class="hljs-constant" style="box-sizing: border-box;">Users</span>/zhanggeng/<span class="hljs-constant" style="box-sizing: border-box;">Desktop</span>/after/app-release.apk 
    </span>-<span class="ruby" style="box-sizing: border-box;">t /<span class="hljs-constant" style="box-sizing: border-box;">Users</span>/zhanggeng/<span class="hljs-constant" style="box-sizing: border-box;">Desktop</span>/before/app-release.apk 
    </span>-<span class="ruby" style="box-sizing: border-box;">o /<span class="hljs-constant" style="box-sizing: border-box;">Users</span>/zhanggeng/<span class="hljs-constant" style="box-sizing: border-box;">Desktop</span>/apatch/ 
    </span>-<span class="ruby" style="box-sizing: border-box;">k /<span class="hljs-constant" style="box-sizing: border-box;">Users</span>/zhanggeng/<span class="hljs-constant" style="box-sizing: border-box;">Desktop</span>/fixdemo.jks
    </span>-<span class="ruby" style="box-sizing: border-box;">p <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">123456</span> 
    </span>-<span class="ruby" style="box-sizing: border-box;">a fixdemo 
    </span>-<span class="ruby" style="box-sizing: border-box;">e <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">123456</span></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>
  • 运行程序,检测是否可以使用时,抛出异常:java.util.zip.ZipException: File too short to be a zip file: 0

    关于该问题的github issue: java.util.zip.ZipException: File too short to be a zip file: 0

    解决方法:

    这个问题是,上面那个问题造成的。只要补丁文件生成正确,是不会有这个问题的。

    参考文章:


Demo下载地址:AndFixDemo


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值