android 性能、內存優化

Android 設備一種移動設備,不管是內存還是CPU都受到限制,必然要考慮性能的設計。文章將從以下方面給出一些建議。

1.佈局優化

減少佈局文件之間的層級。首先要刪除佈局文件中無用的控件和層級,再者選擇使用想能較低的控件。在選擇佈局控件時,LinearLayout/FrameLayout優先,RelativaLayout其次,最後是ViewGroup。還有在佈局文件中採用<include> 標籤、  <merge>標籤、ViewStub。

a.  <include> 標籤:將一個指定的佈局文件加載到當前的佈局文件,其他佈局複用時需要時可以直接通過<include>複用。

b.  <merge>標籤:<merge>標籤一般是和<include> 標籤一起使用的。假設現在當前佈局是一個豎直方向的LinearLayout,抽取的<include> 標籤內也是一個豎直方向的LinearLayout,無疑<include> 標籤內的豎直方向的LinearLayout是多餘的,通過<merge>標籤替換,就可以出去多餘的LinearLayout。

<merge xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="match_parent">

    <Button
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_marginTop="20dp"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_marginTop="20dp"
        />

</merge>

c.  ViewStub: viewStub集成了View輕量級,寬高都是0,本身不會參於任何佈局和繪製,它會在需要的時候去加載。舉例:在網絡異常的時候一般有一個顯示界面,正常情況下不會顯示。如果在開始的時候把它加載進來,很有可能是用不到的,這時可以使用ViewStub. 

<ViewStub
        android:id="@+id/text_viewStub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout=“@layout/hide_layout"/>

text_viewStub是ViewStub的id,hide_layout是隱藏佈局的id,在需要加載隱藏佈局的時候,我們可以在以下兩種方法中任選其一

 ((ViewStub)findViewById(R.id.text_viewStub)).setVisibility(View.VISIBLE);

((ViewStub)findViewById(R.id.text_viewStub)).inflate();

ViewStub不支持<merge>標籤

2. 繪製優化

在View的onDraw方法中避免執行大量的操作。一是在onDraw中避免大量創建臨時對象,可以把這部分對象初始化在構造函數中,比如 Paint類,String 類,以免頻繁觸發 GC。二是在onDraw不要做耗時操作,也不能執行很多的循環操作。

3. 內存優化

內存泄露:在實際開發中,不在用到的對象因爲被錯誤的引用導致無法回收的問題。

內存溢出 OOM(OutOfMemory):每個 APP 的堆(heap)內存大小有硬性限制,如果您的 APP 已達到堆內存限制,並嘗試分配更多的內存,系統會拋出 OutOfMemoryError 。

內存泄露出現的幾種常用方法:

a.靜態類持有導致的內存泄露:

一般來說是當前的一個靜態變量如Context、View、數組等,持有了一個Activity的引用,導致在回收Activity時回收失敗,這種例子比較極端。

public class TextActivity extends Activity {

    private static View view;
    private static Context mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recyclerview_layout);
        mContext = this;
        view = new View(this);
    }
}

b. 單例模式導致的內存泄露

最常應用的一個場景是我們寫了一個單利模式,

和一個List<Activity>來管理所有Activity的退出問題。如果在finish當前界面的時候我們沒有去主動調用單例模式刪除當前的Activty實例,必然會造成內存泄露。在單例模式中持有單例模式的類的生命週期是和Application保持一致的。

public class ActivityManager {

    static ActivityManager mActivityManager;
    List<Activity> mActivities = new ArrayList<>();

    public static synchronized ActivityManager getInstance() {
        if (null == mActivityManager) {
            mActivityManager = new ActivityManager();
        }

        return mActivityManager;
    }

    public void addActivity(Activity activity) {
        if (null != activity) {
            mActivities.add(activity);
        }
    }


    public void removeActivty(Activity activity) {
        if (null != activity) {
            mActivities.remove(activity);
        }
    }
}
public class TextActivity extends Activity {

    private static View view;
    private static Context mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recyclerview_layout);
        ActivityManager.getInstance().addActivity(this);
    }
    
    public void back(){
        finish();
    }

    //遺漏,造成內存溢出
    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityManager.getInstance().removeActivty(this);
    }
}

c. 屬性動畫導致的內存泄露

在android3.0以後屬性動畫添加了一種無限循環的動畫。如果在Activity中播放並且沒有在onDestory中去停止動畫,動畫會一直播放下去。Activity中的View被動畫持有,View又持有Activity.

ObjectAnimator animator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recyclerview_layout);
        TextView mButton = (TextView) findViewById(R.id.tv_app_name);
        animator = ObjectAnimator.ofFloat(mButton, "rotation", 0, 360).setDuration(1000);
        animator.setRepeatMode(ValueAnimator.RESTART);
        animator.setRepeatCount(ValueAnimator.INFINITE);//無線循環
        animator.start();
    }


    //遺漏,造成內存溢出
    @Override
    protected void onDestroy() {
        super.onDestroy();
        animator.cancel();
    }
內存管理的工具:

1)LeakCanary監測內存泄露:https://github.com/square/leakcanary

2)Memory Monitor工具:Android Studio 中的 Android Monitor ,選擇其中的 Memory,跟蹤整個 APP 的內存變化情況

3)Heap Viewer 工具:實時查看App分配的內存大小和空閒內存大小,發現 Memory Leaks 。

詳細使用教程:https://www.kancloud.cn/digest/itfootballprefermanc/100907

4)Allocation Tracker:追蹤內存對象的來源,詳細使用教程:https://www.kancloud.cn/digest/itfootballprefermanc/100908

內存優化的建議:

1)檢查使用內存的數量,做一個閥值:

調用系統 getMemoryInfo() 查詢,返回 一個ActivityManager.MemoryInfo 對象,它提供該設備當前存儲器的狀態信息,包括可用的存儲器,總存儲器,和低於該閾值存儲器。

2)界面不可見時釋放一些內存:

實現 ComponentCallbacks2 API 中 onTrimMemory()) ,當回調參數 level 爲 TRIM_MEMORY_UI_HIDDEN ,是用戶點擊了Home鍵或者Back鍵退出應用,所有UI界面被隱藏,這時候應該釋放一些不可見的時候非必須的資源。

3)謹慎使用服務

離開了 APP 還在運行服務是最糟糕的內存管理錯誤之一,當 APP 處在後臺,我們應該停止服務,除非它需要運行的任務。我們可以使用 JobScheduler 替代實現,JobScheduler 把一些不是特別緊急的任務放到更合適的時機批量處理。如果必須使用一個服務,最佳方法是使用 IntentService ,限制服務壽命,所有請求處理完成後,IntentService 會自動停止。

4)使用一些優化後的數據容器

優化過數據的容器 SparseArray / SparseBooleanArray / LongSparseArray 代替 HashMap 等傳統數據結構,通用 HashMap 的實現可以說是相當低效的內存,因爲它需要爲每個映射一個單獨的條目對象。

5)不要過多的使用枚舉,枚舉佔用的內存空間要比整形大。

6)減少使用靜態變量,靜態常量。

7)cursor關閉:如查詢數據庫的操作,使用到Cursor,也要對Cursor對象及時關閉。

8)監聽器的註銷:Android程序裏面存在需要register與unregister的監聽器,手動添加的listener,要及時刪除這個listener。

9)刪除剔除不需要的代碼,可以使用ProGuard。

10)適當使用軟引用和弱引用。

11)採用內存緩存和磁盤緩存。

12)幀動畫使用注意尺寸的大小和圖片的數量,避免高頻的調用。

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

相應優化速度是避免在主線程中做耗時操作,可以將耗時操作放在線程中去執行,採用異步的方式執行耗時操作。如Activity啓動時有太多事情處理,導致速度太慢會出現黑屏現象,甚至ANR。Activity是5秒鐘無法響應屏幕觸摸事件或鍵盤輸入就會出現ANR,BroadCastReceiver是十秒鐘。出現了ARN,系統會在/data/anr目錄下創建一個文件traces.txt,可以對它分析,找出問題所在。在實際開發中,會出現的一種情況是在子線程與主線程中同時調用了持有同步鎖的方法,導致ANR,需要注意。

5.ListView/GridView/RecyclerView優化

a. 採用ViewHolder。ViewHolder 的關鍵好處是緩存了顯示數據的視圖(View),加快了 UI 的響應速度。ViewHolder模式通過getView()方法返回的視圖的標籤(Tag)中存儲一個數據結構,這個數據結構包含了指向我們要綁定數據的視圖的引用,從而避免每次調用getView()的時候調用findViewById())。(在ListView/GridView重用緩存convertView傳遞給getView()方法來避免填充不必要的視圖)

b. 在ListView/GridView中的getView/RecyclerView的onBindViewHolder方法中避免耗時操作,比如在要加載圖片,可以通過異步的方法實現。

c. 根據列表的滑動狀態來控制執行頻率。監聽控件的滑動狀態,當控件滑動停止時,在發送新的請求加載圖片,滑動過程中不處理。

d. 開啓硬件加速來使ListView/GridView/RecyclerView滑動更流暢。android:hardwareAccelerated=“true”

6.Bitmap優化

Bitmap是內存消耗的大頭,當使用時要及時回收,同時採用BitmapFactory.Options來加載所需要尺寸的圖片。BitmapFactory.Options可以按照一定的採樣率來加載縮小後的圖片,降低內存,避免OOM。

7.線程優化

採用線程池,避免在程序中存在大量的Thread。線程池可以重用內容的線程,避免線程的創建和銷燬帶來的性能開銷,同時線程池還能有效的控制線程的最大併發數,避免大量的線程因互相搶佔資源從而阻塞。

=================================>

參考:1.http://wuxiaolong.me/2017/04/15/memory/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

2.android 開發藝術與探索。

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