2BAB's Engineering Blog

Gradle 日常崩 - 再谈 AAR 与混淆

之前写了篇文章讲到了由于 buildTypes 默认设置的原因导致 library module 无法 debug 的情况。事实上,当时只解决了打 Debug 包的情况,而忽略了打 Release 包时还埋了一个隐藏的问题。

问题还原:我们在做全局的 rebuild 或者 assembleRelease 时,会出现有些类找不到的情况,而 assembleDebug 不会。仔细观察会发现,这些报错的类都是被外部 module 引用的部分,例如 module A 有类 Clazz,被 module B 引用,则 Clazz 报错。

Gradle 日常崩 - 2.3.0 Plugin 再无 exploded-aar

升级 Android Gradle Plugin 至 2.3.0 后,会发现 exploded-aar 这个 build 目录下的文件夹大部分情况下不存在了(但是仍然有时候会出现一两个 aar 的解压包,有些诡异)。这个改动最相关的原因是 2.3.0 默认开启了 Build Cache,具体在这里有说明。

Gradle 日常崩 - Library Module BuildTypes

最近工作中换了一个工程,重新配了一遍 Gradle 的环境,然后发现所有的 Library Module 都无法 Debug 或者只能取到某些全局变量(局部变量找不到)。百思不得其解时,突然发现我明明打的是 Debug 包 assembleDebug,我的 Library Module 执行的却都是 transformClassesAndResourcesWithProguardForRelease。明明在这些 module 都配置了 debugbuildTypes,但却不生效,反而打了混淆的 release 包。

Android源码笔记-2-源码结构及Framework层结构

源码结构

框架图

图片来自 Android Source Overview

android_framework_details

Android源码笔记-1-编译&烧录的一些坑

由于最近工作需要 + 自己也挺感兴趣,折腾起 Android 的下层开发。

环境描述:

  • macOS 10.12 Sierra
  • Xcode 8(安装各种环境会用到,然而正式编译时没用这个)
  • android-5.1.1_r14,LMY48M,Nexus 7(flo)

Android插件化笔记-6-StartPluginService

Demo: https://github.com/2BAB/Android-Plugin-Dev-Notes

上节学习到了插件化资源 ID 冲突的问题和解法,本节主要讨论 Service 的插件化启动。按照本系列的尿性,肯定要简单易搞,所以预注册 Service 是本节的讨论前提。具体原因在 Activity 那节写了,但是需要额外说明的是其实 Service 由于量少且新增少,是比较少做复杂插件化方案的。

Android插件化笔记-5-ResModificationPlugin

Demo: https://github.com/2BAB/Android-Plugin-Dev-Notes

上节学习到「各插件构造各自的 Resource 对象,各个插件的资源互不影响」,本节使用另外一种方案——「所有插件的资源都加载到一个 AssetManager,全局可用」。

单一 Resource(AssetManager)的方案,主要问题在于资源 ID 冲突,解决的方案大体上分三种:

  1. 修改 AAPT 的源码
  2. 修改 AAPT 的生成产物(R.java,resource.arsc,各类 xml 包括 layout)
  3. 使用 public.xml 手动设置 padding

其中方案 1 出现的较早,原理也比较简单,修改的部分不多,携程的 DynamicApk 等开源项目都在使用。而方案 2 则鲜为人知,但是 Small 项目给我们做了一个完整的实例,本节的 Gradle 插件就是基于 Small 的源码「抽离 + 修改」而来。方案 3 不涉及到打包流程改动,在此不做阐释。

Android插件化笔记-4-LoadPluginResource

Demo: https://github.com/2BAB/Android-Plugin-Dev-Notes

插件化的资源加载大体上也分两种:

  • 每个插件构造单一的 Resource 对象,各个插件的资源互不影响
  • 所有插件的资源都加载到一个 AssetManager,全局可用,但是会出现资源 ID 冲突的现象,必须在打包流程中做修改

本节以构造单一对象为基础讲解,资源冲突的问题和方案下节讲。

资源的寻找过程

在Activity中的getResources()方法会走到ContextWrapper的实现上,而ContextWrapper顾名思义它只是一个包装类,最终的调用是ContextWrapper的实际类ContextImpl中的方法。

ContextImpl中getResources()方法返回了它的成员变量mResource,我们看一下ContextImpl的构造函数,其中mResources被第一次赋值是通过下面的函数调用:

1
Resources resources = packageInfo.getResources(mainThread);

Android插件化笔记-3-StartPluginActivity

Demo: https://github.com/2BAB/Android-Plugin-Dev-Notes

其实从这一节开始,就需要区分两种插件化的方案:

  • 需要提前在 Manifest 里注册 Activity 、Service 的
  • 不需要的

网路上大多是研究不需要注册的方案,需要 hook 各种 Activity、Service 的启动流程和生命周期。一般来说 hook 的原则是越少越好,越少越不会和系统的变动有冲突,自然也就不会出问题。

当然,也有不做深度 hook 的方案,比如被反编译出来的 Atlas (现在改名叫 ACDD,https://github.com/zjf1023/ACDDExtension)。下面都是按预先注册的方案来解释,这样的方案较为简单,hook的量极少,稳定可靠,当然也就牺牲了一定的动态性。

Android插件化笔记-2-LoadPluginClass

Demo: https://github.com/2BAB/Android-Plugin-Dev-Notes

如何获取能够被加载的 .dex 文件

准备如下两个测试类,其中TestDexInterface还需要拷贝一份到工程中

TestDexClass.java

1
2
3
4
5
6
7
8
9
10
package example.com.classeasyload;
public class TestDexClass implements TestDexInterface{
@Override
public float getPiValue() {
return 3.14f;
}
}

TestDexInterface.java

1
2
3
4
5
6
7
package example.com.classeasyload;
public interface TestDexInterface {
float getPiValue();
}
  1. javac *.java -> .class
  2. jar cvf origin.jar . -> .jar
  3. dx --dex --output=target.dex origin.jar -> .dex