Android-App性能優(yōu)化

上一篇我們講了java的引用機(jī)制,今天我們來(lái)一下和它有關(guān)的app性能優(yōu)化(其實(shí)也不是很大)。

性能優(yōu)化的目標(biāo)

     在網(wǎng)上也看到過(guò)很多相關(guān)的文章,他們基本總結(jié)為:快,穩(wěn),省,小,描述的很準(zhǔn)確.如下圖

 如何讓app在運(yùn)行過(guò)程過(guò)不卡頓,運(yùn)行流暢,速度快,也就是說(shuō)如何解決卡頓呢?我們先看看那些因素影響卡頓?
1. UI,包括ui的繪制,刷新等
2. 啟動(dòng),包括冷啟動(dòng),熱啟動(dòng),溫啟動(dòng)等
3. 跳轉(zhuǎn),頁(yè)面跳轉(zhuǎn),前后天切換
4. 及時(shí)反饋,點(diǎn)擊事件,滑動(dòng),系統(tǒng)事件

UI

這個(gè)涉及到android的系統(tǒng)顯示原理,我們簡(jiǎn)單了解一下:
Android 顯示過(guò)程可以簡(jiǎn)單概括為:Android 應(yīng)用程序把經(jīng)過(guò)測(cè)量,布局、繪制后的 surface 緩存數(shù)據(jù),通過(guò) SurfaceFlinger 把數(shù)據(jù)渲染到顯示屏幕上, 通過(guò) Android 的刷新機(jī)制來(lái)刷新數(shù)據(jù)。也就是說(shuō)應(yīng)用層負(fù)責(zé)繪制,系統(tǒng)層負(fù)責(zé)渲染,通過(guò)進(jìn)程間通信把應(yīng)用層需要繪制的數(shù)據(jù)傳遞到系統(tǒng)層服務(wù),系統(tǒng)層服務(wù)通過(guò)刷新機(jī)制把數(shù)據(jù)更新到屏幕上。
換一種方式說(shuō):Android 系統(tǒng)每隔 16ms 發(fā)出 VSYNC 信號(hào),觸發(fā)對(duì) UI 進(jìn)行渲染,如果每次渲染都成功,這樣就能夠達(dá)到流暢的畫(huà)面所需的 60FPS。(注:FPS 表示每秒傳遞的幀數(shù)。)在理想情況下,60 FPS 就感覺(jué)不到卡,這意味著每個(gè)繪制時(shí)長(zhǎng)應(yīng)該在16 ms 左右。如果某個(gè)操作花費(fèi)的時(shí)間是 24ms ,系統(tǒng)在得到 VSYNC 信號(hào)時(shí)就無(wú)法正常進(jìn)行正常渲染,這樣就發(fā)生了丟幀現(xiàn)象。也就是延遲了,這種現(xiàn)象在執(zhí)行動(dòng)畫(huà)或滑動(dòng)列表比較常見(jiàn),還有可能是你的 Layout 太過(guò)復(fù)雜,層疊太多的繪制單元,無(wú)法在 16ms 完成渲染,最終引起刷新不及時(shí).

那么我們?nèi)绾谓鉀Q呢,主要從兩點(diǎn)入手:ui布局,繪制優(yōu)化和主線程優(yōu)化?

布局優(yōu)化
  • 避免ui布局優(yōu)化可以先從合理使用背景色開(kāi)始,比如:如果子view和父布局公用一個(gè)背景色就沒(méi)有必要了。
  • 減少不必要的嵌套,一般建議不超過(guò)5層
  • 合理使用各種布局,盡量使用LinearLayout和FrameLayout,因?yàn)镽elativeLayout在進(jìn)行message時(shí)會(huì)執(zhí)行兩次,而LinearLayout只有設(shè)置weight的情況下執(zhí)行兩次(LinearLayout會(huì)避開(kāi)設(shè)置過(guò)weight屬性的view做第一次measure,完了再對(duì)設(shè)置過(guò)weight屬性的view做第二次measure),還有如果你是ListView或者GridView,不建議使用LinearLayout,因?yàn)槭褂?LinearLayout 容易產(chǎn)生多層嵌套的布局結(jié)構(gòu),這在性能上是不好的,一個(gè)RelativeLayout可以代替多個(gè)LinearLayout這也就是我們上面說(shuō)的減少嵌套。具體視情況而定。
  • 合理使用include、merge和ViewStub,使用include和merge增加復(fù)用,減少層級(jí);ViewStub按需加載。
  • 推薦使用google已經(jīng)出來(lái)的新的布局ConstraintLayout,這個(gè)有機(jī)會(huì)說(shuō)。
繪制優(yōu)化

-我們之前說(shuō)過(guò)根據(jù)Android系統(tǒng)顯示的原理,View的繪制頻率保證60fps是最佳的,這就要求每幀繪制時(shí)間不超過(guò)16ms(16ms = 1000/60),因此要減輕onDraw()的負(fù)擔(dān)。所以在繪制時(shí)要注意兩點(diǎn):

1 .onDraw中不要?jiǎng)?chuàng)建新的局部對(duì)象。

  1. onDraw方法中不要做耗時(shí)的任務(wù)。

還有就是刷新,刷新的話盡量減少不必要的刷新和盡可能減少刷新面積

啟動(dòng)優(yōu)化

冷啟動(dòng)

冷啟動(dòng)是指安裝apk后首次啟動(dòng)應(yīng)用程序,或者應(yīng)用程序上次結(jié)束,進(jìn)程被殺死后重新打開(kāi)app.
在冷啟動(dòng)開(kāi)始時(shí),系統(tǒng)有三個(gè)任務(wù)。這些任務(wù)是:
1、加載并啟動(dòng)應(yīng)用程序
2、啟動(dòng)后立即顯示應(yīng)用程序的空白啟動(dòng)窗口
3、創(chuàng)建應(yīng)用程序進(jìn)程

當(dāng)系統(tǒng)為我們創(chuàng)建了應(yīng)用進(jìn)程之后,會(huì)執(zhí)行以下的操作:

  • application的初始化
  • 啟動(dòng)UI線程
  • 創(chuàng)建Activity
  • 導(dǎo)入視圖(inflate view)
  • 計(jì)算視圖大?。╫nmesure view)
  • 得到視圖排版(onlayout view)
  • 繪制視圖(ondraw view)

應(yīng)用程序進(jìn)程完成首次繪制后,系統(tǒng)進(jìn)程會(huì)交換當(dāng)前顯示的背景窗口,將其替換為主活動(dòng)。此時(shí)至此啟動(dòng)完成,用戶可以使用程序(app)了,那么這里就會(huì)有兩類創(chuàng)建:

  • Application的創(chuàng)建
    當(dāng)Application啟動(dòng)時(shí),會(huì)有一個(gè)空白的啟動(dòng)窗口保留在屏幕上,直到系統(tǒng)首次完成繪制應(yīng)用程序,白屏才會(huì)消失,這也是為什么啟動(dòng)app會(huì)出現(xiàn)白屏,這個(gè)問(wèn)題,我也有提到過(guò)解決方式Anroid 白屏
  • Activity的創(chuàng)建
    當(dāng)Application首次啟動(dòng)完成繪制后,我們的UI線程會(huì)執(zhí)行主活動(dòng)進(jìn)行以下操作:
    • 初始化值。
    • 執(zhí)行其構(gòu)造函數(shù)。
    • 執(zhí)行其回調(diào)方法,比如 Activity.的onCreate()對(duì)應(yīng)生命周期的狀態(tài),onCreate() 方法做的事情越多,冷啟動(dòng)消耗的時(shí)間越長(zhǎng)。
暖(溫)啟動(dòng)

暖啟動(dòng)比冷啟動(dòng)時(shí)間更短。在暖啟動(dòng)中,系統(tǒng)都會(huì)把你的Activity帶到前臺(tái)。如果應(yīng)用程序的Activity仍然駐留在內(nèi)存中,那么應(yīng)用程序可以避免重復(fù)對(duì)象初始化、布局加載和渲染,但系統(tǒng)依然會(huì)展示閃屏頁(yè),直到第一個(gè) Activity 的內(nèi)容呈現(xiàn)為止。比如:當(dāng)應(yīng)用中的 Activities 被銷毀,但在內(nèi)存中常駐時(shí),應(yīng)用的啟動(dòng)方式就會(huì)變?yōu)榕瘑?dòng) 。

熱啟動(dòng)

熱啟動(dòng)的啟動(dòng)時(shí)間比暖啟動(dòng)還要更短。你比如,我用戶Back退出應(yīng)用,但應(yīng)用程序進(jìn)程保存在后臺(tái),然后又重新啟動(dòng),在已有進(jìn)程的情況下,這種啟動(dòng)會(huì)從已有的進(jìn)程中來(lái)啟動(dòng)應(yīng)用,應(yīng)用程序會(huì)再次執(zhí)行Activity的onCreate(),但會(huì)從Bundle(savedInstanceState)獲取數(shù)據(jù)(注意:獲取的前提是之前在onSaveInstanceState()方法中進(jìn)行了保存),我們平時(shí)應(yīng)用程序內(nèi)存不足崩潰,不也是通過(guò)該方法保存數(shù)據(jù)的嗎。

針對(duì)啟動(dòng)方式的優(yōu)化

  • Application的創(chuàng)建過(guò)程中盡量少的進(jìn)行耗時(shí)操作。
    比如:
    Application的onCreate()中進(jìn)行友盟,bugly,okhttp,地圖,推送等init()等操作。如果是必須在onCreate中進(jìn)行的如:okhttp等網(wǎng)絡(luò)請(qǐng)求框架我們?cè)趏nCreate中進(jìn)行,其他的友盟,百度地圖啥的我們可以等程序起來(lái)后再onResume方法中執(zhí)行,bugly等sdk可以異步加載。
  • 在生命周期回調(diào)的方法中盡量減少耗時(shí)的操作
    這個(gè)里面的優(yōu)化方式就是:避免I/O操作、反序列化、網(wǎng)絡(luò)操作、布局嵌套等。

穩(wěn)

主線程優(yōu)化

主線程的優(yōu)化大部分是指內(nèi)存優(yōu)化,不要內(nèi)存泄漏,那么通常那些地方容易引起內(nèi)存泄漏呢?

  • 集合類泄漏
  • 單例/靜態(tài)變量造成的內(nèi)存泄漏
  • 匿名內(nèi)部類/非靜態(tài)內(nèi)部類
  • 資源未關(guān)閉造成的內(nèi)存泄漏

解決方式:

  1. 比如我們的List集合add()元素之后,會(huì)引用著集合元素對(duì)象,導(dǎo)致該集合中的元素對(duì)象無(wú)法被回收,從而導(dǎo)致內(nèi)存泄露。當(dāng)我們的List集合沒(méi)有用的時(shí)候,一定要
     list.clear()
     list=null
  1. 針對(duì)單例引起的內(nèi)存泄漏,通常是由于引用的context是生命周期短造成的,也就是說(shuō)生命周期長(zhǎng)的持有了生命周期短的引用,造成了內(nèi)存泄漏。比如Toast,我們傳入的是MainActivity,但MainActivity沒(méi)有用了,需要被銷毀,但我們的Tost依然持有其引用導(dǎo)致無(wú)法回收,這就導(dǎo)致了內(nèi)存泄漏。

  2. 匿名內(nèi)部類或非靜態(tài)內(nèi)部類導(dǎo)致的內(nèi)存泄漏,這個(gè)我們可以采用合理使用JAVA的引用機(jī)制來(lái)解決,我上一篇文章有詳解,參考Android-強(qiáng),軟,弱,虛引用.

4.資源未關(guān)閉導(dǎo)致的內(nèi)存泄漏就比較好說(shuō)了,我們平時(shí)要多檢查,用完后及時(shí)關(guān)閉無(wú)用資源。

4.1 網(wǎng)絡(luò)、文件等流忘記關(guān)閉
4.2 手動(dòng)注冊(cè)廣播時(shí),退出時(shí)忘記 unregisterReceiver()
4.3 Service 執(zhí)行完后忘記 stopSelf()
4.4 EventBus 等觀察者模式的框架忘記手動(dòng)解除注冊(cè)
4.5 注意Bitmap,用完及時(shí)Recycle().

小大多指應(yīng)用程序apk體積要小。我們先看看一個(gè)apk文件有哪些解壓后有哪些文件:

  • assets文件夾
    存放一些配置文件、資源文件,assets不會(huì)自動(dòng)生成對(duì)應(yīng)的 ID,而是通過(guò) AssetManager 類的接口獲取。

  • res目錄
    res 是 resource 的縮寫(xiě),這個(gè)目錄存放資源文件,會(huì)自動(dòng)生成對(duì)應(yīng)的 ID 并映射到 .R 文件中,訪問(wèn)直接使用資源 ID。

  • META-INF
    保存應(yīng)用的簽名信息,簽名信息可以驗(yàn)證 APK 文件的完整性。

  • AndroidManifest.xml
    這個(gè)文件用來(lái)描述 Android 應(yīng)用的配置信息,一些組件的注冊(cè)信息、可使用權(quán)限等。

  • classes.dex
    Dalvik 字節(jié)碼程序,讓 Dalvik 虛擬機(jī)可執(zhí)行,一般情況下,Android 應(yīng)用在打包時(shí)通過(guò) Android SDK 中的 dx 工具將 Java 字節(jié)碼轉(zhuǎn)換為 Dalvik 字節(jié)碼。

  • resources.arsc
    記錄著資源文件和資源 ID 之間的映射關(guān)系,用來(lái)根據(jù)資源 ID 尋找資源。

通常我減小apk體積的方式都是:先用studio自帶的代碼掃描分析工具lint刪除無(wú)用資源;開(kāi)啟混淆,設(shè)置 shrinkResources true和 minifyEnabled true;當(dāng)然你也可以借助第三方工具如 :樂(lè)固加固,360壓縮啥的;還有注意不要重復(fù)使用庫(kù);插件化,比如功能模塊放在服務(wù)器上,按需下載,可以減少安裝包大小等都是常見(jiàn)的減少apk體積的方式。

  • 省電:谷歌推薦使用JobScheduler,來(lái)調(diào)整任務(wù)優(yōu)先級(jí)等策略來(lái)達(dá)到降低損耗的目的。JobScheduler可以避免頻繁的喚醒硬件模塊,造成不必要的電量消耗。避免在不合適的時(shí)間(例如低電量情況下、弱網(wǎng)絡(luò)或者移動(dòng)網(wǎng)絡(luò)情況下的)執(zhí)行過(guò)多的任務(wù)消耗電量。這個(gè)我們以后說(shuō)。
  • 省內(nèi)存:主要是加載圖片,動(dòng)不動(dòng)就 OOM,對(duì)于圖片的壓縮無(wú)非是:
  1. 圖片尺寸壓縮
  2. 圖片質(zhì)量壓縮
    此處代碼省略,網(wǎng)上一大堆。
    Glide就是采用了Lrucache和LruDiskCache推薦使用。
  • 省cpu資源.
    比如:線程的使用,這里我推薦使用線程池,我也寫(xiě)過(guò)相關(guān)文章,感興趣的可以了解一下。Android-ThreadPooll.

其他

這都是本人的一些建議:

  • 序列化采用推薦的Parcelable代替Serializable
  • 集合如果是插入和刪除用的多,建議使用LinkList。如果修改用的多,建議ArrayList。
  • 寫(xiě)程序要思考,避免創(chuàng)建不必要的對(duì)象。
  • 對(duì)常量使用static final,適用于基本類型和String常量。
  • 使用增強(qiáng)的for循環(huán)語(yǔ)法(foreach)。
  • 避免使用浮點(diǎn)數(shù),浮點(diǎn)數(shù)比Android設(shè)備上的整數(shù)慢約2倍。
  • 盡可能少用wrap_content,wrap_content 會(huì)增加布局 measure 時(shí)計(jì)算成本。
  • 刪除控件中無(wú)用的屬性。
  • 合理使用動(dòng)畫(huà),某些情況下可以用硬件加速方式來(lái)提供流暢度,或者采用自定義view代替動(dòng)畫(huà),最后記得在Activity的ondestory()方法中調(diào)用Animation.cancle()進(jìn)行動(dòng)畫(huà)停止。
  • 注意webview和handler,一般在首次加載后webview就會(huì)存在于內(nèi)存中,容易內(nèi)存泄漏。
  • 考慮StringBuilder代替String
  • 數(shù)據(jù)量比較大或者內(nèi)存比較寬??紤]HashMap,其他建議使用SpareArray

最后,我們一定要學(xué)會(huì)使用Android Studio自帶的各種工具如:

  • Lint:提示未使用到資源,不規(guī)范的代碼,優(yōu)化建議等。
    使用:選擇Analyze > Inspect Code .具體百度
  • 使用 Android Profiler 查看內(nèi)存,已經(jīng)各個(gè)操作內(nèi)存和網(wǎng)絡(luò)的變化。
  • 借助第三方工具,這個(gè)就多了去了,比如LeakCanary,MemoryAnalyzer等


    PgOnSI.png

基本也說(shuō)這么多,以后再補(bǔ)充。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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