• 修改aapt和自定义资源ID


    源码查看

    aapt的源码在所在的目录:Android/frameworks/base/tools/aapt/。

    Main.cpp位置:android-6.0.0_r1/frameworks/base/tools/aapt/Main.cpp
    首先查看Main.cpp的main方法:

    1. int main(int argc, char* const argv[])
    2. {
    3. char *prog = argv[0];
    4. Bundle bundle;
    5. bool wantUsage = false;
    6. int result = 1; // pessimistically assume an error.
    7. ...
    8. //进行资源打包的参数p或package
    9. else if (argv[1][0] == 'p')
    10. bundle.setCommand(kCommandPackage);
    11. ...
    12. /*
    13. * Pull out flags. We support "-fv" and "-f -v".
    14. */
    15. while (argc && argv[0][0] == '-') {
    16. /* flag(s) found */
    17. const char* cp = argv[0] +1;
    18. while (*cp != '\0') {
    19. ...
    20. //收集appt命令输入的参数,这些参数以"-"开头
    21. case '-':
    22. if (strcmp(cp, "-debug-mode") == 0) {
    23. bundle.setDebugMode(true);
    24. }
    25. ...
    26. break;
    27. default:
    28. fprintf(stderr, "ERROR: Unknown flag '-%c'\n", *cp);
    29. wantUsage = true;
    30. goto bail;
    31. }
    32. cp++;
    33. }
    34. argc--;
    35. argv++;
    36. }
    37. /*
    38. * We're past the flags. The rest all goes straight in.
    39. */
    40. bundle.setFileSpec(argv, argc);
    41. //根据bundle收集的参数进行资源处理
    42. result = handleCommand(&bundle);
    43. //输入参数错误时会跳转到此
    44. bail:
    45. if (wantUsage) {
    46. usage();
    47. result = 2;
    48. }
    49. return result;
    50. }
    • 1

    在main函数内,首先创建一个Bundle对象,这个对象用来存储输入的操作类型和相关的参数。argv相当于java中的字符串数组。取该数组的第2个字符串的第一个char值。因为是执行资源打包,所以它是’p’。bundle记录执行类型为kCommandPackage,即资源打包。while循环处理剩余的char数组(即字符串数组),将参数按照类型设置到bundle中。参数解析完毕,则会执行handleCommand(&bundle)。如果在解析输入的参数时出现了错误,便使用goto跳转到bail代码块。在bail代码块中可能会执行usage();,这个方法会打印出aapt所有的命令类型和相关的参数。

    先看看usage()函数,省略了与资源打包无关的信息。

    1. void usage(void)
    2. {
    3. ...
    4. fprintf(stderr,
    5. " %s p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \\\n"
    6. " [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \\\n"
    7. " [--debug-mode] [--min-sdk-version VAL] [--target-sdk-version VAL] \\\n"
    8. " [--app-version VAL] [--app-version-name TEXT] [--custom-package VAL] \\\n"
    9. " [--rename-manifest-package PACKAGE] \\\n"
    10. " [--rename-instrumentation-target-package PACKAGE] \\\n"
    11. " [--utf16] [--auto-add-overlay] \\\n"
    12. " [--max-res-version VAL] \\\n"
    13. " [-I base-package [-I base-package ...]] \\\n"
    14. " [-A asset-source-dir] [-G class-list-file] [-P public-definitions-file] \\\n"
    15. " [-S resource-sources [-S resource-sources ...]] \\\n"
    16. " [-F apk-file] [-J R-file-dir] \\\n"
    17. " [--product product1,product2,...] \\\n"
    18. " [-c CONFIGS] [--preferred-density DENSITY] \\\n"
    19. " [--split CONFIGS [--split CONFIGS]] \\\n"
    20. " [--feature-of package [--feature-after package]] \\\n"
    21. " [raw-files-dir [raw-files-dir] ...] \\\n"
    22. " [--output-text-symbols DIR]\n"
    23. " [--apk-module moduleName]\n"
    24. "\n"
    25. " Package the android resources. It will read assets and resources that are\n"
    26. " supplied with the -M -A -S or raw-files-dir arguments. The -J -P -F and -R\n"
    27. " options control which files are output.\n\n"
    28. , gProgName);
    29. ...
    30. }
    • 1

    打印appt执行的操作类型和对应的参数。

    先看看handleCommand()函数,

    1. int handleCommand(Bundle* bundle)
    2. {
    3. switch (bundle->getCommand()) {
    4. case kCommandVersion: return doVersion(bundle);
    5. ...
    6. case kCommandPackage: return doPackage(bundle);
    7. ...
    8. default:
    9. fprintf(stderr, "%s: requested command not yet supported\n", gProgName);
    10. return 1;
    11. }
    12. }
    • 1

    在main函数中bundle设置的cmmand类型为kCommandPackage,所以会执行doPackage(bundle)代码。使用操作系统自带的搜索,检索aapt目录内的文件内容’doPackage’。会查找到doPackage()是Command.cpp中的函数。

    Command.cpp位置:android-6.0.0_r1/frameworks/base/tools/aapt/Command.cpp
    doPackage()函数中处理资源相关的代码:

    1. int doPackage(Bundle* bundle)
    2. {
    3. ...
    4. // If they asked for any fileAs that need to be compiled, do so.
    5. if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
    6. err = buildResources(bundle, assets, builder);
    7. if (err != 0) {
    8. goto bail;
    9. }
    10. }
    11. ...
    12. }
    • 1

    继续搜索buildResources()函数所在的文件,查找到Resource.cpp。

    Resource.cpp位置:android-6.0.0_r1/frameworks/base/tools/aapt/Resource.cpp
    buildResources()函数中创建资源表相关的代码:

    1. status_t buildResources(Bundle* bundle, const sp& assets, sp& builder)
    2. {
    3. ...
    4. //设置资源类型
    5. ResourceTable::PackageType packageType = ResourceTable::App;
    6. if (bundle->getBuildSharedLibrary()) {
    7. packageType = ResourceTable::SharedLibrary;
    8. } else if (bundle->getExtending()) {
    9. packageType = ResourceTable::System;
    10. } else if (!bundle->getFeatureOfPackage().isEmpty()) {
    11. packageType = ResourceTable::AppFeature;
    12. }
    13. //创建资源表
    14. ResourceTable table(bundle, String16(assets->getPackage()), packageType);
    15. ...
    16. }
    • 1

    继续查看ResourceTable.cpp的构造函数。

    ResourceTable.cpp位置:android-6.0.0_r1/frameworks/base/tools/aapt/ResourceTable.cpp
    ResourceTable构造函数

    1. ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type)
    2. : mAssetsPackage(assetsPackage)
    3. , mPackageType(type)
    4. , mTypeIdOffset(0)
    5. , mNumLocal(0)
    6. , mBundle(bundle)
    7. {
    8. ssize_t packageId = -1;
    9. switch (mPackageType) {
    10. case App:
    11. case AppFeature:
    12. packageId = 0x7f;
    13. break;
    14. case System:
    15. packageId = 0x01;
    16. break;
    17. case SharedLibrary:
    18. packageId = 0x00;
    19. break;
    20. default:
    21. assert(0);
    22. break;
    23. }
    24. sp package = new Package(mAssetsPackage, packageId);
    25. mPackages.add(assetsPackage, package);
    26. mOrderedPackages.add(package);
    27. // Every resource table always has one first entry, the bag attributes.
    28. const SourcePos unknown(String8("????"), 0);
    29. getType(mAssetsPackage, String16("attr"), unknown);
    30. }
    • 1

    应用的资源id从0x7f开始,系统的资源id从0x01开始,共享类库的从0x00开始。如果我们想要自定义应用的资源id的起始值,则需要在switch结束后重新设置packageId值。这个自定义值可以Main.cpp的main函数中解析获取,并存放到bundle中。

    修改源码

    1,修改Bundle.h文件。
    Bundle.h位置:android-6.0.0_r1/frameworks/base/tools/aapt/Bundle.h

    添加如下代码:

    1. class Bundle {
    2. public:
    3. ...
    4. //添加的获取和设置自定义id的函数
    5. const android::String8& getApkModule() const {return mApkModule;}
    6. void setApkModule(const char* str) { mApkModule=str;}
    7. ...
    8. private:
    9. /* commands & modifiers */
    10. ...
    11. //自定义id
    12. android::String8 mApkModule;
    13. ...
    14. }
    • 1

    2,修改Main.cpp的main函数,添加解析自定义id的参数并设置到bundle。
    代码如下:

    1. int main(int argc, char* const argv[])
    2. {
    3. ...
    4. while (argc && argv[0][0] == '-') {
    5. ...
    6. while (*cp != '\0') {
    7. ...
    8. case '-':
    9. ...
    10. //添加解析-apk-module参数信息
    11. } else if(strcmp(cp, "-apk-module") == 0){
    12. argc--;
    13. argv++;
    14. if (!argc) {
    15. fprintf(stderr, "ERROR: No argument supplied for '--apk-model' option\n");
    16. wantUsage = true;
    17. goto bail;
    18. }
    19. bundle.setApkModule(argv[0]);
    20. } else if (strcmp(cp, "-feature-of") == 0) {
    21. ...
    22. break;
    23. ...
    24. }
    25. ...
    26. }
    27. ...
    28. }
    • 1

    3,修改ResourceTable.cpp的构造函数,添加判断是否存在自定义id,如果存在,则修改packageId为自定义id。

    1. ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type)
    2. ...
    3. {
    4. ssize_t packageId = -1;
    5. switch (mPackageType) {
    6. ...
    7. }
    8. //判断和设置自定义id
    9. if(!bundle->getApkModule().isEmpty()){
    10. android::String8 apkModuleVal = bundle->getApkModule();
    11. packageId = apkStringToInt(apkModuleVal);
    12. }
    13. sp package = new Package(mAssetsPackage, packageId);
    14. ...
    15. }
    16. //将字符串转换为ssize_t类型
    17. ssize_t ResourceTable::apkStringToInt(const String8& s){
    18. size_t i = 0;
    19. ssize_t val = 0;
    20. size_t len=s.length();
    21. if (s[i] < '0' || s[i] > '9') {
    22. return -1;
    23. }
    24. // Decimal or hex?
    25. if (s[i] == '0' && s[i+1] == 'x') {
    26. i += 2;
    27. bool error = false;
    28. while (i < len && !error) {
    29. val = (val*16) + apkgetHex(s[i], &error);
    30. i++;
    31. }
    32. if (error) {
    33. return -1;
    34. }
    35. } else {
    36. while (i < len) {
    37. if (s[i] < '0' || s[i] > '9') {
    38. return false;
    39. }
    40. val = (val*10) + s[i]-'0';
    41. i++;
    42. }
    43. }
    44. if (i == len) {
    45. return val;
    46. }
    47. return -1;
    48. }
    49. //转换为16进制
    50. uint32_t ResourceTable::apkgetHex(char c, bool* outError){
    51. if (c >= '0' && c <= '9') {
    52. return c - '0';
    53. } else if (c >= 'a' && c <= 'f') {
    54. return c - 'a' + 0xa;
    55. } else if (c >= 'A' && c <= 'F') {
    56. return c - 'A' + 0xa;
    57. }
    58. *outError = true;
    59. return 0;
    60. }
    • 1

    还需要在ResourceTable.h文件中声明apkStringToInt()和apkgetHex()函数。
    ResourceTable.h位置:android-6.0.0_r1/frameworks/base/tools/aapt/ResourceTable.h
    添加到public或private中。

    1. ssize_t apkStringToInt(const String8& s);
    2. uint32_t apkgetHex(char c, bool* outError);
    • 1

    编译和修改错误

    修改完毕,开启终端(或控制台)进入到android-6.0.0_r1目录,运行. build/envsetup.sh命令,配置运行环境。配置命令运行完毕,运行cd frameworks/base/tools/aapt,进入到aapt目录。执行mm命令,该命令会编译aapt模块中的代码,并生成可执行文件。运行结果如下:

    1. ============================================
    2. PLATFORM_VERSION_CODENAME=REL
    3. PLATFORM_VERSION=6.0
    4. TARGET_PRODUCT=full
    5. TARGET_BUILD_VARIANT=eng
    6. TARGET_BUILD_TYPE=release
    7. TARGET_BUILD_APPS=
    8. TARGET_ARCH=arm
    9. TARGET_ARCH_VARIANT=armv7-a
    10. TARGET_CPU_VARIANT=generic
    11. TARGET_2ND_ARCH=
    12. TARGET_2ND_ARCH_VARIANT=
    13. TARGET_2ND_CPU_VARIANT=
    14. HOST_ARCH=x86_64
    15. HOST_OS=darwin
    16. HOST_OS_EXTRA=Darwin-16.0.0-x86_64-i386-64bit
    17. HOST_BUILD_TYPE=release
    18. BUILD_ID=MRA58K
    19. OUT_DIR=out
    20. ============================================
    21. host C++: aapt <= frameworks/base/tools/aapt/Main.cpp
    22. host Executable: aapt (out/host/darwin-x86/obj/EXECUTABLES/aapt_intermediates/aapt)
    23. clang: warning: argument unused during compilation: '-pie'
    24. Install: out/host/darwin-x86/bin/aapt
    25. \e[0;32m#### make completed successfully (3 seconds) ####\e[00m
    • 1

    生产的文件为aapt,在’android-6.0.0_r1/out/host/darwin-x86/bin/’目录中。
    将aapt拷贝到指定目录,进入PluginDemo工程目录,使用aapt对工程进行资源打包,执行的命令如下:

    1. ../../../devTools/aapt/aapt package -f -m --apk-module 0x8f -J gen -S res -M AndroidManifest.xml -I ../../../devTools/android/android-sdk-macosx/platforms/android-23
    2. /android.jar -F build/out/res.ap_
    • 1

    该命令执行失败,某个资源id找不到对应的资源。错误信息:

    res/layout/xxx.xml:9: error: Error: No resource found that matches the given name (at 'text' with value '@string/xxx').
    • 1

    搜索相关的博客,在区长的专栏Android aapt实现资源分区(补充携程aapt源码)这篇博客找到了出错的原因和解决方法。

    继续看代码,

    1. bool ResTable::stringToValue(Res_value* outValue, String16* outString,
    2. const char16_t* s, size_t len,
    3. bool preserveSpaces, bool coerceType,
    4. uint32_t attrID,
    5. const String16* defType,
    6. const String16* defPackage,
    7. Accessor* accessor,
    8. void* accessorCookie,
    9. uint32_t attrType,
    10. bool enforcePrivate) const
    11. {
    12. ...
    13. if (*s == '@') {
    14. if (accessor) {
    15. uint32_t rid = accessor->getCustomResourceWithCreation(package, type, name,
    16. createIfNotFound);
    17. if (rid != 0) {
    18. if (kDebugTableNoisy) {
    19. ALOGI("Pckg %s:%s/%s: 0x%08x\n",
    20. String8(package).string(), String8(type).string(),
    21. String8(name).string(), rid);
    22. }
    23. uint32_t packageId = Res_GETPACKAGE(rid) + 1;
    24. if (packageId == 0x00) {
    25. outValue->data = rid;
    26. outValue->dataType = Res_value::TYPE_DYNAMIC_REFERENCE;
    27. return true;
    28. } else if (packageId == APP_PACKAGE_ID || packageId == SYS_PACKAGE_ID ) {
    29. // We accept packageId's generated as 0x01 in order to support
    30. // building the android system resources
    31. outValue->data = rid;
    32. return true;
    33. }
    34. }
    35. }
    36. }
    37. if (accessor != NULL) {
    38. accessor->reportError(accessorCookie, "No resource found that matches the given name");
    39. }
    40. return false;
    41. }
    42. ...
    43. }
    • 1

    在if(accessor)代码中对packageId进行验证。如果packageId不是ResourceTable构造函数中设置的3种类型,则出错,无法生成R.java文件和资源包。需要将ResourceTable构造函数中设置的packageId值存储下来,并在if (packageId == APP_PACKAGE_ID || packageId == SYS_PACKAGE_ID )添加一个自定义id判断。

    按照区长的博客添加头文件和cpp文件,并添加相应的代码。具体的代码请查看Android aapt实现资源分区(补充携程aapt源码)博客,本文不再写出。
    1,创建文件
    创建Help.h文件,存放在android-6.0.0_r1/frameworks/base/include/androidfw/。
    创建Help.cpp文件,存放在android-6.0.0_r1/frameworks/base/libs/androidfw/。并将Help.cpp添加到android-6.0.0_r1/frameworks/base/libs/androidfw/Android.mk文件。
    2,修改代码
    在ResourceTable::ResourceTable()函数判断和设置自定义id代码后面添加使用Help记录packageId代码。
    在ResTable::stringToValue()函数中添加对Help中记录的packageId判断代码。

    重新编译aapt模块,获取新生成的aapt可执行文件。再次运行生成R.java和资源打包命令(与之前的命令相同),成功运行。

    生成的R.java文件内容

    1. package com.plugin.test;
    2. public final class R {
    3. public static final class attr {
    4. }
    5. public static final class drawable {
    6. public static final int p_icon_play=0x8f020000;
    7. }
    8. public static final class id {
    9. public static final int activity_main=0x8f070000;
    10. }
    11. public static final class layout {
    12. public static final int plugin_item=0x8f040000;
    13. }
    14. public static final class mipmap {
    15. public static final int p_icon_lock=0x8f030000;
    16. }
    17. public static final class string {
    18. public static final int app_name=0x8f050000;
    19. public static final int p_str=0x8f050001;
    20. }
    21. public static final class style {
    22. public static final int AppBaseTheme=0x8f060000;
    23. public static final int AppTheme=0x8f060001;
    24. }
    25. }
    • 1

    不再是原来系统默认的0x7f,都是自定义的0x8f。

    继续执行命令编译代码,将class转换为dex,并将dex和资源包合并生成的apk。对apk签名,运行apk。apk正常运行

  • 相关阅读:
    UDP网络编程
    交互设计工具排行榜,你必须知道!
    react-beautiful-dnd拖拽插件指定拖拽元素
    vue electron 下载Vue-devtools ChromeExtension扩展失败
    Linux | 第一篇——常见指令汇总【超全、超详细讲解】
    Elasticsearch中object类型与nested类型之间的区别
    【Discuz】x3.5论坛修改记录
    @PostConstruct详解
    如何用Three.js + Blender打造一个web 3D展览馆
    前端技术点滴整理-1
  • 原文地址:https://blog.csdn.net/cqn2bd2b/article/details/125556656