Gradle 日常崩#2 - AGP 2.3.0 再无 exploded-aar

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

Gradle 日常崩#1 - Library Module BuildTypes

最近工作中换了一个工程,重新配了一遍 Gradle 的环境,然后发现所有的 Library Module 都无法 Debug 或者只能取到某些全局变量(局部变量找不到)。百思不得其解时,突然发现

Android 源码笔记#1 - 编译&烧录

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

环境描述:

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

插件化笔记#5 - 资源分区修改插件

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 不涉及到打包流程改动,在此不做阐释。

插件化笔记#4 - 加载插件资源文件

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

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

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

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

资源的寻找过程

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

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

 Resources resources = packageInfo.getResources(mainThread);

插件化笔记#3 - 启动插件 Activity

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的量极少,稳定可靠,当然也就牺牲了一定的动态性。

插件化笔记#2 - 加载插件代码

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

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

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

[TestDexClass.java]

package example.com.classeasyload;

public class TestDexClass implements TestDexInterface{

    @Override
    public float getPiValue() {
        return 3.14f;
    }

}

[TestDexInterface.java]

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

插件化笔记#1 - ClassLoader 初探

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

有几个ClassLoader

如MainActivity的代码所示,

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ClassLoader classLoader = getClassLoader();
        if (classLoader != null) {
            Log.i("[onCreate]", classLoader.toString());
            while (classLoader.getParent() != null) {
                classLoader = classLoader.getParent();
                Log.i("[onCreate While]", classLoader.toString());
            }
        }
    }

打印出来的结果是

I/[onCreate]: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/example.com.classloaderdemo-1/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
I/[onCreate While]: [email protected]
I/[onCreate While]: [email protected]

卧槽,怎么有三个,这跟文章说的只有两个不一样啊 - -,而且我也只听师兄说过PathClassLoader和BootClassLoader,没见过这个IncrementalClassLoader啊,它是什么鬼?