android常見的效能優化方面的總結

韓明澤發表於2019-02-12

年假即將結束,這篇文章也算是我自己梳理android知識的最後幾篇了。文章中的整體思路是根據《android開發藝術》結合平時開發經驗以及網上的資料完成的。內容用的原始碼都可以在GitHub上的專案中檢視到,希望閱讀完這篇文章能讓你有所收穫。

專案原始碼

目錄

  • 佈局優化
  • 繪製優化
  • 記憶體洩漏優化
  • ListView和Bitmap優化

佈局優化

  • 減少佈局檔案的層級
  • 刪除佈局中無用的控制元件和佈局
  • 儘量使用簡單高效的ViewGroup,比如FrameLayoutLinaerLayout
  • 可以使用include標籤複用佈局,使用merge標籤減少層級

include、merge標籤案例

在layout檔案中建立layout/incloude_merge_memory.xml檔案內容如下:

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

    <TextView
        android:id="@+id/mTV_incloud_merge"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_red_light"
        android:gravity="center"
        android:padding="5dp"
        android:text="這是一個include的merge" />
</merge>
複製程式碼

在Activity的layout佈局引入:

<include layout="@layout/incloude_merge_memory" />
複製程式碼

ViewStub

  • 它是一個輕量級的佈局寬度、高度只有0,不參與繪製過程。
  • 按需載入,不佔用空間。
  • 當顯示ViewStub中的佈局時候,ViewStub會被替換掉,並且會被從佈局中移除。

xml程式碼:

<ViewStub
    android:id="@+id/mVS_layoutMemory"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:background="@android:color/holo_blue_bright"
    android:inflatedId="@id/mRL_viewStubMemory"
    android:layout="@layout/viewsutub_memory"
    android:padding="10dp" />
複製程式碼

上面程式碼中id為mVS_layoutMemory為ViewStub的id,而inflatedId是引入佈局@layout/viewsutub_memory跟佈局的id。需要注意的是ViewStub中layout佈局是不支援merge標籤的,接下來看一下java程式碼的呼叫:

mVS_layoutMemory = findViewById(R.id.mVS_layoutMemory);
mVS_layoutMemory.setVisibility(View.VISIBLE);
複製程式碼

繪製優化

  • 不要在onDraw中建立新的佈局物件
  • 不要在onDraw中做大量的耗時操作

記憶體洩漏優化

  • 靜態變數引起的洩漏
  • 單例模式引起的洩漏
  • 非靜態內部類持有外部引用導致的洩漏
  • Handler引起的記憶體洩漏
  • 屬性動畫引起的洩漏

靜態變數導致的記憶體洩漏

這種情況常見的是Context的使用,比如我們寫了一個工具類,裡面的方法需要用到Context。如果我們將Activity的this傳給這個方法,那麼Activity在被回收的時候由於這個靜態變數持有Activity的引用,導致不能被回收從而引起記憶體洩漏。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_layoyt_memory);
  AppUtil.getTesLeak(this);
}
static Context context;
public static void getTesLeak(Context context) {
    context=context;
    Toast.makeText(context, "您的記憶體洩漏啦", Toast.LENGTH_SHORT).show();
}
複製程式碼

解決上面的問題也很多簡單,如果我們的工具類不是一定需要Activity的Context,難麼我們可以考慮使用getApplicationContext()。因為getApplicationContext()是和我們App的生命週期一樣長,如果App不退出他就不會被回收。

單例模式導致的記憶體洩漏

單例引起的記憶體洩漏,大概思路上面差不多,也是因為靜態變數的生命週期太長,如果程式不退出,系統就不會對其回收,這將導致本應該不用的物件不能回收,我們可以指定Context為getApplicationContext();來避免記憶體洩漏。

public class MemorySingle {
    //如果傳入上下文
    private static Context context;
    private MemorySingle() {
    }

    public static MemorySingle getInstance(Context context) {
        //防止記憶體洩漏
        MemorySingle.context = context.getApplicationContext();
        return Menory.single;
    }
    
    static class Menory {
        private static final MemorySingle single = new MemorySingle();
    }
}
複製程式碼

非靜態內部類持有外部引用引起的洩漏

因為非靜態內部類的生命週期是和外部類的生命週期繫結在一起的,非靜態內部類會持有外部類的引用,如果我們在內部類中做一些耗時操作,如下面內部類sleepThread()方法讓執行緒睡10秒,在這個時候如果Activy要銷燬,但是因為內部類持有外部類的引用,它的sleepThread()方法還沒執行完,所以導致Activy不能被回收,引起記憶體洩漏。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_layoyt_memory);
    TestLeak testLeak = new TestLeak();
    testLeak.sleepThread();
}
 class TestLeak {
    private void sleepThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //睡10秒
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
複製程式碼

解決方法將TestLeak改成靜態內部類

static class TestLeak {
    private void sleepThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //睡10秒
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
複製程式碼

Handler引起的記憶體洩漏

我們使用Handler做訊息處理的時候可能不注意會用下面這種寫法:

private Handler mHanlder = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 200:
                mTV_incloud_merge.setText((String) msg.obj);
                break;
        }
        super.handleMessage(msg);
    }
};
複製程式碼

上面的mHanlder是Handler的非靜態匿名內部類,上面我們提到過非靜態匿名內部類會持有外部引用,所以如果使用上面的寫法也會引起記憶體洩漏。下面有兩種方式可以避免洩漏。

第一種方式: 使用靜態內部類+弱引用方式

static class MyHanlder extends Handler {
    //弱引用
    WeakReference<Activity> mWeakRef;
    public MyHanlder(Activity activity) {
        mWeakRef = new WeakReference<>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case 200:
                LayoutMemoryActivity activity = (LayoutMemoryActivity) mWeakRef.get();
                activity.mTV_incloud_merge.setText((String) msg.obj);
                break;
        }
    }
}
複製程式碼

第二種: Handler.Callback方式處理訊息

Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case 200:
                mTV_incloud_merge.setText((String) msg.obj);
                break;
        }
        return false;
    }
})
複製程式碼

屬性動畫導致的記憶體洩漏

當一個Activy中有一個無限迴圈的屬性動畫,在Activy銷燬的時候沒有停止動畫也會引起記憶體洩漏

 ObjectAnimator oA = ObjectAnimator.ofFloat(mBnt_layoutMemory, "rotation", 0, 360).setDuration(20000);
               oA.start();
複製程式碼

上面的是一個按鈕選裝動畫,20秒後執行完,如果在動畫還為執行完的時候銷燬Activy,將會導致Activy無法釋放引起記憶體洩漏。下面是解決辦法

@Override
protected void onDestroy() {
    super.onDestroy();
    //取消動畫
    oAnimator.cancel();
}
複製程式碼

ListView和Bitmap優化

ListView優化

ListView的優化是一個很長見的問題,主要是通過ViewHolder實現對item的複用,這裡不做過多的解釋了。在這我推薦一篇文章感興趣的可以看看,下面有一個例子:

@Override
public View getView(final int position, View convertView,        ViewGroup parent) {
    MyHolder myHolder = null;
    //判斷是否有快取佈局
    if (convertView == null) {
        convertView =LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, null);
        myHolder = new MyHolder(convertView);
        convertView.setTag(position);
    } else {
        //得到快取佈局
        myHolder = (MyHolder) convertView.getTag();
    }
    myHolder.mTV_test1.setText(textContent);
}          
class MyHolder {
    TextView mTV_test1;

    MyHolder(View view) {
        mTV_test1 = view.findViewById(android.R.id.text1);
    }
}
複製程式碼

Bitmap優化

我們大部分圖片處理是使用glide、'picasso',這些框架在圖片載入速度和效能優化方面已經很好了,但有些特殊情況可能需要我們自己實現圖片的處理,主要注意下面幾個方面。

  • 對圖片進行壓縮
  • 快取策略
  • 圖片不使用的時候要記得釋放

總結

android的效能優化需要了解的方面還有很多比如電量的優化、包大小、啟動速度的優化等等,上面列出的只是一部分常見的問題和解決辦法。在開發過程中需要優化的放要遠比上面寫道的多,還需要我們自己多積累經驗和結合實際考慮來優化。

參考

Android 記憶體洩漏總結 Android效能優化的淺談

相關文章