Android资源管理中的SharedLibrary和Dynamic Reference-------之aapt的处理(二)

    前面我们讲过了资源共享库的概念和应用,现在我们来看看它是怎么实现的吧,顺便也能了解一下Android的资源管理中的一些机制。

     这里面包括了两部分:资源共享库的编译和使用这个库的App的编译。我们仍旧以上一篇文章中framework里的那个项目为例来分别讨论。那个资源共享库的包名为:com.google.android.test.shared_library,项目路径为frameworks/base/tests/SharedLibrary/lib;引用这个资源共享库的App包名为:com.google.android.test.lib_client, 项目路径为:frameworks/base/tests/SharedLibrary/client。

资源共享库com.google.android.test.shared_library的编译
    我们看到,在其Android.mk中,有这么一行:LOCAL_AAPT_FLAGS := --shared-lib。这行的作用就是告诉aapt,在编译的时候,把当前项目当做一个资源共享库来处理,我们可以认为aapt执行了如下命令:aapt package -f --shared-lib -J ~ -S frameworks/base/tests/SharedLibrary/lib/res/ -I ~/android-sdk-linux/platforms/android-22/android.jar -M frameworks/base/tests/SharedLibrary/lib/AndroidManifest.xml -F ~/lib-out.apk

对这个命令稍作说明:

aapt package 表示打包,也就是要编译我们的资源共享库

-f 表示强制覆盖已经存在的文件

–shared-lib 表示我们要编译资源共享库,而不是普通的APK

-J指定生成的R.java的路径,我们为了方便就把它放在home下面了

-S指定我们要编译的资源路径,当然就是res目录了

-I指定编译的依赖库,当然就是SDK里面的android.jar了

-M指定我们的AndroidManifest.xml文件

-F编译后的out文件路径

    需要说明的是,-I指定编译时的依赖库为android.jar。可能大家会觉得奇怪,我们不是编译资源吗,怎么会依赖一个jar包?其实如果我们解压android.jar,就会发现里面不仅有class文件,还有很多资源。也就是说,android.jar不仅是一个java库,也是一个资源库。这里我们完全可以猜想,在aapt编译并生成android.jar里的资源的时候,肯定不用 -I参数,因为android.jar是不会依赖其它资源了的。

    比较有意思的是,我们可以把android.jar里的东西删得只剩下AndroidManifest.xml和resources.arsc,然后重新执行上面的那条命令,仍然可以编译成功,为什么呢,我们不是依赖android.jar里的资源吗,为啥都删除了还能编译成功呢?其实android.jar作为我们依赖的资源库,编译的时候aapt和AssetManger只会检查其AndroidManifest.xml文件,然后加载resources.arsc这个资源索引表,至于其它文件一律忽略。因为resources.arsc已经包含了android.jar里的所有资源的信息了。这个我们在后面介绍aapt生成resources.arsc的过程和AssetManager的时候会详细介绍。

    关于android.jar就说到这里,aapt在编译资源共享库和一般APK时,到底会有什么不同的处理呢?这个我们就得看aapt的代码了,其路径为:framework/base/tools/aapt/。 GO!

    打开Main.cpp,直奔其main函数:
在这里插入图片描述
我们看到,它会把相关信息存进Bundle类的一个实例里,Bundle主要用来存储我们执行aapt命令时的参数的。它有两个动作:设置BuildSharedLibrary为true很好理解,我们要编译资源共享库嘛。但这个setNonConstantId是要干啥?我们知道一般应用程序的R.java里面的资源id都是public static final的,难不成它要玩出什么花活儿?打开我们刚才执行aapt 命令生成的那个R.java瞧瞧:

在这里插入图片描述
    好家伙,果然资源的id都不是final的了,为什么去掉了呢?final的不能改,难不成,这些资源的id还会动态改变?这个疑问我们暂且按下不表。我们继续往下看,发现R.java还多了一个方法:

在这里插入图片描述
果然,这个方法会改变我们资源的包id!为什么要改变呢?既然我们不清楚,那就继续看代码吧。

Command.cpp负责aapt具体命令的逻辑,我们看它的doPackage方法:

/*
 * Package up an asset directory and associated application files.
 */
int doPackage(Bundle* bundle)
{
        //do something

        // If they asked for any fileAs that need to be compiled, do so.
        if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
               err = buildResources(bundle, assets, builder);   //关键点1 build resources
               if (err != 0) {
                    goto bail;
               }
         }

         //do some thing

         // Write out R.java constants
        if (!assets->havePrivateSymbols()) {
              if (bundle->getCustomPackage() == NULL) {
                   // Write the R.java file into the appropriate class directory
                    // e.g. gen/com/foo/app/R.java
                    err = writeResourceSymbols(bundle, assets, assets->getPackage(), true,
                         bundle->getBuildSharedLibrary());
                } else {
                      const String8 customPkg(bundle->getCustomPackage());
                      err = writeResourceSymbols(bundle, assets, customPkg, true,
                      bundle->getBuildSharedLibrary());//关键点2,生成R.java文件,最后一个参数为 true
                 }
                 if (err < 0) {
                       goto bail;
                 }
           }

           //do something

}

关键点1是负责资源的编译,关键点2是R.java文件的生成。我们先看buildResources()的实现,在Resource.cpp中:

status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder)
{
    // First, look for a package file to parse.  This is required to
    // be able to generate the resource information.
    sp<AaptGroup> androidManifestFile =
            assets->getFiles().valueFor(String8("AndroidManifest.xml"));
    if (androidManifestFile == NULL) {
        fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
        return UNKNOWN_ERROR;
    }

    status_t err = parsePackage(bundle, assets, androidManifestFile);
    if (err != NO_ERROR) {
        return err;
    }

    NOISY(printf("Creating resources for package %s\n",
                 assets->getPackage().string()));

    ResourceTable::PackageType packageType = ResourceTable::App;
    if (bundle->getBuildSharedLibrary()) { //3 确定我们要编译的是资源共享库
        packageType = ResourceTable::SharedLibrary;
    } else if (bundle->getExtending()) {
        packageType = ResourceTable::System;
    } else if (!bundle->getFeatureOfPackage().isEmpty()) {
        packageType = ResourceTable::AppFeature;
    }

 

    //4 创键ResourceTable,要编译的包的类型为 ResourceTable::SharedLibrary;

    ResourceTable table(bundle, String16(assets->getPackage()), packageType);

    //..........省略无关代码

}

我们要编译的包的Type有四种:

// frameworks/base/tools/aapt/ResourceTable.h
    // The type of package to build.
    enum PackageType {
        App,
        System,
        SharedLibrary,
        AppFeature
    };

它们被定义在ResourceTable.h中,分别对应一般的App资源包、系统资源包、资源共享库、最后一种是APK 分片相关的。

OK,我们继续看看ResourceTable的构造函数:

ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, 
								ResourceTable::PackageType type)
    : mAssetsPackage(assetsPackage)
    , mPackageType(type)
    , mTypeIdOffset(0)
    , mNumLocal(0)
    , mBundle(bundle)
{
    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;
    }
    sp<Package> package = new Package(mAssetsPackage, packageId);
    mPackages.add(assetsPackage, package);
    mOrderedPackages.add(package);

    // Every resource table always has one first entry, the bag attributes.
    const SourcePos unknown(String8("????"), 0);
    getType(mAssetsPackage, String16("attr"), unknown);
}

    一目了然,只要我们指定了–shared-lib选项,我们要编译的包Id就会被设置为0x00。另外,一般应用及其分片都会被设置成0x7f,系统资源包的id则被设置为0x01。另外多说一些,一般各个SOC厂商也会有自己的系统资源包,比如这个mtk的是0x08;手机厂商也会加入自己的系统资源包,比如我们的是0x09。这样,一个App运行起来,它内部至少会加载四个资源包:Google源生资源包、SOC厂商资源包、手机厂商资源包、以及这个App本身也是一个资源包。

    以前我一直有个疑问,App的包id都是0x7f,那么系统怎么区分哪个应用对应哪个包呢?其实,包id的作用不是用来这么区分不同的应用的,不同的应用是根据包的不同路径来区分的。那么包id是用来干什么的呢?包id是用来区分同一进程,确切地说是同一个AssetManager中的不同包的。比如,Android源生资源包、SOC厂商的资源包、手机厂商的资源包、资源共享包、以及应用本身这个资源包,它们会被加载到同一个AssetManager中,包id是用来区分它们的。至此,资源共享库的包id怎么来的我们已经清楚了。

我们回过头来继续分析R.java的生成,看看加了–shared-lib选项之后,会有什么特殊的处理:

status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets,
    const String8& package, bool includePrivate, bool emitCallback)//5 emitCallback 就是我们传过来的buildSharedLibrary,true
{
        { 

             .....

             //构建R.java的路径,并创建打开这个文件为fp

             .....

        }

        //5 emitCallback 就是我们传过来的buildSharedLibrary,true

        status_t err = writeSymbolClass(fp, assets, includePrivate, symbols,
                className, 0, bundle->getNonConstantId(), emitCallback);

}

继续看

static status_t writeSymbolClass(
    FILE* fp, const sp<AaptAssets>& assets, bool includePrivate,
    const sp<AaptSymbols>& symbols, const String8& className, int indent,
    bool nonConstantId, bool emitCallback)
{
          {
                 //写入R文件的各个类,变量,非本文重点,后面会有专门文章分析

          }

          if (emitCallback) {//true

         //开始写入onResourcesLoaded方法了
          fprintf(fp, "%spublic static void onResourcesLoaded(int packageId) {\n",
                getIndentSpace(indent));

          //这个方法会递归地写入每一项,这里不再展开。
          writeResourceLoadedCallback(fp, assets, includePrivate, symbols, className, indent + 1);
          fprintf(fp, "%s}\n", getIndentSpace(indent));
    }

}

通过以上分析,我们确认了,在编译资源共享包时:

1.资源的包id会被指定成0x00;

2.R.java中的每个id不再是final的,并且还会生成一个onResourcesLoaded方法,它会修改每一个资源的包id。

但我们也应该有两个疑问:

1.Android源生资源包占了id 0x01,SOC厂商资源包占了0x08,手机厂商占了后面的一个,应用自己占了0x7f,那么剩下的呢?特别是0x02~0x07之间的id给谁用呢?

2.资源共享库的R.java中的这个onResourcesLoaded方法为啥要改变包id呢?

带着这两个疑问,我们继续分析引用这个资源共享包的App的编译过程

引用库的App:com.google.android.test.lib_client的编译
先看它的Android.mk文件:
在这里插入图片描述

我们看到它也有一行比较特殊:

LOCAL_RES_LIBRARIES := SharedLibrary

这一行有啥效果呢,我们可以把它翻译成aapt命令:

aapt package -f -J ~ -S frameworks/base/tests/SharedLibrary/client/res/ -I ~/android-sdk-linux/platforms/android-22/android.jar

-I ~/lib-out.apk -M frameworks/base/tests/SharedLibrary/client/AndroidManifest.xml -F ~/app-out.apk

    也就是说,加了一个 -I 参数,它的值为我们刚才编译出来的资源共享库的路径,我们之前编译出来的资源共享库的地位现在和android.jar一样了,将会作为一个系统库,参与App的编译。这时候我们的App com.google.android.test.lib_client的编译和一般的App相比,只有一处不一样了,这处不同在生成resources.arsc的时候。我们来简单看下:

aapt在编译资源的时候会调到这个方法:

status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
        const sp<AaptFile>& dest,
        const bool isBase)
{
       // The libraries this table references.
       Vector<sp<Package> > libraryPackages;

       //获取我们引用的资源包,这里是android源生包和我们的资源共享包,共2个
       const ResTable& table = mAssets->getIncludedResources();

       //basePackageCount = 2
       const size_t basePackageCount = table.getBasePackageCount();
       for (size_t i = 0; i < basePackageCount; i++) {
            size_t packageId = table.getBasePackageId(i);
            String16 packageName(table.getBasePackageName(i));

            //过滤掉Android源生包,因为它的包id是固定的,

            //libraryPackages里只有我们的资源共享包了
            if (packageId > 0x01 && packageId != 0x7f &&
                    packageName != String16("android")) {
                  //libraryPackages  size = =1;
                 libraryPackages.add(sp<Package>(new Package(packageName, packageId)));
             }
        }

        ........

        //收集value字符串,并将他们写入全局字符串池中

        ........

        //写入包信息、type string pool、key string pool

        ........

        if (isBase) {
            //这里会写入我们的App引用的资源库的信息
            status_t err = flattenLibraryTable(data, libraryPackages);
            if (err != NO_ERROR) {
                fprintf(stderr, "ERROR: failed to write library table\n");
                return err;
            }
        }

        ......

        //写入所有type、entry

}

继续进入flattenLibraryTable()方法:

status_t ResourceTable::flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs) {
    // Write out the library table if necessary
    if (libs.size() > 0) {
        NOISY(fprintf(stderr, "Writing library reference table\n"));

        const size_t libStart = dest->getSize();
        const size_t count = libs.size();

        //如果有必要,会重新分配内存
        ResTable_lib_header* libHeader = (ResTable_lib_header*) dest->editDataInRange(
                libStart, sizeof(ResTable_lib_header));

        //写入ResTable_lib_header,它的定义在 AssetManager部分的ResourceTypes.h中

        //最主要的信息就是引用了多少个资源库

        memset(libHeader, 0, sizeof(*libHeader));
        libHeader->header.type = htods(RES_TABLE_LIBRARY_TYPE);
        libHeader->header.headerSize = htods(sizeof(*libHeader));
        libHeader->header.size = htodl(sizeof(*libHeader) + (sizeof(ResTable_lib_entry) * count));
        libHeader->count = htodl(count);

        // Write the library entries
        for (size_t i = 0; i < count; i++) {
            const size_t entryStart = dest->getSize();
            sp<Package> libPackage = libs[i];
            NOISY(fprintf(stderr, "  Entry %s -> 0x%02x\n",
                        String8(libPackage->getName()).string(),
                        (uint8_t)libPackage->getAssignedId()));

            ResTable_lib_entry* entry = (ResTable_lib_entry*) dest->editDataInRange(
                    entryStart, sizeof(ResTable_lib_entry));
            memset(entry, 0, sizeof(*entry));

            //6,这里需要特别说明,非常重要!!!
            entry->packageId = htodl(libPackage->getAssignedId());
            strcpy16_htod(entry->packageName, libPackage->getName().string());
        }
    }
    return NO_ERROR;
}

这个函数,其实就是写入正在编译的这个资源包都引用了哪些资源包,写入他们的name,和id。But!!!

并没有这么简单,//6处已经特别标明。这里的变量libs是怎么来的,我们再复习一遍:

    // The libraries this table references.
    Vector<sp<Package> > libraryPackages;
    const ResTable& table = mAssets->getIncludedResources();
    const size_t basePackageCount = table.getBasePackageCount();
    for (size_t i = 0; i < basePackageCount; i++) {
        size_t packageId = table.getBasePackageId(i);
        String16 packageName(table.getBasePackageName(i));
        if (packageId > 0x01 && packageId != 0x7f &&
                packageName != String16("android")) {
            libraryPackages.add(sp<Package>(new Package(packageName, packageId)));
        }
    }

    重点看标红的这两行,packageId是从ResTable实例中获取的,此时的ResTable中,会有两个资源包,一个是Android源生的,id为0x01;另一个是我们的共享资源包,packageId 是0x00;是这样,没错吧!?
错!错!错!
我们的共享资源包,这里拿到的packageId是0x02,是0x02,是0x02!!

为什么呢?不是说好的我们的共享包的id是0x00吗?!!!

这里就要说ResTable这个类了,它是AssetManager中最重要的一个类,没有之一。我们编译App资源时候的依赖库,都会被加入到它里面去。假设我们要编译的App有三个依赖的共享库他们分别是 libaa.apk、lib11.apk、libαα.apk,它们的包id都是0x00,android源生资源库是 android.jar,它的包id是0x01。并且我们编译的时候aapt 命令中这样写:aapt package -f -I libαα.apk -I android.jar -I lib11.apk -I libaa.apk …

请注意这四个-I参数的顺序。

那么当这个ResTable去加载这些资源包的时候,也会按照这个顺序来加载:

先加载libαα.apk,一看,它的包id是0x00,马上就明白了,这是个资源共享库,于是拿出小本本写下:0x00----->libαα.apk

但它转念一想,不对,如果后面还有许多个资源共享库,他们的包id也是0x00,我咋弄,到时后给我个0x00,我怎么找到是哪个包?不行,必须区分它们。既然0x01给google了,那资源共享库就从0x02开始,一个一个增长吧,于是,它的小本本最终如下:

0x02----->libαα.apk
0x03----->lib11.apk
0x04----->libaa.apk

     以上只是个例子,由于我们这次编译,依赖的资源库只有android.jar和一个共享库,所以我们拿到的共享库的id一定是0x02。并且这个0x02----->com.google.android.test.shared_library会被写入到resources.arsc中。

    libaa.apk、lib11.apk、libαα.apk 它们三个看着0x02----->com.google.android.test.shared_library被写入到resources.arsc,心中一定在等着看ResTable的笑话,你这样乱弹琵琶,就等着出问题吧。

    aapt的处理,到此已经结束,最后再来回忆一下我们的疑问:

    1.Android源生资源包占了id 0x01,SOC厂商资源包占了0x08,手机厂商占了后面的一个,应用自己占了0x7f,那么剩下的呢?特别是0x02~0x07之间的id给谁用呢?

    2.资源共享库的R.java中的这个onResourcesLoaded方法为啥要改变包id呢?

    同时,我们也期待着ResTable的翻车现场。

原文链接:https://blog.csdn.net/dayong198866/article/details/95315337

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: aapt2-7.3.0-8691043是Android应用程序打包工具(aapt2)的版本号。aapt2是Android开发工具包(SDK)的一个重要工具,用于将应用程序的资源文件(如布局、图像和字符串)打包到一个应用程序包(APK)。 版本号的“7.3.0”表示这是aapt2的第7.3.0版本。版本号的“8691043”是该版本的唯一标识符,用于在开发者社区唯一地标识该版本。 每个版本的aapt2都带来了一些改进和修复,例如性能优化、Bug修复和新功能。通过升级aapt2到最新版本,开发者可以从这些改进受益。此外,更新aapt2还能保持与最新Android操作系统版本的兼容性。 为了使用aapt2-7.3.0-8691043,开发者需要下载并安装适当的Android SDK版本。然后,他们可以通过命令行界面或集成开发环境(如Android Studio)使用aapt2来构建和打包他们的应用程序。 总之,aapt2-7.3.0-8691043是Android开发工具包的一个重要版本,它为开发者提供了打包应用程序资源的功能,并提供了许多改进和修复来改善开发体验和应用程序的性能。 ### 回答2: aapt2-7.3.0-8691043是Android Asset Packaging Tool (AAPT)的一个版本。AAPTAndroid的一项重要工具,用于处理和管理Android应用程序的资源。它负责将应用程序的资源文件(如图片、布局文件和字符串)编译成进制格式,以便在应用程序运行时能够有效地加载和使用这些资源。 aapt2-7.3.0-8691043是AAPT在特定时间内的一个具体版本。版本号的数字表示软件的版本,8691043是该版本的构建号。 每个AAPT版本都会带来一些新功能、改进和修复。这些更新可能包括对资源文件的更好的处理、提高了性能、修复了已知的错误和漏洞等。用户可以通过更新AAPT工具,以便在开发和构建Android应用程序时能够享受到最新的功能和改进。 在使用aapt2-7.3.0-8691043时,开发者可以通过命令行或构建脚本使用工具编译资源文件。该工具可以将资源文件编译成进制格式,并生成与Android应用程序兼容的APK文件。开发者也可以使用它来检查资源文件的有效性和完整性,以确保应用程序在运行时正确加载和使用这些资源。 总之,aapt2-7.3.0-8691043是Android开发不可或缺的工具之一,用于管理和处理应用程序的资源文件。通过及时更新AAPT工具,开发者可以获得更好的性能和更多的功能,从而提高他们的应用程序的质量和用户体验。 ### 回答3: aapt2-7.3.0-8691043 是 Android Asset Packaging Tool (AAPT) 的一个版本。AAPT 是一个 Android 开发工具,用于编译和打包应用程序的资源文件。它可以将应用程序的资源文件(如布局文件,图标,字符串,颜色等)处理进制格式,以便在 Android 设备上运行。 这个版本号的“7.3.0”代表主要版本为7,次要版本为3,修订版本为0。版本号的增加通常意味着在之前版本的基础上进行了更改、添加或修复了一些功能或问题。 最后的“8691043”是构建号,用于标识编译过程的特定版本。 aapt2-7.3.0-8691043 是一个较新的版本,意味着其可能包含了一些新的功能或修复了之前版本的一些问题。如果你在进行 Android 应用开发时遇到了与资源文件相关的问题,可以尝试升级到这个版本,以获得最新的功能和修复。 总的来说,aapt2-7.3.0-8691043 是 Android 开发工具的一个版本,它用于编译和打包应用程序的资源文件,可以提供更好的开发体验和应用的性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值