轉載請注明出處: http://www.cnblogs.com/soaringEveryday/p/5254520.html
隨着Android移動開發的需求越來越復雜,我們不可避免的遇到發布出去的apk體積越來越大的問題,目前超過10MB、20MB的apk已經是很常見的事情了,但是依然能夠看到一些apk的體積控制的很小。apk體積增大源於:
- 新需求不斷的提出
- 需要支持高分辨率的屏幕而加入了高分圖片
- 依賴了更多的第三方庫
本文將從我自己的經歷項目中探討如何有效減小apk的體積。減小Apk體積是一件很有用處的android優化手段,降低了用戶需要下載的比特數,同時也降低了分發安裝時失敗的概率。
將apk解壓后發現,體積占大頭的分辨是lib文件夾、res文件夾和dex文件。所以我們的降低apk體積的策略也應當從如何縮減so文件、資源圖片、控制代碼質量上來入手。
使用Progruard
Proguard是Android很早就使用的代碼混淆工具,除了用於混淆代碼提高安全性以外,他在代碼編譯的時候也會通過遍歷代碼的方式來發現沒有被調用的代碼,從而將其在打包成apk時剔除,最終一定程度上降低了apk的大小。
但是Proguard使用時候是要注意的,因為代碼中利用反射機制的地方會被Proguard工具破壞,所以要慎重的編寫混淆例外文件,同時對於混淆后打包出來的apk要重新充分回歸測試下。
使用Android Lint
Proguard提供了代碼的縮減方式,而Lint對於res下面的資源進行了充分的優化,他會提供一份報告給你,從而通知你哪些資源沒有被用到,顯然剔除這些資源是可以減少apk體積的。這些資源包括res文件夾下所有的內容,比如圖片、字串、尺寸等等。現在Android Lint已經集成到了Android Studio中,用法很簡單。
進入Android Studio的菜單中選擇Analyze->Inspecting Code即可
分析完畢后在Inspection選項卡中會有一份詳細的報告,找到Android Lint項目
拉到下面Unused resource這一欄打開,即是未被使用的資源列表,用戶可以參照來手動刪除資源
清理Assert文件夾
Assert文件夾經常會放置一些不被編譯的資源,時間久了,里面可能一些文件或者資源已經不用了,然而這個文件夾也是會被打包到apk里面的。所以定期清理這個里面的內容也是減小apk體積的重要一步。
用代碼代替圖片
開發的時候有些地方能用代碼做出來的就盡量不用圖片來渲染,這樣子可以減少圖片資源的數量從而減少體積,這里舉幾個例子。
- 用shape代替背景圖
- 用RotateDrawable代替僅僅是方向不同的“內容相同”的圖片
- 用layer-list來制作多層圖片從而達到復用
- 使用屬性動畫而不是多圖片連續播放的幀動畫
用shape代替背景圖
很多背景圖比如按鈕的背景、純色背景都是可以用shape來制作的,這樣子僅用xml代碼就代替了png資源。比如這么要給按鈕背景圖(純色背景、帶邊框、圓角)可以用shape而不是Png圖片來制作
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item> <shape> <stroke android:width="0.5dp" android:color="@color/white"/> <gradient android:startColor="#ffffff" android:endColor="#ffffff" android:angle="0.0" /> <corners android:topLeftRadius="4dp" android:topRightRadius="0dp" android:bottomLeftRadius="4dp" android:bottomRightRadius="0dp" /> </shape> </item> </selector>
用RotateDrawable代替僅僅是方向不同的“內容相同”的圖片
這里兩個圖片是兩個按鈕箭頭,但是僅僅方向不同而已,其實可以只用其中一個圖片即可,而另一個用RotateDrawable來讓其“調轉”180度
<?xml version="1.0" encoding="utf-8"?> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/ic_arrow_left" android:fromDegrees="180" android:pivotX="50%" android:pivotY="50%" android:toDegrees="180" />
用layer-list來制作多層圖片從而達到復用
有些需求中需要一種圖片,但是明顯這個圖片是其他幾個圖片簡單疊加而已,那么可以使用layer-list來達到目的
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <!-- 最底層的圖片,以x,y軸坐標為中心進行旋轉--> <rotate android:pivotX="0" android:pivotY="0" android:fromDegrees="-10" android:toDegrees="-10"> <bitmap android:src="@drawable/chatting_bg_default_thumb"/> </rotate> </item> <!-- 第二層的圖片,以x,y軸坐標為中心進行旋轉--> <item> <rotate android:pivotX="0" android:pivotY="0" android:fromDegrees="15" android:toDegrees="15"> <bitmap android:src="@drawable/chatting_bg_purecolor_thumb"/> </rotate> </item> <!-- 最上層的圖片,以x,y軸坐標為中心進行旋轉--> <item> <rotate android:pivotX="0" android:pivotY="0" android:fromDegrees="35" android:toDegrees="55"> <bitmap android:src="@drawable/mark"/> </rotate> </item> </layer-list>
使用屬性動畫而不是多圖片連續播放的幀動畫
這里就不過多解釋了,比如你做一個圖片的旋轉,通過屬性動畫即可用代碼搞定。如果你要用多張圖片“連續播放”的話,那要用很多張預備圖片才行,無疑加大了apk的體積
放棄一些圖片資源
我們知道Android是支持多分辨率的,提供了多套圖片適配的機制,我們根據不同尺寸的屏幕來放入多套的圖片,比如ldpi、mdpi、hdpi、xhdpi等。但是實際上有些圖片你可能是不需要放置的。
- 對於ldpi,系統會自動的將hdpi的圖片縮放到達到目的,所以你就不需要把ldpi的圖片拷貝到你的res文件夾下了
- 有些分辨率的圖片你可能是用不到的,比如ldpi和xxxhdpi。前者的手機目前很少了,而后者是針對2k屏幕的,目前還沒有普及開。所以對於這點,你可以考慮下是否需要放入這兩套圖片資源
此外,我們所引入的第三方包中可能也引用了資源圖片,但是其中的某些我們可能是不想要的,比如ldpi和xxxhdpi,那么是否可以設置什么東西來讓打包的時候剔除他們呢?是可以的,可以配置下build.gradle
defaultConfig {
// ...
resConfigs "en", "de", "fr", "it"
resConfigs "hdpi", "xhdpi", "xxhdpi"
}
defaultConfig提供了resConfig這個flavor來指定打包出只打包某些資源,比如字串、圖片等等
壓縮圖片
圖片從美工那邊拿到的時候是比較漂亮的,但是代價是size通常也高居不下,我們可以用一些工具來“近乎無損”的縮小圖片資源,同時不降低圖片效果。這里推薦pngquant這個工具,可以參考我的上篇博客
http://www.cnblogs.com/soaringEveryday/p/5148881.html
so的優化
我們在接入百度地圖的時候,發現需要引入很多很多so
這些so文件占了很多大體積,如果你不加控制,所有的so都會打包到你的apk了,最后發現這些so文件盡然占了我們apk的近乎三分之一的體積。然而考慮下我們的用戶,基本都是跑在手機上的(沒有人跑在模擬器上),所以明顯x86和x86_64的so是不需要支持,那么我們可以通過配置gradle來制定只打包某些so,依然是在defualtConfig中:
defaultConfig {
... ...
ndk {
//設置支持的SO庫架構
abiFilters 'arm64-v8a', 'armeabi' //, 'x86', , 'x86_64', 'arm64-v8a'
}
}
最后打包出來的apk真的是減少了好幾個MB,這是太好了
當然如果有一些so是你們自己開發的,那么可以參考這個文章來參考如果用ndk開發的時候減少so本身的體積,這里就不過多介紹了
https://blog.algolia.com/android-ndk-how-to-reduce-libs-size/
對第三方庫進行重新定制(重新打jar包)
開發中引入大量的第三方開發庫也是一個增加apk體積的重要原因,因為你把人家的代碼和資源全給包含進來了。但是想想人家的代碼,並不一定全要的,是否可以只引入人家的一部分代碼,而不是在build.gradle中僅僅添加一行“compile”來全部依賴呢?答案是可以得!這里舉一個例子
我們開發中有一個需求是將數據通過圖標的方式顯示出來,這里我們站在巨人的肩膀上,使用了MPAndroidChart這個優秀的開源項目(https://github.com/PhilJay/MPAndroidChart),但是發現他們的東西太多了,我們僅僅需要使用其中一種chart。如果在build.gradle里面加一句:
dependencies {
compile 'com.github.PhilJay:MPAndroidChart:v2.2.3'
}
這要把他們的庫全給引用過來了。想到他們是開源的,代碼有,所以我們僅僅把他們的我們所用到的代碼給剝離出來,單獨打包了一個jar包引入到我們的項目里面,就OK了,減少了大量的無用依賴代碼!
動態加載技術(插件化)
現在大型互聯網移動App很多都采用了動態加載的技術,因為他們的業務需求太大,通過動態加載技術可以將一部分業務模塊獨立出來,以插件的方式分割出去,這樣子主apk的體積就大大減小。當用戶安裝主apk后,靜默的在后台下載插件apk,當用戶點擊使用到相關的子模塊項目時候,動態的加載插件apk。
動態加載技術無疑從根本上減少了apk的體積,但是引入這個技術是有代價的,增加了項目的維護難度和開發難度。所以該技術適用於大型的移動應用,當你的業務大到不分開模塊難以高效率開發維護的時候,再考慮動態加載技術吧,否則如果小規模應用,還是老老實實考慮傳統的android官方推薦的開發方式。
這里推薦幾個比較好的動態加載開源框架項目供大家研究