App性能優化淺談

前言

前段時間給公司的小夥伴們進行了關於app性能優化的技術分享,這裏我稍微整理一下也給大家分享一下,關於性能優化這個話題很大,涉及面可以很廣,也可以很深入,本人能力有限,不會給大家講特別難懂,特別底層的東西,都是我們開發能着手去做的點,大家都在講性能優化,但對於項目經驗不夠豐富的朋友很難有一個概念,做優化的時候也會比較茫然,這裏我就給大家指明方向。

從何講起?

筆者在做產品開發的時候,也遇到性能瓶頸,測試工程師反饋了一些比較明顯的問題,比如UI界面的過度繪製,列表滑動有明顯卡頓,比較耗內存等等,但以往的都沒有針對性的去做相應的優化,所以藉着保證產品質量的出發點,自己定了相關的性能優化方案,可能不太成熟,不過可以逐步完善,並找到最適合自己產品的優化方案。

這裏我定了四個方向: 
- 響應時間(Response Time) 
- 界面卡頓(ANR) 
- 耗內存(Memory) 
- 內存泄露(Out of memory)

響應時間

這裏指的是客戶端與服務端交互,拿到數據、解析、再到顯示到界面整個過程耗費的時間。

這個部分涉及客戶端的優化,也涉及服務端的優化,這裏只討論客戶端。

HTTP請求方式

我們的app一般離不開網絡,請求接口是最平常的操作了,如何請求,請求什麼我們在開發初期就要定好,服務端給我的提供的接口,大致可以通過GET、POST、HEAD、PUT、DELETE這幾種請求方式,不同的請求方式有不同應用場景,比如GET請求,應當用來請求返回結果,參數是作爲url的一部分;POST請求,用於請求會更改服務端數據或狀態;HEAD請求跟GET一樣,只是服務器不能在響應裏返回消息主體;PUT請求,用於將網頁放置正確的地方;DELETE請求用於刪除服務器指定文檔。

使用優秀的開源Http框架是我們比較好的選擇,它的優點是經過市場的驗證,很多坑都被填過,缺點也是我們需要去深究它才能對其進行擴展,遇到坑也不一定能填。

如果自己造輪子的話,還需要我們花時間去驗證去適應我們的業務需求,但好處是我們可以自己去擴展可把控,不過這很考量開發者的素質。

數據解析

實際開發當中服務端的返回數據格式無非就兩種: 
- JSON 
- XML

這兩種格式數據格式各有優劣,從可讀性來看,xml略微好一點,不過JSON也有規範的標籤,從解析難度和速度來看,大家都比較傾向使用JSON,目前JSON也是主流的數據格式。

在Android中均可以使用優秀的解析庫來加快我們的解析速度,XML中有dom4j,JSON有Jackson、Gson,我們通過這些庫實現我們更快的完成數據解析,提高我們的開發效率。

數據存儲

上一節講的是數據解析,我們解析完後的數據,可能就需要將數據存儲在某個地方,Android的五種存儲方式: 
- Content Provider(主要用來向其他應用程序共享數據) 
- SQLite(存儲數據到數據庫中) 
- File(本地文件保存) 
- SharedPreference(主要用來保存簡單的配置信息) 
- 網絡存儲(WebService返回的數據或是解析HTTP協議實現網絡數據交互)

爲了提高應用程序的響應時間,數據緩存是一個比較好的方式,我們可以預處理服務器返回的數據,對數據進行緩存刷新。

優化點: 
- 異步請求網絡數據 
- 預處理服務器返回數據 
- 異步進行數據存儲操作 
- 數據緩存刷新 
- Timeout超時重試 
- 在主線程中操作UI

界面卡頓

ANR表示”應用程序無響應”,這個是需要我們避免發生的事情,出現這個異常的原因: 
- 主線程 (“事件處理線程” / “UI線程”) 在5秒內沒有響應輸入事件 
- BroadcastReceiver在10秒內沒有執行完畢

導致ANR的原因有很多,一般情況就是在UI線程做了耗時的操作,例如”網絡請求”、數據庫操作。

那麼如何避免? 
- UI線程只做界面刷新,不做任何耗時操作,耗時操作放在子線程來做 
- 可以使用Thread+handle或者AsyncTask來進行邏輯處理

耗內存

每部手機的內存有限,我們這裏所說的內存指的是手機的RAM,它是Ramdom Access Memory的縮寫,我們應用程序的需要隨機讀寫的數據就存在RAM中,Android手機之所以會比較耗內存,這跟Android後臺的處理有關,我們知道Android應用是使用Java開發的,運行Java需要有虛擬機,說明每開啓一個應用都會創建一個虛擬機,而這是需要內存的,所以我們開的應用越多,後臺進程越多,內存都分配出去了,才導致內存消耗的嚴重。

其實這個問題我們是沒得破的,只要內存不夠,我們的應用還是會卡。我們開發的應用依賴與系統給我們分配的堆內存,一般上限在16M~48M,但我們可以通過在AndroidManifest設置Application屬性largeHeap=“true”來申請更多的堆內存。

通過以下代碼獲取可用堆內存限制:

mActivityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
 mMaxMemory = mActivityManager.getMemoryClass();
  • 1
  • 2

內存泄露

內存泄露這個問題已經被說爛了,大家都知道有內存泄露這個問題存在,但爲什麼會發生內存泄露?

這裏的內存泄露並不是真正意思上的泄露,而是因爲內存不足不能進行GC操作,從而導致佔用內存過大,拋出out of memory異常,而被系統Kill掉。

JVM回收機制

是時候講講JVM的回收機制了,看下圖:

JVM分代

JVM對Java對象分了三個代進行管理,分別爲年輕代、年老代、永久代。 
年輕代(Young Generation):絕大多數的Java對象會在年輕代被分配,也會在年輕代被回收。 
年老代(Old Generation):在年輕代長期存在沒有被回收的Java對象會轉移到年老代,這個堆空間通常會被比年輕代的堆空間要大。 
永久代:存放VM和Java類的元數據,以及interned字符串和類的靜態變量。

這裏涉及到JVM的相關知識,這裏不繼續深入探討。

但我們應該可以知道垃圾回收器的作用: 
- 分配內存 
- 保證所有正在被引用的對象還存在於內存中 
- 回收執行代碼已經不再引用的對象所佔的內存

對象引用

Java的引用類型可以分爲以下幾種: 
- 強引用(Strong Ref):強可達,去掉強可達,纔會被回收。 
- 軟引用(Soft Ref):內存夠用,就保持,內存吃緊,則回收,主要用來做緩存。 
- 弱引用(Weak Ref):比Soft Ref弱,即使內存不吃緊也會被回收。 
- 虛引用(Phantom Ref):不會在內存保持任何對象。

一圖勝千言:

對象引用

利用Strong Ref,存儲大量數據,直到heap撐破,利用inter strings(或者class loader加載大量的類)把perm gen撐破,然後就是內存泄露了。

如何優化?

前面講了一些背景知識,對我們理解內存優化有一定的幫助,下面就簡單說一下我們優化的方向: 
- 佈局優化 
- 內存優化

佈局優化

大家可以拿出你們的Android機 
開發者工具-Profile GPU Rendering-選擇在屏幕上顯示條形圖

-藍色代表測量繪製Display List的時間 
-紅色代表OpenGL渲染Display List所需要的時間 
-黃色代表CPU等待GPU處理的時間 
-中間綠色橫線代表VSYNC時間16ms,儘量將所有條形圖控制在這條綠線下

爲什麼是16ms?

Android 通知界面渲染和重繪的時間要在16ms內完成,如果超過16ms,就會導致丟幀,也就是我們常說的卡頓。

優化點: 
- 避免OverDraw 
- 優化佈局層級 
- 避免過多無用嵌套 
- 使用<include>標籤重用layout 
- 使用<ViewStub>延遲加載 
- Hierarchy View進行層級分析

具體的使用方法,這裏不介紹了,不懂就百度。

內存優化

內存優化的點有很多,這裏我主要分爲兩大塊: 
- Bitmap優化 
- 代碼優化

Bitmap優化

  1. 使用適當分辨率和大小的圖片
  2. 及時回收內存(bitmap.recycle())
  3. 使用圖片緩存(LruCache和DiskLruCache)

第一點,就是按需顯示,比如列表中的圖片,你可以顯示縮略圖,詳情頁,你就可以加載相應的分辨率的圖片,這樣可以減少內存消耗,一般可以要求服務端提供多種分辨率的圖片。

第二點,Bitmap是很耗內存,尤其是加載比較大的bitmap,可以想到的優化方案就是使用記得回收,對Bitmap進行壓縮,使用BitmapFactory.Options設置inSampleSize就可以縮小圖片。

第三點,圖像緩存,這個可以利用成熟的圖片加載框架,比如Universal-ImageLoader、Fresco、Picasso,這些框架都對圖片進行了很好的優化,大家可以對比一下,選擇使用即可。

代碼優化

關於代碼這個就有的說了,任何能改進我們程序的優化點都能寫在這裏,這裏沒辦法把所有優化的點列在這裏,只提供相關的參考,剩下的就好各位經驗總結和積累了。

優化點: 
- 對常量使用static修飾符 
- 使用靜態方法 
- 減少不必要的成員變量 
- 儘量不要使用枚舉,少用迭代器 
- 對Cursor、Receiver、Sensor、File等對象,要注意它們的創建、回收與註冊、反註冊 
- 避免大量使用註解、反射 
- 使用RenderScript、OpenGL來進行復雜的繪圖操作 
- 使用SurfaceView來替代View進行大量、頻繁的繪圖操作 
- 儘量使用視圖緩存,而不是每次都執行inflate()方法解析視圖

注:這裏引用了Android羣英傳的相關優化點

  • 創建新的對象都需要額外的內存空間,要儘量減少創建新的對象。
  • 將類、變量、方法等等的可見性修改爲最小。
  • 針對字符串的拼接,使用StringBuffer替代String。
  • 不要在循環當中聲明臨時變量,不要在循環中捕獲異常。
  • 如果對於線程安全沒有要求,儘量使用線程不安全的集合對象。
  • 使用集合對象,如果事先知道其大小,則可以在構造方法中設置初始大小。
  • 文件讀取操作需要使用緩存類,及時關閉文件。
  • 慎用異常,使用異常會導致性能降低。
  • 如果程序會頻繁創建線程,則可以考慮使用線程池。

以上都是些經驗總結,大致都相差無幾,朋友們在做代碼優化的時候,可以根據這些優化點,有針對性去重構代碼,其實最重要還是代碼的可讀性,結構清晰。

性能優化工具

  • Memory Monitor - 內存監視工具
  • TraceView
  • MAT

Android開發者對與以上幾個性能調優的工具一定不陌生,這裏我也不再寫那麼多廢話了,關於它們的使用方法,官網還有一些大牛的博客都有介紹。

最後

寫這篇文章的出發點也是對Android性能優化有個比較清楚的認識,任何事情都不可能一蹴而就,需要循循漸進,對一個初學者你談優化很不現實,我們先把基本的做好,再去考慮相應的優化,筆者也在不斷學習當中,借鑑別人好的優化方案,提高產品的質量,感謝大家對筆者的關注。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章