如何实现携程动态加载插件中对aapt的改造

原文出处:大头鬼Bruce的 http://blog.csdn.net/lzyzsd/article/details/49768283 

前几天,携程无线部门开源了他们的插件框架,使用该框架可以方便的实现app的插件化开发和热更新。 
在陈博士发表的关于该框架的blog中,有这么一段

为aapt增加–apk-module参数。 

如前所述,资源ID其实有一个PackageID的内部字段。我们为每个插件工程指定独特的PackageID字段,这样根据资源ID就很容易判明,此资源需要从哪个插件apk中去查找并加载了。在后文的资源加载部分会有进一步阐述。

很多同学都很关心这里应该怎么修改aapt来实现为不同的插件工程指定不同的PackageID,这里我来分析一下aapt的源码,提供一个大概的思路吧。

个人才疏学浅,如有不对,还请携程的同学指正一下。

appt相关的源码都在framework/base/tools/aapt目录下。

首先查看ResourceTable.cpp中的构造函数

ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type)
ssize_t packageId = -1;
switch (mPackageType) {
    case App:
    case AppFeature:
        packageId = 0x7f;
        break;
    case System:
        packageId = 0x01;
        break;
    case SharedLibrary:
        packageId = 0x00;
        break;
    default:
        assert(0);
        break;
}

资源的packageId就是在这里根据packageType来确定的,其中0x01是给系统资源使用的,在R.java中可以看到系统资源的id都是以0x01开头的,0x7f是给我们的应用程序资源使用的, 
同样在R.java中,你可以看到,自己的app的资源id都是以0x7f开头的。这也就是说0x01到0x7f之间的的值我们都可以作为packageId来用。

接下来我们看看是在哪里创建了ResourceTable对象。

打开Resource.cpp,buildResources方法,就是在这个方法中构造了resourceTable对象。

status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder)
ResourceTable::PackageType packageType = ResourceTable::App;
if (bundle->getBuildSharedLibrary()) {
    packageType = ResourceTable::SharedLibrary;
} else if (bundle->getExtending()) {
    packageType = ResourceTable::System;
} else if (!bundle->getFeatureOfPackage().isEmpty()) {
    packageType = ResourceTable::AppFeature;
}
ResourceTable table(bundle, String16(assets->getPackage()), packageType);

可以看到这里是根据bundle的几个方法的返回值,来确定生成的资源的packageId的。

那么,我们就可以在这里做一些手脚,来生成我们自己想要的packageId。我的做法就是给bundle对象加上一个getPackageId方法, 
该方法会返回我们在命令行传入的packageId。代码类似下面

else if (!bundle->getFeatureOfPackage().isEmpty()) {
    packageType = ResourceTable::AppFeature;
} else if (bundle->getPackageId() != 0) {
  packageType = bundle.getPackageId();
}

bundle类上还会定义一个setPackageId方法,用来保存packageId信息。 
bundle对象是在Main.cpp的main方法,也就是appt程序的入口中构造出来的,下面列出一个代码片段

switch (*cp) {
case 'v':
    bundle.setVerbose(true);
    break;
case 'a':
    bundle.setAndroidList(true);
    break;

bundle根据命令行传入的各种参数,来设置自己的一些状态,这里我们要加入自己的逻辑,来出来–apk-module参数,同时调用setPackageId方法就好了。

另外Dr.Chen的blog中还提到了

为aapt增加–public-R-path参数。

这里无非就是在Main.cpp中检测到这个参数的时候,也保存到bundle中,然后在Resource.cpp的writeResourceSymbols方法中, 
在生成 R.java的时候,把传入的参数所指定的R.java中的变量插入到这个要生成的 R.java文件中就可以了。

以上就是我个人的一些猜测,如有不对,请大家帮忙指正。