總結Android 性能優化的幾個方面

一 、總結Android 性能優化的幾個方面

1. 佈局優化

儘量減少佈局文件的層級,這樣Android 在繪製的時候工作量減少了,就會提高性能

  1. 刪除佈局中無用的控件和層級
  2. 選擇使用性能較低的ViewGroup,比如RelativeLayout
    RelativeLayoutLinearLayout 中勁量使用 LinearLayout,因爲RelativeLayout相對於比較複雜,佈局過程需要花費更多的CPU時間,LinearLayoutFrameLayout 都是一種簡單高效的ViewGroup,如果單純的使用LinearLayoutFrameLayout不能達到想要的效果,需要嵌套纔可以,那麼還是使用RelativeLayout,嵌套相當於添加了佈局的層級,這樣更影響效率
  3. 佈局優化還可以使用 <include>標籤、 <merge> 標籤 、ViewStub

    先簡單介紹下它們的作用:

    <include>標籤:用於佈局重用,將一個佈局文件引入到當前佈局
    <merge> 標籤:一般配合<include>標籤 使用,他可以降 低佈局的層級
    ViewStub:提供了動態加載的功能,當需要的時候纔會將ViewStub中的佈局加載出來,提高了程序初始化的效率

    下面介紹下它們的使用:

    <include> 標籤的使用:

    先創建佈局文件,名稱爲 manager_top

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_marginLeft="20dp"
    android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="one" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="two" />


</LinearLayout>
在佈局文件中引用並一個佈局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <include
        layout="@layout/top"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="text" />

</LinearLayout>

如果別的地方也要用到manager_top 佈局文件中的佈局,就不需要再重新寫一次,直接使用include,達到複用效果

<merge> 標籤 的使用:

以上面的代碼爲例,由於外層佈局是豎直方向的LinearLayout ,這個時候被包裹的佈局文件也是一個豎直方向的LinearLayout,顯然被包裹的佈局文件的LinearLayout是多餘的,這個時候可以使用<merge> 標籤,可以去掉多餘的那個LinearLayout 層級

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_marginLeft="20dp"
    android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="one" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="two" />

</merge>

使用<include>包含上面的佈局的時候,系統會自動忽略merge層級,而把兩個button直接放置與include平級

ViewStub繼承了View,它非常輕量級且寬,高都是0,因此它本身不參加與任何的佈局和繪製過程,ViewStub的意義在於按需加載所需的佈局文件,有很多佈局文件在正常情況下不顯示,不如網絡異常時的界面,這個時候沒有必要在整個界面初始化的時候將其加載進來,通過ViewStub就可以在使用的時候再加載這個佈局,提高程序初始化時的性能

使用:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ViewStub
        android:id="@+id/view_stub"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout="@layout/top" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="text" />

</LinearLayout>

佈局文件top

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_marginLeft="20dp"
    android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="one" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="two" />

</LinearLayout>

注意ViewStub 引用的佈局文件中暫不支持<merge>標籤,<ViewStub>中的佈局文件默認是不顯示,要顯示需要這樣做

  ViewStub view_stub = (ViewStub)findViewById(R.id.view_stub);
        view_stub.setVisibility(ViewStub.VISIBLE);//或者view_stub.inflate();

2. 繪製優化

繪製優化是指View 的onDraw 方法要避免執行大量的操作,這主要體現在兩個方面。

  1. onDraw 中不要創建新的佈局對象,因爲onDraw方法可能會被頻繁調用,防止產生大量的臨時對象
  2. onDraw 方法中不能做耗時的任務,也不能執行大量的循環操作,儘量是輕量級的,大量的循環會佔用CPU,會造成View的繪製過程不流暢

3. 內存泄露優化

內存泄漏優化從兩個方面
  1. 在開發中避免寫出內存泄漏的代碼
  2. 通過一些檢查內存泄漏的工具來查找內存泄漏的地方,進行解決
一些導致內存泄漏的地方
  1. 注意使用靜態屬性
  2. 在Activity 中播放動畫,注意要在onDestroy 中去停止動畫
  3. 要注意一些資源的回收
內存檢測工具的使用

LeakCanary的使用請看文章:http://blog.csdn.net/hjiangshujing/article/details/51690187
MAT 內存泄漏的工具的使用後續有時間會在另一篇補上的


4. 響應速度優化–ANR 日誌分析

Android 規定,Activity如果5秒鐘之內無法響應屏幕觸摸事件或者鍵盤輸入事件就會出現 ANR,在BroadcastReceiver中,如果10秒鐘之內還未執行完操作也會出現ANR,當出現ANR 後,系統會在/data/anr目錄下創建一個文件traces.txt 可以通過分析這個文件能定位出ANR的原因

下面我們模擬下能出現ANR的場景

在應用中添加代碼

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main1);
        SystemClock.sleep(30000);
    }

然後不斷點擊屏幕就會出現ANR ,這時可以通過DDMS 中的File Explorer 導出traces.txt 文件,也可以通過adb 命令

adb pull /data/anr/traces.txt

traces.txt 文件內容比較多,可以搜索下自己應用的包名

截取部分日誌如下:

DALVIK THREADS (14):
"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 obj=0x74c67000 self=0xb4025800
  | sysTid=1953 nice=0 cgrp=default sched=0/0 handle=0xb772cea0
  | state=S schedstat=( 272113617 55357345 236 ) utm=12 stm=14 core=1 HZ=100
  | stack=0xbf151000-0xbf153000 stackSize=8MB
  | held mutexes=
  at java.lang.Thread.sleep!(Native method)
  - sleeping on <0x093ab465> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:1031)
  - locked <0x093ab465> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:985)
  at android.os.SystemClock.sleep(SystemClock.java:120)
  at com.customview.jsj.customview.MyView.onCreate(MyView.java:20)
  at android.app.Activity.performCreate(Activity.java:5990)
  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
  at android.app.ActivityThread.access$800(ActivityThread.java:151)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:135)
  at android.app.ActivityThread.main(ActivityThread.java:5254)
  at java.lang.reflect.Method.invoke!(Native method)
  at java.lang.reflect.Method.invoke(Method.java:372)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

通過以上日誌可以看出,Thread.sleep 線程睡了,在com.customview.jsj.customview.MyView.onCreate(MyView.java:20) 20行的位置,
這樣就能定位到問題了,仔細查看日誌,在一下幾行日誌中清楚的說明了產生ARN的原因

at java.lang.Thread.sleep!(Native method)
  - sleeping on <0x093ab465> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:1031)
  - locked <0x093ab465> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:985)
  at android.os.SystemClock.sleep(SystemClock.java:120)
  at 

5. Bitmap

Bitmap高效加載,使用BitmapFactory.Obtions,可以對圖片進行採樣縮放
通過BitmapFactory.Obtions 來縮放圖片,主要是用到它的inSampleSize 參數,即採樣率。當inSampleSize爲1時,採樣後的圖片大小爲圖片的原始大小,當inSampleSize的大小大於1時,比如2,那麼採樣後的圖片其寬,高爲原圖片的1/2,而像素數爲原圖的1/4,其佔用的內存大小也爲原圖的1/4.

圖片像素:
Android中圖片有四種屬性,分別是:
ALPHA_8:每個像素佔用1byte內存
ARGB_4444:每個像素佔用2byte內存
ARGB_8888:每個像素佔用4byte內存 (默認)
RGB_565:每個像素佔用2byte內存

比如:一張圖片的分辨率爲1024*1024,假定採用ARGB_8888格式存儲,那麼它佔有的內存爲1024*1024*4,即4MB,如果inSampleSize 爲2,那麼採樣後的圖片所佔有的內存大小爲 512*512*4 即1MB。採樣率inSampleSize 必須是大於1的整數,圖片纔會有縮小的效果,並且採樣率同事作用於寬,高,這將導致縮放後的圖片大小以採樣率的2次方形式遞減,即縮放比例1/(inSampleSize 的 2 次方),比如inSampleSize 爲 4,那麼縮放比例就是 1/16。
注意:當inSampleSize小於1時,其作用相當於1,即無縮放效果,
官網指出,inSampleSize的取值應該總是爲2的指數,比如:1,2,4,6,8,16等,如果inSampleSize不爲2的指數,那麼系統會向下取整並選擇一個最近的2的指數來代替,比如3,系統會選擇2來代替,這個結論並非所有的Android 版本上都成立,因此把它當成一個開發建議

緩存策略
目前常用的緩存算法LRU(Least Recently Used),是近期最少使用的
核心思想是:當緩存滿時,會優先淘汰那些近期最少使用的緩存對象,採用LRU算法的緩存有兩種:LruCache 和 DiskLruCache, LruCache 用於實現內存緩存,DiskLruCache 用於存儲設備緩存(磁盤緩存),LruCache 和 DiskLruCache具體在這裏不做解釋了
在項目中我們也可以藉助一些三方庫,如:ImageLoader
ImageLoader 內部對BitMap 高效加載進行了處理,以及LruCache 和 DiskLruCache


6. 線程優化

線程優化的思想是採用線程池,避免程序中存在大量的Thread ,線程池可以複用內部的線程,從而避免了線程的創建和銷燬所帶來的性能開銷,同時線程池還能有效的控制線程池的最大併發數,避免大量的線程因互相搶佔系統資源從而導致堵塞現象

有關線程池的知識請看:http://blog.csdn.net/hjiangshujing/article/details/51719573

線程池終結版:http://www.xuanyusong.com/archives/2439


7. 資源的回收

  1. Cursor(遊標)回收:cursor.close();
  2. Receiver(接收器)回收:當我們Activity中使用了registerReceiver()方法註冊了BroadcastReceiver,一定要在Activity的生命週期內調用unregisterReceiver()方法取消註冊
  3. 圖片回收:使用Bitmap過後,就需要及時的調用Bitmap.recycle()方法來釋放Bitmap佔用的內存空間
// 先判斷是否已經回收  
if(bitmap != null && !bitmap.isRecycled()){  
    // 回收並且置爲null  
    bitmap.recycle();  
    bitmap = null;  
}  
System.gc(); 

4.Stream/File(流/文件)回收:主要針對各種流,文件資源等等如:
InputStream/OutputStream,SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O,Bitmap圖片等操作等都應該記得顯示關閉。

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