1. 貨拉拉 Android 動(dòng)態(tài)資源管理系統(tǒng)原理與實(shí)踐(上)

        共 5389字,需瀏覽 11分鐘

         ·

        2022-07-13 18:14

        點(diǎn)擊上方藍(lán)字關(guān)注我,知識(shí)會(huì)給你力量


        ?

        jary,貨拉拉高級(jí)客戶端工程師,目前負(fù)責(zé)貨拉拉App Android端穩(wěn)定性提升,包體積優(yōu)化相關(guān)工作。

        ?
        1. 前言

        • 隨著公司業(yè)務(wù)的擴(kuò)展,貨拉拉用戶端apk包的體積也不斷變大,過去一年,用戶端android組進(jìn)行了大量的瘦身工作,取得了較為顯著的成果。再使用常規(guī)方法,已經(jīng)很難優(yōu)化包體積了。
        • 我們可以把一些使用頻率相對(duì)較低的資源不打包進(jìn)apk,并在需要時(shí)下載到本地(例如動(dòng)畫文件,字體,zip壓縮包,so庫等)
        • 我們注意到,貨拉拉用戶端apk中,使用了35個(gè)以上的so庫,并且都支持arm64-v8a和armeabi-v7a這2種abi,結(jié)果就是so體積成倍上漲。用戶端生產(chǎn)環(huán)境下的apk,解壓縮后,存放so包的lib目錄,占據(jù)了整個(gè)應(yīng)用41%的大小。
        • 因此動(dòng)態(tài)資源管理系統(tǒng)是下一個(gè)優(yōu)化的重點(diǎn),動(dòng)畫,字體和zip包只是普通文件,完全可以支持動(dòng)態(tài)下載并使用。而so文件本質(zhì)上就是一種可動(dòng)態(tài)加載并執(zhí)行的文件,將 so文件動(dòng)態(tài)下發(fā)是切實(shí)可行的,但是要將它從 apk中剔除并保證穩(wěn)定性并不是一件易事。
        1. 行業(yè)方案

        • 未找到現(xiàn)成的github項(xiàng)目或者三方sdk方案,來實(shí)現(xiàn)動(dòng)態(tài)資源管理。
        • 部分博客提供了動(dòng)態(tài)管理so文件的思路,但是缺少完整流程。
        • 行業(yè)目前并未提供完整的成熟方案供我們使用,需要我們自己造輪子。
        1. 功能和方案

        • 實(shí)現(xiàn)功能

        • 資源分類,預(yù)定義了字體,幀動(dòng)畫,so這3種內(nèi)置資源,以及單個(gè)文件,多個(gè)文件這2種可自定義資源。
        • 提供通用的加載動(dòng)態(tài)資源方法,所有資源均可由此加載。
        • 內(nèi)置資源,提供默認(rèn)的應(yīng)用方法,外部可以直接應(yīng)用。自定義資源,用戶自行決定如何應(yīng)用。
        • 對(duì)于所有資源,提供可配置的方便快捷打包方式,減少手動(dòng)操作。
        • 幾個(gè)概念

        • 資源加載:將動(dòng)態(tài)資源通過下載,校驗(yàn),解壓等方式,映射到本地文件的過程。

        該過程對(duì)所有資源通用,sdk使用方無需修改資源加載方式。

        • 資源應(yīng)用:動(dòng)態(tài)資源對(duì)應(yīng)的本地文件應(yīng)用到具體業(yè)務(wù)中。例如動(dòng)態(tài)字體資源的應(yīng)用,就是為TextView設(shè)置一個(gè)新的字體。

        該過程每個(gè)資源不同,sdk使用方無需修改內(nèi)置資源的應(yīng)用方式,對(duì)于自定義資源,需要使用方自行決定應(yīng)用方式。

        • 資源打包:包括生成一個(gè)待上傳的資源文件,以及生成資源的Java描述(DynamicPkgInfo類)。so資源還包含了一些方法的hook操作。

        該過程對(duì)所有資源都適用,統(tǒng)一使用可配置的dynamic_plugin插件完成。sdk使用方無需修改資源打包方式,但是可通過配置dyanmic_plugin.gradle文件,配置打包過程。

        • 通用資源加載

        • 如何確定資源已經(jīng)下載過了,避免重復(fù)下載?

        Java代碼中,使用DynamicPkgInfo類來描述資源,該類中包含了資源的版本號(hào)。我們比較該類和本地?cái)?shù)據(jù)庫中的資源版本號(hào),如果不同,才會(huì)下載資源。

        • 下載資源是否提供多線程下載,斷點(diǎn)續(xù)傳等功能?

        本sdk只提供了下載接口,未提供實(shí)際下載功能,因此如需這些功能,需要調(diào)用者自己實(shí)現(xiàn)。

        • 如何校驗(yàn)資源,防止被篡改?

        DynamicPkgInfo類中包含了資源校驗(yàn)信息,我們利用該類,對(duì)下載好的文件進(jìn)行md5碼,文件長(zhǎng)度,文件名稱的校驗(yàn)。

        • 如何判斷資源是否壓縮包,以及如何解壓縮?

        目前簡(jiǎn)單的采用后綴名是否為.zip判斷,使用使用Java內(nèi)置java.util.zip包下工具解壓。

        • 如何校驗(yàn)解壓后的資源子文件,防止被篡改?

        DynamicPkgInfo同樣包含了zip包中所有子文件的校驗(yàn)信息,我們利用它,來校驗(yàn)所有解壓后的文件。

        資源應(yīng)用

        • 字體資源應(yīng)用,從加載好的本地文件中,創(chuàng)建系統(tǒng)Typeface字體對(duì)象,并設(shè)置到TextView上。
        • 幀動(dòng)畫資源應(yīng)用,從加載好的本地文件中,創(chuàng)建系統(tǒng)AnimationDrawable幀動(dòng)畫對(duì)象,并設(shè)置到ImageView上。
        • 字體和幀動(dòng)畫資源的應(yīng)用流程,見第5章,內(nèi)置資源應(yīng)用流程。
        • so資源應(yīng)用流程,見第7章,so資源加載和應(yīng)用解決方案。
        • 自定義資源的應(yīng)用,需要sdk使用者自己定義。
        • 資源打包

        我們使用dynamic_plugin gradle插件來完成所有資源的打包。

        • 字體資源打包

        • 掃描輸入目錄字體文件,將他們拷貝到輸出目錄。
        • 為每個(gè)字體生成一個(gè)DynamicPkgInfo類的常量,代表該動(dòng)態(tài)資源。
        • 幀動(dòng)畫資源打包

        • 掃描輸入目錄幀動(dòng)畫文件夾,將它們逐個(gè)壓縮,并將壓縮包輸出到指定目錄。
        • 為每一組幀動(dòng)畫生成一個(gè)DynamicPkgInfo類的常量,代表該動(dòng)態(tài)資源。
        • so資源打包

        • Hook系統(tǒng)System.loadLibrary方法的調(diào)用。
        • 系統(tǒng)打包流程中,刪除配置文件指定so文件,并將他們拷貝到指定目錄。
        • 掃描上面的so文件目錄,將他們逐個(gè)壓縮,并將壓縮包輸出到指定目錄。
        • 為每一個(gè)so壓縮包生產(chǎn)一個(gè)DynamicPkgInfo類的常量,代表該動(dòng)態(tài)資源。
        • 自定義資源打包

        • 單個(gè)文件的資源打包同字體資源
        • 多個(gè)文件的資源打包同幀動(dòng)畫資源
        • 運(yùn)行產(chǎn)物

        • 下圖為該打包插件運(yùn)行一次之后的產(chǎn)物。
        • input目錄,所有待打包資源的存放目錄,我們需要手動(dòng)把要打包的資源拷貝這里,例如字體文件拷貝到input/typeface目錄下。注意so資源會(huì)在打包過程中,自動(dòng)生成,無需手動(dòng)處理。
        • output目錄,則是打包出來的產(chǎn)物,包括字體資源,so資源,幀動(dòng)畫資源等,我們可以手動(dòng)將此目錄下的打包后資源上傳到服務(wù)器。
        • DynamicResConst.java文件,該文件中生成了所有資源的信息。
        • DynamicResConst.java文件的內(nèi)容,我們?cè)谶@里也稍微看一下,圖中為字體資源和幀動(dòng)畫資源的java描述??梢钥吹剿袆?dòng)態(tài)資源,都用DynamicPkgInfo類來描述。
        • 單個(gè)文件資源,包含了資源的id,文件名稱,資源類型,下載地址,版本號(hào),文件長(zhǎng)度以及md5碼。
        • 多個(gè)文件資源,除了包含上述信息外。還包含了該壓縮包解壓后,里面每個(gè)文件的名稱,文件長(zhǎng)度以及md5碼
        1. 整體架構(gòu)

        由于整個(gè)系統(tǒng)功能較復(fù)雜,我們將其分為3個(gè)module。

        • lib_dynamic_base:只包含md5,壓縮解壓等通用操作以及代表資源的實(shí)體類DynamicPkgInfo,該module為后面2個(gè)module的基礎(chǔ)。
        • lib_dynamic_res:提供了資源的加載和應(yīng)用功能,目前包含字體資源,幀動(dòng)畫資源,so資源以及自定義資源。
        • dynamic_plugin:為一個(gè)gradle plugin工程,提供了資源打包功能。
        • lib_dynamic_res模塊架構(gòu)

        該庫包括了動(dòng)態(tài)資源加載和應(yīng)用全過程,我們分為5層實(shí)現(xiàn)

        • 外部接口層,主要為加載管理器和加載監(jiān)聽器,提供了所有外部的接口。
        • 資源應(yīng)用層,封裝了幾種內(nèi)置動(dòng)態(tài)資源的應(yīng)用,字體資源,幀動(dòng)畫資源,so資源。
        • 加載流程層,具體完成了資源的加載過程,主要采用狀態(tài)模式實(shí)現(xiàn),包括一個(gè)狀態(tài)管理器,以及各種狀態(tài),例如檢查本地版本狀態(tài),下載狀態(tài),校驗(yàn)文件狀態(tài)等。
        • 接口隔離層,主要是一些功能接口,例如下載功能,解壓縮功能,上報(bào)功能等,隔離了底層實(shí)現(xiàn)。
        • 具體實(shí)現(xiàn)層,各個(gè)具體功能的實(shí)現(xiàn),例如數(shù)據(jù)庫操作,java zip庫等。
        • dynamic_plugin插件架構(gòu)

        • 系統(tǒng)插件層,主要為系統(tǒng)gradle plugin的實(shí)現(xiàn),以及對(duì)dynamic_plugin.gradle配置文件的讀取和解析
        • 任務(wù)模塊層,包含了各個(gè)任務(wù),例如刪除并拷貝so文件任務(wù),壓縮zip包任務(wù)等。
        • 底層實(shí)現(xiàn)層,包含了具體功能的實(shí)現(xiàn),例如asm框架和transform api,zip壓縮,javepoet代碼生成等。
        1. 通用資源加載,內(nèi)置資源應(yīng)用流程

        • 通用資源加載主流程

        加載普通資源的主流程如下,首先判斷資源包指定版本號(hào)和本地?cái)?shù)據(jù)庫版本號(hào)是否相同,如果想同,進(jìn)入本地資源校驗(yàn)流程,否則進(jìn)入下載流程。

        • 下載校驗(yàn)解壓流程

          • 我們首先調(diào)用下載接口下載資源。
          • 如果下載成功,我們校驗(yàn)下載文件,下載失敗,則嘗試刪除文件,并直接跳到失敗結(jié)果。
          • 校驗(yàn)下載文件成功,我們?cè)谂袛嗍欠駷閦ip文件,對(duì)于zip文件,我們執(zhí)行解壓縮操作,非zip文件,直接成功。
          • 解壓縮完成后,我們?cè)趯?duì)解壓后的所有文件執(zhí)行校驗(yàn)操作。
        • 本地資源校驗(yàn)流程

          • 對(duì)于下載并解壓的壓縮包資源,以及本地?cái)?shù)據(jù)庫版本和資源實(shí)體類版本號(hào)相同的資源,我們需要進(jìn)行本地資源校驗(yàn)流程。
          • 遍歷資源包指定的字文件列表,對(duì)他們進(jìn)行逐個(gè)文件檢驗(yàn)就可以了。
        • 單個(gè)文件校驗(yàn)流程

        資源實(shí)體類中指定的文件名稱,文件長(zhǎng)度,文件md5碼和本地文件相同時(shí),我們認(rèn)為該文件校驗(yàn)成功了

        • 加載恢復(fù)流程

        動(dòng)態(tài)資源加載過程中,可能因?yàn)楦鞣N原因,導(dǎo)致加載未能得到成功或者失敗的結(jié)果,而在中間狀態(tài)被中斷,如應(yīng)用進(jìn)程被殺死,手機(jī)關(guān)機(jī)等等。為了避免加載意外中斷的情況下,完全從頭開始進(jìn)行加載,我們?cè)O(shè)計(jì)了一個(gè)動(dòng)態(tài)資源加載的恢復(fù)流程,如果異常中斷,我們下次加載資源時(shí),可以恢復(fù)到當(dāng)前狀態(tài),繼續(xù)進(jìn)行加載。

        • 下載過程的恢復(fù)和斷點(diǎn)續(xù)傳,需要下載接口的實(shí)現(xiàn)者負(fù)責(zé)。
        • 其他狀態(tài),我們?cè)跔顟B(tài)改變時(shí),將資源id,當(dāng)前狀態(tài)和待處理文件路徑,保存到數(shù)據(jù)庫。
        • 每次加載動(dòng)態(tài)開始時(shí),根據(jù)資源id查找數(shù)據(jù)庫中是否有待恢復(fù)數(shù)據(jù)。
        • 有待恢復(fù)數(shù)據(jù),轉(zhuǎn)到待恢復(fù)的狀態(tài),否則,直接去檢查版本號(hào)狀態(tài)。
        • 資源加載成功或者失敗時(shí),從數(shù)據(jù)庫中刪除當(dāng)前資源id對(duì)應(yīng)的恢復(fù)狀態(tài)。
        • 內(nèi)置資源應(yīng)用流程

        前面我們總結(jié)了動(dòng)態(tài)資源的加載流程,資源加載完成后,我們還需要將該資源進(jìn)行應(yīng)用,而這里我們要說的就是將動(dòng)態(tài)資源應(yīng)用到對(duì)應(yīng)View上的流程。

        • 根據(jù)資源id,從緩存中獲取動(dòng)態(tài)資源對(duì)應(yīng)的本地文件。
        • 文件獲取成功,直接設(shè)置到view上,獲取失敗,進(jìn)入下一步。
        • 參數(shù)列表中的占位資源不為空,則將占位資源設(shè)置到View上。
        • 將資源id設(shè)置到View的tag上,嘗試清除上次動(dòng)態(tài)資源加載失敗狀態(tài)。
        • 使用管理器Manager類的load方法,執(zhí)行之前的加載流程。
        • 異步等待加載完成回調(diào),判斷資源id是否和View的tag相同,防止view被復(fù)用,導(dǎo)致的資源錯(cuò)亂情況。
        • 如果Activity沒有被銷毀,則將資源設(shè)置到View上。
        1. lib_dynamic_res模塊類設(shè)計(jì)

        可與第4章,整體架構(gòu)分層圖對(duì)照著看

        • 外部接口層

        DynamicResManager類負(fù)責(zé)和外部交互,提供了初始化(init),加載資源(load),isResReady(判斷資源是否就緒),clearFailState(清除錯(cuò)誤狀態(tài)等方法)等方法。

        Config類,則可以向管理器提供線程池,下載器接口,本地資源信息接口,本地資源狀態(tài)接口等配置信息。

        AbsResInfo抽象類,代表動(dòng)態(tài)資源。

        DynamicPkgInfo類,AbsResInfo的子類,提供給外部使用,代表了一個(gè)動(dòng)態(tài)資源實(shí)體。

        DynamicPkgInfo.FileInfo,AbsResInfo的子類,資源實(shí)體內(nèi)部類,代表了資源中的一個(gè)子文件。

        DynamicPkgInfo.FolderInfo,AbsResInfo的子類,資源實(shí)體內(nèi)部類,代表了資源中的一個(gè)子文件夾。

        ILoadResListener接口,提供了加載資源時(shí)的回調(diào)功能,會(huì)回調(diào)加載成功,失敗,狀態(tài)變化,下載中進(jìn)度

        • 資源應(yīng)用層

        AbsResApply抽象類,實(shí)現(xiàn)了動(dòng)態(tài)資源在ui元素上的應(yīng)用。

        TypefaceResApply類,AbsResApply的子類,代表了字體資源的應(yīng)用。

        FrameAnimApply類,AbsResApply的子類,代表了幀動(dòng)畫資源的應(yīng)用。

        AbsSoLoad抽象類,實(shí)現(xiàn)了so動(dòng)態(tài)資源的應(yīng)用。

        RelinkerSoLoad類,AbsSoLoad的子類,使用Relinker第三方庫最終load so庫。

        SystemSoLoad類,AbsSoLoad的子類,使用系統(tǒng)System.loadLibrary方法最終load so庫

        • 加載流程層

        我們使用狀態(tài)模式來控制整個(gè)動(dòng)態(tài)資源的加載流程。

        IState,狀態(tài)接口,代表了加載流程中的一個(gè)狀態(tài)。

        InitState類,初始化狀態(tài)。

        CheckVersionState類,檢查資源實(shí)體類版本號(hào)與數(shù)據(jù)庫版本號(hào)是否相同狀態(tài)。

        DownloadState類,下載資源狀態(tài)。

        VerrifyFileState,校驗(yàn)下載資源狀態(tài)。

        UnZipState,解壓縮下載資源狀態(tài)。

        VerifyZipState,校驗(yàn)解壓后的所有文件狀態(tài)。

        IStateMechine,狀態(tài)管理機(jī)接口,負(fù)責(zé)管理前面所有的IState對(duì)象。

        DefaultStateMachine類,狀態(tài)管理機(jī)的默認(rèn)實(shí)現(xiàn)。

        ResCtx類,狀態(tài)管理機(jī)運(yùn)行過程中的全局context對(duì)象,存儲(chǔ)了路徑信息,加載成功信息,加載失敗異常等全局信息。

        • 接口隔離和具體實(shí)現(xiàn)層

        這2層的類,較為雜亂,限于篇幅,我們就不一一列舉了。

        • 類uml圖

        ……未完待續(xù)……


        向大家推薦下我的網(wǎng)站 https://xuyisheng.top/  點(diǎn)擊原文一鍵直達(dá)

        專注 Android-Kotlin-Flutter 歡迎大家訪問



        往期推薦


        本文原創(chuàng)公眾號(hào):群英傳,授權(quán)轉(zhuǎn)載請(qǐng)聯(lián)系微信(Tomcat_xu),授權(quán)后,請(qǐng)?jiān)谠瓌?chuàng)發(fā)表24小時(shí)后轉(zhuǎn)載。
        < END >
        作者:徐宜生

        更文不易,點(diǎn)個(gè)“三連”支持一下??


        瀏覽 74
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 激情伊人 | 91美女视频在线观看 | 免费高清无码视频在线观看 | 日本护士三级少妇三级999 | 逼特逼在线视频 |