深入探索 Android 包瘦身(中)

碼個蛋(codeegg) 第 945 次推文

作者:jsonchao鏈接:https://juejin.im/post/5e7ad1c0e51d450edc0cf053

複習上篇:《深入探索 Android 包瘦身(上)》

資源瘦身方案探索

眾所周知,Android構建工具鏈中使用了AAPT/AAPT2工具來對資源進行處理,Manifest、Resources、Assets 的資源經過相應的 ManifesMerger、ResourcesMerger、AssetsMerger 資源合併器將多個不同 moudule 的資源合併為了 MergedManifest、MergedResources、MergedAssets。然後,它們被 AAPT 處理後生成了 R.java、Proguard Configuration、Compiled Resources。如下圖左上方所示:

深入探索 Android 包瘦身(中)

其中 Proguard Configuration、Compiled Resources作用如下所示:

  • Proguard Configuration:這是AAPT工具為Manifest中聲明的四大組件與佈局文件中使用的各種Views所生成的混淆配置,該文件通常存放在<code>${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/proguard-rules/${flavorName}/${buildType}/aapt_rules.txt/<code>。

  • Compiled Resources:它是一個Zip格式的文件,這個文件的路徑通常為<code>${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/res/resources-${flavorName}-${buildType}-stripped.ap_/<code>。在經過zip解壓之後,可以發現它包含了res、AndroidManifest.xml和resources.arsc 這三部分。並且,從上面的APK構建流程中可以得知,Compiled Resources會被apkbuilder打包到APK包中,它其實就是APK資源包。因此,我們可以通過 Compiled Resources 文件來修改不同後綴文件資源的壓縮方式來達到瘦身效果的

    。但是需要注意的是,resources.arsc 文件最好不要壓縮存儲,如果壓縮會影響一定的性能,尤其是在冷啟動時間方面造成的影響。並且,如果在 Android 6.0 上開啟了 android:extractNativeLibs=”false” 的話,So 文件也不能被壓縮


1、冗餘資源優化

1、使用 Lint 的 Remove Unused Resource

APK的資源主要包括圖片、XML,與冗餘代碼一樣,它也可能遺留了很多舊版本當中使用而新版本中不使用的資源,這點在快速開發的App中更可能出現。我們可以通過點擊右鍵,選中Refactor,然後點擊Remove Unused Resource => preview可以預覽找到的無用資源,點擊Do Refactor可以去除冗餘資源。如下圖所示:

深入探索 Android 包瘦身(中)

需要注意的,Android Lint 不會分析 assets 文件夾下的資源,因為 assets 文件可以通過文件名直接訪問,不需要通過具體的引用,Lint 無法判斷資源是否被用到

2、優化 shrinkResources 流程真正去除無用資源

resources.arsc中可能會存在很多無用的資源映射,我們可以使用 android-arscblamer,它是一個命令行工具,能夠解析 resources.arsc 文件並檢查出可以優化的部分,比如一些空的引用。

此外,當我們通過 shrinkResources true開啟資源壓縮,資源壓縮工具只會把無用的資源替換成預定義的版本而不是移除。那麼,如何高效地對無用資源自動進行去除呢?

我們可以 在 Android 構建工具執行 package${flavorName}Task 之前通過修改 Compiled Resources 來實現自動去除無用資源,具體的實現原理如下:

1)、首先,收集 Compiled Resources 中被替換的預定義版本的資源名稱

通過查看 Zip 格式資源包中每個 ZipEntry 的 CRC-32 checksum 來尋找被替換的預定義資源,預定義資源的 CRC-32 定義在 ResourceUsageAnalyze 中,如下所示:

<code>// A 1x1 pixel PNG of type BufferedImage.TYPE_BYTE_GRAY/<code><code>public static final long TINY_PNG_CRC = 0x88b2a3b0L;/<code>
<code>// A 3x3 pixel PNG of type BufferedImage.TYPE_INT_ARGB with 9-patch markers/<code><code>public static final long TINY_9PNG_CRC = 0x1148f987L;/<code>
<code>// The XML document as binary-packed with AAPT/<code><code>public static final long TINY_XML_CRC = 0xd7e65643L;/<code>

2)、然後,使用 android-chunk-utils 把 resources.arsc 中對應的定義移除。

3)、最後,刪除資源包中對應的資源文件即可。

2、重複資源優化

在大型 App項目的開發中,一個App一般會有多個業務團隊進行開發,其中每個業務團隊在資源提交時的資源名稱可能會有重複的,這將會引發資源覆蓋的問題,因此,每個業務團隊都會為自己的資源文件名添加前綴。這樣就導致了這些資源文件雖然內容相同

,但因為名稱的不同而不能被覆蓋,最終都會被集成到APK包中。這裡,我們還是可以在 Android 構建工具執行 package${flavorName}Task 之前通過修改 Compiled Resources 來實現重複資源的去除,具體放入實現原理可細分為如下三個步驟:

  • 1)、首先,通過資源包中的每個ZipEntry的CRC-32 checksum來篩選出重複的資源

  • 2)、然後,通過android-chunk-utils修改resources.arsc,把這些重複的資源都重定向到同一個文件上

  • 3)、最後,把其它重複的資源文件從資源包中刪除,僅保留第一份資源

具體的實現代碼如下所示:

<code>variantData.outputs.each {/<code><code> def apFile = it.packageAndroidArtifactTask.getResourceFile;/<code> 

<code> it.packageAndroidArtifactTask.doFirst {/<code><code> def arscFile = new File(apFile.parentFile, "resources.arsc");/<code><code> JarUtil.extractZipEntry(apFile, "resources.arsc", arscFile);/<code>
<code> def HashMap> duplicatedResources = findDuplicatedResources(apFile);/<code>
<code> removeZipEntry(apFile, "resources.arsc");/<code>
<code> if (arscFile.exists) {/<code><code> FileInputStream arscStream = ;/<code><code> ResourceFile resourceFile = ;/<code><code> try {/<code><code> arscStream = new FileInputStream(arscFile);/<code>
<code> resourceFile = ResourceFile.fromInputStream(arscStream);/<code><code> List chunks = resourceFile.getChunks;/<code>
<code> HashMap toBeReplacedResourceMap = new HashMap(1024);/<code>
<code> // 處理arsc並刪除重複資源/<code><code> Iterator>> iterator = duplicatedResources.entrySet.iterator;/<code><code> while (iterator.hasNext) {/<code><code> Map.Entry> duplicatedEntry = iterator.next;/<code>
<code> // 保留第一個資源,其他資源刪除掉/<code><code> for (def index = 1; index < duplicatedEntry.value.size; ++index) {/<code><code> removeZipEntry(apFile, duplicatedEntry.value.get(index).name);/<code>
<code> toBeReplacedResourceMap.put(duplicatedEntry.value.get(index).name, duplicatedEntry.value.get(0).name);/<code><code> }/<code><code> }/<code>
<code> for (def index = 0; index < chunks.size; ++index) {/<code><code> Chunk chunk = chunks.get(index);/<code><code> if (chunk instanceof ResourceTableChunk) {/<code><code> ResourceTableChunk resourceTableChunk = (ResourceTableChunk) chunk;/<code><code> StringPoolChunk stringPoolChunk = resourceTableChunk.getStringPool;/<code><code> for (def i = 0; i < stringPoolChunk.stringCount; ++i) {/<code><code> def key = stringPoolChunk.getString(i);/<code><code> if (toBeReplacedResourceMap.containsKey(key)) {/<code><code> stringPoolChunk.setString(i, toBeReplacedResourceMap.get(key));/<code><code> }/<code><code> }/<code><code> }/<code><code> }/<code>
<code> } catch (IOException ignore) {/<code><code> } catch (FileNotFoundException ignore) {/<code><code> } finally {/<code><code> if (arscStream != ) {/<code><code> IOUtils.closeQuietly(arscStream);/<code><code> }/<code>
<code> arscFile.delete;/<code><code> arscFile << resourceFile.toByteArray;/<code>
<code> addZipEntry(apFile, arscFile);/<code><code> }/<code><code> }/<code><code> }/<code><code>}/<code>

然後,我們再看看圖片壓縮這一項。

3、圖片壓縮

一般來說,1000行代碼在APK中才會佔用 5kb 的空間,而圖片呢,一般都有100kb左右,所以說,對圖片做壓縮,它的收益明顯是更大的,而往往處於快速開發的App沒有相關的開發規範,UI設計師或開發同學如果忘記了添加圖片時進行壓縮,添加的就是原圖,那麼包體積肯定會增大很多。對於圖片壓縮,我們可以在 tinypng 這個網站進行圖片壓縮,但是如果

App的圖片過多,一個個壓縮也是很麻煩的。因此,我們可以使用 McImage、TinyPngPlugin 或 TinyPIC_Gradle_Plugin 來對圖片進行自動化批量壓縮。但是,需要注意的是,在 Android 的構建流程中,AAPT 會使用內置的壓縮算法來優化 res/drawable/ 目錄下的 PNG 圖片,但這可能會導致本來已經優化過的圖片體積變大,因此,可以通過在build.gradle設置 cruncherEnabled 來禁止 AAPT 來優化 PNG 圖片,代碼如下所示:

<code>aaptOptions {/<code><code> cruncherEnabled = false/<code><code>}/<code>

此外,我們還要注意對圖片格式的選擇,對於我們普遍使用更多的 png或者是jpg格式來說,相同的圖片轉換為webp格式之後會有大幅度的壓縮。對於 png 來說,它是一個無損格式,而 jpg 是有損格式。jpg 在處理顏色圖片很多時候根據壓縮率的不同,它有時候會去掉我們肉眼識別差距比較小的顏色,但是 png 會嚴格地保留所有的色彩。所以說,在圖片尺寸大,或者是色彩鮮豔的時候,

png的體積會明顯地大於jpg

下面,我們就著重講解下如何針對性地選擇圖片格式。

4、使用針對性的圖片格式

Google I/O 2016中,講到了如何選擇相應的圖片格式。首先,如果能用 VectorDrawable 來表示的話,則優先使用 VectorDrawable;否則,看是否支持 WebP,支持則優先用 WebP;如果也不能使用 WebP,則優先使用 PNG,而 PNG 主要用在展示透明或者簡單的圖片,對於其它場景可以使用 JPG 格式。簡單來說可以歸結為如下套路:

<code>VD(純色icon)->WebP(非純色icon)->Png(更好效果) ->jpg(若無alpha通道)/<code>

圖形化的形式如下所示:

深入探索 Android 包瘦身(中)

使用矢量圖片之後,它能夠有效的減少應用中圖片所佔用的大小,矢量圖形在 Android 中表示為 VectorDrawable 對象。它僅僅需100字節的文件即可以生成屏幕大小的清晰圖像,但是,Android 系統渲染每個 VectorDrawable 對象需要大量的時間,而較大的圖像需要更長的時間。因此,建議只有在顯示純色小 icon 時才考慮使用矢量圖形。(我們可以利用這個 在線工具 將矢量圖轉換成 VectorDrawable)。

最後,如果要在項目中使用 VD,則以下幾點需要著重注意:

  • 1)、必須通過 app:arcCompat 屬性來使用 svg,如果通過 src,則在低版本手機上會出現不兼容的問題

  • 2)、可能會不兼容selector,在Activity中手動兼容即可,兼容代碼如下所示:

    <code>static { AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) }/<code>

  • 3)、不兼容第三方庫

  • 4)、性能問題:當Vector比較簡單時,效率肯定比Bitmap高,複雜則效率會不如Bitmap

  • 5)、不便於管理:建議原則為同目錄多類型文件,以前綴區別,不同目錄相同類型文件,以意義區分

VD類似,還有一種矢量圖標iconFont,即字體圖標,圖標就在字體文件裡面,它看著是個圖標,其實卻是個文字。它的優勢有如下三個方面:

  • 1)、同 VD 一樣,由於 IconFont 是矢量圖標,所以可以輕鬆解決圖標適配問題

  • 2)、圖標以 .ttf 字體文件的形式存在項目中,而 .ttf 文件一般放在 assets 文件夾下,它的體積很小,可以減小 APK 的體積

  • 3)、一套圖標資源可以在不同平臺使用且資源維護方便

它的 缺點也很明顯,大致有如下三個方面:

  • 1)、需要自定義 svg 圖片,並將其轉換為 ttf 文件,圖標製作成本比較高

  • 2)、添加圖標時需要重新制作 ttf 文件

  • 3)、只能支持單色,不支持漸變色圖標

如果你想要使用 iconfont,可以在阿里的 iconfont 上尋找資源。此外,使用 Android-Iconics 可以在你的應用中便於使用任何的 iconfont 或 .svg 圖片作為 drawable。最後,如果我們僅僅想提取僅需要的美化文字,以壓縮 assets 下的字體文件大小,可以使用 FontZip 字體提取工具

如果不是純色小 icon類型的圖片,則建議使用WebP。只要你的AppminSdkVersion高於 14(Android 4.0+) 即可。WebP不僅支持透明度,而且壓縮率比JPEG更高,在相同畫質下體積更小。但是,只有 Android 4.2.1+ 才支持顯示含透明度的 WebP,此外,它的兼容性不好,並且不便於預覽,需使用瀏覽器打開

對於應用之前就存在的圖片,我們可以使用 PNG轉換WebP 的轉換工具來進行轉換。但是,一個一個轉換開發效率太低,因此我們可以 使用WebpConvert_Gradle_Plugin 這個 gradle 插件去批量進行轉換,它的實現原理是在 mergeXXXResource Task 和 processXXXResource Task 之間插入了一個 WebpConvertPlugin task 去將 png、jpg 圖片批量替換成了 webp 圖片

此外,在 Gradle構建APK的過程中,我們可以判斷當前AppminSdkVersion以及圖片文件的類型來選用是否能使用WebP,代碼如下所示:

<code>boolean isPNGWebpConvertSupported {/<code><code> if (!isWebpConvertEnable) {/<code><code> return false/<code><code> }/<code>
<code> // Android 4.0+/<code><code> return GradleUtils.getAndroidExtension(project).defaultConfig.minSdkVersion.apiLevel >= 14/<code><code> // 4.0/<code><code>}/<code>
<code>boolean isTransparencyPNGWebpConvertSupported {/<code><code> if (!isWebpConvertEnable) {/<code><code> return false/<code><code> }/<code>
<code> // Lossless, Transparency, Android 4.2.1+/<code><code> return GradleUtils.getAndroidExtension(project).defaultConfig.minSdkVersion.apiLevel >= 18/<code><code> // 4.3/<code><code>}/<code>
<code>def convert {/<code><code> String resPath = "${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/res/merged/${variant.dirName}"/<code><code> def resDir = new File("${resPath}")/<code><code> resDir.eachDirMatch(~/drawable[a-z0-9-]*/) { dir ->/<code><code> FileTree tree = project.fileTree(dir: dir)/<code><code> tree.filter { File file ->/<code><code> return (isJPGWebpConvertSupported && (file.name.endsWith(SdkConstants.DOT_JPG) || file.name.endsWith(SdkConstants.DOT_JPEG))) || (isPNGWebpConvertSupported && file.name.endsWith(SdkConstants.DOT_PNG) && !file.name.endsWith(SdkConstants.DOT_9PNG))/<code><code> }.each { File file ->/<code><code> def shouldConvert = true/<code><code> if (file.name.endsWith(SdkConstants.DOT_PNG)) {/<code><code> if (!isTransparencyPNGWebpConvertSupported) {/<code><code> shouldConvert = !Imaging.getImageInfo(file).isTransparent/<code><code> }/<code><code> }/<code><code> if (shouldConvert) {/<code><code> WebpUtils.encode(project, webpFactorQuality, file.absolutePath, webp)/<code><code> }/<code><code> }/<code><code> }/<code><code>}/<code>

最後,這裡再補充下在平時項目開發中對 圖片放置優化的大概思路,如下所示:

  • 1)、聊天表情出一套圖 => hdpi

  • 2)、純色小 icon 使用 VD => raw

  • 3)、背景大圖出一套 => xhdpi

  • 4)、logo 等權重比較大的圖片出兩套 => hdpi,xhdpi

  • 5)、若某些圖在真機中有異常,則用多套圖

  • 6)、若遇到奇葩機型,則針對性補圖

然後,我們來講解下資源如何進行混淆。

5、資源混淆

同代碼混淆類似,資源混淆將 資源路徑混淆成單個資源的路徑,這裡我們可以使用AndroidResGuard,它可以使冗餘的資源路徑變短,例如將res/drawable/wechat變為r/d/a

AndroidResGuard 項目地址

下面,我們就使用 AndroidResGuard來對資源進行混淆。

1、AndroidResGuard 實戰

1、首先,我們在項目的根 build.gradle 文件下加入下面的插件依賴:

<code>classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.17'/<code>

2、然後,在項目 module 下的 build.gradle 文件下引入其插件:

<code>apply plugin: 'AndResGuard'/<code>

3、接著,加入 AndroidResGuard 的配置項,如下是默認設置好的配置:

<code>andResGuard {/<code><code> // mappingFile = file("./resource_mapping.txt")/<code><code> mappingFile = /<code><code> use7zip = true/<code><code> useSign = true/<code><code> // 打開這個開關,會keep住所有資源的原始路徑,只混淆資源的名字/<code><code> keepRoot = false/<code><code> // 設置這個值,會把arsc name列混淆成相同的名字,減少string常量池的大小/<code><code> fixedResName = "arg"/<code><code> // 打開這個開關會合並所有哈希值相同的資源,但請不要過度依賴這個功能去除去冗餘資源/<code><code> mergeDuplicatedRes = true/<code><code> whiteList = [/<code><code> // for your icon/<code><code> "R.drawable.icon",/<code><code> // for fabric/<code><code> "R.string.com.crashlytics.*",/<code><code> // for google-services/<code><code> "R.string.google_app_id",/<code><code> "R.string.gcm_defaultSenderId",/<code><code> "R.string.default_web_client_id",/<code><code> "R.string.ga_trackingId",/<code><code> "R.string.firebase_database_url",/<code><code> "R.string.google_api_key",/<code><code> "R.string.google_crash_reporting_api_key"/<code><code> ]/<code><code> compressFilePattern = [/<code><code> "*.png",/<code><code> "*.jpg",/<code><code> "*.jpeg",/<code><code> "*.gif",/<code><code> ]/<code><code> sevenzip {/<code><code> artifact = 'com.tencent.mm:SevenZip:1.2.17'/<code><code> //path = "/usr/local/bin/7za"/<code><code> }/<code> 

<code> /**/<code><code> * 可選:如果不設置則會默認覆蓋assemble輸出的apk/<code><code> **//<code><code> // finalApkBackupPath = "${project.rootDir}/final.apk"/<code>
<code> /**/<code><code> * 可選: 指定v1簽名時生成jar文件的摘要算法/<code><code> * 默認值為“SHA-1”/<code><code> **//<code><code> // digestalg = "SHA-256"/<code><code>}/<code>

4、最後,我們點擊右邊的項目 module/Tasks/andresguard/resguardRelease 即可生成資源混淆過的 APK。如下圖所示:

深入探索 Android 包瘦身(中)

APK生成目錄如下:

深入探索 Android 包瘦身(中)

對於 AndResGuard 工具,主要有 兩個功能,一個是資源混淆,一個是資源的極限壓縮。下面,我們就來分別瞭解下它們的實現原理。

2、AndResGuard 的資源混淆原理

資源混淆工具主要是通過 短路徑的優化,以達到減少 resources.arsc、metadata 簽名文件以及 ZIP 文件大小的效果,其效果分別如下所示:

  • 1)、resources.arsc:它記錄了資源文件的名稱與路徑,使用混淆後的短路徑 res/s/a,可以減少文件的大小

  • 2)、metadata 簽名文件:簽名文件 MANIFEST.MF 與 CERT.SF 需要記錄所有文件的路徑以及它們的哈希值,使用短路徑可以減少這兩個文件的大小

  • 3)、ZIP 文件:ZIP 文件格式裡面通過其索引記錄了每個文件 Entry 的路徑、壓縮算法、CRC、文件大小等等信息。短路徑的優化減少了記錄文件路徑的字符串大小

3、AndResGuard 的極限壓縮原理

AndResGuard使用了7-Zip 的大字典優化APK整體壓縮率可以提升 3% 左右,並且,它還支持針對 resources.arsc、PNG、JPG 以及 GIF 等文件進行強制壓縮(在編譯過程中,這些文件默認不會被壓縮)。那麼,為什麼 Android 系統不會去壓縮這些文件呢?主要基於以下兩點原因

  • 1)、壓縮效果不明顯:上述格式的文件大部分已經被壓縮過,因此,重新做

    Zip壓縮效果並不明顯。比如 重新壓縮PNGJPG格式只能減少3%~5%的大小。

  • 2)、基於讀取時間和內存的考慮:針對於沒有進行壓縮的文件,系統可以使用 mmap 的方式直接讀取,而不需要一次性解壓並放在內存中。

此外,抖音 Android 團隊還開源了針對於海外市場 App Bundle APK 的 AabResGuard 資源混淆工具,對它的實現原理有興趣的同學可以去了解下。然後,我們再看看資源瘦身的其它方案。

6、R Field 的內聯優化

我們可以通過內聯 R Field來進一步對代碼進行瘦身,此外,它也解決了 R Field 過多導致 MultiDex 65536 的問題。要想實現內聯R Field,我們需要

通過 Javassist 或者 ASM 字節碼工具在構建流程中內聯 R Field,其代碼如下所示:

<code>ctBehaviors.each { CtBehavior ctBehavior ->/<code><code> if (!ctBehavior.isEmpty) {/<code><code> try {/<code><code> ctBehavior.instrument(new ExprEditor {/<code><code> @Override/<code><code> public void edit(FieldAccess f) {/<code><code> try {/<code><code> def fieldClassName = JavassistUtils.getClassNameFromCtClass(f.getCtClass)/<code><code> if (shouldInlineRField(className, fieldClassName) && f.isReader) {/<code><code> def temp = fieldClassName.substring(fieldClassName.indexOf(ANDROID_RESOURCE_R_FLAG) + ANDROID_RESOURCE_R_FLAG.length)/<code><code> def fieldName = f.fieldName/<code><code> def key = "${temp}.${fieldName}"/<code>
<code> if (resourceSymbols.containsKey(key)) {/<code><code> Object obj = resourceSymbols.get(key)/<code><code> try {/<code><code> if (obj instanceof Integer) {/<code><code> int value = ((Integer) obj).intValue/<code><code> f.replace("\$_=${value};")/<code><code> } else if (obj instanceof Integer[]) {/<code><code> def obj2 = ((Integer[]) obj)/<code><code> StringBuilder stringBuilder = new StringBuilder/<code><code> for (int index = 0; index < obj2.length; ++index) {/<code><code> stringBuilder.append(obj2[index].intValue)/<code><code> if (index != obj2.length - 1) {/<code><code> stringBuilder.append(",")/<code><code> }/<code><code> }/<code><code> f.replace("\$_ = new int{${stringBuilder.toString}};")/<code><code> } else {/<code><code> throw new GradleException("Unknown ResourceSymbols Type!")/<code><code> }/<code><code> } catch (NotFoundException e) {/<code><code> throw new GradleException(e.message)/<code><code> } catch (CannotCompileException e) {/<code><code> throw new GradleException(e.message)/<code><code> }/<code><code> } else {/<code><code> throw new GradleException("******** InlineRFieldTask unprocessed ${className}, ${fieldClassName}, ${f.fieldName}, ${key}")/<code><code> }/<code><code> }/<code><code> } catch (NotFoundException e) {/<code><code> }/<code><code> }/<code><code> })/<code><code> } catch (CannotCompileException e) {/<code><code> }/<code><code> }/<code><code>}/<code>

這裡,我們可以 直接使用蘑菇街的 ThinRPlugin。它的實現原理為:android 中的 R 文件,除了 styleable 類型外,所有字段都是 int 型變量/常量,且在運行期間都不會改變。所以可以在編譯時,記錄 R 中所有字段名稱及對應值,然後利用 ASM 工具遍歷所有 Class,將除 R$styleable.class 以外的所有 R.class 刪除掉,並且在引用的地方替換成對應的常量,從而達到縮減包大小和減少Dex個數的效果。此外,最近ByteX也增加了 shrink_r_class 的gradle插件,它不僅可以在編譯階段對R文件常量進行內聯,而且還可以針對 App 中無用 Resource 和無用 assets 的資源進行檢查

7、資源合併方案

我們可以把所有的資源文件合併成一個大文件,而

一個大資源文件就相當於換膚方案中的一套皮膚。它的效果比資源混淆的效果會更好,但是,在此之前,必須要解決解析資源管理資源的問題。其相應的解決方案如下所示:

  • 模擬系統實現資源文件的解析:我們需要使用自定義的方式把 PNG、JPG 以及 XML 文件轉換為 Bitmap 或者 Drawable

  • 使用 mmap 加載大資源與資源緩存池管理資源:使用 mmap 加載大資源的方式可以充分減少啟動時間與系統內存的佔用。而且,需要使用 Glide 等圖片框架的資源緩存池 ResourceCache 去釋放不再使用的資源文件

8、資源文件最少化配置

我們需要 根據 App 目前所支持的語言版本去選用合適的語言資源,例如使用了

AppCompat,如果不做任何配置的話,最終APK包中會包含AppCompat中所有已翻譯語言字符串,無論應用的其餘部分是否翻譯為同一語言。對此,我們可以通過 resConfig 來配置使用哪些語言,從而讓構建工具移除指定語言之外的所有資源。同理,也可以使用 resConfigs 去配置你應用需要的圖片資源文件類,如 "xhdpi"、"xxhdpi" 等等,代碼如下所示:

<code>android {/<code><code> .../<code><code> defaultConfig {/<code><code> .../<code><code> resConfigs "zh", "zh-rCN"/<code><code> resConfigs "nodpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"/<code><code> }/<code><code> .../<code><code>}/<code>

此外,我們還以 利用 Density Splits 來選擇應用應兼容的屏幕尺寸大小,代碼如下所示:

<code>android {/<code><code> .../<code><code> splits {/<code><code> density {/<code><code> enable true/<code><code> exclude "ldpi", "tvdpi", "xxxhdpi"/<code><code> compatibleScreens 'small', 'normal', 'large', 'xlarge'/<code><code> }/<code><code> }/<code><code> .../<code><code>}/<code>

9、儘量每張圖片只保留一份

比如說,我們統一隻把圖片放到 xhdpi這個目錄下,那麼在不同的分辨率下它會做自動的適配,即等比例地拉伸或者是縮小

10、資源在線化

我們可以 將一些圖片資源放在服務器,然後結合圖片預加載的技術手段,這些既可以滿足產品的需要,同時可以減小包大小

11、統一應用風格

如設定統一的 字體、尺寸、顏色和按鈕按壓效果、分割線 shape、selector 背景等等。

  • 阿里大佬十年面試了 2000 人,總結這7 條金科玉律

  • 深入探索 Android 包瘦身(上)

  • Android 多線程技術哪家強?

資源瘦身的這些方法用過哪些?


分享到:


相關文章: