1. 程式人生 > >Android的效能優化方法

Android的效能優化方法

佈局優化

佈局優化的思想:儘量減少佈局檔案的層級。佈局的層級少了,這就意味著Android的繪製工作量少了,那麼程式的效能自然提高了。

如何進行佈局優化呢?

  • 刪除佈局中無用的控制元件和層級

  • 有選擇地使用效能較低的ViewGroup,比如RelativeLayout。

    • 如果佈局中既可以使用LinearLayout,也可以使用RelativeLayout,那麼就採用LinearLayout,這是因為RelativeLayout的功能比較複雜,他的佈局過程需要花費更多的CPU時間。
    • FrameLayout和LinearLayout一樣都是簡單高效的ViewGroup,因此優先考慮使用它們。如果需要巢狀的話,建議採用RelativeLayout.
  • 採用標籤、和ViewStub

    • 標籤主要用於佈局重用
    • 標籤一般和配合使用,它可以降低佈局層次
    • ViewStub則提供了按需載入的功能,當需要時才會將ViewStub的佈局載入到記憶體,這提高了程式的初始化效率。

標籤

標籤只支援android:layout_開頭的屬性。不過id是特例。

<include android:id="@+id/new_title"
        anddroid:layout_width="match_parent"
        android:layout_height="match_parent"
        layout="@layout/title"
/>

標籤一般和標籤一起使用從而減少佈局的層級。當包含的佈局與的佈局是同樣的viewGroup,比如LinearLayout。那麼顯然中的佈局的LinearLayout是多餘的,通過標籤就可以去掉多餘的那一層LinearLayout.

ViewStub

ViewStub繼承了View,它非常輕量級且寬/高都是0;因此本身不參與任何的佈局和繪製過程。ViewStub的意義在於:按需要載入所需的佈局檔案,在實際開發中,很多佈局檔案在正常情況下不會顯示,比如網路異常時的介面,這個時候就沒必要再整個介面初始化的時候將其載入進來。因此ViewStub提高了程式初始化的效能。

<ViewStub
    android:id="@+id/stub_import"
    android:inflatedId="@+id/panel_import"
    android:layout="@layout/layout_network_error"
    android:layout_width="match_parent"
    android:layout_height="wrap=content"
    androiyeayout_gravity="bootom"

其中stub_importViewSrub的id,而panel_import是layout_network_error這個佈局的根元素的id.那麼如何做到按需載入呢?有兩種方式:

findViewById(R.id.stub.import).setVisibility(View.VISIBLE);

或者

View importPanel = findViewById(R.id.stub_import).inflate();

當ViewStub通過setVisibility或者inflate方法載入後,ViewStub就會被它內部的佈局替換掉。

繪製優化

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

  • onDraw中不要建立新的區域性物件,這是因為onDraw方法可能會被頻繁呼叫,這樣就會在一瞬間產生大量的臨時物件,不僅佔用了過多的記憶體還會導致系統頻繁gc,降低了程式的執行效率。
  • onDraw方法中不要做耗時的任務,也不能執行上千萬次的迴圈操作,儘管每次操作都很輕量級,但大量迴圈仍然十分搶佔CPU的時間片,這會造成View的繪製過程不流暢。按照Goole效能優化的標準,View的繪製幀率保證60fps是最佳的,這就要求每幀繪製時間不超過16ms(1000/60)這個時間。

記憶體洩漏優化

記憶體優化在開發過程中是一個需要重視的問題,也是開發人員最最容易犯的錯誤之一。記憶體洩漏的優化主要分為兩個方面:

  • 開發過程中避免寫出有記憶體洩漏的程式碼
  • 通過一些分析工具比如MAT來找出潛在的記憶體洩漏繼而解決

場景1:靜態變數導致的記憶體洩漏

下面的這種情形是一種最簡單的記憶體洩漏,下面的程式碼直接導致Acitvity無法正常銷燬,因為靜態變數mContext引用了它。

public class MainActivity extends Activity {
    
    private static Context mContext;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
    }
}

場景2:單例模式導致的記憶體洩漏

單例模式帶來的記憶體洩漏是我們最容易疏忽的。

public class TestManger {
    
    private List<OnDataArrivedListener> mOnDataArrivedListeners = new ArrayList<>();
    
    private static final class SingletonHolder {
        public static final TestManager INSTANCE = new TestManager();
    }
    
    private TestManager {
    }
    
    public static TestManager getInstance() {
        return SingertonHolder.INSTANCE;
    }
    
    public synchronized void registerListener(onDataArrivedListener listener) {
        if(!mOnDataArrivedListeners.contains(listener)) {
            mOnDataArrivedListeners.add(listener);
        }
    }
    
    public synchronized void unregisterListener(OnDataArrivedListener listener) {
        mOndataArrivedListeners.remove(listener);
    }
    
    public interface OnDataArrivedListener {
        void onDataArrived(Objecet data);
    }
}

接著再讓 Activity實現OnDataArrivedListener介面並向TestManager註冊監聽,如下所示。下面的程式碼由於缺少解註冊的操作所以會引起記憶體洩漏,洩漏的原因是:Activity的物件被單例模式的TestManager所持有,而單例模式的特點是其生命週期和Application保持一致,因此Activity物件無法被即時釋放。

protected void onCreate(Bundle savedInstanceState) {
    ·······
    
    TestManager.getInstance.registerListener(this);
}

場景3:屬性動畫導致的記憶體洩漏

屬性動畫中有一類無限迴圈的動畫,如果在Activity中播放此類動畫而沒有在onDestroy中停止動畫,那麼動畫就會一直播放下去,儘管無法在介面上看到動畫效果,並且這時候Activity的View會被動畫持有,而View持有Activity,最終Activity無法釋放。

響應速度優化和ANR日誌分析

響應速度優化的核心思想是:避免在主執行緒中做耗時操作。如果有耗時操作應該放線上程中執行,即採用非同步的方式執行耗時操作。響應速度過慢主要體現在Activity的啟動速度上,如果主執行緒做太多事,會導致Activity啟動時出現黑屏,甚至ANR。**Android規定,Activity如果5秒之內無法響應螢幕觸控事件或者鍵盤輸入事件就會出現ANR,而BroadcastReceive如果10秒之內還未執行操作也會出現ANR。**實際開發中ANR是很難從程式碼中發現的。那麼在開發過程中遇到了ANR,怎麼解決呢?其實當出現ANR後,系統會在/data/anr目錄下建立一個traces.txt,通過分析這個檔案就能定位ANR的原因

ListView和Bitmap優化

  • ListView優化
    • 採用ViewHolder並避免在getView中執行耗時操作
    • 根據列表的滑動狀態來控制任務的執行頻率,比如當列表快速滑動時顯然是不適合開啟大量的非同步任務的
    • 可以嘗試開啟硬體加速使ListView的滑動更加流暢
    • ListView 的優化策略完全適用於GirdView
  • Bitmap的優化:主要通過Bitmap.Options來根據需要對圖片進行取樣,取樣的過程中主要運用到了Bitmap.Options的inSampleSize引數,具體參考筆記Bitmap的高效載入

執行緒優化

執行緒優化的思想為:採用執行緒池,避免程式中存在大量的Thread。執行緒池可以重用內部的執行緒,從而避免了執行緒的建立和銷燬所帶來的效能開銷,同時執行緒池還能有效的控制執行緒池的最大併發數,避免大量的執行緒因互相搶佔系統資源從而導致阻塞現象的發生。因此在實際開發中,要儘量採用執行緒池,而不是每次都要建立一個Thread物件

一些效能優化的建議

  • 避免建立過多的物件
  • 不要過多的使用列舉,列舉佔用的記憶體空間要比整型大
  • 常量使用static final來修飾
  • 使用一些Android特有的資料結構,比如SparseArray和Pair等,它們都具有更好的效能
  • 適當使用軟引用和弱引用
  • 儘量採用靜態內部類,這樣可以避免潛在的由於內部類而導致的記憶體洩漏

注:本筆記來源於《Android開發藝術探索》相關知識