1. 程式人生 > >App性能優化淺談

App性能優化淺談

app code 事件 詳情 網絡數據 記得 依賴 con 情況

前言

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

從何講起?

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

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

響應時間

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

這個部分涉及client的優化。也涉及服務端的優化,這裏僅僅討論client。

HTTP請求方式

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

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

假設自己造輪子的話。還須要我們花時間去驗證去適應我們的業務需求,但優點是我們能夠自己去擴展可把控,只是這非常考量開發人員的素養。

數據解析

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

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

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

數據存儲

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

為了提高應用程序的響應時間,數據緩存是一個比較好的方式,我們能夠預處理server返回的數據,對數據進行緩存刷新。

優化點:
- 異步請求網絡數據
- 預處理server返回數據
- 異步進行數據存儲操作
- 數據緩存刷新
- 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();

內存泄露

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

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

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性能優化有個比較清晰的認識,不論什麽事情都不可能一蹴而就,須要循循漸進,對一個剛開始學習的人你談優化非常不現實。我們先把主要的做好,再去考慮對應的優化,筆者也在不斷學習其中。借鑒別人好的優化方案,提高產品的質量。感謝大家對筆者的關註。

App性能優化淺談