让 Terminal 和 Android Studio 使用同一个 Gradle Daemon

在同时使用 Terminal 和 Android Studio 开发和编译 Android 项目时,跑 gradle 任务经常有一个提示,类似:

Starting a Gradle Daemon, 2 incompatible Daemons could not be reused, use --status for details

这表示 Gradle 又开启了一个新的 Daemon,已经存在的没有被重用。

执行gradlew --status可以看到已经有3个Daemon 存活,对于我这台13年末产的老爷机来说是个严重的负担

1
2
3
4
5
6
7
$ ./gradlew --status
PID STATUS INFO
33346 IDLE 4.6
31842 IDLE 4.6
29669 IDLE 4.6

Only Daemons for the current Gradle version are displayed. See https://docs.gradle.org/4.6/userguide/gradle_daemon.html#sec:status

存在多个 Daemon 的原因

Gradle 官方给的解释是:

  1. Gradle 版本不一样,比如一个项目用了 Gradle 4.4 而另一个用了 Gradle 4.6;
  2. Java 版本不一样,比如一个项目使用 JDK 7 而另一个则是 JDK 8 执行的 Gradle;
  3. Daemon JVM 参数不一样,比如一个项目指定了 -Xmx1024m 而另一个 -Xmx2048m
  4. 存在的 Daemon 都处于 BUSY 状态,比如Android Studio 和 Terminal 同时跑 Gradle 任务。

如何共用一个Daemon

针对上述原因,可以得到以下方案:

对于同一个项目来说,使用同一个 JDK 执行 Gradle即可;如果是多个项目则另外需要指定使用同一个版本的gradle、并设置同样的 JVM 参数。

瞎拍(5)- 枸杞岛之旅

大王村沙滩

一直存着愿,想去看碧海蓝天,趁着 5.1 假便去舟山的枸杞岛走了一遭。

三礁江大桥

~


可惜天公不作美,小岛上一直有雾罩着,能见度不高,幻想中的海天一色,终究没看着。


~

瞎拍(4)

老家门口的油菜花开的正盛,花丛中许多蜜蜂穿梭,忍不住追着瞎拍了几张 (´・ω・`)

✿

✿

✿

✿

✿

一个小插件解决组件化引发的DEX字段数爆炸的问题

TL;DR

插件名:shrinker

项目地址: https://github.com/yrom/shrinker(其实很早之前就已经发布到github上了,不过无人问津→_→)

插件效果:与removeUnusedCode 同用可以起到最佳效果

这里有一个简单的测试项目,大部分类来自于依赖的support库,结果如下:

选项 methods fields classes
原始项目 22164 14367 2563
应用shrinker 插件 21979 7805 2392
应用shrinker 并开启 removeUnusedCode 11335 3302 1274

如果应用于依赖众多的大型项目则效果惊人。

ps. 其实已经在 b站的APP 上使用很久了,插件稳定、可靠且无副作用。

原理

不论组件化或者说模块化,都有个核心思想:拆分,拆成一个又一个独立的Library。

拆分 Library 引入的问题

举个例子

现一个 APP,它为了实践组件/模块化,拆分出了 common-ui ,business-a, business-b… 依赖关系如下图所示:

R 文件生成的大致流程如下图:

其中processReleaseResources 实际是调用的 aapt工具来给每个依赖的Library都生成一个最终确定的R.java

可想而知,第一个问题: 拆分的Android Library越多,R 文件越多!

然而,Library 的 R 文件只会在最终编译成 APK 时确定字段常量值,输出 aar 时只有一个R.txt用于记录声明的资源。

假设 common-ui 声明了15个公共drawable资源,则生成的 R 文件中将有 15个相关的用于记录的字段,而且每个依赖于它的上层的library 生成的R都会有这15个同名的字段,如下图:

由此可得,第二个问题: 越底层的依赖所声明资源越多,最终生成的 R 文件越庞大 ! 因为这些字段没有得到有效内联,最终生成的DEX字段数就会严重超标。

为了解决组件/模块化进程中出现的上述两个问题,shrinker 应运而生。

《Android Kotlin 指南》中译版

虽然很可惜没有在Kotlin 刚发布的时候跟进学习,现在也还不晚~

官方很早在 Github 上发布了一个名为 android kotlin guides 的网站供 Android 开发者学习使用,但似乎没有中文版(至今没找到╮( ̄▽ ̄)╭)。

于是乎,自己动手丰衣足食。也就几个页面,将它们都翻译了一下~

链接:https://yrom.github.io/kotlin-guides/
项目:https://github.com/yrom/kotlin-guides/

又试着将一个小工具用kotlin 重写了一遍,用以实践kotlin:https://github.com/Bilibili/xpref

That’s ALL.

瞎拍(3)

又闲来无事到植物园瞎拍了几张,也不枉这秋高气爽ヽ(゚∀゚*)ノ

✿

✿

✿

✿

Re: 用Go实现渠道打包工具walle

Walle,是美团点评技术团队针对 Android Signature V2 Scheme 签名过的APK的一个多渠道打包工具

近来恰巧在学习Go,就想着把它“翻译”成Go,权当作练手,同时还能给“包管理后台”用用。

Walle 的工作原理

想要把一个工具用另一种语言实现,首先得明白它的原理。

其实原理官方已经解释的很详细了,这里就不再详细缀述,主要列出一些关键的点:

APK 签名的本质

APK 文件其实是一个 ZIP(Jar包),所谓签名其实就是对包或者包中的文件做签名

V1 其实就是 Jar 签名,“签名”要保护的是包中的文件不被修改,并不保护包本身(如ZIP Comment);V2 则是针对V1的缺陷,在V1的基础上将包本身视为Blob再做一次签名。

这就要求 V2 需要在不破坏、不影响 ZIP 文件格式(能被普通的ZIP工具解压),不污染V1签名正确性(APK能被老Andriod系统识别)的前提下写入到包中。
APK 签名验证过程
(图来自https://source.android.com/security/apksigning/)
故而需要针对性的在不伤害 ZIP 文件格式的前提下做一些“魔改”,将 V2 签名数据块插入到 ZIP 的 Central Directory 之前。
APK V2 签名之前与之后

V2 签名之后的APK

APK 被分为 4 部分:

  1. Contents of ZIP entries (从文件头开始直到 APK Signing Block)
  2. APK Signing Block
  3. ZIP Central Directory
  4. ZIP End of Central Directory

1、3、4的内容受到APK Signing Block 保护,准确的说是受 APK Signing Block 中的签名信息保护

APK Signing Block 格式

  • Signing Block 字节数 (不含自身计数) (uint64)
  • “ID-value” 对序列:
    • 此“ID-value” 长度 (uint64)
    • ID (uint32)
    • value (可变长度: “ID-value” 长度 - 4个字节)
  • Signing Block 字节数 (与前面的数值相同) (uint64)
  • magic “APK Sig Block 42” (16 个字节)

为向后兼容考虑,ID-value对设计成了可以存在多个。

V2 签名是数据块中的一个ID-value对,其ID0x7109871a,值即为“签名信息”(signed data)。

在验证 V2 签名过程中,会跳过验证器不认识的 ID

插入渠道信息

从上面的原理可以得到结论APK Signing Block中可以增加一个ID-value来存储渠道,且不会破坏原有签名信息,即毋须重签名做到插入渠道

并发与并行之我见(concurrency vs parallelism)

引子

近来把玩了Java 8 的一些特性,如Lambda 表达式Stream,实在过瘾,有初学编程之快感,相见恨晚之情且按下不表。

其中 Stream 有一个parallelStream()的API则着实勾起了我的好奇心,parallel(译过来是“并行”)这个词于我而言可谓陌生,在不算深入的使用和品味一番后,记以本文。本人才疏学浅,如有谬误之处,请不吝指教。

咬文嚼字

在 Android 的 Java 代码世界里并发(concurrency)这个词比较常见,而并行(parallelism)这个词则少之又少,且常常被人与前者混为一谈。实质上,这两个词是完全不同的概念。

援引Java官方的文档中定义(翻译可能不够准确,建议看原文):

parallelism 译为并行,指至少两个线程同一时间在运行

concurrency 译为并发,指存在至少两个线程在执行着任务。更为一般性的并行,以时间分片作为执行单元的虚拟的并行。

我的理解:

首先得跟官方文档一样明确一个词义范围,否则就没有一点“可比性”了,提到这两个词多用其程序上执行“任务”层面的含义。

并行,就是字面意思,同一时刻执行多个任务。指执行程序代码的机器的物理上的一种能力,各个任务如同平行世界般的,形式上可以是多线程乃至多进程,被多个CPU同时运作。任务之间无任何依赖关系,不互斥、不交叉。

并发,指程序逻辑上的一种能力,支持多个任务同时存在一起被处理的能力。一个任务通常被划分为一个个的时间分片,比肩接踵般轮流的、可交替切换的被一个或多个CPU所执行,即不同任务的时间分片之间会有互斥、等待,一个任务可能因为CPU资源被抢走而被中断执行。

举个简单例子,单核CPU的机器,执行多线程化处理过的Java程序,一样称该程序支持并发,但它囿于机器性能永远做不到并行。

所提的并发和并行的概念都是为了最大化的利用多核CPU的能力,从而加速程序的执行。从这点来讲,那些将两个词混为一谈的人多半为一知半解的(看到这里的你已经不是了~)。

瞎拍(2)

闲来无事到植物园瞎拍了几张,也不枉这春光无限好

✿

✿

✿

✿

又走了一遭步行街,远远的观摩了一下大都市的夜

南京路

Android 不想和你说话,抛了个 java.lang.VerifyError

一个奇怪的崩溃

E/AndroidRuntime(22035): FATAL EXCEPTION: main
E/AndroidRuntime(22035): java.lang.VerifyError: com/sample/FileUtils
E/AndroidRuntime(22035): at com.sample.App.onCreate(App.java:16)
E/AndroidRuntime(22035): at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:999)
E/AndroidRuntime(22035): at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4220)
E/AndroidRuntime(22035): at android.app.ActivityThread.access$1300(ActivityThread.java:137)
E/AndroidRuntime(22035): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1262)
E/AndroidRuntime(22035): at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(22035): at android.os.Looper.loop(Looper.java:137)
E/AndroidRuntime(22035): at android.app.ActivityThread.main(ActivityThread.java:4819)
E/AndroidRuntime(22035): at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(22035): at java.lang.reflect.Method.invoke(Method.java:511)
E/AndroidRuntime(22035): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
E/AndroidRuntime(22035): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
E/AndroidRuntime(22035): at dalvik.system.NativeStart.main(Native Method)

为什么说奇怪?一般地,java.lang.VerifyError 是说 JVM 在加载一个类时,会去校验类的正确性,只有类文件不合法才会报这个Error。
比如,一个类试图extends一个标记为final的类,或者试图override final方法(发生在外部依赖类改变声明且应用没有完整重新编译的情况下)。
Android 中会发生这种情况的,一般是需要兼容API的时候,比如用到了高版本SDK中有的类,低版本没有,或者使用高版本API中有低版本没有的方法。

然而这个FileUtils类在com.sample.App中使用时候并没有用到与Android 版本相关的兼容性方法。

百思不得其解

Debug,有时候看堆栈是不够的,还需要查看Logcat中一些有用的上下文

W/dalvikvm(22035): VFY: unable to resolve static method 13457: Landroid/system/Os;.stat (Ljava/lang/String;)Landroid/system/StructStat;
W/dalvikvm(22035): VFY: unable to resolve exception class 1594 (Landroid/system/ErrnoException;)
W/dalvikvm(22035): VFY: unable to find exception handler at addr 0xe
W/dalvikvm(22035): VFY: rejected Lcom/sample/FileUtils;.getUid (Ljava/lang/String;)I
W/dalvikvm(22035): VFY: rejecting opcode 0x0d at 0x000e
W/dalvikvm(22035): VFY: rejected Lcom/sample/FileUtils;.getUid (Ljava/lang/String;)I
W/dalvikvm(22035): Verifier rejected class Lcom/sample/FileUtils;

Log也似乎与平常使用高版本SDK类时的兼容性警告类似:

W/dalvikvm(22524): VFY: unable to resolve virtual method 684: Landroid/content/res/Resources;.getColor (ILandroid/content/res/Resources$Theme;)I
W/dalvikvm(22524): VFY: unable to resolve virtual method 686: Landroid/content/res/Resources;.getColorStateList (ILandroid/content/res/Resources$Theme;)Landroid/content/res/ColorStateList;
W/dalvikvm(22524): VFY: unable to resolve virtual method 693: Landroid/content/res/Resources;.getDrawable (ILandroid/content/res/Resources$Theme;)Landroid/graphics/drawable/Drawable;

回到崩溃开始的警告,”unable to resolve static method” 这条日志应该不会是导致VerifyError的元凶。(注:出现这个警告意味着你如果运行时用到了这个方法,运行时将会报错,如InstantiationError、NoSuchMethodError之类)

那么应该是关键的一句:”unable to find exception handler at addr 0xe“,导致后面的”rejected Lcom/sample/FileUtils;.getUid (Ljava/lang/String;)I” 并最终导致”Verifier rejected class Lcom/sample/FileUtils;”

仔细查看 FileUtils 这个类里的方法getUid()是否有try-catch代码块:

1
2
3
4
5
6
7
8
9
10
11
@TargetApi(21)
public static int getUid(String path) {
if (Build.VERSION.SDK_INT >= 21) {
try {
return Os.stat(path).st_uid;
} catch (android.system.ErrnoException e) {
return -1;
}
}
return -1;
}

确实有尝试catch一个低版本不存在的Exception,但问题在于这个方法并没有使用到!!
而且看起来也十分的正常,一般兼容老版本SDK不都是这样的写法吗?为何单单这里会导致FileUtils类“不合法”?

为了证明是这个在低版本不存在的Exception导致的,对该方法里的try-catch做了简单的处理:

1
2
3
4
5
try {
return Os.stat(path).st_uid;
} catch (Exception e) {
return -1;
}

不出所料,警告只剩下了VFY: unable to resolve static method 13457: Landroid/system/Os;.stat (Ljava/lang/String;)Landroid/system/StructStat;而且没有导致VerifyError

what ??????

想必看到现在的你也是一脸问号……