1.Android性能優(yōu)化

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的合適目錄。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容