一 、總結Android 性能優化的幾個方面
1. 佈局優化
儘量減少佈局文件的層級,這樣Android 在繪製的時候工作量減少了,就會提高性能
- 刪除佈局中無用的控件和層級
- 選擇使用性能較低的
ViewGroup
,比如RelativeLayout
在RelativeLayout
和LinearLayout
中勁量使用LinearLayout
,因爲RelativeLayout
相對於比較複雜,佈局過程需要花費更多的CPU時間,LinearLayout
和FrameLayout
都是一種簡單高效的ViewGroup
,如果單純的使用LinearLayout
和FrameLayout
不能達到想要的效果,需要嵌套纔可以,那麼還是使用RelativeLayout
,嵌套相當於添加了佈局的層級,這樣更影響效率 佈局優化還可以使用
<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 方法要避免執行大量的操作,這主要體現在兩個方面。
- onDraw 中不要創建新的佈局對象,因爲onDraw方法可能會被頻繁調用,防止產生大量的臨時對象
- onDraw 方法中不能做耗時的任務,也不能執行大量的循環操作,儘量是輕量級的,大量的循環會佔用CPU,會造成View的繪製過程不流暢
3. 內存泄露優化
內存泄漏優化從兩個方面
- 在開發中避免寫出內存泄漏的代碼
- 通過一些檢查內存泄漏的工具來查找內存泄漏的地方,進行解決
一些導致內存泄漏的地方
- 注意使用靜態屬性
- 在Activity 中播放動畫,注意要在onDestroy 中去停止動畫
- 要注意一些資源的回收
內存檢測工具的使用
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. 資源的回收
- Cursor(遊標)回收:cursor.close();
- Receiver(接收器)回收:當我們Activity中使用了registerReceiver()方法註冊了BroadcastReceiver,一定要在Activity的生命週期內調用unregisterReceiver()方法取消註冊
- 圖片回收:使用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圖片等操作等都應該記得顯示關閉。