乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Android AAPT詳解 轉(zhuǎn)

       ala咪s 2017-09-15

      目錄

      • AAPT解釋,作用
      • AAPT基本命令
      • AAPT編譯資源源碼解析
      • AAPT打包和系統(tǒng)不一致的資源ID

      AAPT是什么

      AAPT - Android Asset Packaging Tool

      看全稱,就可知道AAPT是Android資源打包工具。講這個(gè)之前,是有必要簡(jiǎn)單說下Android是如何構(gòu)建一個(gè)APK的。



      上圖是Google官方發(fā)布的一張非常經(jīng)典的Apk打包流程圖。

      流程概述:

      1. 工程的資源文件(res文件夾下的文件),通過AAPT打包成R.java類(資源索引表),以及.arsc資源文件
      2. 如果有aidl,通過aidl工具,打包成java接口類
      3. R.java和aidl.java通過java編譯成想要的.class文件。
      4. 源碼class文件和第三方j(luò)ar或者library通過dx工具打包成dex文件。dx工具的主要作用是將java字節(jié)碼轉(zhuǎn)換成Dalvik字節(jié)碼,在此過程中會(huì)壓縮常量池,消除一些冗余信息等。
      5. apkbuilder工具會(huì)將所有沒有編譯的資源,.arsc資源,.dex文件打包到一個(gè)完成apk文件中中。
      6. 簽名,5中完成apk通過配置的簽名文件(debug和release都有),jarsigner工具會(huì)對(duì)齊簽名。得到一個(gè)簽名后的apk,signed.apk
      7. zipAlign工具對(duì)6中的signed.apk進(jìn)行對(duì)齊處理,所謂對(duì)齊,主要過程是將APK包中所有的資源文件距離文件起始偏移為4字節(jié)整數(shù)倍,這樣通過內(nèi)存映射訪問apk文件時(shí)的速度會(huì)更快。對(duì)齊的作用主要是為了減少運(yùn)行時(shí)內(nèi)存的使用。

      總結(jié):

      • 輸入:res文件夾所有的資源(layout\drawable\string\array等),asset下的資源,AndroidManifest.xml,Android.jar文件
      • 工具: aapt 地址(/your sdk path/build-tools/your build tools version/aapt)
      • 輸出:res下的資源都會(huì)被編譯成一個(gè)資源索引文件resource.arsc以及一個(gè)R.java類。asset下的資源不會(huì)編譯,直接壓縮進(jìn)apk。

      AAPT命令詳解

      按照上面aapt的地址配置好環(huán)境變量后,在終端中輸入 aapt v 會(huì)得到aapt版本信息,如下:


      再輸入 aapt 命令會(huì)列出所有的aapt命令集合,下面我們一條條來使用并且分析其作用:

      1.aapt l[ist] [-v] [-a] file.{zip,jar,apk}

      作用:列出壓縮文件(zip,jar,apk)中的目錄內(nèi)容。

      例如:

          aapt l /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk
      

      結(jié)果如下:




      可以看出來不加任何參數(shù),aapt只是簡(jiǎn)單的羅列壓縮文件中每一項(xiàng)的內(nèi)容。

          aapt l -v /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk
      

      結(jié)果如下:




      從圖中可以看出,加上-v后,輸出的內(nèi)容很詳細(xì),并且以列表的形式標(biāo)識(shí)出很多參數(shù),其中表目有:

      • Length:原始文件的長(zhǎng)度

      • Date:日期

      • Time:時(shí)間

      • Name:名稱

      • Method:壓縮方法,Deflate及Stored兩種,即該Zip目錄采用的算法是壓縮模式還是存儲(chǔ)模式;可以看出resources.arsc、*.png采用壓縮模式,而其它采用壓縮模式。

      • Ratio:壓縮率

      • Size:這個(gè)是壓縮省掉的大小,即如果壓縮率是xx%。那Size是原始長(zhǎng)度*(1-xx%)。

      • CRC-32:循環(huán)冗余校驗(yàn)。這個(gè)計(jì)算是有特定的算法的。

      • offset:zipfile中偏移量的意思

          aapt l -a /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk 
        

      結(jié)果如下:




      -a表示會(huì)詳細(xì)輸出壓縮文件中所有目錄的內(nèi)容,詳細(xì)到什么程度的,可以看上圖,上圖截取的只是很小的一部分,這部分是manifest.xml文件的所有數(shù)據(jù),可以看出來基本上所有的manifest信息都列了出來。

      2.aapt d[ump] [--values] [--include-meta-data] WHAT file.{apk} [asset [asset ...]]

      作用:通過參數(shù)配置可以dump apk中各種詳細(xì)信息。

      • strings 官方解釋:

      Print the contents of the resource table string pool in the APK

      即打印apk中所有string資源表

          aapt dump strings /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk
      

      結(jié)果:




      不太了解這個(gè)結(jié)果是什么具體的意思,猜測(cè)應(yīng)該是序列化的string字段。

      • bading 官方解釋:

      Print the label and icon for the app declared in APK.

          aapt dump badging /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk
      

      結(jié)果:






      查看APK中的配置信息,包括包名,versionCode,versionName,platformBuildVersionName(編譯的時(shí)候系統(tǒng)添加的字段,相當(dāng)targetSdkVersionCode的描述)等。同事該命令還列出了manifest.xml的部分信息,包括啟動(dòng)界面,manifest里配置的label,icon等信息。還有分辨率,時(shí)區(qū),uses-feature等信息。

      • permissions 官方解釋:Print the permissions from the APK

      較簡(jiǎn)單,輸出APK中使用到的權(quán)限信息。

      • resources 官方解釋:

      Print the resource table from the APK

          aapt dump resources /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk
      

      結(jié)果:




      輸出了apk中所有的資源信息,從這里也可以看出來aapt打包時(shí)也包含了android系統(tǒng)很多資源。并且這里也發(fā)現(xiàn),系統(tǒng)通過標(biāo)準(zhǔn)的aapt構(gòu)建出來的資源絕大部分的資源id都是0x7f開頭的,這個(gè)也是和我們?cè)赗.java文件中看到的資源編號(hào)是對(duì)應(yīng)起來的。

      • configurations 官方解釋:Print the configurations in the APK

           aapt dump configurations /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk
        

      結(jié)果:


      可以看出該命令輸出了apk所有的資源目錄,僅僅是目錄,里面有語言,分辨率,夜間模式相關(guān)的數(shù)據(jù)。

      • xmltree 官方解釋:

      Print the compiled xmls in the given assets

          aapt d xmltree /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk res/layout/activity_main.xml
      

      結(jié)果:


      該命令直接反編譯除了apk中某一個(gè)xml布局文件的組織結(jié)構(gòu)。命令需要兩個(gè)參數(shù) 第一是apk的地址 第二后面是apk中某個(gè)編譯好的xml的相對(duì)路徑地址

      • xmlstrings 官方解釋:

      Print the strings of the given compiled xml assets

          aapt d xmlstrings /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk res/layout/activity_main.xml
      

      結(jié)果:


      從字面解釋,輸出xml文件中所有的string信息。看結(jié)果,實(shí)際上并沒看出來什么特殊的,也并不是簡(jiǎn)單的string信息,猜測(cè)可能是索引吧。

      3.aapt p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml]

      
      aapt p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml]         [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile]         [--debug-mode] [--min-sdk-version VAL] [--target-sdk-version VAL]         [--app-version VAL] [--app-version-name TEXT] [--custom-package VAL]         [--rename-manifest-package PACKAGE]         [--rename-instrumentation-target-package PACKAGE]         [--utf16] [--auto-add-overlay]         [--max-res-version VAL]         [-I base-package [-I base-package ...]]         [-A asset-source-dir]  [-G class-list-file] [-P public-definitions-file]         [-D main-dex-class-list-file]         [-S resource-sources [-S resource-sources ...]]         [-F apk-file] [-J R-file-dir]         [--product product1,product2,...]         [-c CONFIGS] [--preferred-density DENSITY]         [--split CONFIGS [--split CONFIGS]]         [--feature-of package [--feature-after package]]         [raw-files-dir [raw-files-dir] ...]         [--output-text-symbols DIR]
      
      

      官方解釋:

      Package the android resources. It will read assets and resources that are supplied with the -M -A -S or raw-files-dir arguments. The -J -P -F and -R options control which files are output.

      android 編譯資源打包資源文件的命令。

      • -d:包括一個(gè)或多個(gè)設(shè)備資源,由逗號(hào)分隔;
      • -f:覆蓋現(xiàn)有的文件命令,加上后編譯生成直接覆蓋目前已經(jīng)存在的R.java;
      • -m:使生成的包的目錄放在-J參數(shù)指定的目錄;
      • -u:更新現(xiàn)有的包 u = update;
      • -v:詳細(xì)輸出,加上此命令會(huì)在控制臺(tái)輸出每一個(gè)資源文件信息,R.java生成后還有注釋。
      • -x:創(chuàng)建擴(kuò)展資源ID;
      • -z:需要本地化的資源屬性標(biāo)記定位。
      • -M:AndroidManifest.xml的路徑
      • -0:指定一個(gè)額外的擴(kuò)展. apk文件將不會(huì)存儲(chǔ)壓縮
      • -g:制定像素迫使圖形的灰度
      • -j:指定包含一個(gè)jar或zip文件包,這個(gè)命令很特別
      • –debug-mode:指定的是調(diào)試模式下的編譯資源;
      • –min-sdk-versopm VAL:最小SDK版本 如是7以上 則默認(rèn)編譯資源的格式是 utf-8
      • –target-sdk-version VAL:在androidMainfest中的目標(biāo)編譯SDK版本
      • –app-version VAL:應(yīng)用程序版本號(hào)
      • –app-version-name TEXT:應(yīng)該程序版本名字;
      • –custom-package VAL:生成R.java到一個(gè)不同的包
      • –rename-mainifest-package PACKAGE:修改APK包名的選項(xiàng);
      • –rename-instrumentation-target-package PACKAGE:重寫指定包名的選項(xiàng);
      • –utf16:資源編碼修改為更改默認(rèn)utf – 16編碼;
      • –auto-add-overlay:自動(dòng)添加資源覆蓋
      • –max-res-version:最大資源版本
      • -I:指定的SDK版本中android.jar的路徑
      • -A:assert文件夾的路徑
      • -G:一個(gè)文件輸出混淆器選項(xiàng),后面加文件逗號(hào)隔開.
      • -P:指定的輸出公共資源,可以指定一個(gè)文件 讓資源ID輸出到那上面;
      • -S:指定資源目錄 一般是 res
      • -F:指定把資源輸出到 apk文件中
      • -J:指定R.java輸出的路徑
      • raw-file-dir:附加打包進(jìn)APK的文件

      該命令也是aapt最核心、最復(fù)雜的命令。這邊我只嘗試了一下簡(jiǎn)單的實(shí)踐,講工程的資源編譯到一個(gè)包里。下面是命令

      
      aapt package -f -S /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/src/main/res -I /Users/zfxu/Library/Android/sdk/platforms/android-25/android.jar -A /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/src/main/assets -M /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/src/main/AndroidManifest.xml -F /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/out.apk
      
      

      輸出了一個(gè)apk文件,解壓以后文件格式如下:


      這個(gè)apk文件除了沒有代碼dex,資源都在的。這個(gè)是aapt打包的關(guān)鍵步驟之一,還有一個(gè)步驟就是把資源文件編譯成R.java,供程序調(diào)用。命令如下:

      
      aapt package -m -J <R.java目錄> -S <res目錄> -I <android.jar目錄>  -M <AndroidManifest.xml目錄>
      
      

      4.aapt r[emove] [-v] file.{zip,jar,apk} file1 [file2 ...]

      官方解釋:

      Delete specified files from Zip-compatible archive

      就是從一個(gè)zip archive文件中刪除一個(gè)文件。較簡(jiǎn)單,不做實(shí)例了。

      5.aapt a[dd] [-v] file.{zip,jar,apk} file1 [file2 ...]

      官方解釋:

      Add specified files to Zip-compatible archive.

      即在一個(gè)zip包中添加一個(gè)一個(gè)指定的文件。

      6.aapt c[runch] [-v] -S resource-sources ... -C output-folder ...

      官方解釋:

      Do PNG preprocessing on one or several resource folders and store the results in the output folder.

      對(duì)多個(gè)或者單個(gè)資源文件夾進(jìn)行處理,并且將結(jié)果保存在輸出文件夾中

      6.aapt s[ingleCrunch] [-v] -i input-file -o outputfile

      官方解釋:

      Do PNG preprocessing on a single file

      預(yù)處理一個(gè)文件

      AAPT源碼解析

      首先下載Android源碼

      Android Source

      我這邊下載的是Android 6.0的源碼

      AAPT代碼地址:***/frameworks/tools/aapt/目錄下。

      我們這里以一個(gè)命令來跟蹤源碼的流程,即用aapt是如何構(gòu)建一個(gè)R.java的,命令格式如下:

      
      aapt package –m –J <R.java目錄> -S <res目錄> -I <android.jar目錄> -M <AndroidManifest.xml目錄>
      
      

      實(shí)踐:

      
      aapt package -m -J  /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/ -S /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/src/main/res/ -I /Users/zfxu/Library/Android/sdk/platforms/android-25/android.jar -M /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/src/main/AndroidManifest.xml
      
      

      運(yùn)行該命令后,在配置的R.java目錄下 會(huì)生成一個(gè)自己app包名的目錄,里面會(huì)生成R.java文件,如下:



      R.java里會(huì)生成這樣的索引ID類,都是以0x7f開頭

      public final class R {
          public static final class attr {
          }
          public static final class color {
              public static final int colorAccent=0x7f040002;
              public static final int colorPrimary=0x7f040000;
              public static final int colorPrimaryDark=0x7f040001;
          }
          public static final class layout {
              public static final int activity_main=0x7f030000;
          }
          public static final class mipmap {
              public static final int ic_launcher=0x7f020000;
              public static final int ic_launcher_round=0x7f020001;
          }
          public static final class string {
              public static final int app_name=0x7f050000;
          }
      }
      

      好,既然我們知道了輸入以及輸出,那讓我們來分析這塊的代碼。

      PS:由于某些函數(shù)較長(zhǎng),不會(huì)貼出所有的源碼

      1. 入口 /frameworks/base/tools/aapt/Main.cpp
      int main(int argc, char* const argv[]) {
          ***
          else if (argv[1][0] == 'p')
              bundle.setCommand(kCommandPackage);
          ***
          while (argc && argv[0][0] == '-') {
              //通過case比較,去除命令中所有的參數(shù),并且放進(jìn)bundle中
              /* flag(s) found */
              const char* cp = argv[0] +1;
              while (*cp != '\0') {
                  ***
                  switch (*cp) {
                  case 'M':
                      argc--;
                      argv++;
                      if (!argc) {
                          fprintf(stderr, "ERROR: No argument supplied for '-M' option\n");
                          wantUsage = true;
                          goto bail;
                      }
                      //這個(gè)僅僅是把傳進(jìn)來的地址坐下系統(tǒng)路徑分割線的轉(zhuǎn)換
                      convertPath(argv[0]);
                      bundle.setAndroidManifestFile(argv[0]);
                      break;
                  }
                  ***
              }
          }
          ***
          result = handleCommand(&bundle);
          ***
      }
      

      當(dāng)通過所有的匹配規(guī)則后,該函數(shù)實(shí)際調(diào)用是 handleCommand(&bundle)。 至于執(zhí)行什么命令說白了也是命令指定的,-p 設(shè)置的command參數(shù)是kCommandPackage。

      1. 分發(fā)指令 /frameworks/base/tools/aapt/Main.cpp
       int handleCommand(Bundle* bundle){
          switch (bundle->getCommand()) {
              case kCommandVersion:      return doVersion(bundle);
              case kCommandList:         return doList(bundle);
              case kCommandDump:         return doDump(bundle);
              case kCommandAdd:          return doAdd(bundle);
              case kCommandRemove:       return doRemove(bundle);
              case kCommandPackage:      return doPackage(bundle);
              case kCommandCrunch:       return doCrunch(bundle);
              case kCommandSingleCrunch: return doSingleCrunch(bundle);
              case kCommandDaemon:       return runInDaemonMode(bundle);
              default:
                  fprintf(stderr, "%s: requested command not yet supported\n", gProgName);
                  return 1;
          }
      }
      
      1. 處理package指令 /frameworks/base/tools/aapt/Command.cpp
      int doPackage(Bundle* bundle) {
          const char* outputAPKFile;
          int retVal = 1;
          status_t err;
          sp<AaptAssets> assets;
          int N;
          FILE* fp;
          String8 dependencyFile;
          sp<ApkBuilder> builder;
      
          sp<WeakResourceFilter> configFilter = new WeakResourceFilter();
          //見注釋3-1
          err = configFilter->parse(bundle->getConfigurations());
          if (err != NO_ERROR) {
              goto bail;
          }
      
          //資源本地化相關(guān)的配置,具體什么含義也沒有理解清楚
          if (configFilter->containsPseudo()) {
              bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
          }
          if (configFilter->containsPseudoBidi()) {
              bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
          }
      
          //校驗(yàn)命令中是否傳入正確的參數(shù)
          N = bundle->getFileSpecCount();
          if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
                  && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDirs().size() == 0) {
              fprintf(stderr, "ERROR: no input files\n");
              goto bail;
          }
      
          outputAPKFile = bundle->getOutputAPKFile();
      
          // 如果輸出文件存在,但是是不合格的,則直接報(bào)錯(cuò)結(jié)束,如果不存在,則新建空文件
          if (outputAPKFile) {
              FileType type;
              type = getFileType(outputAPKFile);
              if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
                  fprintf(stderr,
                      "ERROR: output file '%s' exists but is not regular file\n",
                      outputAPKFile);
                  goto bail;
              }
          }
      
          // Load the assets.
          assets = new AaptAssets();
          
          // 設(shè)置res和asset的成員,僅僅是外層new一個(gè)對(duì)象賦值給AaptAssets
          if (bundle->getGenDependencies()) {
              sp<FilePathStore> resPathStore = new FilePathStore;
              assets->setFullResPaths(resPathStore);
              sp<FilePathStore> assetPathStore = new FilePathStore;
              assets->setFullAssetPaths(assetPathStore);
          }
          //調(diào)用AaptAssets類的成員函數(shù)slurpFromArgs將AndroidManifest.xml文件,目錄assets和res下的資源目錄和資源文件收錄起來保存到AaptAssets中的
          //成員變量中
          err = assets->slurpFromArgs(bundle);
          if (err < 0) {
              goto bail;
          }
          //如果命令中指定需要詳細(xì)日志輸出,這里會(huì)打印所有的資源信息
          if (bundle->getVerbose()) {
              assets->print(String8());
          }
      
          // Create the ApkBuilder, which will collect the compiled files
          // to write to the final APK (or sets of APKs if we are building
          // a Split APK.
          //new一個(gè)ApkBuilder對(duì)象,如果需要生成多個(gè)apk,則需要將上層的配置寫入改對(duì)象中
          builder = new ApkBuilder(configFilter);
          // If we are generating a Split APK, find out which configurations to split on.
          if (bundle->getSplitConfigurations().size() > 0) {
              const Vector<String8>& splitStrs = bundle->getSplitConfigurations();
              const size_t numSplits = splitStrs.size();
              for (size_t i = 0; i < numSplits; i++) {
                  std::set<ConfigDescription> configs;
                  if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) {
                      fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string());
                      goto bail;
                  }
      
                  err = builder->createSplitForConfigs(configs);
                  if (err != NO_ERROR) {
                      goto bail;
                  }
              }
          }
      
          // If they asked for any fileAs that need to be compiled, do so.
          //這是最核心的一步,編譯資源(res和asset)。這個(gè)成功后,下面的步驟就僅僅是寫輸出文件了
          if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
              err = buildResources(bundle, assets, builder);
              if (err != 0) {
                  goto bail;
              }
          }
      
          // At this point we've read everything and processed everything.  From here
          // on out it's just writing output files.
          if (SourcePos::hasErrors()) {
              goto bail;
          }
      
          // Update symbols with information about which ones are needed as Java symbols.
          assets->applyJavaSymbols();
          if (SourcePos::hasErrors()) {
              goto bail;
          }
      
          // If we've been asked to generate a dependency file, do that here
          if (bundle->getGenDependencies()) {
              // If this is the packaging step, generate the dependency file next to
              // the output apk (e.g. bin/resources.ap_.d)
              if (outputAPKFile) {
                  dependencyFile = String8(outputAPKFile);
                  // Add the .d extension to the dependency file.
                  dependencyFile.append(".d");
              } else {
                  // Else if this is the R.java dependency generation step,
                  // generate the dependency file in the R.java package subdirectory
                  // e.g. gen/com/foo/app/R.java.d
                  dependencyFile = String8(bundle->getRClassDir());
                  dependencyFile.appendPath("R.java.d");
              }
              // Make sure we have a clean dependency file to start with
              fp = fopen(dependencyFile, "w");
              fclose(fp);
          }
      
          // 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());
              }
              if (err < 0) {
                  goto bail;
              }
              // If we have library files, we're going to write our R.java file into
              // the appropriate class directory for those libraries as well.
              // e.g. gen/com/foo/app/lib/R.java
              if (bundle->getExtraPackages() != NULL) {
                  // Split on colon
                  String8 libs(bundle->getExtraPackages());
                  char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
                  while (packageString != NULL) {
                      // Write the R.java file out with the correct package name
                      err = writeResourceSymbols(bundle, assets, String8(packageString), true,
                              bundle->getBuildSharedLibrary());
                      if (err < 0) {
                          goto bail;
                      }
                      packageString = strtok(NULL, ":");
                  }
                  libs.unlockBuffer();
              }
          } else {
              err = writeResourceSymbols(bundle, assets, assets->getPackage(), false, false);
              if (err < 0) {
                  goto bail;
              }
              err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true, false);
              if (err < 0) {
                  goto bail;
              }
          }
      
          // Write out the ProGuard file
          err = writeProguardFile(bundle, assets);
          if (err < 0) {
              goto bail;
          }
      
          // Write the apk
          if (outputAPKFile) {
              // Gather all resources and add them to the APK Builder. The builder will then
              // figure out which Split they belong in.
              err = addResourcesToBuilder(assets, builder);
              if (err != NO_ERROR) {
                  goto bail;
              }
      
              const Vector<sp<ApkSplit> >& splits = builder->getSplits();
              const size_t numSplits = splits.size();
              for (size_t i = 0; i < numSplits; i++) {
                  const sp<ApkSplit>& split = splits[i];
                  String8 outputPath = buildApkName(String8(outputAPKFile), split);
                  err = writeAPK(bundle, outputPath, split);
                  if (err != NO_ERROR) {
                      fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string());
                      goto bail;
                  }
              }
          }
      
          // If we've been asked to generate a dependency file, we need to finish up here.
          // the writeResourceSymbols and writeAPK functions have already written the target
          // half of the dependency file, now we need to write the prerequisites. (files that
          // the R.java file or .ap_ file depend on)
          if (bundle->getGenDependencies()) {
              // Now that writeResourceSymbols or writeAPK has taken care of writing
              // the targets to our dependency file, we'll write the prereqs
              fp = fopen(dependencyFile, "a+");
              fprintf(fp, " : ");
              bool includeRaw = (outputAPKFile != NULL);
              err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);
              // Also manually add the AndroidManifeset since it's not under res/ or assets/
              // and therefore was not added to our pathstores during slurping
              fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
              fclose(fp);
          }
      
          retVal = 0;
      bail:
          if (SourcePos::hasErrors()) {
              SourcePos::printErrors(stderr);
          }
          return retVal;
      }
      

      注釋:

      1. 編譯res和xml資源 /frameworks/base/tools/aapt/Resource.cpp

      ps:改函數(shù)較長(zhǎng),截取部分代碼分步解析

      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;
          }
      
          ......
      }
      

      首先解析manifest文件,調(diào)用的是parsePackage函數(shù),解析之前,manifest被封裝成一個(gè)AaptGroup對(duì)象。

      static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets,
          const sp<AaptGroup>& grp)
      {
          if (grp->getFiles().size() != 1) {
              fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
                      grp->getFiles().valueAt(0)->getPrintableSource().string());
          }
      
          sp<AaptFile> file = grp->getFiles().valueAt(0);
      
          ResXMLTree block;
          status_t err = parseXMLResource(file, &block);
          if (err != NO_ERROR) {
              return err;
          }
          ......省略代碼
          return NO_ERROR;
      }
      

      沒有具體細(xì)看里面的代碼,說下具體思路,通過傳進(jìn)來的形參AaptGroup拿到具體的AaptFile對(duì)象。在調(diào)用公共類的parseXmlResource解析xml文件得到具體數(shù)據(jù)后,存放在對(duì)象ResXmlTree中。parseXMLResource函數(shù)在類 frameworks/base/tools/aapt/XMLNode.cpp 中。有興趣的可以自己去讀下,這里就不貼了。解析玩manifest.xml后,我們繼續(xù)buildResources的分析。

      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);
          err = table.addIncludedResources(bundle, assets);
          if (err != NO_ERROR) {
              return err;
          }
          ...省略的代碼
      

      這段代碼的目的主要是收集當(dāng)前編譯的資源需要依賴的的資源并且存放在ResourceTable這個(gè)數(shù)據(jù)結(jié)構(gòu)中。這邊簡(jiǎn)單介紹一下ResourceTable這個(gè)數(shù)據(jù)結(jié)構(gòu),首先我們得知道R.java里面的資源標(biāo)識(shí)id的構(gòu)成,比方說 0x7f040002 其中0x7f表示是packageID,也就是上面的packageType,它是一個(gè)命名空間,限定資源的來源,7f表明是當(dāng)前應(yīng)用程序的資源,系統(tǒng)的資源是以0x01開頭。04 表示TypeID。資源的類型animator、anim、color、drawable、layout、menu、raw、string和xml等等若干種,每一種都會(huì)被賦予一個(gè)ID。最后四位是EntryID,指的是每一個(gè)資源在起對(duì)應(yīng)的TypID中出現(xiàn)的順序。




      而ResouceTable里面存儲(chǔ)的最核心的元素就是這個(gè)id的區(qū)分。

      收集完成當(dāng)前應(yīng)用依賴的資源以后,就要編譯當(dāng)前應(yīng)用自己的資源。這里由于代碼太過于復(fù)雜,本人也沒有完全看懂,就不貼了,邏輯基本上就是通過命令的輸出,一個(gè)個(gè)的編譯資源文件和png。然后存儲(chǔ)在一個(gè)xml文件中,為后面生成R.java文件中做準(zhǔn)備。實(shí)際上前面也有提到,所有的資源都會(huì)存在ResouceTable這個(gè)數(shù)據(jù)結(jié)構(gòu)中,做完編譯工作以后,只需要去遍歷這個(gè)向量表,然后對(duì)里面的packageID,typeID,EvtryID進(jìn)行拼接,就可以得到我們所熟悉的 0x7f040002這種資源ID。ResouceTable的構(gòu)造函數(shù)也可以看出來里面的過程:

      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);
      }
      
      1. 完成上述的編譯資源的工作以后,細(xì)心的讀者就會(huì)發(fā)現(xiàn),對(duì)于manifest.xml一直都是讀取里面的配置信息,并沒有編譯,所以最后一步就是把manifest.xml編譯成二進(jìn)制文件。這個(gè)就不貼出源碼了。

      2. 最后一步,將上述的編譯結(jié)果輸出到R.java和Apk中。其中還會(huì)輸出混淆文件,java符號(hào)表等。

      ...省略代碼
      // 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());
              }
              if (err < 0) {
                  goto bail;
              }
              // If we have library files, we're going to write our R.java file into
              // the appropriate class directory for those libraries as well.
              // e.g. gen/com/foo/app/lib/R.java
              if (bundle->getExtraPackages() != NULL) {
                  // Split on colon
                  String8 libs(bundle->getExtraPackages());
                  char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
                  while (packageString != NULL) {
                      // Write the R.java file out with the correct package name
                      err = writeResourceSymbols(bundle, assets, String8(packageString), true,
                              bundle->getBuildSharedLibrary());
                      if (err < 0) {
                          goto bail;
                      }
                      packageString = strtok(NULL, ":");
                  }
                  libs.unlockBuffer();
              }
          } else {
              err = writeResourceSymbols(bundle, assets, assets->getPackage(), false, false);
              if (err < 0) {
                  goto bail;
              }
              err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true, false);
              if (err < 0) {
                  goto bail;
              }
          }
      ...省略代碼
      

      綜上所述,分析完成了了apk資源編譯的過程,由于本人c++功底不佳,有的東西也只是靠猜測(cè)來完成,基本上能夠理清楚大體的邏輯。如果想更詳細(xì)的內(nèi)容,可以自己參考源碼。網(wǎng)上有篇博客,有點(diǎn)亂,但是很細(xì),可以看下http://www.cnblogs.com/dyllove98/p/3144950.html

      AAPT命令修改,完成修改資源ID

      在第三節(jié)我們講AAPT是如何編譯資源并且生成R.java文件的,也提到R.java中資源ID的含義,在組件化框架中,由于組件和宿主分開編譯,為了防止組件的資源ID和宿主的資源ID沖突,所以就需要修改AAPT源碼?;舅悸肪褪敲總€(gè)組件分配一個(gè)不一樣的ID,宿主的ID是以0x7f開頭,組件的ID是0x**開頭,這樣就避免沖突。



      可以看出如果不修改AAPT源碼重新構(gòu)建,就會(huì)導(dǎo)致組件之間或者組件與宿主之間的ID沖突。所以就會(huì)有如下模型:


      既然分析AAPT的編譯過程,那思路就很清晰了,在命令中添加一個(gè)自定義的ID,然后在代碼中拿到這個(gè)ID,拼接的時(shí)候替換上即可。當(dāng)然這只是最簡(jiǎn)單的,而實(shí)際情況呢,比方說宿主里有一部分資源是其他組件公用的,如何保證這部分資源和id和組件本身的id不會(huì)發(fā)生沖突呢?又如何在我們工程里自動(dòng)化這套自定義的aapt從而代替系統(tǒng)標(biāo)準(zhǔn)的aapt呢?下篇博客,我會(huì)詳細(xì)分析。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多