前言
本文將會介紹一些Android的性能優(yōu)化的方法,主要內(nèi)容包括布局優(yōu)化、繪制優(yōu)化、內(nèi)存泄露、響應(yīng)速度優(yōu)化、ListView優(yōu)化、Bitmap優(yōu)化、線程優(yōu)化以及一些性能優(yōu)化的建議。
布局優(yōu)化
不具優(yōu)化的思想很簡單,就是盡量減少布局文件的層級,這個道理是很簡單的,布局中的層級少了,這就意味著Android繪制時的工作量減少了,那么程序的性能自然就提高了。
那么如何進行布局優(yōu)化了,首先就是刪除那些無用的控件很層級,其次有選擇性的是有那些性能比較低的ViewGroup,比如RelativeLayout、如果布局中RelativeLayout也可以被替換成LinearLayout的話,那就使用LinearLayout。這是因為RelativeLayout的功能比較復(fù)雜,它的布局過程需要花費更多的CPU時間,F(xiàn)rameLayout和LinearLayout都是一種簡單高效的ViewGroup,因此可以考慮使用它們。但是有很多時候單純通過一個LinearLayout或者FrameLayout是無法實現(xiàn)想要的效果的,需要通過多層嵌套的方式來完成,這種情況下還是建議采用RelativeLayout。因為ViewGroup的嵌套就相當于增加了布局的層級,同樣會降低程序的性能。
布局優(yōu)化的另外一種采用<include>標簽、<marge>標簽和ViewStub。<include>標簽主要用于布局重用,<marge>標簽一般配合<include>標簽來使用,它可以降低減少布局的層級,而ViewStub控件則提供了按需加載的功能。當需要的時候才會將ViewStub中的布局加載到內(nèi)存,這提高了程序的初始化效率。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--使用include標簽-->
<include layout="@layout/nav_bar" />
<com.ju.mumusic.views.InputView
android:id="@+id/input_phone"
android:layout_width="match_parent"
android:layout_height="@dimen/inputViewHeight"
android:layout_marginTop="@dimen/marginSize"
app:input_hint="手機號"
app:input_icon="@mipmap/phone"
app:is_password="false" />
......
</LinearLayout>
通過<include>標簽可以指定一個布局文件,這樣就可以不用重復(fù)把這個布局的內(nèi)容在輸入一遍了,在你想要使用的layout文件里面就可以直接使用了。<marge>標簽一般和<include>標簽一起使用來減少布局的層級,比如說當前布局已經(jīng)是一個豎直方向的LinearLayout,而這個時候如果被包含的布局文件中也采用了豎直方向的LinearLayout,那么顯現(xiàn)這個LinearLayout是多余的,通過<marge>標簽就可以去掉這個多余的層級了。
<merge>
<Button
android:id="@+id/btn_login"
style="@style/commitBtn"
android:text="登 錄" />
</merge>
接下來來講解一下ViewStub,它繼承了View,它非常輕量級,寬和高都是0,因此它本身不參與任何的布局和繪制過程。ViewStub的意義在于按需加載所需要的布局文件,在實際開發(fā)中,有很多布局在正常情況下是不會心事的,比如網(wǎng)絡(luò)異常時的界面,這個時候就沒有必要在這邊改革界面初始化的時候?qū)⑵浼虞d進來,而通過ViewStub就可以做到在使用的時候再加載,提高了程序初始化的性能。
// 在布局文件中,你就當做普通 View 使用即可
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent"
android:orientation="vertical">
<TextView
android:id="@+id/tv"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="內(nèi)容區(qū)域"/>
<ViewStub
android:id="@+id/vs"
android:layout_width="match_parent"
android:layout_height="300dp"
// 當 ViewStub 加載后,就會被下面這個布局占用
android:layout="@layout/activity_main"/>
</LinearLayout>
//兩種加載方式
findViewById(R.id.vs).setVisibility(View.VISIBLE);
View view = (findViewById(R.id.vs).inflate());
繪制優(yōu)化
繪制優(yōu)化指的是View的OnDraw方法要避免執(zhí)行大量的操作,主要體現(xiàn)在兩個方面。
首先,onDraw中不要創(chuàng)建新的局部對象,這是因為onDraw方法可能會被頻繁的調(diào)用。這樣就會在一瞬間產(chǎn)生大量的臨時對象,這不僅占用了過多的內(nèi)存而且還會導(dǎo)致系統(tǒng)更加頻繁GC,減低了程序的執(zhí)行效率。
另一方面,onDraw方法中不要做耗時的任務(wù),也不能執(zhí)行成千上萬次的循環(huán)操作,盡管每次循環(huán)都很輕量級,但是大量的循環(huán)仍然十分搶占CPU的時間片,者會造成View的繪制過程不流暢,按照Google官方給出的行優(yōu)化典范中的標準,View的繪制幀率保證60fps是最佳的,這就要求每幀的繪制時間不操作16ms(16ms = 1000 / 60),雖然程序很難保證這個事件,盡量降低onDraw方法的復(fù)雜度是切實有效的。
內(nèi)存泄露優(yōu)化
內(nèi)存泄露在開發(fā)過沖中是一個需要重視的問題,但是由于內(nèi)存泄露問題對開發(fā)人員的經(jīng)驗和開發(fā)意識有較高的要求,因為這也是最容易犯的錯誤之一,內(nèi)存泄漏的優(yōu)化分為兩個方面,一方面是在開發(fā)過程中避免寫出有內(nèi)存泄露的代碼,另一方面就是通過一些分析工具比如MAT來找出潛在的內(nèi)存泄露繼而解決,這里主要介紹下在開發(fā)過程中有可能會出現(xiàn)內(nèi)存泄漏的幾個場景。
- 靜態(tài)變量導(dǎo)致的內(nèi)存泄露
//場景1
public class MainActivity extends AppCompatActivity {
private static Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
}
}
//場景2
public class MainActivity extends AppCompatActivity {
private static View view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
view = new View(this);
}
}
代碼很簡單,因為是一個靜態(tài)變量,而且內(nèi)部持有了Activity,所以會導(dǎo)致Activity無法釋放,就會造成內(nèi)存泄漏了。
- 單例模式導(dǎo)致的內(nèi)存泄露
單例模式一般引起內(nèi)存泄露的原因單例模式的對象持有對Activity的引用,而單例模式的特點就是其生命周期和Application是保持一致的,因為這個特點,所以會導(dǎo)致Activity對象無法被及時的釋放,從而導(dǎo)致內(nèi)存泄露。
- 屬性動畫導(dǎo)致的內(nèi)存泄露
從Android 3.0開始,Google提供了屬性動畫,屬性動畫中有一類是無限循環(huán)的動畫,如果在Activity中播放此類動畫且沒有在onDestroy()中去停止動畫,那么動畫將會一直播放下去,盡管已經(jīng)無法在界面上看到播放效果了,而且這個時候Activity的View會被動畫持有,而View又持有了Activity,這樣就會導(dǎo)致Activity無法被最終釋放,引起內(nèi)存泄露。解決辦法就是在Activity的onDestroy()調(diào)用animator.cancel()來停止動畫。
響應(yīng)速度優(yōu)化
響應(yīng)速度優(yōu)化的核心思想就是避免在主線程中做耗時操作,比如網(wǎng)絡(luò)請求之類的,但是有時候的確有很多好吃操作,怎么辦呢,可以將這些好吃操作放在主線程中執(zhí)行,即采用異步的方式去進行耗時操作。響應(yīng)速度過慢更多的體現(xiàn)在Activity的啟動速度上面,如果在主線程中做太多事情,會導(dǎo)致Activity啟動時出現(xiàn)黑屏現(xiàn)象,甚至出現(xiàn)ANR(Android Not Responding),就是應(yīng)用程序無響應(yīng)。Android規(guī)定,Activity如果在5秒鐘之類無法響應(yīng)屏幕觸摸事件或者鍵盤輸入的話就會出現(xiàn)ANR,而BroadcastReceiver如果10秒鐘之類還未執(zhí)行完操作的話也會出現(xiàn)ANR。那么怎么定位問題呢?其實,當ANR發(fā)生了之后,系統(tǒng)會在/data/anr 目錄下創(chuàng)建一個文件traces.txt,通過分析這個文件就能定位ANR的原因了,具體怎么分析,以后單獨寫一篇文章進行講解。
ListView和Bitmap優(yōu)化
ListView的優(yōu)化主要是3個方面,首先就是采用ViewHolder并避免在getView中執(zhí)行耗時操作,其次要根據(jù)列表的滑動狀態(tài)來控制任務(wù)的執(zhí)行·頻率,比如當列表快速滑動時顯然是不太適合開啟大量的異步任務(wù)的。最后可以嘗試一下開始硬件加速來讓LIstView更加流暢,這里的優(yōu)化策略也完全適用GridView。
Bitmap的優(yōu)化主要通過BitmapFactory.OPtions來根據(jù)需要對圖片進行采樣,采樣過程中主要用到了BitmapFactory.Options的inSampleSize參數(shù),具體的優(yōu)化以后會單獨寫一篇文章講解。
線程優(yōu)化
線程優(yōu)化的思想就是采用線程池,避免程序中存在大量的Thread,線程池可以重用內(nèi)部的線程,從而避免線程的創(chuàng)建和銷毀所帶來的性能開銷,同時線程池還能有效控制線程池的最大并發(fā)數(shù),避免大量的線程因互相搶占系統(tǒng)資源從而導(dǎo)致阻塞現(xiàn)象的發(fā)生。因此在實際開發(fā)中,我們要盡量采用線程池,而不是每次都要創(chuàng)建一個Thread對象。
一些性能優(yōu)化建議
- 避免創(chuàng)建過多的對象。
- 不要過多的使用枚舉,枚舉占用的內(nèi)存空間比整形要大。
- 常量請使用static final 來修飾。
- 使用一些Android特有的數(shù)據(jù)結(jié)構(gòu),比如SparseArray和Pair等,它們都具有很好的性能。
- 適當使用軟引用和弱引用。
- 采用內(nèi)存緩存和磁盤緩存。
- 盡量采用內(nèi)部靜態(tài)類,這樣可以避免潛在的由于內(nèi)部類而導(dǎo)致的內(nèi)存泄露。
參考
- [任玉剛]Android開發(fā)藝術(shù)探索