1.冷啟動(dòng)與熱啟動(dòng)優(yōu)化
1.測(cè)量APP的啟動(dòng)時(shí)間 指令:
adb shell? am -w [packgeName]/[packageName.MainActivity]
我們自己的項(xiàng)目
adb shell am start -W cn.com.weilaihui3/.app.ui.activity.HomeActivity
冷啟動(dòng)
熱啟動(dòng):
總共三個(gè)測(cè)量時(shí)間:
1.ThisTime:一般和TotalTime的時(shí)間一樣,除非在應(yīng)用啟動(dòng)時(shí)開了一個(gè)透明的Activity預(yù)先處理依稀耗時(shí)的操作后再顯示出主Activity,這樣將比TotalTime??;
2.TotalTime:應(yīng)用的啟動(dòng)時(shí)間,包含了創(chuàng)建進(jìn)程+Application初始化+Activity初始化到界面的顯示;
3.WaitTime:一般比TotalTime大點(diǎn),包含系統(tǒng)的耗時(shí)。
啟動(dòng)優(yōu)化:
1.視覺優(yōu)化:
給定一個(gè)默認(rèn)主題,并設(shè)置閃屏圖片主題
2.代碼優(yōu)化
2.1.在Application的構(gòu)造方法中,attachBaseContenx(),onCreate()方法中不要進(jìn)行耗時(shí)操作的初始化,一些數(shù)據(jù)預(yù)取放在異步線程中,能夠采取Callable實(shí)現(xiàn)。
2.2.對(duì)于SP的初始化,由于SP的特性在初始化時(shí)會(huì)對(duì)文件中的所有數(shù)據(jù)讀出來存進(jìn)內(nèi)存中,所以這個(gè)初始化放在主線程中明顯不合適,反而會(huì)延遲應(yīng)用的啟動(dòng)時(shí)間,因此必須放在異步線程中去做。
2.3.對(duì)于MainActivity由于在獲取第一幀前需要對(duì)ContentView進(jìn)行測(cè)量布局繪制操作,因此需要盡量降低布局的層次。考慮到StubView的延遲載入策略,必須在onCreate,onStart,onResume中避免做耗時(shí)的操作。
同時(shí)首先需要考慮哪些數(shù)據(jù)可以延時(shí)加載,哪些數(shù)據(jù)必須立即加載。
2.包大小的優(yōu)化
1.包大小的查看
將Apk下載下來,拖動(dòng)到AS中,AS會(huì)自動(dòng)分析包的大小
如圖所示Raw File Size是應(yīng)用打包后的真實(shí)大小,Download size是Google play上面用戶下載的大小,用于用戶下載,因此一般只針對(duì)前一項(xiàng)的APK Size 進(jìn)行優(yōu)化。
2.APK的組成:
文件/目錄描述
lib/用于存放so文件,可能會(huì)有armeabi,armeabi-v7a,arm64-v8a,x86,x86-64,mips等等,但是目前大多數(shù)情況下只需要支持armeabi與x86即可,如果沒有必要,可以考慮把x86也可以拿掉,(據(jù)我所知小米的機(jī)型中目前只有兩款使用的x86架構(gòu),一款是小米pad(2)(版本號(hào)好像是4.4),還有一款手機(jī)忘了什么記性了(版本號(hào)也是4.4)其他的所有的機(jī)型都是arm結(jié)構(gòu)的,因此如果最低支持的版本號(hào)是從5.0以上支持的是可以拿掉x86的)
res/存放編譯后的資源文件,如drawable、layout等
assets/應(yīng)用程序資源,應(yīng)用程序可以使用AssetManager來檢索該資源,如mp3,json字符串等資源
META-INF/該文件夾一般存放于已經(jīng)簽名的apk中,它包含了APK中所有文件的簽名摘要等信息
classes(n).dexclasses文件是Java Class,被Dex編譯后可供Dalvik/ART虛擬機(jī)所理解的文件格式。包含了所有的java文件編譯后的class文件,class文件最終轉(zhuǎn)化為dex文件。一般文件都比較大,有的APP有好幾個(gè)dex文件,這是因?yàn)閱蝹€(gè)的dex文件限制方法數(shù)載65535的問題,所以當(dāng)代碼量過大時(shí),就需要通過multidex進(jìn)行分包,拆分成多個(gè)dex文件
resources.arsc編譯后的二進(jìn)制文件包含了所有可以被編譯的位于res/values目錄下的xml資源。打包工具在打包的過程中會(huì)把xml的內(nèi)容編譯成二進(jìn)制的形式,或者把相關(guān)的資源引用路徑編譯成二進(jìn)制,然后存儲(chǔ)在該文件中,例如string文件,layout的路徑,圖片的路徑等等
AndroidManifest.xmlAndroid清單文件
3.具體的優(yōu)化邏輯
1)lib優(yōu)化
(1)so文件的裁剪和壓縮
對(duì)于App中引入的so文件進(jìn)行分析確認(rèn),哪些是不需要的,哪些是可以裁剪壓縮的,哪些是可以避免引入的,哪些是可以自定義替換的。例如如果引入的so僅需要一個(gè)上傳和下載功能而多引入了cURL庫(kù),則會(huì)導(dǎo)致so文件增大,此時(shí)我們可以使用在java中自定義,然后讓so文件進(jìn)行調(diào)用,從而避免了引入cURL庫(kù)。
(2)架構(gòu)文件的保留
Android系統(tǒng)目前支持7種CPU架構(gòu),每一種都關(guān)聯(lián)著對(duì)應(yīng)的ABI(二進(jìn)制接口,Application Binary Interface),而每一種ABI都定義了對(duì)應(yīng)的二進(jìn)制文件尤其是SO文件,因此就有七種對(duì)應(yīng)的so文件,但是目前主流的機(jī)型一般都使用arm結(jié)構(gòu),少部分使用x86架構(gòu)(據(jù)我所知小米的機(jī)型中目前只有兩款使用的x86架構(gòu),一款是小米pad(2)(版本號(hào)好像是4.4),還有一款手機(jī)忘了什么記性了(版本號(hào)也是4.4)其他的所有的機(jī)型都是arm結(jié)構(gòu)的,因此如果最低支持的版本號(hào)是從5.0以上支持的是可以拿掉x86的)。因此如果支持的Android版本最低是5.0以及以上的版本,完全可以只保留armeabi或者armeabi-v7a即可,操作也相對(duì)簡(jiǎn)單,只需要在根目錄的build.gradle下配置:
Android{
buildTypes{
ndk{
abiFilters "armeabi-v7a"
}
}
}
如果你的APP需要支持多種架構(gòu),那么就可以在abiFilters里面吧多種架構(gòu)加進(jìn)去,當(dāng)然也可以只保留一種,然后分渠道打包。
2)res目錄優(yōu)化
(1)只保留一套圖片即可
資源文件一般占APP的很大一部分,例如NIO APP種res目錄下的資源文件占了整個(gè)空間的接近40%,這是一個(gè)十分恐怖的數(shù)字,尤其是APP為了適應(yīng)多種分辨率而存放多套圖事(NIO APP還好大多數(shù)只存放了xxh的圖片,我在小米瀏覽器的時(shí)候,還需要xh甚至xxxh的圖)。由于Android設(shè)備在加載圖片事,首先會(huì)加載對(duì)應(yīng)分辨率文件夾下的圖片,如果對(duì)應(yīng)分辨率下沒有所需的圖片,則需要查找高分辨率對(duì)應(yīng)文件夾下的圖片,那么是不是把圖片放在最高分辨率的文件夾下就可以了呢?不是的,因?yàn)檫@樣會(huì)導(dǎo)致低分辨率手機(jī)加載圖片時(shí)會(huì)消耗更多的內(nèi)存,而且是指數(shù)級(jí)別的,所以如果盲目的放在一個(gè)目錄下也不合適,一般情況下對(duì)應(yīng)分辨率的圖片放在對(duì)應(yīng)分辨率的文件夾下。國(guó)內(nèi)一般只需要一套即可:xxh(我在小米時(shí)小米也只提供兩套圖片(xh:低于1000元的紅米手機(jī),xxh:據(jù)我所知千元以上的機(jī)型屏幕都是1080*1920的(全面屏以寬度算)),只有一款機(jī)型即小米note plus是使用的2k屏幕,在極個(gè)別的情況下需要xxxh圖片其他的都使用xxh即可)。
(2)非重要圖片動(dòng)態(tài)加載
APP中其實(shí)有很多圖片是可以從網(wǎng)上下載,嚴(yán)格意義上說,非首頁(yè)的圖片都可以動(dòng)態(tài)加載,但是為了用戶的體驗(yàn)我們可以將圖片保存在本地,但是對(duì)于用戶很少使用,或者圖片較大時(shí)建議采用動(dòng)態(tài)加載模式
(3)圖片保真壓縮,或者使用webp代替png
設(shè)計(jì)師提供的圖片如果較大時(shí),建議使用第三方工具進(jìn)行保真壓縮或者轉(zhuǎn)化為webp,在轉(zhuǎn)化為webp的情況下,建議保真度超過90%即可(因此png轉(zhuǎn)化為webp時(shí),優(yōu)勢(shì)94%的保真度的大小不到95%保真度大小的1/3)
(4)能用xml實(shí)現(xiàn)的圖片盡量使用xml實(shí)現(xiàn)
xml實(shí)現(xiàn)的圖片不僅可以有效的減少APP包的大小,而且可以渲染的更加清晰例如純色圖,漸變圖等等,都可以使用xml自己畫(小米瀏覽器中夜間模式的切換的背景圖,一開始是測(cè)試提供的,每張圖片在1.8MB之間,也就是說我僅做夜間模式的切換動(dòng)畫就需要增加3.6MB的包大小,相當(dāng)于瀏覽器的包增加了12%,這時(shí)就可以和設(shè)計(jì)師商議,讓他們提供色值和少量的圖片,然后自己去實(shí)現(xiàn)。)。
(5)使用lint刪除無用資源
方法一:手動(dòng)刪除
打開AS ,打開對(duì)應(yīng)的項(xiàng)目,在狀態(tài)欄中找到Analyze -> Run Inspection by Name
彈出該對(duì)話框,輸入U(xiǎn)nused Res
雙擊后會(huì)彈出下面的對(duì)話框
選擇對(duì)應(yīng)的模塊或者整個(gè)項(xiàng)目,然后點(diǎn)擊ok,稍等以后,輸出框中會(huì)輸出沒有用的資源文件
如上圖所示,最后點(diǎn)擊去查看同時(shí)刪除無效資源。
方法二自動(dòng)刪除無效的資源
在AS 1.4.0 及其以后的版本已經(jīng)支持自動(dòng)刪除無效的圖片或者資源,自序一在Gradle構(gòu)建系統(tǒng)中配置如下的信息即可:
在主工程的build.gradle的的buildTypes->release中配置shrinkResource 等于true即可
buildTypes {
? ? release {
? ? ? ? minifyEnabled true // 開啟混淆
? ? ? ? zipAlignEnabled true // 包優(yōu)化
? ? ? ? shrinkResources true // 移除無用資源
? ? ? ? debuggable isReleaseDebuggable
? ? ? ? proguardFiles getDefaultProguardFile('proguard-android.txt'),
}
}
lint也是有缺點(diǎn)的,如果一個(gè)圖片在java代碼中被引用,而這個(gè)段代碼缺沒有被使用,那么對(duì)應(yīng)的圖片或者資源是無法被刪除的。
app的包體積優(yōu)化,lint、proguard、andresguard原理,字節(jié)碼注入,刪除R.java的變量,刪除access001方法,壓縮圖片資源和使用redex等。
3.65535數(shù)
Android系統(tǒng)中,一個(gè)APP所有的代碼都在一個(gè)dex文件中,dex文件是一個(gè)類似于jar的存儲(chǔ)多個(gè)Java編譯字節(jié)碼的歸檔文件,因?yàn)锳ndroid系統(tǒng)使用了Dalvik虛擬機(jī),所以需要使用Java Compiler編譯后的cless問卷轉(zhuǎn)化成dalvik能夠執(zhí)行的class文件,這里需要強(qiáng)調(diào)的是,Dex和jar一樣是一個(gè)歸檔文件,里面仍然是Java源碼對(duì)應(yīng)的字節(jié)碼文件。當(dāng)Android啟動(dòng)時(shí),有一步是對(duì)Dex進(jìn)行優(yōu)化的,這個(gè)過程用一個(gè)專門的工具進(jìn)行處理,叫做DexOpt。DexOpt會(huì)吧每一個(gè)類的Id檢索出來并保存到一個(gè)鏈表結(jié)構(gòu)中,但是這個(gè)鏈表的長(zhǎng)度是用一個(gè)short類型來保存的,因此導(dǎo)致了方法的數(shù)目不能超過65536個(gè)。
常見的解決辦法:分包
Google推薦使用MultiDexApplication
配置也很簡(jiǎn)單:
Gradle配置:在defaultConfig中定義multiDexEnabled 為true
defaultConfig {
...
? ? ? ? multiDexEnabled true //Enabling multidex support
...
? ? }
dependencies中引用multidexjar包
dependencies {
...
? ? implementation "com.android.support:multidex:1.0.2"
...
}
在Application中重寫attachBaseContext方法
@Override
protected void attachBaseContext(Context base) {
? super.attachBaseContext(base);
? MultiDex.install(this);
}
Multidex的局限性
1.如果第二個(gè)或者某一個(gè)dex文件很大的話,安裝dex文件到date分區(qū)時(shí),可能會(huì)導(dǎo)致ANR
2.由于Dalvik LinearAlloc 的bug問題,使用了Multidex的應(yīng)用可能無法在Android 4.0(API level 14或者以前版本的設(shè)備上使用)
3.由于使用了multidex的應(yīng)用在安裝時(shí),需要請(qǐng)求分配很大的內(nèi)存,但是Dalvik LinearAlloc是一個(gè)固定的緩沖區(qū)大小,Android 4.0以前只有5MB,到了Android4.0以后才提高到了8/16MB,當(dāng)方法數(shù)量超過了緩沖區(qū)大小時(shí),會(huì)造成dexOpt崩潰。
4.在Dalvik運(yùn)行中,某些類或者方法必須放在主dex中,Android構(gòu)建工具可能無法確保所有有此需求的類被編譯進(jìn)主dex中。
需要注意的是,一些在二級(jí)Dex加載之前,可能會(huì)被調(diào)用到的類(比如靜態(tài)變量的類),需要放在主Dex中,否則會(huì)ClassNotFoundError,通過修改Gradle,可以顯示的把一些類放在Main dex中。
afterEvaluate {
? ? tasks.matching {
? ? ? ? it.name.startsWith('dex')
? ? }.each { dx ->
? ? ? ? if (dx.additionalParameters == null) {
? ? ? ? ? ? dx.additionalParameters = []
? ? ? ? }
? ? ? ? dx.additionalParameters += '--multi-dex'? ? ? ? dx.additionalParameters += "--main-dex-list=$projectDir/<filename>".toString()
? ? }
}
注意上面是修改后的Gradle,其中是一個(gè)文本文件的文件名,存放在和這個(gè)build.gradle腳本同一級(jí)的文件目錄下,而不是 項(xiàng)目根目錄。可以把這個(gè)文本文件起名為multidex.keep,內(nèi)容如下.實(shí)際就是把需要放在Main Dex的類羅列出來。
android/support/multidex/BuildConfig/class
android/support/multidex/MultiDex$V14/class
android/support/multidex/MultiDex$V19/class
android/support/multidex/MultiDex$V4/class
android/support/multidex/MultiDex/class
android/support/multidex/MultiDexApplication/class
android/support/multidex/MultiDexExtractor$1/class
android/support/multidex/MultiDexExtractor/class
android/support/multidex/ZipUtil$CentralDirectory/class
android/support/multidex/ZipUtil/class
project.afterEvaluate標(biāo)簽在特定的project配置完成后運(yùn)行,而gradle.projectsEvaluated在所有projects配置完成后運(yùn)行。 注意afterEvaluate需要放在android{}里,不可放外面。
這樣做了之后并不一定解壓apk之后會(huì)出現(xiàn)多個(gè)dex文件,可能仍然只有一個(gè)dex。因?yàn)橹挥斜仨毞职臅r(shí)候才會(huì)分,如果不需要就不會(huì)。 如果要強(qiáng)制分dex,還需要加上dx.additionalParameters += ‘–minimal-main-dex’。完整的配置如下:
afterEvaluate {
? ? ? ? tasks.matching {
? ? ? ? ? ? it.name.startsWith('dex')
? ? ? ? }.each { dx ->
? ? ? ? ? ? if (dx.additionalParameters == null) {
? ? ? ? ? ? ? ? dx.additionalParameters = []
? ? ? ? ? ? }
? ? ? ? ? ? dx.additionalParameters += '--multi-dex'
? ? ? ? ? ? // 設(shè)置multidex.keep文件中class為第一個(gè)dex文件中包含的class,如果沒有下一項(xiàng)設(shè)置此項(xiàng)無作用
? ? ? ? ? ? dx.additionalParameters += "--main-dex-list=$projectDir/multidex.keep".toString()
? ? ? ? ? ? //此項(xiàng)添加后第一個(gè)classes.dex文件只能包含-main-dex-list列表中class?
? ? ? ? ? ? dx.additionalParameters += '--minimal-main-dex'
? ? ? ? }
? ? }
這樣配置了之后就按照multidex.keep里面的內(nèi)容拆分出了第一個(gè)dex文件。其他內(nèi)容在第二個(gè)里面。 那么如何把需要的類放在multidex.keep文件里呢?其實(shí)不用手動(dòng)一個(gè)類一個(gè)類寫,我們進(jìn)入這個(gè)文件: 項(xiàng)目\build\intermediates\multi-dex\release(或debug)\maindexlist.txt。 將maindexlist.txt中沒有在application中初始化的類刪除一部分之后,剩余的復(fù)制到multidex.keep文件中就可以了。 當(dāng)然也可以自行增加沒有被包含進(jìn)去的類,因?yàn)椴恢苯右玫念惗疾辉趍aindexlist.txt中。 注意,如果需要混淆的話需要寫混淆之后的 class 。
如果需要配置每一個(gè)dex最大的方法數(shù),可以如下配置:
afterEvaluate {
? ? ? ? tasks.matching {
? ? ? ? ? ? it.name.startsWith('dex')
? ? ? ? }.each { dx ->
? ? ? ? ? ? if (dx.additionalParameters == null) {
? ? ? ? ? ? ? ? dx.additionalParameters = []
? ? ? ? ? ? }
? ? ? ? ? ? dx.additionalParameters += '--set-max-idx-number=48000'
? ? ? ? }
? ? }
通過上面的number參數(shù)代表每個(gè)Dex文件中的最大id數(shù),默認(rèn)是65535,通過修改這個(gè)值可以減少M(fèi)ain Dex文件的大小和個(gè)數(shù)。 這樣可以拆分出多個(gè)dex。但是這個(gè)number不可設(shè)置的太小,因?yàn)橹鱠ex需要加載足夠app啟動(dòng) 需要的類,太小則無法加載完,直接報(bào)錯(cuò)。
如果用使用其他Lib,要保證這些Lib沒有被preDex,否則可能會(huì)拋出下面的異常:
UNEXPECTED TOP-LEVEL EXCEPTION:
? ? com.android.dex.DexException: Library dex files are not supported in multi-dex mode
? ? ? ? at com.android.dx.command.dexer.Main.runMultiDex(Main.java:337)
? ? ? ? at com.android.dx.command.dexer.Main.run(Main.java:243)
? ? ? ? at com.android.dx.command.dexer.Main.main(Main.java:214)
? ? ? ? at com.android.dx.command.Main.main(Main.java:106)
遇到這個(gè)異常,需要在Gradle中修改,讓它不要對(duì)Lib做preDexing
android {
? ? //? ...
? ? ? ? dexOptions {
? ? ? ? ? ? preDexLibraries = false
? ? ? ? }
? ? }
MultiDex實(shí)現(xiàn)原理
Dex自動(dòng)拆包
Dex拆分步驟
1.掃描整個(gè)工程代碼,得到main_dex_list
2.根據(jù)main_dex_list 對(duì)整個(gè)工程編譯后的所有class進(jìn)行拆分打包,將主,從dex文件分開。3.用dex工具對(duì)主從dex文件分別帶包成。dex的文件,并放在apk的合適目錄。