ROM UI 多主題打包流程研究

簡述:

? ?一直希望有個(gè)機(jī)會(huì)可以好好研究一下android手機(jī)的多主題功能逗宜,借此機(jī)會(huì)將自己所能分析到的內(nèi)容記錄一下奶段,防止以后遺忘澎胡。
? ?目前大多數(shù)廠商的手機(jī)都具備的切換主題的功能垦垂,以一個(gè)apk的形式將所有資源打包沪哺,切換主題時(shí)會(huì)動(dòng)態(tài)提醒每個(gè)應(yīng)用的資源管理器,將需要使用的資源信息添加進(jìn)去并重新刷新界面邻奠,使其能夠使用已經(jīng)替換過的資源笤喳,從而達(dá)到整個(gè)ROM UI風(fēng)格的更新。
? ?目前所有的學(xué)習(xí)都是基于魔趣OS的多主題功能框架代碼碌宴,先看下一個(gè)主題 apk中主要目錄結(jié)構(gòu):

  • Samsung_theme.apk


    圖1 主題包目錄結(jié)構(gòu)

? ?從上圖可以看出所有資源都在assets目錄下杀狡,該APK安裝后會(huì)同時(shí)修改壁紙、鈴聲贰镣、app皮膚呜象、鎖屏壁紙和應(yīng)用圖標(biāo)等,接下是圍繞著多主題中' Icon '資源的分析碑隆。

學(xué)習(xí)目的:

? ?通過研究多主題框架代碼恭陡,進(jìn)一步了解AAPT工具打包apk流程。

整體架構(gòu):

? ?分析完整個(gè)編譯流程和查找資源的流程上煤,畫了張圖:

圖2 資源框架圖

上圖為本人對整個(gè)多主題-Icon資源的理解休玩,暫時(shí)先這樣,可能畫的不夠詳細(xì),有不對的地方還望大俠們指出拴疤。

? ?通過上圖可知永部,Icon資源包的創(chuàng)建可分為兩步:

? ?第一步:安裝主題APK,路徑: ' /data/data/包名/base.apk '呐矾;
? ?第二步:主題APK安裝完畢時(shí)PMS會(huì)接受到廣播苔埋,緊接著通過AAPT工具為剛才安裝的' 主題apk '打包一個(gè)"resource.apk",我把它簡稱為“索引apk”蜒犯。

? ?資源的查找這邊不花時(shí)間贅述组橄,可以通過上圖了解到下大體的查找流程。

打包流程:

? ?由于對 c/c++ 代碼不熟愧薛,百度了一點(diǎn)知識(呵呵噠)晨炕。在走到 c++ 流程時(shí)記得盡量詳細(xì)一些衫画,錯(cuò)誤的地方還請大拿們指出毫炉。先放出整個(gè)資源打包的流程圖,流程對我而言是相當(dāng)復(fù)雜繁瑣削罩,而打包的流程是有兩個(gè)入口:

  1. 當(dāng)主題包安裝完成時(shí)瞄勾,PMS會(huì)對主題包進(jìn)行二次打包處理,為資源創(chuàng)建新的資源索引包弥激;
  2. 當(dāng)開機(jī)完成进陡,系統(tǒng)服務(wù)準(zhǔn)備就緒,PMS會(huì)立即檢查當(dāng)前應(yīng)用中的主題微服,如有必要會(huì)重新通過aapt工具打包趾疚。

下面的流程圖是從安裝主題包完成時(shí)開始分析(圖太大,需要單獨(dú)查看)以蕴。

圖3 資源打包流程

一糙麦、主題包安裝完成

step 1 - 3 :

先列出這部分所有類的位置 :

  • source\frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java (簡稱 PMS)
  • source\packages\appsThemeManagerService\src\com\gome\themeservice\ThemeManagerService.java (簡稱 TMS)
  • source\frameworks\base\services\core\java\org\cm\platform\internal\ThemeManagerServiceBroker.java (簡稱 TMSBroker)

? ?當(dāng)主題包安裝完成,PMS會(huì)接受到一個(gè)' POST_INSTALL '的 Handle 消息丛肮,在接下的方法中先判斷當(dāng)前 apk 包安裝成功與否赡磅,再接著對當(dāng)前的包信息進(jìn)行判斷,如果當(dāng)前 apk 是主題包就進(jìn)入特殊處理宝与。PMS會(huì)將主題包的包名傳遞給TMS 服務(wù)焚廊,從而開始進(jìn)行下一步處理妇蛀。
TMS 服務(wù)并非是系統(tǒng)服務(wù)整葡,坐落于app層兔沃。TMSBroker 為系統(tǒng)服務(wù)尚猿,是 TMS 的在 framework 層的服務(wù)代理登刺,所有與app層的交互都是通過 TMSBroker 來代理)

private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions,
            boolean killApp, String[] grantedPermissions,
            boolean launchedForRestore, String installerPackage,
            IPackageInstallObserver2 installObserver) {
           
        if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                ...
            // if this was a theme, send it off to the theme service for processing
            if(res.pkg.mIsThemeApk || res.pkg.mIsLegacyIconPackApk) {
                processThemeResourcesInThemeService(res.pkg.packageName);
            } 
                ...
        }

private void processThemeResourcesInThemeService(String pkgName) {
        IThemeService ts = IThemeService.Stub.asInterface(ServiceManager.getService(
                MKContextConstants.MK_THEME_SERVICE));
        if (ts == null) {
            Slog.e(TAG, "Theme service not available");
            return;
        }
        try {
            ts.processThemeResources(pkgName);
        } catch (RemoteException e) {
            /* ignore */
        }
    }
step 4 - 7 :

? ?TMS 拿到包名后會(huì)重新調(diào)用PMSprocessThemeResources 方法繼續(xù)走打包流程屹逛,如果編譯成功TMS會(huì)將這些信息收集起來并做其他處理嗤朴。此時(shí)上層主題的操作交由TMS的來處理剥懒,而具體打包流程由PMS來進(jìn)行。TMS 這塊本章不做任何介紹匿乃,接來下看看PMS如何工作桩皿。

private class ResourceProcessingHandler extends Handler {
        ...
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                 ...
                case MESSAGE_DEQUEUE_AND_PROCESS_THEME:
                    ...
                   if (pkgName != null) {
                        String name;
                        try {
                            PackageInfo pi = mPM.getPackageInfo(pkgName, 0);
                            name = getThemeName(pi);
                        } catch (PackageManager.NameNotFoundException e) {
                            name = null;
                        }
                        //走打包流程
                        int result = mPM.processThemeResources(pkgName);
                        if (result < 0) {
                            postFailedThemeInstallNotification(name != null ? name : pkgName);
                        }
                        //根據(jù)打包結(jié)果處理上層邏輯
                        sendThemeResourcesCachedBroadcast(pkgName, result);

                        synchronized (mThemesToProcessQueue) {
                            mThemesToProcessQueue.remove(0);
                            if (mThemesToProcessQueue.size() > 0 &&
                                    !hasMessages(MESSAGE_DEQUEUE_AND_PROCESS_THEME)) {
                                this.sendEmptyMessage(MESSAGE_DEQUEUE_AND_PROCESS_THEME);
                            }
                        }
                        //提醒打包完成
                        postFinishedProcessing(pkgName);
                    }
                    break;
            }
        }
    }

二、為索引 apk 的打包作準(zhǔn)備

step 8 - 15 :

? ?這部分主要根據(jù)當(dāng)前主題包名來判斷是否需要進(jìn)一步的打包處理幢炸,判斷依據(jù)就是讀取對應(yīng)目錄下' hash '值泄隔,并與當(dāng)前主題包 ' versionCode '作對比,一致則不需要再次編譯。當(dāng)一個(gè)主題包首次安裝時(shí)宛徊,對應(yīng)目錄下是不存在這些緩存文件的佛嬉。而在每次開機(jī)時(shí)會(huì)動(dòng)態(tài)檢查緩存,如果之前不慎刪除了緩存闸天,此時(shí)才會(huì)重新打包暖呕。

? ?緩存目錄(以 Samsung_theme.apk 為例) : " /data/resource-cache/com.wsdeveloper.galaxys7/icons/ "
該目錄下就存在兩個(gè)文件,' hash '文件用來檢驗(yàn)主題包的合法性苞氮,而' resources.apk '才是這次需要重點(diǎn)研究的對象湾揽,可以參考上面的資源架構(gòu)圖來理解。


圖4 索引包緩存目錄

從下面的代碼可知笼吟,如果需要編譯索引包库物,就先根據(jù)包名創(chuàng)建對應(yīng)的緩存目錄。

    @Override
    public int processThemeResources(String themePkgName) {
        ...
        // Process icons
        if (isIconCompileNeeded(pkg)) {
            try {
                ThemeUtils.createCacheDirIfNotExists();
                ThemeUtils.createIconDirIfNotExists(pkg.packageName);
                //開始準(zhǔn)備創(chuàng)建“icon”目錄下的兩個(gè)文件
                compileIconPack(pkg);
            } catch (Exception e) {
                //出現(xiàn)異常就立即將主題包卸載
                uninstallThemeForAllApps(pkg);
                deletePackageX(themePkgName, getCallingUid(), PackageManager.DELETE_ALL_USERS);
                return PackageManager.INSTALL_FAILED_THEME_AAPT_ERROR;
            }
        }

        // 以下是關(guān)于 overley 資源的編譯流程贷帮,暫不記錄
        ....
        return 0;
    }

這個(gè)方法主要為了創(chuàng)建零時(shí)的' Manifest '文件和 ' hash '文件戚揭,' resources.apk '的打包流程接著往下分析。

private void compileIconPack(Package pkg) throws Exception {
        if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Compile resource table for " + pkg.packageName);
        OutputStream out = null;
        DataOutputStream dataOut = null;
        try {
            //創(chuàng)建臨時(shí) AndroidManifest.xml 文件
            createTempManifest(pkg.packageName);
            //創(chuàng)建"icon/hash"文件
            int code = pkg.mVersionCode;
            String hashFile = ThemeUtils.getIconHashFile(pkg.packageName);
            out = new FileOutputStream(hashFile);
            dataOut = new DataOutputStream(out);
            dataOut.writeInt(code);
            //準(zhǔn)備編譯 resources.apk
            compileIconsWithAapt(pkg);
        } finally {
            IoUtils.closeQuietly(out);
            IoUtils.closeQuietly(dataOut);
            cleanupTempManifest();
        }
    }

? ?準(zhǔn)備使用aapt命令打包資源撵枢,為了能夠適配多主題民晒,CM修改了aapt工具的部分流程,使之適應(yīng)主題索引包的編譯锄禽。工具打包時(shí)潜必,上層傳入的參數(shù)主要有四個(gè):

  • 主題資源包路徑(pkg.baseCodePath): /data/app/com.wsdeveloper.galaxys7-1/base.apk
  • 索引包路徑(resPath):/data/resource-cache/com.wsdeveloper.galaxys7/icons
  • 資源前綴(APK_PATH_TO_ICONS): assets/icons/
  • 圖標(biāo)資源包 package id(Resources.THEME_ICON_PKG_ID) : 98

? ?描述一下,在查找主題資源時(shí)沟绪,' package id ' 用于定位索引包位置刮便;索引包路徑用于獲取單一資源的' 相對路徑 '(這里只討論圖片資源);此時(shí)資源管理器會(huì)通過 ' 主題資源包路徑 ' 來解壓' base.apk ',并通過' 資源前綴+相對路徑 '得到資源在' base.apk '中的絕對位置绽慈,得到包中的所有文件資源恨旱,從而獲取到資源返回給上層的Resources

private void compileIconsWithAapt(Package pkg) throws Exception {
        String resPath = ThemeUtils.getIconPackDir(pkg.packageName);
        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
        try {
            if (mInstaller.aapt(pkg.baseCodePath, APK_PATH_TO_ICONS, resPath, sharedGid,
                    Resources.THEME_ICON_PKG_ID,
                    pkg.applicationInfo.targetSdkVersion,
                    "", "") != 0) {
                throw new AaptException("Failed to run aapt");
            }
        } catch (InstallerException ignored) {
        }
    }
step 16 - 20:

先列出這部分所有類的位置 :

  • source\frameworks\base\services\core\java\com\android\server\pm\Installer.java
  • source\frameworks\base\core\java\com\android\internal\os\InstallerConnection.java
  • source\frameworks\native\cmds\installd\installd.cpp

? ?具體代碼不全貼了坝疼,都是邏輯上的處理搜贤,簡要概括下:Installer 重組 aapt 參數(shù)接著傳給 InstallerConnection 來繼續(xù)工作,而InstallerConnection負(fù)責(zé)與 installd服務(wù)進(jìn)程 進(jìn)行通訊钝凶,將aapt命令跨進(jìn)程傳送 installd 服務(wù)仪芒,由native層來執(zhí)行命令。可以通過下面大神的博客詳細(xì)了解一下native層的installd服務(wù)進(jìn)程:
? ?http://blog.csdn.net/yangwen123/article/details/11104397

? ?installd服務(wù)進(jìn)程的啟動(dòng)時(shí)在Android啟動(dòng)腳本' init.rc '中通過服務(wù)配置的掂名,而PMS是通過套接字的方式訪問 installd服務(wù)据沈,在以下代碼中可以了解大概流程,如果沒有連接則先嘗試連接socket饺蔑,再將aapt指令通過socket發(fā)送給installd服務(wù)锌介。

  public synchronized String transact(String cmd) {
        ...
        //嘗試連接 installd 服務(wù)的socket,
        if (!connect()) {
            return "-1";
        }
        //發(fā)送cmd指令
        if (!writeCommand(cmd)) {
            if (!connect() || !writeCommand(cmd)) {
                return "-1";
            }
        }
        ...
  }

此時(shí)接受到來自PMS傳來的指令猾警,接下來開始執(zhí)行打包指令孔祸。

static int installd_main(const int argc ATTRIBUTE_UNUSED, char *argv[]) {
    ...
    //自installd服務(wù)啟動(dòng)后,會(huì)一直等待來自PMS的 socket 數(shù)據(jù)流
    for (;;) {
        alen = sizeof(addr);
        s = accept(lsocket, &addr, &alen);
        for (;;) {
            ...
            //buf 將裝載 aapt 指令每個(gè)參數(shù)
            if (execute(s, buf)) break;
        }
    }
}
step 21 - 25:

先列出這部分所有類的位置 :

  • source\frameworks\base\services\core\java\com\android\server\pm\Installer.java
  • source\frameworks\native\cmds\installd\commands.cpp

這邊通過打印可以查看指令字串:
aapt /data/app/com.wsdeveloper.galaxys7-1/base.apk assets/icons/ /data/resource-cache/com.wsdeveloper.galaxys7/icons 50089 98 0

? ?下面代碼中將執(zhí)行 cmdsinfo 中對應(yīng)的函數(shù)发皿,arg 數(shù)組保存在所有參數(shù)的地址崔慧,接下來看下cmdsinfo 數(shù)組中的各個(gè)位對應(yīng)的函數(shù)。

/* Tokenize the command buffer, locate a matching command,
 * ensure that the required number of arguments are provided,
 * call the function(), return the result.
 */
static int execute(int s, char cmd[BUFFER_MAX])
{
    ...
    for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) {
        if (!strcmp(cmds[i].name,arg[0])) {
            if (n != cmds[i].numargs) {
                ALOGE("%s requires %d arguments (%d given)\n",
                     cmds[i].name, cmds[i].numargs, n);
            } else {
                //ALOGE("wangjian func arg + 1 :%d reply :%s "(arg + 1),reply.string());
                ret = cmds[i].func(arg + 1, reply);
            }
            goto done;
        }
}

? ?多主題功能在 cmds 中添加了兩個(gè)對應(yīng)參數(shù) aaptaapt_with_common穴墅,也就是說剛才輸入的參數(shù)指令惶室,會(huì)調(diào)用 cmds[19] 中對應(yīng)的 do_aapt 函數(shù)。在do_aapt函數(shù)也是做了很多判斷和過濾封救,主要功能代碼在run_aapt函數(shù)中拇涤。

struct cmdinfo cmds[] = {
   
    ...
    { "aapt",                 7, do_aapt },
    { "aapt_with_common",     8, do_aapt_with_common },
    ...
};

static int do_aapt(char **arg, char reply[REPLY_MAX] __unused)
{
    return aapt(arg[0], arg[1], arg[2], atoi(arg[3]), atoi(arg[4]), atoi(arg[5]), arg[6], "");
}

函數(shù)中考慮情況太多,我只貼出用到的主干部分代碼誉结,這邊打印出各個(gè)參數(shù)的值:

  • source_apk : /data/app/com.wsdeveloper.galaxys7-1/base.apk
  • internal_path : assets/icons/
  • out_restable : /data/resource-cache/com.wsdeveloper.galaxys7/icons
  • uid : 50089
  • pkgId : 98
  • min_sdk_version : 0
  • app_res_path : null
  • common_res_path : null

? ?此時(shí) app_res_pathcommon_res_path 值是為空的,因此下面函數(shù)中不走的代碼都注釋掉了券躁。最后執(zhí)行了 execl 函數(shù)惩坑,定義在 unistd.h 頭文件。從代碼中得知也拜,最終會(huì)通過 execl 函數(shù)來啟動(dòng) aapt tools 工具以舒,具體 aapt tools 功能入口的代碼在源碼中的位置 : ' /frameworks/base/tools/aapt/Main.cpp '。

static void run_aapt(const char *source_apk, const char *internal_path,
                     int resapk_fd, int pkgId, int min_sdk_version,
                     const char *app_res_path, const char *common_res_path)
{
    static const char *AAPT_BIN = "/system/bin/aapt";
    ...
    static const size_t MAX_INT_LEN = 32;
    char resapk_str[MAX_INT_LEN];
    char pkgId_str[MAX_INT_LEN];
    char minSdkVersion_str[MAX_INT_LEN];

    bool hasCommonResources = (common_res_path != NULL && common_res_path[0] != '\0');
    bool hasAppResources = (app_res_path != NULL && app_res_path[0] != '\0');

    if (hasCommonResources) {
        ...
    } else {
        // 執(zhí)行"/system/bin/"目錄下的aapt工具, 其功能代碼在framework/base/tools/aapt/下
        execl(AAPT_BIN, AAPT_BIN, "package",
                          "--min-sdk-version", minSdkVersion_str,
                          "-M", MANIFEST,
                          "-S", source_apk,
                          "-X", internal_path,
                          "-I", FRAMEWORK_RES,
                          "-r", resapk_str,
                          "-x", pkgId_str,
                          "-f",
                          hasAppResources ? "-I" : (char*)NULL,
                          hasAppResources ? app_res_path : (char*) NULL,
                          (char*)NULL);
    }
    ALOGE("execl(%s) failed: %s\n", AAPT_BIN, strerror(errno));
}
step 26 - 28:

先列出這部分所有類的位置 :

  • source\frameworks\base\tools\aapt\Main.cpp

? ?從這部分開始慢哈,主題的打包工作正式交由 aapt tools工具來執(zhí)行蔓钟。首先由入口函數(shù) Main() 將打包的一系列參數(shù)全都封裝進(jìn)Bundle中,結(jié)合 (step 21 - 25 )代碼可以知道每個(gè)命令對應(yīng)參數(shù)代表的意思卵贱。最后由 handleCommand()函數(shù)來分配任務(wù)滥沫。

/*
 * Parse args.
 */
int main(int argc, char* const argv[])
{
    char *prog = argv[0];
    Bundle bundle;
    ...
    /* 設(shè)置默認(rèn)的壓縮方式 */
    bundle.setCompressionMethod(ZipEntry::kCompressDeflated);
    ...
    if (argv[1][0] == 'v')
        ...
    // package 對應(yīng)功能是打包資源的操作,kCommandPackage 對應(yīng) doPackage 函數(shù)
    else if (argv[1][0] == 'p') 
        bundle.setCommand(kCommandPackage);
    else {
        goto bail;
    }
    /*
     * Pull out flags.  We support "-fv" and "-f -v".
     */
    while (argc && argv[0][0] == '-') {
        /* flag(s) found */
        const char* cp = argv[0] +1;

        while (*cp != '\0') {
            switch (*cp) {
            case '-':
                if (strcmp(cp, "-debug-mode") == 0) {
                    ...
                } else if (strcmp(cp, "-min-sdk-version") == 0) {
                    ...
                    bundle.setMinSdkVersion(argv[0]); //設(shè)置最小sdk版本
                }
                break;
            case 'M':
                ...
                bundle.setAndroidManifestFile(argv[0]); 
                break;
            case 'S':
                ...
                bundle.addResourceSourceDir(argv[0]); // 設(shè)置主題包資源 apk 的絕對路徑
                break;
            case 'X':
                ...
                bundle.setInternalZipPath(argv[0]); // 設(shè)置主題查找路徑前綴
                break;
            case 'I':
                bundle.addPackageInclude(argv[0]);// 設(shè)置原生資源包“framework-res.apk”的絕對路徑
                break;
            case 'r':
                ...
                bundle.setOutputResApk(argv[0]);// 設(shè)置主題“索引包”的絕對路徑
                break;
            case 'x':
                ...
                bundle.setExtendedPackageId(atoi(argv[0]));//設(shè)置主題包“索引包”的 package ID
                break;
          default:
                goto bail;
       }
    ...
    bundle.setFileSpec(argv, argc);
    result = handleCommand(&bundle);
    return result;
}

/*
 * Dispatch the command.
 */
int handleCommand(Bundle* bundle)
{
    switch (bundle->getCommand()) {
    ...
    case kCommandPackage:      return doPackage(bundle);
    ...
    default:
        return 1;
    }
}

三键俱、aapt 工具打包

step 29 - 72:

先列出這部分所有類的位置 :

  • source\frameworks\base\tools\aapt\Command.cpp
  • source\frameworks\base\tools\aapt\AaptAssets.cpp
  • source\frameworks\base\tools\aapt\AaptConfig.cpp
  • source\frameworks\base\tools\aapt\Resource.cpp
  • source\frameworks\base\tools\aapt\ResourceTable.cpp
  • source\frameworks\base\tools\aapt\ApkBuilder.cpp
  • source\frameworks\base\tools\aapt\Package.cpp
  • source\frameworks\base\tools\aapt\ZipEntry.cpp
  • source\frameworks\base\tools\aapt\OutputSet.cpp
  • source\frameworks\base\tools\aapt\ZipEntry.cpp
  • source\frameworks\base\tools\aapt\StringPool.cpp
  • source\frameworks\base\libs\androidfw\AssetManager.cpp

? ?前面所有的步驟都是在為這部分服務(wù)兰绣,用一句話概括下面要分析函數(shù)就是:打包索引apk。但是過程相當(dāng)復(fù)雜编振,關(guān)鍵代碼都需要分將近40個(gè)步驟來記錄缀辩。
? ?結(jié)合上部分代碼可以了解到 Bundle 中保存的變量有哪些,也可以查看下圖(紅框框起來的參數(shù)不需要考慮)。即將要分析的是這些變量在打包過程中的作用臀玄,整個(gè)打包的流程全部都在函數(shù) doPackage 中完成的瓢阴。

圖5 Bundle 儲(chǔ)存的變量

? ?在分析 doPackage 詳細(xì)工作之前需要了解一下 索引apk 中有那些文件需要打包。從' 圖6 ' 可以看出 apk 中存在三個(gè)資源文件健无,' AndroidManifest.xml ' 和 ' appfilter.xml ' 文件最終會(huì)以二進(jìn)制方式打包進(jìn)apk中炫掐,現(xiàn)在只分析 ' resources.arsc '的生成過程,' resources.arsc '可以理解為資源索引表睬涧,其表結(jié)構(gòu)的描述可以提前看下下面大神的博客募胃,具體生成過程還得看流程:
? ?http://www.reibang.com/p/3cc131db2002

圖 6

先整體看下這個(gè)函數(shù)的主干部分:

/*
 * Package up an asset directory and associated application files.
 */
int doPackage(Bundle* bundle)
{
    ...
    sp<AaptAssets> assets;
    ...
    sp<ApkBuilder> builder;
    ...
    //實(shí)例化出 AaptAssets 對象,用來收集“資源apk”中所有的資源
    assets = new AaptAssets();

    //通過給定的參數(shù)畦浓,開始收集資源
    err = assets->slurpFromArgs(bundle);

    //在解析過程中發(fā)生問題導(dǎo)致收集有誤痹束,就立即返回
    if (err < 0) {
        goto bail;
    }

    ...

    //創(chuàng)建Apkbuilder 對象,用來將已經(jīng)收集完畢的資源信息打包讶请,編譯出最終apk文件祷嘶。
    builder = new ApkBuilder(configFilter);

    //分包處理,這邊不需要分析
    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)) {
                goto bail;
            }

            err = builder->createSplitForConfigs(configs);
            if (err != NO_ERROR) {
                goto bail;
            }
        }
    }

    // 編譯收集到的資源夺溢,此時(shí) assets 中保存了所有資源的基本信息
    if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
        err = buildResources(bundle, assets, builder);
        if (err != 0) {
            goto bail;
        }
    }

    // Write the apk
    if (outputAPKFile || bundle->getOutputResApk()) {
        // 將所有收集到的資源添加到Builder中
        err = addResourcesToBuilder(assets, builder);
        if (err != NO_ERROR) {
            goto bail;
        }
    }

    ALOGW("aapt, write apk ------");

    //Write the res apk
    if (bundle->getOutputResApk()) {
        const char* resPath = bundle->getOutputResApk();
        char *endptr;
        int resApk_fd = strtol(resPath, &endptr, 10);

        if (*endptr == '\0') {
            //生成最終apk
            err = writeAPK(bundle, resApk_fd, builder->getBaseSplit(), true);
        } else {

        }

        if (err != NO_ERROR) {
            goto bail;
        }
        ALOGW("aapt, write Res apk OK ");
    }
}

? ?先大體概括一下doPackage 為主題包做了那些工作:
? ?1. 收集資源信息论巍,將主題資源包中的所有資源信息全部打包進(jìn) AaptAssets 中;
? ?2. 編譯資源风响,將所有收集到的資源進(jìn)行編譯嘉汰,并將資源信息賦給 APKBuilder;
? ?3. 由 APKBuilder 來進(jìn)行最終的打包APK工作状勤。
(以上待分析完在更正)

收集資源信息

? ?在編譯資源之前需要先將主題包中所有的資源信息全部收集起來鞋怀,再統(tǒng)一處理。等這段流程走完再回過頭來分析 AaptAsset 對象的結(jié)構(gòu)持搜。圖 7 是 icon 目錄下所有的圖標(biāo)資源密似,接下來就以' aospMusic.png '為例來記錄整個(gè)收集流程。

圖 7 主題包 icon 目錄

? ?這個(gè)函數(shù)的主要工作:先收集' AndroidManifest.xml '文件信息葫盼,里面包含了主題包的包名残腌,在編譯資源時(shí)會(huì)用到。接著從Bundle中取出主題包資源apk路徑并解壓贫导,最終調(diào)用本類的 slurpResourceZip()收集壓縮包中的資源信息抛猫。

ssize_t AaptAssets::slurpFromArgs(Bundle* bundle)
        {
    int count;
    int totalCount = 0;
    FileType type;
    // 主題包資源 apk 的絕對路徑
    const Vector<const char *>& resDirs = bundle->getResourceSourceDirs();
    // 集合中只有一個(gè)資源路徑
    const size_t dirCount =resDirs.size();
    sp<AaptAssets> current = this;
    ...
    /*
     * AndroidManifest.xml 存在的話就先將這個(gè)文件加入到 AaptAsset 中
     */
    if (bundle->getAndroidManifestFile() != NULL) {
        // place at root of zip.
        String8 srcFile(bundle->getAndroidManifestFile());
        addFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir(),
            NULL, String8());
        //記錄收集的資源總數(shù)
        totalCount++;
    }

    /*
     * 由于集合中只存在主題包資源,所有只需要對主題包進(jìn)行資源信息收集即可
     */
    for (size_t i=0; i<dirCount; i++) {
        const char *res = resDirs[i];
        if (res) {
            //定義在 misc.cpp 的函數(shù)脱盲,判斷當(dāng)前文件類型
            type = getFileType(res);
            ...
            if (type == kFileTypeDirectory) {
                ...
            } else if (type == kFileTypeRegular) { //.apk文件為資源文件類型
                ZipFile* zip = new ZipFile;
                //創(chuàng)建ZipFile對象來解壓縮主題包文件
                status_t err = zip->open(String8(res), ZipFile::kOpenReadOnly);
                if (err != NO_ERROR) {
                    delete zip;
                    totalCount = -1;
                    goto bail;
                }
                //核心函數(shù)邑滨,準(zhǔn)備收集主題包內(nèi)的資源信息
                count = current->slurpResourceZip(bundle, zip, res);
                delete zip;
                if (count < 0) {
                    totalCount = count;
                    goto bail;
                }
            } else {
                ...
                return UNKNOWN_ERROR;
            }
        }
    }

? ?解壓出' /data/app/com.wsdeveloper.galaxys7-1/base.apk '下所有的文件,每個(gè)文件對應(yīng)一個(gè) ZipEntry對象钱反,此時(shí) current->slurpResourceZip開始遍歷出主題包中所有的資源文件掖看。從' 圖1 '可以知道匣距,一個(gè)主題包中包含了ROM中需要的所有資源,但是對于' Icon資源 '的編譯打包來說哎壳,是不需要把其他資源也一并打包進(jìn)來的毅待。所以在收集Icon資源時(shí),需要先過濾掉同包中的其他類型資源归榕,單獨(dú)打包' Icon資源 '尸红,看下 addEntry具體如何收集信息。

AaptAssets::slurpResourceZip(Bundle* bundle, ZipFile* zip, const char* fullZipPath)
{
    status_t err = NO_ERROR;
    int count = 0;
    SortedVector<AaptGroupEntry> entries;

    //ZipFile 解壓出主題包中的總文件數(shù)
    const int N = zip->getNumEntries();
    for (int i=0; i<N; i++) {
        ZipEntry* entry = zip->getEntryByIndex(i);
        //過濾掉internalPath以外的文件路徑刹泄,對于打包 Icon 資源外里,這里只解析assets/icon下的資源文件
        if (!isEntryValid(bundle, entry)) {
            continue;
        }
        //entryName 對應(yīng)具體資源文件路徑: "assets/icons/res/drawable-xxxhdpi/aospMusic.png"
        String8 entryName(entry->getFileName());
        //entryLeaf 對應(yīng)具體資源文件名: "aospMusic.png"
        String8 entryLeaf = entryName.getPathLeaf();
        //entryDirFull 對應(yīng)具體資源文件夾路徑: "assets/icons/res/drawable-xxxhdpi"
        String8 entryDirFull = entryName.getPathDir();
        //entryDir 對應(yīng)具體資源文件類型: "drawable-xxxhdpi"
        String8 entryDir = entryDirFull.getPathLeaf();
        //收錄資源
        err = addEntry(entryName, entryLeaf, entryDirFull, entryDir, String8(fullZipPath), 0);
        if (err) continue;

        count++;
    }

    return count;
}

? ?全部的收集過程都在下面的代碼中,該函數(shù)的邏輯可以分幾個(gè)階段來分析:

AaptAssets::addEntry(const String8& entryName, const String8& entryLeaf,
                         const String8& /* entryDirFull */, const String8& entryDir,
                         const String8& zipFile, int compressionMethod)
{
    AaptGroupEntry group;
    String8 resType;
    //通過initFromDirName函數(shù)確定該資源的類型特石,同時(shí)作為AaptFile的成員變量
    bool b = group.initFromDirName(entryDir, &resType);
    if (!b) {
        return -1;
    }
    //先從緩存中查找是否已經(jīng)創(chuàng)建了屬于該資源類型的AaptDir對象盅蝗,沒有就創(chuàng)建并保存在mDir中
    sp<AaptDir> dir = makeDir(resType); //Does lookup as well on mdirs
    //為 aospMusic.png 創(chuàng)建對應(yīng)的 AaptFile 對象
    sp<AaptFile> file = new AaptFile(entryName, group, resType, zipFile);
    file->setCompressionMethod(compressionMethod);
    //先從緩存mFiles中查找是否已經(jīng)創(chuàng)建了屬于該資源的AaptFile對象,存在即通過對應(yīng)的AaptGroup將AaptFile保存
    dir->addLeafFile(entryLeaf, file);
    //將AaptDir添加進(jìn)mResDirs緩存
    sp<AaptDir> rdir = resDir(resType);
    if (rdir == NULL) {
        mResDirs.add(dir);
    }

    return NO_ERROR;
}

第1步,確定資源類型

? ?initFromDirName的工作是為了解析出資源的類型姆蘸,確定 resType 墩莫,并為AaptGroupEntryConfigDescription屬性確定配置信息。
? ?AaptConfig::parse函數(shù)不貼出來了逞敷,具體邏輯就是通過對' -xxxhdpi '的解析來確定ConfigDescription的配置信息 ' density '和' sdkVersion '的值狂秦。

bool AaptGroupEntry::initFromDirName(const char* dir, String8* resType)
{
    //查找字符串dir中首次出現(xiàn)字符'-'的地址,如'drawable-xxxhdpi'返回的為‘-’的地址
    const char* q = strchr(dir, '-');
    size_t typeLen;
    //獲取常規(guī)資源類型,如drawable-xxxhdpi推捐,常規(guī)資源類型為 drawable
    if (q != NULL) {
        typeLen = q - dir;//獲取"drawable"長度
    } else {
        typeLen = strlen(dir);
    }

    String8 type(dir, typeLen);
    //如果不是正常的資源類型裂问,直接pass, resType 為空。
    if (!isValidResourceType(type)) {
        return false;
    }
    if (q != NULL) {
        //通過對"-xxxhdpi"的解析來對 mParams 的屬性賦值 (out->density = ResTable_config::DENSITY_XXXHIGH)
        if (!AaptConfig::parse(String8(q + 1), &mParams)) {
            return false;
        }
    }

    *resType = type;
    return true;
}

bool isValidResourceType(const String8& type)
{
    return type == "anim" || type == "animator" || type == "interpolator"
        || type == "transition"
        || type == "drawable" || type == "layout"
        || type == "values" || type == "xml" || type == "raw"
        || type == "color" || type == "menu" || type == "mipmap";
}

? ?此時(shí)* resType指向' drawable '玖姑,而 AaptGroupEntryConfigDescription屬性信息如下(ConfigDescription 繼承自 ResTable_config):

density(像素密度) sdkVersion mcc(移動(dòng)國家碼 ) mnc(移動(dòng)網(wǎng)絡(luò)碼) ...
ResTable_config::DENSITY_XXXHIGH 4 null null null

第2步愕秫,通過第一步確定的資源類型,創(chuàng)建AaptDir對象焰络,并將 drawable 資源信息添加進(jìn)去

sp<AaptDir> AaptDir::makeDir(const String8& path)
{
    String8 name;
    String8 remain = path;

    sp<AaptDir> subdir = this;
    // 獲取remain所描述文件的根目錄
    // remain = “drawable”,name=“drawable”,remain = “”
    while (name = remain.walkPath(&remain), remain != "") {
        subdir = subdir->makeDir(name);
    }

    ssize_t i = subdir->mDirs.indexOfKey(name);
    if (i >= 0) {
        //如果該path已經(jīng)存在于mDirs中符喝,就直接返回
        return subdir->mDirs.valueAt(i);
    }
    sp<AaptDir> dir = new AaptDir(name, subdir->mPath.appendPathCopy(name));
    //將該path添加到mDirs中闪彼,并返回
    subdir->mDirs.add(name, dir);
    return dir;
}

? ?此時(shí)AaptAsset::AaptDir 中保存了的屬性狀態(tài):

mLeaf (資源目錄名稱) mPath (資源目錄路徑) mFiles(AaptGroup集合) mDirs(AaptDir集合)
drawable drawable size: 0 size: 1

第3步,為 aospMusic.png 創(chuàng)建一個(gè)AaptFile對象

attribute value
mSourceFile (資源全路徑) assets/icons/res/drawable-xxxhdpi/aospMusic.png
mGroupEntry (資源配置信息) density:ResTable_config::DENSITY_XXXHIGH,sdkVersion: 4
mResourceType (資源類型) drawable
mZipFile(資源包路徑) /data/app/com.wsdeveloper.galaxys7-1/base.apk
mData (數(shù)據(jù)源) null
... ...

以上的AaptFile對象目前儲(chǔ)存的屬性狀態(tài)协饲。

第4步畏腕,為' asopMusic.png '資源創(chuàng)建一個(gè)AaptGroup對象,并將AaptFile 加入到AaptGroup.mFile中去茉稠,最終將 AaptGroup與其對應(yīng)的資源名稱' leafName '一同打包進(jìn)AaptDir.mFiles

status_t AaptDir::addLeafFile(const String8& leafName, const sp<AaptFile>& file,
        const bool overwrite, const bool isAssetsDir)
{
    sp<AaptGroup> group;

#ifdef MTK_GMO_ROM_OPTIMIZE
    ...
#else
    UNUSED(isAssetsDir);
#endif
    //此時(shí) mFiles 中是不包含"asopMusic.png"這個(gè)文件名的
    if (mFiles.indexOfKey(leafName) >= 0) {
        group = mFiles.valueFor(leafName);
    } else {
        //為"asopMusic.png" 創(chuàng)建一個(gè) AaptGroup
        group = new AaptGroup(leafName, mPath.appendPathCopy(leafName));
        //將"asopMusic.png"文件名和對應(yīng)的 AaptGroup 添加進(jìn) mFiles
        mFiles.add(leafName, group);
    }
    //將第3步初始化完成的AaptFile 添加到 group 中
    return group->addFile(file, overwrite);
}

? ?AaptGroup::addFile做了兩件事描馅,將自己的屬性mPath值替換成AaptFile.mPath,將AaptFile 與對應(yīng)的AaptGroupEntry添加至mFiles中而线。這幾部分析完在回頭分析一下铭污,這幾個(gè)重要類的結(jié)構(gòu)關(guān)系恋日。

status_t AaptGroup::addFile(const sp<AaptFile>& file, const bool overwriteDuplicate)
{
    //先從 mFiles 查找是否有同配置信息的 AaptGroupEntry
    ssize_t index = mFiles.indexOfKey(file->getGroupEntry());
    if (index >= 0 && overwriteDuplicate) {
        removeFile(index);
        index = -1;
    }

    if (index < 0) {
        //賦值
        file->mPath = mPath;
        //以AaptGroupEntry為key 將 AaptFile 加入到AaptGroup:mFiles中
        mFiles.add(file->getGroupEntry(), file);
        return NO_ERROR;
    }
}

? ?最終,經(jīng)過以上幾步的信息收集嘹狞,關(guān)于' aospMusic.png '的資源信息全部都收集到各個(gè)類中岂膳,最終打包到AaptAsset.mResDirs集合中。通過一個(gè)表格來看下各個(gè)類的狀態(tài):

*AaptGroupEntry

density(像素密度) sdkVersion mcc(移動(dòng)國家碼 ) mnc(移動(dòng)網(wǎng)絡(luò)碼) ...
ResTable_config::DENSITY_XXXHIGH 4 null null null

*AaptFile

attribute value
mPath(資源路徑) drawable/aospMusic.png
mSourceFile (資源全路徑) assets/icons/res/drawable-xxxhdpi/aospMusic.png
mGroupEntry (資源配置信息) *AaptGroupEntry
mResourceType (資源類型) drawable
mZipFile(資源包路徑) /data/app/com.wsdeveloper.galaxys7-1/base.apk
mData (數(shù)據(jù)源) null
... ...

*AaptGroup

attribute value
mLeaf(資源名稱) aospMusic.png
mPath(資源路徑) drawable/aospMusic.png
mFiles (*AaptFile集合) key:*AaptGroupEntry磅网,value: *AaptFile谈截,目前size : 1

*AaptDir

attribute value
mLeaf (資源目錄名稱) drawable
mPath (資源目錄路徑) drawable
mFiles(*AaptGroup集合) key: "aospMusic.png",value: *AaptGroup,目前size : 1
mDirs(AaptDir集合) *AaptDir集合涧偷,目前size : 1

通過上面幾個(gè)表格簸喂,理一下這幾個(gè)類的關(guān)系:

  • AaptAsset 對象表示正在編譯的整個(gè)資源,;
    他的幾個(gè)重要屬性:
    String8 mPackage :表示正在編譯的主題包的包名燎潮;
    Vector<sp<AaptDir> > mResDirs :表示正在編譯的目錄集合,保存所有收集到的 AaptDir喻鳄;
    KeyedVector<String8, sp<ResourceTypeSet> >* mRes:表示正在編譯的資源包的資源類型集合,以資源類型分類來保存跟啤;
  • AaptDir 對象表示正在編譯的資源目錄
    他的幾個(gè)重要屬性:
    String8 mLeaf:表示正在編譯的資源目錄名稱诽表,主題包的話就只有"drawable"這個(gè)目錄;
    String8 mPath:表示正在編譯的資源目錄路徑隅肥;
    DefaultKeyedVector<String8, sp<AaptGroup> > mFiles:表示正在編譯的資源集合竿奏,這里需要強(qiáng)調(diào)一下,這個(gè)集合以資源名為key保存的腥放。以"aospMusic.png"為例泛啸,一個(gè)"aospMusic.png"對應(yīng)著一個(gè)AaptGroup。AaptGroup 在下面解釋秃症。候址;
  • AaptGroup 表示一個(gè)同名資源在不同配置文件夾下的組合;
    他的幾個(gè)重要屬性:
    String8 mLeaf:表示一個(gè)單一的資源名种柑,如"aospMusic.png"岗仑;
    String8 mPath:表示"aospMusic.png"資源的公共路徑;
    DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > mFiles:表示"aospMusic.png"資源在不同配置下對應(yīng)的AaptFile集合聚请,比如手機(jī)為Nexus 6荠雕,那么它的屏幕像素密度為 640dpi,此時(shí)匹配到的AaptGroupEntry.densityResTable_config::DENSITY_XXXHIGH驶赏,對應(yīng)的資源為drawable-xxxhdpi目錄下的"aospMusic.png"炸卑。
  • 一個(gè) AaptFile 對應(yīng)著一個(gè)資源;
    他的幾個(gè)重要屬性:
    String8 mPath:表示資源路徑煤傍;
    AaptGroupEntry mGroupEntry:表示當(dāng)前的資源對應(yīng)的配置信息盖文,上面有說到;
    String8 mResourceType:資源類型蚯姆,"aospMusic.png"為drawable類型五续;
    String8 mSourceFile:表示目前需要編譯的資源在 apk 中的絕對路徑洒敏;
    void* mData:二進(jìn)制數(shù)據(jù);
    size_t mDataSize:數(shù)據(jù)大蟹蹬痢桐玻;
    int mCompression:壓縮方式;
    String8 mZipFile:需要解壓的主題包資源路徑荆萤。

它們的之間的關(guān)系就是:
? ?AaptAsset中保存著所有的AaptDir,AaptDir中以資源目錄來分類(比如layout镊靴、values、還有本次重點(diǎn)分析的drawable)链韭,保存著每個(gè)AaptGroup偏竟,而AaptGroup中又是以資源名來分,分別保存著各個(gè)配置對應(yīng)的AaptFile,此時(shí)AaptFile已經(jīng)為最小單位了敞峭,這樣解釋有點(diǎn)直白了踊谋。

? ?收集流程算是分析完畢思灌,回到slurpResourceZip這個(gè)函數(shù)可以知道初婆,這樣的流程需要循環(huán)執(zhí)行將ZipFile中的所有資源全部收集到AaptAsset中,由于目前只分析了' aospMusic.png '這個(gè)資源文件微驶。圖標(biāo)資源文件加上' appfilter.xml '文件和' AndroidManifest.xml '文件沉迹,如果全部收集完成睦疫,AaptFile對象的數(shù)量應(yīng)該是 38 個(gè)。

編譯資源

? ?此時(shí)AaptAsset基本文件算是收集完成了鞭呕,之前有簡要概括過這一部分的工作蛤育,接下好好看看buildResources函數(shù)是如何編譯這些資源的,先分析流程在總結(jié)葫松。
? ?此函數(shù)需要的三個(gè)參數(shù)分別是:Bundle*瓦糕、AaptAssetsApkBuilder腋么」韭Γ可以從' 圖 5 ' 知道Bundle*中目前儲(chǔ)存著哪些屬性,AaptAssets才分析過珊擂,而ApkBuilder為空谭胚。下面代碼經(jīng)過刪除,只留下主干部分未玻,先大體看一下吧:

status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder)
{
    // slurpFromArgs 函數(shù)開始時(shí)已經(jīng)將"AndroidManifest.xml"收集到 AaptAssets 中。
    sp<AaptGroup> androidManifestFile =
            assets->getFiles().valueFor(String8("AndroidManifest.xml"));
    if (androidManifestFile == NULL) {
        return UNKNOWN_ERROR;
    }
    //解析androidManifest.xml,需要從中解析出 package name
    status_t err = parsePackage(bundle, assets, androidManifestFile);
    if (err != NO_ERROR) {
        return err;
    }
   //為 ResourceTable::PackageType 確定類型
   ResourceTable::PackageType packageType = ResourceTable::App;
    if (bundle->getBuildSharedLibrary()) {
        ...
    } else if (bundle->getExtending()) { //標(biāo)注主題包類型為System類型
        packageType = ResourceTable::System;
    } 
    //可以從step 21 - 25中查到胡控,主題包的 packageId 等于 98
    int extendedPackageId = bundle->getExtendedPackageId();
    // 根據(jù)package信息創(chuàng)建 ResourceTable
    ResourceTable table(bundle, String16(assets->getPackage()), packageType, extendedPackageId);
    //將 framework_res.apk 路徑添加進(jìn) path
    err = table.addIncludedResources(bundle, assets);
    if (err != NO_ERROR) {
        return err;
    }
    // resType -> leafName -> group
    KeyedVector<String8, sp<ResourceTypeSet> > *resources = 
            new KeyedVector<String8, sp<ResourceTypeSet> >;
    //將之前收集的所有信息打包進(jìn) resources 集合
    collect_files(assets, resources);
    //只看 drawables 類型
    sp<ResourceTypeSet> drawables;
    sp<ResourceTypeSet> layouts;
    ...
    ...
    sp<ResourceTypeSet> mipmaps;

    /*將resources中保存的各資源類型的Set拆分扳剿,并保存到drawable 類型的Set中去 */
    ASSIGN_IT(drawable);
    ...
    ASSIGN_IT(mipmap);
    //將已經(jīng)收集的resources賦值給AaptAssets的 mRes 集合變量
    assets->setResources(resources);
    ...
    // 將drawable類型的 ResourceTypeSet 資源整合進(jìn) ResourceType 資源表
    if (drawables != NULL) {
        ...
        if (err == NO_ERROR) {
            err = makeFileResources(bundle, assets, &table, drawables, "drawable");
            if (err != NO_ERROR) {
                hasErrors = true;
            }
        }
    }
    // 將 xml 類型的 ResourceTypeSet 資源整合進(jìn) ResourceType 資源表
    if (xmls != NULL) {
        err = makeFileResources(bundle, assets, &table, xmls, "xml");
        if (err != NO_ERROR) {
            hasErrors = true;
        }
    }
    ...
   // compile resources
   // 編譯values下面的資源信息,這里不討論
    ...

    //為已經(jīng)收集到 resource table 的 Bags 類型資源 分配資源ID昼激,這里只分析 TYPE_ITEM類型資源
    if (table.hasResources()) {
        err = table.assignResourceIds();
        if (err < NO_ERROR) {
            return err;
        }
    }
    ...
    ...
    / --------------------------------------------------------------
    // Generate the final resource table.
    // Re-flatten because we may have added new resource IDs
    // --------------------------------------------------------------
    ResTable finalResTable;
    sp<AaptFile> resFile;
    
    if (table.hasResources()) {
        ...
        //不分包處理庇绽, numSplits 為1
        Vector<sp<ApkSplit> >& splits = builder->getSplits();
        const size_t numSplits = splits.size();

        for (size_t i = 0; i < numSplits; i++) {
            // ApkBuilder 內(nèi)部類 ApkSplit
            sp<ApkSplit>& split = splits.editItemAt(i);
            // 創(chuàng)建一個(gè)名為"resources.arsc"的 AaptFile
            sp<AaptFile> flattenedTable = new AaptFile(String8("resources.arsc"),
                    AaptGroupEntry(), String8());
            //組織一下"resources.arsc"的數(shù)據(jù)表
            err = table.flatten(bundle, split->getResourceFilter(),
                    flattenedTable, split->isBase());
            if (err != NO_ERROR) {
                return err;
            }
            //將"resources.arsc"于對應(yīng)的aaptFile 插入到split :mFiles中去
            split->addEntry(String8("resources.arsc"), flattenedTable);

            if (split->isBase()) {
                resFile = flattenedTable;
                err = finalResTable.add(flattenedTable->getData(), flattenedTable->getSize());
                if (err != NO_ERROR) {
                    fprintf(stderr, "Generated resource table is corrupt.\n");
                    return err;
                }
            } 
        }
    }
    if (resFile != NULL) {
        // 將這個(gè)資源表加入到AssetManager 中供其他模塊應(yīng)用
        err = assets->addIncludedResources(resFile);
        if (err < NO_ERROR) {
            fprintf(stderr, "ERROR: Unable to parse generated resources, aborting.\n");
            return err;
        }
    }
    return err;
 }

? ?這個(gè)函數(shù)真的長锡搜,差點(diǎn)繞暈了,整理完剩下的就是現(xiàn)在需要分析的部分了瞧掺。接下來邏輯代碼分幾個(gè)步驟來分析:

第1步耕餐,獲取包名

? ?從' AndroidManifest,xml '文件中獲取到 package信息,賦值給AaptAsset.mPackage
這下AaptAsset對應(yīng)的包名也確定了辟狈。接著往下面分析吧肠缔,到后面將AaptAsset表整理出來看看保存了什么。

static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets,
    const sp<AaptGroup>& grp)
{
    ...
    //AndroidManifest.xml 文件對應(yīng)的 AaptFile
    sp<AaptFile> file = grp->getFiles().valueAt(0);

    ResXMLTree block;
    //初始化 ResXMLTree 來解析 xml 文件內(nèi)容
    status_t err = parseXMLResource(file, &block);
    if (err != NO_ERROR) {
        return err;
    }

    ResXMLTree::event_code_t code;
    while ((code=block.next()) != ResXMLTree::START_TAG
           && code != ResXMLTree::END_DOCUMENT
           && code != ResXMLTree::BAD_DOCUMENT) {
    }

    size_t len;
    if (code != ResXMLTree::START_TAG) {
        return UNKNOWN_ERROR;
    }
    ...
    //check 解析出的 Attribute 是否有包含 "package" 屬性
    ssize_t nameIndex = block.indexOfAttribute(NULL, "package");
    if (nameIndex < 0) {
        return UNKNOWN_ERROR;
    }
    //將 "package"屬性對應(yīng)的vaule賦值給AaptAsset的 mPackage 屬性
    assets->setPackage(String8(block.getAttributeStringValue(nameIndex, &len)));
}
第2步哼转,初始化資源表明未。

先看一下羅老師博客上畫的圖:


ResourceTable 結(jié)構(gòu)

? ?接下來的代碼邏輯可以結(jié)合羅老師的架構(gòu)圖來看,通過上面核心代碼buildResources()可以知道壹蔓,此時(shí)創(chuàng)建 ResourceTable的四個(gè)參數(shù)已經(jīng)確認(rèn):

  • Bundle* bundle :結(jié)合 圖5 和上面代碼可以知道
  • String16& assetsPackage :com.wsdeveloper.galaxys7
  • ResourceTable::PackageType type :System
  • ssize_t pkgIdOverride :98 ,16進(jìn)制 0x62
/* Add for theme start */
ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type, ssize_t pkgIdOverride)
    : mAssetsPackage(assetsPackage)
    , mPackageType(type)
    , mTypeIdOffset(0)
    , mIsmtkCur(bundle->getmtkCur()), mIsmtkCurNum(bundle->getmtkCurNum()) ///M: add mediatek-res.apk and 3rd-party resource package.
    , mNumLocal(0)
    , mBundle(bundle)
{
    ssize_t packageId = -1;
    //通過 mPackageType 來確定 packageId,但是在編譯主題包的這個(gè)流程上趟妥,pkgIdOverride會(huì)覆蓋原有的packageId。
    switch (mPackageType) {
          ...
    }
    //覆蓋原來的packageId
    if (pkgIdOverride != 0) {
        packageId = pkgIdOverride;
    }
    /* add for theme end */
    //初始化對應(yīng)的 Package 對象佣蓉,并添加進(jìn) mPackages 中披摄。
    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);
    //確定 ResourceTable 的各屬性值
    getType(mAssetsPackage, String16("attr"), unknown);
}

? ?多主題結(jié)構(gòu)有改造ResourceTable的構(gòu)造函數(shù),添加了pkgIdOverride這個(gè)參數(shù)勇凭,默認(rèn)情況下 pkgIdOverride為0疚膊,當(dāng)編譯的包為主題包時(shí),會(huì)覆蓋原先由PackageType確定的packageId套像。緊接著由主題包名和packageId實(shí)例化對應(yīng)的Package對象并保存到mPackagesmOrderedPackages中酿联。

sp<ResourceTable::Type> ResourceTable::getType(const String16& package,
                                               const String16& type,
                                               const SourcePos& sourcePos,
                                               bool doSetIndex)
{
    //取出剛保存的Package對象
    sp<Package> p = getPackage(package);
    if (p == NULL) {
        return NULL;
    }
    //在確定Package對象的各屬性值
    return p->getType(type, sourcePos, doSetIndex);
}

? ?getType()的工作就是通過包名取出剛在保存在mPackage集合中的Package對象,并接著向Package對象中mTypes集合添加新Type夺巩。

sp<ResourceTable::Type> ResourceTable::Package::getType(const String16& type,
                                                        const SourcePos& sourcePos,
                                                        bool doSetIndex)
{
    //此時(shí)mTypes是不包含 "attr "這個(gè)type的
    sp<Type> t = mTypes.valueFor(type);
    if (t == NULL) {
        //sourcePos == unknown
        t = new Type(type, sourcePos);
        //保存名為"attr"的 Type
        mTypes.add(type, t);
        mOrderedTypes.add(t);
        ...
    }
    return t;
}

此時(shí)ResourceTable的初始化已經(jīng)完成了贞让,名為' com.wsdeveloper.galaxys7 '的Package對象已經(jīng)建立,并添加到 ResourceTable.mPackage 集合中柳譬。

第3步喳张,為資源表收集AaptAsset中保存的所有資源信息

? ?初始化一個(gè)resources集合來收集主題包資源信息,逐個(gè)取出保存在AaptAsset.AaptDir的中資源信息美澳,以資源類型來劃分销部,將所有信息全部添加到resources集合中,最終添加到AaptAssetsmRes 集合中制跟,并同步從AaptAsset.mDirs刪除對應(yīng)的AaptDir舅桩。

static void collect_files(const sp<AaptAssets>& ass,
        KeyedVector<String8, sp<ResourceTypeSet> >* resources)
{
    //將前面收集到assets中的各類資源文件重新收集到resources中來
    const Vector<sp<AaptDir> >& dirs = ass->resDirs();
    int N = dirs.size();

    for (int i=0; i<N; i++) {
        sp<AaptDir> d = dirs.itemAt(i);
        collect_files(d, resources);
        ...
        //收集完資源信息后將AaptAsset 中保存的AaptDir對象清除
        ass->removeDir(d->getLeaf());
    }
}

static void collect_files(const sp<AaptDir>& dir,
        KeyedVector<String8, sp<ResourceTypeSet> >* resources)
{
    const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles();
    int N = groups.size();
    for (int i=0; i<N; i++) {
        String8 leafName = groups.keyAt(i);
        //groups 是以文件名為key,AaptGroup為values的集合雨膨,
        const sp<AaptGroup>& group = groups.valueAt(i);
        //files 以AaptGroupEntry(ldpi擂涛、mdpi和hdpi等)為key AaptFile為values的集合
        const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files
                = group->getFiles();

        if (files.size() == 0) {
            continue;
        }
        //收集資源信息時(shí)分析過,此時(shí)AaptFile只有兩種類型 drawable 和 xml
        String8 resType = files.valueAt(0)->getResourceType();
        //檢查是否已經(jīng)添加此資源類型
        ssize_t index = resources->indexOfKey(resType);

        if (index < 0) {
            //初始化 ResourceTypeSet
            sp<ResourceTypeSet> set = new ResourceTypeSet();
            //以文件名為key聊记,AaptGroup為values的形式添加
            set->add(leafName, group);
            //resources 以文件類型為key撒妈,ResourceTypeSet為values的集合
            resources->add(resType, set);
        } else {
            //取出已經(jīng)保存了 drawable 類型的集合
            sp<ResourceTypeSet> set = resources->valueAt(index);
            index = set->indexOfKey(leafName);
            //如果 drawable 類型的集合中不包括"aospMusic.png"恢暖,那就添加新的文件信息
            if (index < 0) {
                set->add(leafName, group);
            } else {
                //如果 drawable 類型的集合中包括"aospMusic.png"的AaptGroup,但是不包括分辨率低的"aospMusic.png"得文件信息狰右,那就添加不同分辨率文件夾下的資源信息杰捂。
                sp<AaptGroup> existingGroup = set->valueAt(index);
                for (size_t j=0; j<files.size(); j++) {
                    existingGroup->addFile(files.valueAt(j));
                }
            }
        }
    }
}

? ?收集完 AaptAsset 保存的資源信息時(shí), KeyedVector<String8, sp<ResourceTypeSet> > *resources中應(yīng)該保存著兩對資源信息棋蚌,分別以' xml '和' drawable '類型來保存嫁佳,最終還是得賦值給AaptAsset.mRes

    //將已經(jīng)收集的resources賦值給AaptAssets的 mRes 集合變量
    assets->setResources(resources);

可以通過表格看下目前mRes中保存著的數(shù)據(jù):

ResourceTypeSet drawable 類型:

key value
aospMusic.png *AaptGroup集合
browser.png *AaptGroup集合
... *AaptGroup集合
weather.png *AaptGroup集合

drawable 類型ResourceTypeSet集合中保存了 36 個(gè)鍵值對。

ResourceTypeSet xml 類型:

key value
appFillter.xml *AaptGroup集合

xml 類型ResourceTypeSet集合中保存了 1 個(gè)鍵值對附鸽。

此時(shí) mRes 為啥沒有保存 AndroidManifest.xml 的信息脱拼?因?yàn)?AndroidManifest.xml 不屬于任何類型的資源文件。

第4步坷备,往資源表中收集 drawable 和 xml 類型的資源信息熄浓。
static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets,
                                  ResourceTable* table,
                                  const sp<ResourceTypeSet>& set,
                                  const char* resType)
{
    String8 type8(resType);
    String16 type16(resType);

    bool hasErrors = false;

    ResourceDirIterator it(set, String8(resType));
    ssize_t res;
    //循環(huán)取出各個(gè)類型的資源信息
    while ((res=it.next()) == NO_ERROR) {
        String16 baseName(it.getBaseName());
        const char16_t* str = baseName.string();
        const char16_t* const end = str + baseName.size();
        while (str < end) {
            if (!((*str >= 'a' && *str <= 'z')
                    || (*str >= '0' && *str <= '9')
                    || *str == '_' || *str == '.')) {
                hasErrors = true;
            }
            str++;
        }
        String8 resPath = it.getPath();
        resPath.convertToResPath();
        /* 將一個(gè)資源文件封裝為一個(gè)Entry添加到ResourceTable中去 */
        table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()),
                        type16,
                        baseName,
                        String16(resPath),
                        NULL,
                        &it.getParams());
        //將重新組織的資源信息添加到 assets 中去
        assets->addResource(it.getLeafName(), resPath, it.getFile(), type8);
    }

    return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR;
}

makeFileResources()的工作可以繼續(xù)分幾步來分析:

1. 創(chuàng)建資源迭代器;
2. 遍歷ResourceTypeSet資源省撑,將資源信息添加到資源迭代器的屬性中赌蔑;

之前有介紹過ResourceTypeSet對象的結(jié)構(gòu),next()函數(shù)主要是將每個(gè)資源信息都解壓出來竟秫,根據(jù)當(dāng)前配置和其他因素來確定當(dāng)前資源的名稱 mLeafName娃惯、配置信息系 mParams、資源路徑 mPath等等肥败。

ssize_t next()
    {
        while (true) {
            sp<AaptGroup> group;
            sp<AaptFile> file;

            // Try to get next file in this current group.
            if (mGroup != NULL && mGroupPos < mGroup->getFiles().size()) {
                group = mGroup;
                file = group->getFiles().valueAt(mGroupPos++);

            // Try to get the next group/file in this directory
            } else if (mSetPos < mSet->size()) {
                mGroup = group = mSet->valueAt(mSetPos++);
                if (group->getFiles().size() < 1) {
                    continue;
                }
                file = group->getFiles().valueAt(0);
                mGroupPos = 1;

            // All done!
            } else {
                return EOD;
            }

            mFile = file;

            String8 leaf(group->getLeaf());
            mLeafName = String8(leaf);
            mParams = file->getGroupEntry().toParams();

            mPath = "res";
            //這一步比較關(guān)鍵趾浅,toDirName函數(shù)很有可能會(huì)改變最終的路徑信息
            mPath.appendPath(file->getGroupEntry().toDirName(mResType));
            mPath.appendPath(leaf);
            // parseResourceName 函數(shù)作用就是將.去掉,取出資源名稱
            mBaseName = parseResourceName(leaf);
            if (mBaseName == "") {
                return UNKNOWN_ERROR;
            }
            return NO_ERROR;
        }
    }

以' aospMusic.png '為例馒稍,看看解析完出的各變量的值:

? ?還記得上面流程由通過解析' drawable-xxxhdpi '來確定 mParams的代碼皿哨,mParamsResTable_config*對象的子類。目前有兩個(gè)屬性' density '和' sdkVersion'纽谒,可以查看*AaptGroupEntry 屬性表格证膨。file->getGroupEntry().toDirName(mResType)最終返回的值是' drawable-xxxhdpi-v4 ',因此 :

  • mPath = res/drawable-xxxhdpi-v4/aospMusic.png
  • mBaseName = aospMusic
String8
AaptGroupEntry::toDirName(const String8& resType) const
{
    String8 s = resType;
    //根據(jù)配置信息確定目錄后綴,如 drawable 目錄最后變成 drawable-xxxhdpi-v4
    String8 params = mParams.toString();
    if (params.length() > 0) {
        if (s.length() > 0) {
            s += "-";
        }
        s += params;
    }
    return s;
}

String8 ResTable_config::toString() const {
    String8 res;
    ...
    if (density != DENSITY_DEFAULT) {
        if (res.size() > 0) res.append("-");
        switch (density) {
            ...
            case ResTable_config::DENSITY_XXXHIGH:
                res.append("xxxhdpi");
                break;
            ...
            default:
                res.appendFormat("%ddpi", dtohs(density));
                break;
        }
    }

? ?AaptGroupEntry::toDirName是個(gè)需要注意的函數(shù)鼓黔,在制作主題包時(shí)央勒,如果在' assets '目錄下創(chuàng)建的圖標(biāo)目錄為' drawable-xxxhdpi '的話,會(huì)出現(xiàn)查找不到資源的問題澳化。所以想要解決這個(gè)問題要么直接將圖標(biāo)目錄設(shè)成' drawable-xxxhdpi-v4 '崔步,要么修改AaptGroupEntry::toDirName函數(shù)的邏輯,不讓加' v4 '后綴缎谷。

3.取出資源迭代器中保存的屬性刷晋,封裝為一個(gè)個(gè)Entry最終添加到ResourceTable中去;
step 53 - 61

可以結(jié)合step 53 - 61圖和羅老師的ResourceType 結(jié)構(gòu)圖來看下面的代碼,

status_t ResourceTable::addEntry(const SourcePos& sourcePos,
                                 const String16& package,
                                 const String16& type,
                                 const String16& name,
                                 const String16& value,
                                 const Vector<StringPool::entry_style_span>* style,
                                 const ResTable_config* params,
                                 const bool doSetIndex,
                                 const int32_t format,
                                 const bool overwrite)
{
    ...

    sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite,
                           params, doSetIndex);
    if (e == NULL) {
        return UNKNOWN_ERROR;
    }
    // 獲取了一個(gè)描述名稱為name資源文件的Entry對象之后,把其相關(guān)信息組織成一個(gè)Item對象然后添加到Entry中眼虱。
    // 此時(shí) Entry 的 mType 屬性會(huì)被設(shè)置為 TYPE_ITEM
    status_t err = e->setItem(sourcePos, value, style, format, overwrite);
    if (err == NO_ERROR) {
        mNumLocal++;
    }
    return err;
}

? ?整個(gè)addEntry函數(shù)就是為了將一個(gè)資源信息打包成Entry對象,類似及AaptFile的作用席纽。與之不同的是捏悬,AaptFileAaptAsset中最小單位,Entry對象并不是ResourceTable中最小的單位润梯,Item對象包含在Entry對象內(nèi)部过牙,表示更具體的資源信息。
? ?而getEntry函數(shù)的工作就是實(shí)現(xiàn)資源具體信息結(jié)構(gòu)的重新組合纺铭,創(chuàng)建與資源對應(yīng)的Entry寇钉,打包到ConfigList對象中,再將ConfigList對象添加到mConfigs 集合舶赔。最終回到addEntry函數(shù)中通過當(dāng)前資源信息組織一個(gè)Item扫倡,設(shè)置到Entry對象中。

sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry,
                                                       const SourcePos& sourcePos,
                                                       const ResTable_config* config,
                                                       bool doSetIndex,
                                                       bool overlay,
                                                       bool autoAddOverlay)
{
    //entry :gome_icon_launcher_appstore
    //sourcePos :res/drawable-xxxhdpi-v4/gome_icon_launcher_appstore.png
    int pos = -1;
    sp<ConfigList> c = mConfigs.valueFor(entry);
    if (c == NULL) {
        c = new ConfigList(entry, sourcePos);
        mConfigs.add(entry, c);
        pos = (int)mOrderedConfigs.size();
        mOrderedConfigs.add(c);
        if (doSetIndex) {
            c->setEntryIndex(pos);
        }
    }
    
    ConfigDescription cdesc;
    if (config) cdesc = *config;
    sp<Entry> e = c->getEntries().valueFor(cdesc);
    if (e == NULL) {
        e = new Entry(entry, sourcePos);
        c->addEntry(cdesc, e);
    }
    
    return e;
}

? ?當(dāng)資源迭代器屬性全部遍歷完竟纳,可以看下各個(gè)ResourceTable對象的成員對象中保存著的數(shù)據(jù):
(表示單個(gè)資源信息的數(shù)據(jù)都以"aospMusic.png"為例)

*Item

attribute value
sourcePos(資源最終位置) res/drawable-xxxhdpi-v4/aospMusic.png
value(資源項(xiàng)的原始值) res/drawable-xxxhdpi-v4/aospMusic.png
isId false
parsedValue null
... ...

*Entry

attribute value
mName(資源名) aospMusic
mType(資源項(xiàng)類型) TYPE_ITEM
mPos(資源最終位置) res/drawable-xxxhdpi-v4/aospMusic.png
mItem(資源項(xiàng)數(shù)據(jù)) *Item
... ...

*ConfigList

attribute value
mName(資源名) aospMusic
mPos(資源最終位置) res/drawable-xxxhdpi-v4/aospMusic.png
mEntries(Entry 集合) 以ConfigDescription 為key*Entry為value的集合
... ...

*Type

attribute value
mName(資源類型名) drawable
mPos(資源類型目錄位置) UNKNOWN
mConfigs(ConfigList集合) 以資源名為key*ConfigList為value的集合
mOrderedConfigs(ConfigList集合) 有序*ConfigList集合
mPublic(Public集合) size : 0
... ...

*Package

attribute value
mName(正在編譯的包名) com.wsdeveloper.galaxys7
mPackageId 0x62
mTypes(Type 集合) 以資源類型名為key*Type為value的集合
mTypeStringsData(AaptFile類型) null
mKeyStringsData(AaptFile類型) null
mTypeStrings(ResStringPool 類型) null
mKeyStrings(ResStringPool 類型) null
... ...

*ResourceTable

attribute value
mAssetsPackage(正在編譯的包名) com.wsdeveloper.galaxys7
mPackageType(資源包類型) System
mAssets(AaptAsset 對象) *AaptAsset
mPackages(Package集合) 以包名為key*Package為value的集合
mOrderedPackages(Package集合) 有序*Package集合
mBundle(aapt 命令參數(shù)對象) *Bundle
... ...

? ?到此為止撵溃,代表主題包的ResourceTable資源表已經(jīng)建成,各個(gè)成員對象的具體含義在這就不一一介紹了锥累,可以從上面的表格了解到每個(gè)對象之間的包含關(guān)系缘挑。最終這幾個(gè)成員對應(yīng)的數(shù)量:

class size value
Package 1 com.wsdeveloper.galaxys7
Type 2 drawable、xml
ConfigList 37 37個(gè)資源 Entry
Entry 37 aospMusic.png桶略、browser.png语淘、... 、weather.png
4.將重新組合起來的資源信息添加到ResourceTableAaptAsset中去际歼。

? ?上述創(chuàng)建ResourceTable資源數(shù)據(jù)表的邏輯分析結(jié)束惶翻,但還是需要回到makeFileResources這個(gè)函數(shù)中,在資源迭代器每遍歷出一個(gè)資源數(shù)據(jù)時(shí)會(huì)向ResourceTable表中添加信息蹬挺,同時(shí)也要向ResourceTableAaptAsset中添加维贺。
? ?在函數(shù)collect_files中有分析過收集完一個(gè)AaptDir時(shí)會(huì)將它從AaptAsset.mDirs中刪除,所以此時(shí)ResourceTableAaptAsset對象中AaptDir集合應(yīng)該是空的巴帮,需要重新添加新的并且正確的資源信息溯泣。看看addResource()函數(shù)中做了什么工作:
(此時(shí)由資源迭代器重組過的數(shù)據(jù)榕茧,以‘a(chǎn)ospMusic.png’為例)

  • leafName :aospMusic.png
  • path:res/drawable-xxxhdpi-v4/aospMusic.png
  • file:還是 aospMusic.png 對應(yīng)的AaptFile
  • resType:drawable
void AaptAssets::addResource(const String8& leafName, const String8& path,
                const sp<AaptFile>& file, const String8& resType)
{
    //創(chuàng)建一個(gè)名為‘res ’的AaptDir對象垃沦,并添加到mDirs中去
    sp<AaptDir> res = AaptDir::makeDir(kResString);
    //為當(dāng)前配置信息匹配最終的dirname
    String8 dirname = file->getGroupEntry().toDirName(resType);
    //再此調(diào)用私有的 makeDir 函數(shù),確定mPath的值
    sp<AaptDir> subdir = res->makeDir(dirname);
    //重新將資源信息封裝承 AaptGroup
    sp<AaptGroup> grr = new AaptGroup(leafName, path);
    //file 的數(shù)據(jù)信息還是不變的用押,將添加到新的 AaptGroup對象中
    grr->addFile(file);
    //將新的 AaptGroup對象添加到新的 AaptDir 中
    subdir->addFile(leafName, grr);
}

? ?記得在分析AaptAsset收集資源數(shù)據(jù)信息時(shí)肢簿,是為'drawable'創(chuàng)建了一個(gè)AaptDir對象,現(xiàn)在需要為'res'目錄創(chuàng)建一個(gè)AaptDir對象,接著通過當(dāng)前配置獲得最終的目錄名:drawable-xxxhdpi-v4池充。
? ?此時(shí)需要注意的是桩引,之前所有創(chuàng)建的臨時(shí)AaptDirpath屬性都是‘drawable’,可以查看之前的AaptDir數(shù)據(jù)表格。而在addResource函數(shù)中為正在編譯的資源確定了正確的AaptDir的各屬性收夸,并重新為資源封裝了新的AaptGroup對象坑匠,看下更新后的數(shù)據(jù)表:

*AaptGroup

attribute value
mLeaf(資源名稱) aospMusic.png
mPath(資源路徑) res/drawable-xxxhdpi-v4/aospMusic.png
mFiles (*AaptFile集合) 與之前不變

*AaptDir

attribute value
mLeaf (資源目錄名稱) res
mPath (資源目錄路徑) res/drawable-xxxhdpi-v4
mFiles(*AaptGroup集合) key: "aospMusic.png",value: *AaptGroup,size : 37
mDirs(AaptDir集合) *AaptDir集合卧惜,size : 2
第5步厘灼,給保存在ResourceTable資源表中的 Bag 資源分配 ' 資源 ID '

? ?BAG資源的定義和ID的分配規(guī)則可以參考最后我貼出的博客鏈接,由于主題包中沒有BAG類型的資源咽瓷,可以忽略设凹。

第6步,生成最終的資源索引表

? ?資源索引表"resources.arsc"是資源查找的基礎(chǔ)茅姜,保存著所有的資源信息闪朱。回顧 buildResources函數(shù)匈睁,當(dāng)所有資源都合入到資源表 ResourceTable時(shí)监透,這時(shí)就需要從資源表 中逐個(gè)取出資源數(shù)據(jù)壓入到名為'resource.arsc'的AaptFile中,最后添加到ApkSplit對象中航唆。

status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder)
{
    ...
    / --------------------------------------------------------------
    // Generate the final resource table.
    // Re-flatten because we may have added new resource IDs
    // --------------------------------------------------------------
    ResTable finalResTable;
    sp<AaptFile> resFile;
    
    if (table.hasResources()) {
        ...
        //不分包處理胀蛮, numSplits 為1
        Vector<sp<ApkSplit> >& splits = builder->getSplits();
        const size_t numSplits = splits.size();

        for (size_t i = 0; i < numSplits; i++) {
            // ApkBuilder 內(nèi)部類 ApkSplit
            sp<ApkSplit>& split = splits.editItemAt(i);
            // 創(chuàng)建一個(gè)名為"resources.arsc"的 空的 AaptFile對象
            sp<AaptFile> flattenedTable = new AaptFile(String8("resources.arsc"),
                    AaptGroupEntry(), String8());
            //組織一下"resources.arsc"的數(shù)據(jù)表數(shù)據(jù)
            err = table.flatten(bundle, split->getResourceFilter(),
                    flattenedTable, split->isBase());
            if (err != NO_ERROR) {
                return err;
            }
            //將"resources.arsc"對應(yīng)的 aaptFile 插入到split :mFiles中去
            split->addEntry(String8("resources.arsc"), flattenedTable);

            if (split->isBase()) {
                resFile = flattenedTable;
                err = finalResTable.add(flattenedTable->getData(), flattenedTable->getSize());
                if (err != NO_ERROR) {
                    return err;
                }
            } 
        }
    }

? ?6.1 組織數(shù)據(jù)表數(shù)據(jù)

? ?flatten函數(shù)會(huì)遍歷資源表 ResourceTable中所有的數(shù)據(jù),遍歷是以從大到小的方式:*Package-->*Type-->ConfigList-->Entry糯钙,每個(gè)正在編譯資源包中以資源類型劃分粪狼,比如 ‘icon 索引包’中只有 'xml' 和 'drawable' 兩種類型的資源,會(huì)先將 'drawable' 類型的所有資源取出并計(jì)算將資源信息依次插入到指定內(nèi)存中任岸。

? ?6.1.1 創(chuàng)建三個(gè)StringPool字符串資源池用來分別保存資源類型(比如:drawable)再榄、資源key(比如:aospMusic)和資源的值(比如:res/drawable-xxxhdpi-v4/aospMusic.png)

    status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
        const sp<AaptFile>& dest,
        const bool isBase)
    {
        const ConfigDescription nullConfig;
        //只有一個(gè)主題包 package,所以 N = 1
        const size_t N = mOrderedPackages.size();
        size_t pi;

        //創(chuàng)建用于保存資源項(xiàng)值的字符串資源池
        StringPool valueStrings(useUTF8);
        //用于保存所有完整數(shù)據(jù)的 Entry
        Vector<sp<Entry> > allEntries;
        for (pi=0; pi<N; pi++) {
            sp<Package> p = mOrderedPackages.itemAt(pi);
            //創(chuàng)建用于保存資源類型的字符串資源池
            StringPool typeStrings(useUTF8);
            //創(chuàng)建用于保存資源名稱的字符串資源池
            StringPool keyStrings(useUTF8);
            ssize_t stringsAdded = 0;
            const size_t N = p->getOrderedTypes().size();
            for (size_t ti=0; ti<N; ti++) {
                sp<Type> t = p->getOrderedTypes().itemAt(ti);

                const String16 typeName(t->getName());
                //獲取 drawable享潜、xml類型的名稱并添加到 typeStrings
                typeStrings.add(typeName, false);
                stringsAdded++;

                String8 configTypeName(typeName);
                if (configTypeName == "drawable" || configTypeName == "layout"
                        || configTypeName == "color" || configTypeName == "anim"
                        || configTypeName == "interpolator" || configTypeName == "animator"
                        || configTypeName == "xml" || configTypeName == "menu"
                        || configTypeName == "mipmap" || configTypeName == "raw") {
                    configTypeName = "1complex";
                } else {
                    configTypeName = "2value";
                }

                const size_t N = t->getOrderedConfigs().size();
                for (size_t ci=0; ci<N; ci++) {
                    sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
                    const size_t N = c->getEntries().size();
                    for (size_t ei=0; ei<N; ei++) {
                        ConfigDescription config = c->getEntries().keyAt(ei);
                        sp<Entry> e = c->getEntries().valueAt(ei);
                        //收集 資源項(xiàng)名稱值 在字符串資源池中的索引位置
                        e->setNameIndex(keyStrings.add(e->getName(), true));
                        ConfigDescription* valueConfig = NULL;
                        if (N != 1 || config == nullConfig) {
                            // 將資源對應(yīng)的 配置信息地址 賦值給 valueConfig
                            valueConfig = &config;
                        }
                        // 將每個(gè)資源的值添加到 valueStrings 中保存
                        status_t err = e->prepareFlatten(&valueStrings, this,
                            &configTypeName, &config);
                        allEntries.add(e);
                    }
                }
            }
            //最終把這些字符串資源池的數(shù)據(jù)合成字符串塊 放入對應(yīng)的package中
            p->setTypeStrings(typeStrings.createStringBlock());
            p->setKeyStrings(keyStrings.createStringBlock());
        }

  //創(chuàng)建package 數(shù)據(jù)塊
  ...
}

? ?代碼流程比較清晰困鸥,先創(chuàng)建一個(gè)保存所有資源項(xiàng)值的字符串資源池valueStringsvalueStrings保存并不是按照package來分類剑按,而是將所有資源統(tǒng)一保存疾就。接著每當(dāng)遍歷一個(gè)package時(shí)就會(huì)創(chuàng)建一個(gè)typeStringskeyStrings,專門來保存每個(gè)package中所有的 '資源類型字符串' 和 'key 字符串'艺蝴,并將字符串保存在對應(yīng)的package中猬腰。

  • StringPool 字符串資源池幾個(gè)重要屬性:
    Vector<entry> mEntries; :為最終保存的字符串的集合;
    Vector<size_t> mEntryArray; :保存著字符串在mEntries中位置的集合猜敢;
    DefaultKeyedVector<String16, ssize_t> mValues;:字符串值與字符串值在 mEntryArray 中的位置的鍵值對姑荷;

  • entry 幾個(gè)重要屬性:
    String16 value :字符串值
    String8 configTypeName; 配置名稱
    Vector<ResTable_config> configs; 具體配置對象

從上面的幾個(gè)屬性可以判斷出資源字符串的添加流程和最終的查找流程盒延,看下字符串資源池的添加流程:

ssize_t StringPool::add(const String16& value,
        bool mergeDuplicates, const String8* configTypeName, const ResTable_config* config)
{
    // mValues保存著一個(gè)字符串的值'value'以及其在 mEntryArray 中的位置值
    ssize_t vidx = mValues.indexOfKey(value);
    ssize_t pos = vidx >= 0 ? mValues.valueAt(vidx) : -1;
    // mEntryArray中保存著某一個(gè)字符串在mEntries中的位置
    ssize_t eidx = pos >= 0 ? mEntryArray.itemAt(pos) : -1;
    if (eidx < 0) {
        // 將 'aospMusic'封裝到 entry 對象中并添加到 mEntries 集合
        eidx = mEntries.add(entry(value));
    }

    if (configTypeName != NULL) {
        entry& ent = mEntries.editItemAt(eidx);
        if (ent.configTypeName.size() <= 0) {
            ent.configTypeName = *configTypeName;
        } else if (ent.configTypeName != *configTypeName) {
            ent.configTypeName = " ";
        }
    }

    if (config != NULL) {
        // Add this to the set of configs associated with the string.
        entry& ent = mEntries.editItemAt(eidx);
        size_t addPos;
        for (addPos=0; addPos<ent.configs.size(); addPos++) {
            int cmp = ent.configs.itemAt(addPos).compareLogical(*config);
            if (cmp >= 0) {
                if (cmp > 0) {
                    ent.configs.insertAt(*config, addPos);
                }
                break;
            }
        }
        if (addPos >= ent.configs.size()) {
            ent.configs.add(*config);
        }
    }

    const bool first = vidx < 0;
    
    const bool styled = (pos >= 0 && (size_t)pos < mEntryStyleArray.size()) ?
        mEntryStyleArray[pos].spans.size() : 0;
    if (first || styled || !mergeDuplicates) {
        // 將 value 對應(yīng)在 mEntries 集合中的位置 添加到 mEntryArray 中
        pos = mEntryArray.add(eidx);
        if (first) {
            // 將 value 與 eidx 在 mEntryArray 中的位置以鍵值對的形式保存在 mValues 集合中
            vidx = mValues.add(value, pos);
        }
        // 
        entry& ent = mEntries.editItemAt(eidx);
        //最后將 entry 對應(yīng)在 mEntryArray的位置pos保存到 entry 的 indices集合中
        ent.indices.add(pos);
    }
    // 返回這個(gè)位置
    return pos;
}

? ?資源類型和資源名稱 字符串資源的添加比較簡潔,在結(jié)構(gòu)體entry保存了這個(gè)字符串的基本名稱鼠冕,并沒有其他的各種配置屬性添寺。而資源項(xiàng)值字符串資源池中需要添加對應(yīng)的配置信息字串和對應(yīng)的ResTable_config*對象。flatten 函數(shù)中具體的添加邏輯就不仔細(xì)記錄了供鸠,代碼比較直觀畦贸。由于packageResourceTable中的數(shù)量只有一個(gè),最后flatten 函數(shù)執(zhí)行完楞捂,三個(gè)字符串資源池的保存狀態(tài)大概如下:

資源類型 StringPool

attribute value
mEntries(entry集合) size :2 ,values:xml 趋厉、drawable

資源名稱 StringPool

attribute value
mEntries(entry集合) size :37 寨闹,entry:value:appfiller 、aospMusic君账、...

資源項(xiàng)值 StringPool

attribute value
mEntries(entry集合) size :37 繁堡,entry:value: res/xml/appfiller .xml 、res/drawable-xxxhdpi-v4/aospMusic.png 乡数、... entry:configTypeName:null椭蹄、xxxhdpi-v4、...

? ?createStringBlock函數(shù)中會(huì)計(jì)算出每個(gè)字符串資源池中的屬性需要分配的內(nèi)存緩沖區(qū)大小净赴,再通過AaptFile:editData方法為其分配空間绳矩,將這些字串通過二進(jìn)制的格式寫入到指定空間中。最終將這些代表著字符串信息的AaptFile保存到對應(yīng)的package:mTypeStringsDatapackage:mKeyStringsData中玖翅,將AaptFile.mDataAaptFile.mDataSize設(shè)置給package:mTypeStringspackage:mKeyStrings翼馆。

此時(shí)的*Package狀態(tài)如下:

attribute value
mName(正在編譯的包名) com.wsdeveloper.galaxys7
mPackageId 0x62
mTypes(Type 集合) 以資源類型名為key*Type為value的集合
mTypeStringsData(AaptFile類型) 資源類型 AaptFile
mKeyStringsData(AaptFile類型) 資源名稱 AaptFile
mTypeStrings(ResStringPool 類型) 資源類型字符串池?cái)?shù)據(jù)
mKeyStrings(ResStringPool 類型) 資源名稱字符串池?cái)?shù)據(jù)
... ...

? ?6.1.2 創(chuàng)建 package 數(shù)據(jù)塊,并將所有資源數(shù)據(jù)填充進(jìn)去

? ?首先為這些package初始化一個(gè)集合金度,用來保存這些裝載 package 數(shù)據(jù)塊的 AaptFile 對象強(qiáng)指針的集合应媚,從上面的ResourceTable表格可以知道這里的ResourceTable.mOrderedPackages的size為1,所以這里的flatPackages最終只保存一個(gè)猜极。
? ?開始為主題包的package 數(shù)據(jù)塊創(chuàng)建一個(gè)AaptFile對象中姜,AaptFile會(huì)為每個(gè)資源塊來分配內(nèi)存緩沖區(qū)空間,每個(gè)資源塊都會(huì)往每段內(nèi)存中填充對應(yīng)的資源數(shù)據(jù)跟伏,組成最終的package 數(shù)據(jù)塊丢胚。通過分析下面的代碼來確定一個(gè)package數(shù)據(jù)塊包含著那些數(shù)據(jù)。

status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
        const sp<AaptFile>& dest,
        const bool isBase)
    {
        // 開始編譯package塊數(shù)組
        Vector<sp<AaptFile>> flatPackages;
        for (pi=0; pi<N; pi++) {
            //取出主題包 package
            sp<Package> p = mOrderedPackages.itemAt(pi);
            //取出剛才組織好的資源類型字符串池?cái)?shù)據(jù)
            const size_t N = p->getTypeStrings().size();
            //初始化 ResTable_package 空間大小
            const size_t baseSize = sizeof(ResTable_package);

            //創(chuàng)建一個(gè)代表著  ResTable_package 的 AaptFile
            sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8());
            //為 ResTable_package 分配緩沖區(qū)空間
            ResTable_package* header = (ResTable_package*)data->editData(baseSize);
            // 初始化這個(gè)頭部的空間
            memset(header, 0, sizeof(*header));
            header->header.type = htods(RES_TABLE_PACKAGE_TYPE);
            header->header.headerSize = htods(sizeof(*header));
            header->id = htodl(static_cast<uint32_t>(p->getAssignedId()));
            strcpy16_htod(header->name, p->getName().string());

            // 寫入資源類型字符串池
            const size_t typeStringsStart = data->getSize();
            sp<AaptFile> strFile = p->getTypeStringsData();
            // 將package中對應(yīng)的資源類型資源池中的數(shù)據(jù) copy 到當(dāng)前 AaptFile 的內(nèi)存中
            ssize_t amt = data->writeData(strFile->getData(), strFile->getSize());
            // 寫入資源名稱字符串池
            const size_t keyStringsStart = data->getSize();
            strFile = p->getKeyStringsData();
            // 將package中對應(yīng)的資源名稱資源池中的數(shù)據(jù) copy 到當(dāng)前 AaptFile 的內(nèi)存中
            amt = data->writeData(strFile->getData(), strFile->getSize());

            strAmt += amt;

            // 在package 內(nèi)部創(chuàng)建一個(gè) 類型規(guī)范數(shù)據(jù)塊
            for (size_t ti=0; ti<N; ti++) {
                size_t len;
                // 獲取類型名稱
                String16 typeName(p->getTypeStrings().stringAt(ti, &len));
                //通過 類型名稱 從 pacakge 類型集合中獲取對應(yīng)的 Type 對象
                sp<Type> t = p->getTypes().valueFor(typeName);
                // N = 37
                const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0;
                //先寫入 typeSpec 塊(類型規(guī)范數(shù)據(jù)塊)頭部酬姆,typeSpec 塊頭部中包含著 Type 對象中每個(gè)資源的entry 信息
                {
                // 先為 typeSpec 塊計(jì)算需要的內(nèi)存大小
                const size_t typeSpecSize = sizeof(ResTable_typeSpec) + sizeof(uint32_t)*N;
                const size_t typeSpecStart = data->getSize();
                    // 由 data 為 typeSpec 塊分配內(nèi)存緩沖區(qū)空間
                    ResTable_typeSpec* tsHeader = (ResTable_typeSpec*)
                    (((uint8_t*)data->editData(typeSpecStart+typeSpecSize)) + typeSpecStart);
                    //初始化 typeSpec 塊內(nèi)存空間
                    memset(tsHeader, 0, sizeof(*tsHeader));
                    tsHeader->header.type = htods(RES_TABLE_TYPE_SPEC_TYPE);
                    tsHeader->header.headerSize = htods(sizeof(*tsHeader));
                    tsHeader->header.size = htodl(typeSpecSize);
                    tsHeader->id = ti+1;
                    tsHeader->entryCount = htodl(N);
                    //初始化一個(gè)為 typeSpecFlags 大小空間的內(nèi)存
                    uint32_t* typeSpecFlags = (uint32_t*)
                    (((uint8_t*)data->editData())
                    + typeSpecStart + sizeof(ResTable_typeSpec));
                    memset(typeSpecFlags, 0, sizeof(uint32_t)*N);

                    for (size_t ei=0; ei<N; ei++) {
                        sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);

                        const size_t CN = cl->getEntries().size();
                        for (size_t ci=0; ci<CN; ci++) {
                            for (size_t cj=ci+1; cj<CN; cj++) {
                                // 用來描述配置差異性
                                typeSpecFlags[ei] |= htodl(
                                        cl->getEntries().keyAt(ci).diff(cl->getEntries().keyAt(cj)));
                            }
                        }
                    }
                }

                SortedVector<ConfigDescription> uniqueConfigs;
                if (t != NULL) {
                    // 將每個(gè)type 中每個(gè) entry 對應(yīng)的 ConfigDescription 按照順序保存在 uniqueConfigs 集合中
                    uniqueConfigs = t->getUniqueConfigs();
                }
                //計(jì)算 所有資源合起來的 類型規(guī)范數(shù)據(jù)塊 總大小
                const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N;

                const size_t NC = uniqueConfigs.size();
                for (size_t ci=0; ci<NC; ci++) {
                    const ConfigDescription& config = uniqueConfigs[ci];
                    //資源類型數(shù)據(jù)塊緊跟著上面的 類型規(guī)范數(shù)據(jù)塊結(jié)尾
                    const size_t typeStart = data->getSize();
                    //先為 類型資源項(xiàng)數(shù)據(jù)塊 分配一個(gè)內(nèi)存緩沖區(qū)
                    ResTable_type* tHeader = (ResTable_type*)
                    (((uint8_t*)data->editData(typeStart+typeSize)) + typeStart);
                    // 為類型資源項(xiàng)數(shù)據(jù)塊初始化內(nèi)存空間
                    memset(tHeader, 0, sizeof(*tHeader));
                    tHeader->header.type = htods(RES_TABLE_TYPE_TYPE);
                    tHeader->header.headerSize = htods(sizeof(*tHeader));
                    tHeader->id = ti+1;
                    tHeader->entryCount = htodl(N);
                    tHeader->entriesStart = htodl(typeSize);
                    tHeader->config = config;
                    tHeader->config.swapHtoD();

                    // 在這個(gè)類型資源項(xiàng)數(shù)據(jù)塊 內(nèi)部創(chuàng)建一個(gè) entries 數(shù)據(jù)塊
                    for (size_t ei=0; ei<N; ei++) {
                        sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
                        sp<Entry> e = NULL;
                        if (cl != NULL) {
                            e = cl->getEntries().valueFor(config);
                        }

                        // 為這個(gè)類型中的每個(gè) entry 設(shè)置一個(gè)偏移位置
                        uint32_t* index = (uint32_t*)
                        (((uint8_t*)data->editData())
                        + typeStart + sizeof(ResTable_type));
                        if (e != NULL) {
                            index[ei] = htodl(data->getSize()-typeStart-typeSize);

                            // 將每個(gè)資源的具體信息(item )寫入到 Res_value 中嗜桌,并將Res_value 的內(nèi)存空間copy 到 data 的結(jié)尾處
                            ssize_t amt = e->flatten(bundle, data, cl->getPublic());

                            validResources.editItemAt(ei) = true;
                        }
                    }

                    // 為 類型資源項(xiàng)數(shù)據(jù)塊 填充下自己占用的內(nèi)存大小信息
                    tHeader = (ResTable_type*)
                    (((uint8_t*)data->editData()) + typeStart);
                    tHeader->header.size = htodl(data->getSize()-typeStart);
                }
                
            }

            // 為 Package資源項(xiàng)元信息數(shù)據(jù)塊頭部 填充資源信息.
            header = (ResTable_package*)data->editData();
            header->header.size = htodl(data->getSize());
            header->typeStrings = htodl(typeStringsStart);
            header->lastPublicType = htodl(p->getTypeStrings().size());
            header->keyStrings = htodl(keyStringsStart);
            header->lastPublicKey = htodl(p->getKeyStrings().size());
            
            flatPackages.add(data);
        }

? ?這個(gè)匿名的AaptFile需要依次為 Package資源項(xiàng)元信息數(shù)據(jù)塊頭部、資源類型字符串池辞色、資源名稱字符串池骨宠、類型規(guī)范數(shù)據(jù)塊以及類型資源項(xiàng)數(shù)據(jù)塊分配內(nèi)存空間緩沖區(qū)浮定,并依次初始化每個(gè)chunk 的內(nèi)存空間,將各個(gè)數(shù)據(jù)信息填充進(jìn)去层亿。每個(gè)chunk 在內(nèi)存中的位置是首尾相連桦卒。組成了最終的package數(shù)據(jù)塊。介紹一下每個(gè)chunk的內(nèi)容:

  • Package資源項(xiàng)元信息數(shù)據(jù)塊頭部(ResTable_package*): 描述整個(gè)package數(shù)據(jù)的基本信息匿又,比如package數(shù)據(jù)塊的大小方灾、字符串資源池的在內(nèi)存中的位置等。為此分配了緩沖區(qū)大新蹈: baseSize
  • 資源類型字符串池(AaptFile): 6.1.1 步中 pacakge 所收集的保存著所有資源類型(ps:drawable)的字符串?dāng)?shù)據(jù)裕偿。為此分配了緩沖區(qū)大小為 資源類型字符串?dāng)?shù)據(jù)的大小
  • 資源名稱字符串池(AaptFile): 6.1.1 步中 pacakge 所收集的保存著所有資源名稱(ps:aospMusic)的字符串?dāng)?shù)據(jù)。為此分配了緩沖區(qū)大小為 資源名稱字符串?dāng)?shù)據(jù)的大小
  • 類型規(guī)范數(shù)據(jù)塊: 描述配置差異性痛单,為此分配了緩沖區(qū)大小為 ResTable_typeSpec*的大小 + 37個(gè)數(shù)組嘿棘,37個(gè)數(shù)組 對應(yīng)著37個(gè)資源配置的差異性,代碼中寫的很詳細(xì)旭绒。
  • 類型資源項(xiàng)數(shù)據(jù)塊: 記錄每個(gè)資源的具體信息(item )鸟妙,e->flatten函數(shù)就是為了將這些資源信息寫入到 Res_value 中,并將Res_value 的內(nèi)存空間copy 到 data的結(jié)尾處挥吵。為此分配了緩沖區(qū)大小為 資源名稱字符串?dāng)?shù)據(jù)的大小:ResTable_type*數(shù)據(jù) + 37 個(gè)Res_value 數(shù)據(jù)的小大

? ?綜合上面創(chuàng)建package數(shù)據(jù)塊的代碼和下面的資源表結(jié)構(gòu)圖中的package部分重父,就更容易理解每個(gè) chunk 的在內(nèi)存中的分布:

圖 8 資源表結(jié)構(gòu)圖

至此,package數(shù)據(jù)塊的創(chuàng)建已經(jīng)完成忽匈。

? ?6.1.2 完成最終的資源索引表

? ?參考圖 8 房午,整個(gè)資源索引表分為三大部分:資源索引表頭部、資源項(xiàng)值字符串資源池和 package數(shù)據(jù)塊脉幢。從下面這段代碼就是整個(gè)所有的部分到資源索引表中歪沃,先寫入資源索引表頭部,接著將?6.1.1 創(chuàng)建的用于保存資源項(xiàng)值的字符串資源池 寫入到資源索引表中的僅次于資源索引表頭部的位置嫌松。最后再將上部分寫好的package數(shù)據(jù)塊 寫入到資源索引表的最后部分沪曙。
? ? 所以分析到這里,終于知道一個(gè)資源索引表中的 package數(shù)據(jù)塊是可以有多個(gè)的萎羔,寫完這個(gè)筆記最后再發(fā)表一下我遇到的問題和想法液走。

status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
        const sp<AaptFile>& dest,
        const bool isBase)
    {
    // dest 是名為'resource.arsc'的AaptFile對象,為最終的資源索引表贾陷,這里先計(jì)算數(shù)據(jù)開始點(diǎn)
    const size_t dataStart = dest->getSize();

    {
        // 初始化資源索引表頭部
        ResTable_header header;
        // 為 資源索引表頭部 初始化內(nèi)存
        memset(&header, 0, sizeof(header));
        header.header.type = htods(RES_TABLE_TYPE);
        header.header.headerSize = htods(sizeof(header));
        header.packageCount = htodl(flatPackages.size());
        // 將頭部信息寫入到內(nèi)存中
        status_t err = dest->writeData(&header, sizeof(header));
    }
    //緊接著 資源索引表頭部 開始
    ssize_t strStart = dest->getSize();
    // 將資源項(xiàng)值寫入到 dest 內(nèi)存中
    status_t err = valueStrings.writeStringBlock(dest);
    if (err != NO_ERROR) {
        return err;
    }
    //循環(huán)所有的 package資源塊缘眶,將每個(gè)chunk寫入到 dest 內(nèi)存中,雖然這里只有一個(gè)package 數(shù)據(jù)塊
    for (pi=0; pi<flatPackages.size(); pi++) {
        err = dest->writeData(flatPackages[pi]->getData(),
                              flatPackages[pi]->getSize());
    }
    //填充 資源索引表頭部 整個(gè)資源索引表占用內(nèi)存總大小
    ResTable_header* header = (ResTable_header*)
        (((uint8_t*)dest->getData()) + dataStart);
    header->header.size = htodl(dest->getSize() - dataStart);

    return NO_ERROR;
}
第7步髓废,將代表資源索引表的 AaptFile對象插入到APK編譯器容器中
status_t ApkSplit::addEntry(const String8& path, const sp<AaptFile>& file) {
    //將"resources.arsc"與對應(yīng)的 AaptFile 打包進(jìn)OutputEntry對象中巷懈,最后插入到 mFiles 容器
    if (!mFiles.insert(OutputEntry(path, file)).second) {
        // Duplicate file.
        return ALREADY_EXISTS;
    }
    return NO_ERROR;
}
生成資源索引包

? ? 回到最初的doPackage函數(shù),當(dāng)所有資源都已經(jīng)編譯完成,接下來就要將這些資源打包成 ' resource.apk' 也就是最終的 ' 資源索引包 '

/*
 * Package up an asset directory and associated application files.
 */
int doPackage(Bundle* bundle)
{
    ...
    ...
    // Write the apk
    if (outputAPKFile || bundle->getOutputResApk()) {
        // 將所有收集到的資源添加到Builder中
        err = addResourcesToBuilder(assets, builder);
    }
    //Write the res apk
    if (bundle->getOutputResApk()) {
        //取出 索引apk的絕對路徑 '/data/resource-cache/com.wsdeveloper.galaxys7/icons'
        const char* resPath = bundle->getOutputResApk();
        char *endptr;
        //將路徑轉(zhuǎn)換成10進(jìn)制的長整型數(shù)
        int resApk_fd = strtol(resPath, &endptr, 10);

        if (*endptr == '\0') {
            //壓縮資源
            err = writeAPK(bundle, resApk_fd, builder->getBaseSplit(), true);
        }
        ALOGW("aapt, write Res apk OK ");
    }
}

? ? 第一步慌洪,將所有收集到的資源添加到ApkBuilder中顶燕。
? ? 第二步凑保,生成最終apk。
? ? ApkBuilder::ApkSplit中的容器已經(jīng)添加了資源索引表AaptFileAaptAsset中所有的資源AaptFile了涌攻,通過資源打包流程圖中的 step 69 - 72欧引,看下writeAPK函數(shù)的打包邏輯:



ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet,
                        bool isOverlay)
{
    ssize_t count = 0;
    // 取出保存在 ApkBuilder 的 OutputEntry 容器捷犹,按之前計(jì)算 OutputEntry 的數(shù)量應(yīng)該是 39 個(gè)
    const std::set<OutputEntry>& entries = outputSet->getEntries();
    std::set<OutputEntry>::const_iterator iter = entries.begin();
    for (; iter != entries.end(); iter++) {
        const OutputEntry& entry = *iter;
        if (entry.getFile() == NULL) {
              ...
        } else {
            // ps :res/drawable-xxxhdpi-v4/aospmusic.png
            String8 storagePath(entry.getPath());
            storagePath.convertToResPath();
            bool ret = isOverlay ? processOverlayFile(bundle, zip, storagePath, entry.getFile())
                                    : processFile(bundle, zip, storagePath, entry.getFile());
            if (!ret) {
                return UNKNOWN_ERROR;
            }
            count++;
        }
    }
    return count;
}

bool processOverlayFile(Bundle* bundle, ZipFile* zip,
                            String8 storageName, const sp<const AaptFile>& file)
{
    const bool hasData = file->hasData();

    storageName.convertToResPath();
    ZipEntry* entry;
    status_t result;
    ...
    if (hasData) {
        const char* name = storageName.string();
        if (endsWith(name, ".9.png") || endsWith(name, ".xml") || endsWith(name, ".arsc")) {
            result = zip->add(file->getData(), file->getSize(), storageName.string(),
                               file->getCompressionMethod(), &entry);
            if (result == NO_ERROR) {
                ...
                entry->setMarked(true);
            } else {
                ...
                return false;
            }
        }
    }

    return true;
}

? ?ZipFile*對象指向的是resApk_fd所代表的索引apk的絕對路徑虑润,最終會(huì)通過ZipFile* :add方法來壓縮保存在AaptFile中的內(nèi)存數(shù)據(jù)。 ApkSplit 繼承自OutputSet寿酌,processAssets函數(shù)中會(huì)循環(huán)取出保存在OutputSet中所有的OutputEntry對象因痛,而OutputEntry對象中封裝著資源的 path 和對應(yīng)的AaptFile婚苹。
? ?回顧之前代碼,通過ApkSplit::addEntry方法添加進(jìn)去的OutputEntry對象總共應(yīng)該是39個(gè):

  • AndroidManifest.xml
  • res/drawable-xxxhdpi-v4/aospmusic.png鸵膏、...租副、res/drawable-xxxhdpi-v4/weather.png
  • res/xml/appfilter.xml
  • resources.arsc

? ?processOverlayFile函數(shù)中會(huì)過濾掉類似于‘res/drawable-xxxhdpi-v4/aospmusic.png’所有的OutputEntry對象,最終需要添加到' resources.apk '壓縮包的文件有' AndroidManifest.xml ' 较性、' res/xml/appfilter.xml ' 和分析到頭疼的 資源索引表' resources.arsc ',最終索引包中的文件見' 圖6 '结胀。

總結(jié):

? ?從安裝主題包到將最終的 主題資源索引apk 輸出到指定目錄下的流程大體上算是分析完了赞咙,還是很多知識點(diǎn)沒有掌握,下次在分析 糟港。
? ?在分析最終生成' resources.arsc '時(shí)了解到攀操,是可以同時(shí)將多個(gè)資源包的資源數(shù)據(jù)打包進(jìn)' resources.arsc
'的。之前在做獨(dú)立資源包時(shí)秸抚,需要編譯出' sdk '中的' android.jar '給上層開發(fā)工具使用速和。但是編譯出的' android.jar '中的' resources.arsc '資源索引表中不包含獨(dú)立資源包中的資源信息,在類似于AS這樣的開發(fā)工具中使用這些資源無法編譯通過剥汤,是否可以專門為 sdk 改下 aapt 的打包流程颠放,讓獨(dú)立資源包和原生包同時(shí)編譯進(jìn)資源索引表呢?如果有哪位大神嘗試過私信我一下吭敢,這個(gè)問題卡了我很久碰凶。

一些大神的博客和學(xué)習(xí)網(wǎng)站的鏈接如下(羅老師的文章看了不下五遍才看懂,看懂了才知道寫的太好了鹿驼,哈哈):

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末欲低,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子畜晰,更是在濱河造成了極大的恐慌砾莱,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凄鼻,死亡現(xiàn)場離奇詭異腊瑟,居然都是意外死亡聚假,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門扫步,熙熙樓的掌柜王于貴愁眉苦臉地迎上來魔策,“玉大人,你說我怎么就攤上這事河胎〈程唬” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵游岳,是天一觀的道長政敢。 經(jīng)常有香客問我,道長胚迫,這世上最難降的妖魔是什么喷户? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮访锻,結(jié)果婚禮上褪尝,老公的妹妹穿的比我還像新娘。我一直安慰自己期犬,他們只是感情好河哑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著龟虎,像睡著了一般璃谨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鲤妥,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天佳吞,我揣著相機(jī)與錄音,去河邊找鬼棉安。 笑死底扳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垂券。 我是一名探鬼主播花盐,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼菇爪!你這毒婦竟也來了算芯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤凳宙,失蹤者是張志新(化名)和其女友劉穎熙揍,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氏涩,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡届囚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年有梆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片意系。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泥耀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蛔添,到底是詐尸還是另有隱情痰催,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布迎瞧,位于F島的核電站夸溶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏凶硅。R本人自食惡果不足惜缝裁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望足绅。 院中可真熱鬧捷绑,春花似錦、人聲如沸氢妈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽允懂。三九已至,卻和暖如春衩匣,著一層夾襖步出監(jiān)牢的瞬間蕾总,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工琅捏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留生百,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓柄延,卻偏偏與公主長得像蚀浆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子搜吧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,162評論 25 707
  • 目錄 AAPT解釋市俊,作用 AAPT基本命令 AAPT編譯資源源碼解析 AAPT打包和系統(tǒng)不一致的資源ID AAPT...
    徐正峰閱讀 84,669評論 7 138
  • Android插件化基礎(chǔ)的主要內(nèi)容包括 Android插件化基礎(chǔ)1-----加載SD上APKAndroid插件化基...
    隔壁老李頭閱讀 7,114評論 13 48
  • 插件化-資源處理 寫的比較長,可以選擇跳過前面2節(jié)滤奈,直接從0x03實(shí)例分析開始摆昧。如有錯(cuò)誤,請不吝指正蜒程。 0x00 ...
    唐一川閱讀 5,338評論 2 22
  • 1.自信來源于準(zhǔn)備充足绅你。譬如害怕上臺演講的人是擔(dān)心自己講砸了伺帘,害怕自己由于緊張而忘詞,或者被臺下聽眾問題難住忌锯,主要...
    冰青玉杰閱讀 209評論 0 1