如何在Android開發中讓你的代碼更有效率

如何在Android開發中讓你的代碼更有效率

最近看瞭一個視頻,名字叫做Doing More With Less: Being a Good Android Citizen,主要是講如何用少少的幾句代碼來改善Android App的性能。在這個視頻裡面,演講者以一個圖片app為例講解如何應用Android中現有的東西來改善app性能問題。

這個圖片app的代碼:https://github.com/penkzhou/iogallery。ppt:https://greenrobot.qiniudn.com/103.pdf。

現在我將視頻裡面的內容記錄如下:

使用LruCache避免OOM

首先我們的圖片app是用來展示手機裡面保存的圖片。當app裡面需要展示大量的圖片的時候,我們需要將這些圖片從disk加載到內存當中。如果我們來回地滑動activity,系統會重復許多disk I/O;而且在一個activity裡面同時加載多張圖片將會占用大量內存,造成系統內存緊張,進而影響用戶體驗。

如何使用LruCache

這個時候我們可以引入LruCache,將我們最常用的圖片緩存到內存裡面,這樣可以避免大量重復的disk I/O,還可以讓app加載圖片時內存占用不超過設定值。具體代碼如下:

 

//這裡假設通過picture的id(Long)來作為key獲取對應的bitmap
public class PictureCache extends LruCache{

//設定最大的byte值,也就是說整個緩存所能占用的最大內存

public PictureCache(int maxByteSizes){

super(maxByteSizes);

}

// 計算每次添加bitmap的時候,給緩存所添加的數字,默認就是數量,

//這裡因為添加的是bitmap,所以每次添加都是計算bitmap對應的字節數

protected int sizeOf(Long key,Bitmap value){

return value.getByteCount();

}

public void put(Long key, Bitmap value);

public Bitmap get(Long key);

}

 

 

 

如何確定Cache大小

一般我們是通過ActivityManager.getMemorySize()來確定Cache的大小。ActivityManager.getMemorySize()表明瞭在系統正常運行的前提下一個App所占內存的極限。所以我們可以使用它來作為Cache大小的一個衡量,比方如下的代碼中,我們使用它的一半來作為Cache的大小:

 

final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

final int memoryClassBytes = am.getMemoryClass() * 1024 * 1024;

PictureCache mCache = new PictureCache(memoryClassBytes / 2);

 

 

app跑到後臺去瞭

當我們用這個圖片app瀏覽完圖片之後呢,我們回到Android 主界面,開始玩遊戲。大傢都知道,遊戲很耗內存。可能我們在玩的過程中,直接用完瞭剩下可用的所有內存都還不夠,那怎麼辦呢?Android會在這個時候kill一些後臺的app獲取相應的內存。

這裡需要先說一命令。我們如何獲取一個app的內存占用?很簡單,使用 “adb shell procrank”命令行。這個命令行顯示所有系統運行的進程所占內存大小,一般包括Vss、Rss、Pss、Uss,其中Uss對我們來說最重要,Uss表示如果這個進程被系統幹掉瞭,那麼系統可以從這個進程上面獲得多少的可用內存。

好瞭回到之前的情景。Android會kill掉一些後臺程序來供給遊戲所需的內存。假設Android因為內存緊張kill掉瞭圖片app,我在玩瞭一會遊戲之後又打開瞭圖片app。這個時候圖片app又要重新佈局,重新加載圖片,整個體驗對用戶來說非常不好。有什麼辦法讓Android在kill應用之前通知app一聲,好讓app有所準備?

這個時候我們就要用到ComponentCallbacks2,詳情看文檔。app在系統處於不同的內存環境時會有相應的callback,我們隻需在activity裡面重寫這個onTrimMemory方法即可,具體示例代碼如下:

 

public void onTrimMemory(int level) {

super.onTrimMemory(level);

if (level >= TRIM_MEMORY_MODERATE) { // 60

// 這個app已經進入後臺有一段時間瞭,基本上表示用戶接下來

//不會重新打開這個app,我們可以清掉所有緩存所占內存

Log.v(TAG, evicting entire thumbnail cache);

mCache.evictAll();

} else if (level >= TRIM_MEMORY_BACKGROUND) { // 40

// 表示app剛進入後臺,我們可以縮減一部分緩存所占內存

// 來保證其他前臺app的內存需要

Log.v(TAG, evicting oldest half of thumbnail cache);

mCache.trimToSize(mCache.size() / 2);

}

}

 

 

 

這個callback隻是建議,不一定會被系統調用。系統在內存緊張的時候可能會直接kill掉app而不去調用這個callback。但是如果這些callback可以調用的話,這將大大地提升我們app的用戶體驗。

 

善用Android自帶容器

現在我們需要為這個app添加一些新特性,我們要讓這個app可以進行收藏操作(為圖片添加一個是否被收藏的屬性即可)。我們會一次性收藏多張圖片,那麼我們可以使用GridView的多選模式。按照常理來說,我們可以使用HashMap()來存儲哪些照片需要被收藏。不過這裡使用HashMap有點大材小用,效率不高。我們可以使用SparseBooleanArray等Android特有的容器來代替HashMap,節省系統開銷(主要是autoboxing帶來的開銷)

 

SQLite中讀寫操作優化

當我們獲得瞭要收藏的圖片信息(保存在SparseBooleanArray中)之後,我們需要講這些數據保存在SQLite當中,示例代碼如下:

 

SQLiteDatabase db;

long[] mPhotoIds;

ContentValues values = new ContentValues();

for (long photoId : mPhotoIds) {

values.put(COLUMN_ID, photoId);

values.put(COLUMN_FAVORITE, favorite);

db.insert(TABLE_FAVORITE, null, values);

}

 

 

上面的代碼中,每次insert都有開銷。這個時候我們可以考慮使用transaction。代碼如下:

 

SQLiteDatabase db;

long[] mPhotoIds;

ContentValues values = new ContentValues();

db.beginTransaction();

try{

for (long photoId : mPhotoIds) {

values.put(COLUMN_ID, photoId);

values.put(COLUMN_FAVORITE, favorite);

db.insert(TABLE_FAVORITE, null, values);

}

db.setTransactionSuccessful();

}finally{

db.endTransaction();

}

 

 

但是使用這個transaction的時候,db會被鎖住,而碰到更重要的操作隻能等待。碰到這種情況怎麼辦?使用db.yieldIfContendedSafely,這個方法表示我現在執行我的多次數據庫操作,如果碰到其他的數據庫操作,我先讓別的操作完 再執行我的操作。具體代碼如下:

 

SQLiteDatabase db;

long[] mPhotoIds;

ContentValues values = new ContentValues();

db.beginTransaction();

try{

for (long photoId : mPhotoIds) {

values.put(COLUMN_ID, photoId);

values.put(COLUMN_FAVORITE, favorite);

db.insert(TABLE_FAVORITE, null, values);

db.yieldIfContendedSafely();

}

db.setTransactionSuccessful();

}finally{

db.endTransaction();

}

 

善用Broadcast

如果我們需要對用戶新增的照片默認添加濾鏡效果,怎麼實現?很好辦啦,因為Android在API14的時候添加瞭一個新的Broadcast Intent,叫做”android.hardware.action.NEW_PICTURE”,就是在系統新增瞭照片時,我們截獲這個intent就可以瞭。然後我們就在相應的BroadcastReceiver裡面進行處理,相關代碼如下:

 

public void onReceive(Context context, Intent intent) {

if (isAutoApplyEnabled) {

// 執行相應的濾鏡操作

final Intent serviceIntent = new Intent(context, EffectService.class);

serviceIntent.setData(intent.getData());

context.startService(serviceIntent);

} else {

Log.d(TAG, Processed no-op broadcast!);

}

}

 

上面的isAutoApplyEnabled變量表示系統是否開啟自動對新增照片進行濾鏡的操作。那如果關閉默認濾鏡功能,即將isAutoApplyEnabled的值設為false呢?結果就是每次新增照片都會有intent過來,隻是在onReceive方法裡面沒有做操作。這樣造成的結果就是每次intent都會被系統傳遞,隻是走瞭不同的分支,系統照樣消耗資源。

因此比較好的方案就是系統中isAutoApplyEnabled變量的值在變化的時候,我們需要相應地對BroadcastReceiver進行開閉操作。具體需要用到PackageManage.setComponentEnabledSetting()方法。

最後的幾句話

本文以一個圖片app為背景講述瞭Android開發中官方推薦的小tip。瞭解瞭這些可以讓你的app性能更上一個臺階。

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *