Android性能优化之内存优化
除了基本使用外,我們還可以自定義處理結果,首先,繼承DisplayLeakService實現一個自定義的監控處理Service,代碼如下:
public class LeakCnaryService extends DisplayLeakServcie {
private final String TAG = “LeakCanaryService”;
@Override
protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
…
}
}
重寫 afterDefaultHanding 方法,在其中處理需要的數據,三個參數的定義如下:
- heapDump:堆內存文件,可以拿到完整的hprof文件,以使用MAT分析。
- result:監控到的內存狀態,如是否泄漏等。
- leakInfo:leak trace詳細信息,除了內存泄漏對象,還有設備信息。
然后在install時,使用自定義的LeakCanaryService即可,代碼如下:
public class BaseApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
mRefWatcher = LeakCanary.install(this, LeakCanaryService.calss, AndroidExcludedRefs.createAppDefaults().build());
}
…
}
經過這樣的處理,就可以在LeakCanaryService中實現自己的處理方式,如豐富的提示信息,把數據保存在本地、上傳到服務器進行分析。
注意
LeakCanaryService需要在AndroidManifest中注冊。
四、優化內存空間
1、對象引用
從Java 1.2版本開始引入了三種對象引用方式:SoftReference、WeakReference 和 PhantomReference 三個引用類,引用類的主要功能就是能夠引用但仍可以被垃圾回收器回收的對象。在引入引用類之前,只能使用Strong Reference,如果沒有指定對象引用類型,默認是強引用。下面,我們就分別來介紹下這幾種引用。
1、強引用
如果一個對象具有強引用,GC就絕對不會回收它。當內存空間不足時,JVM會拋出OOM錯誤。
2、軟引用
如果一個對象只具有軟引用,則內存空間足夠,GC時就不會回收它;如果內存不足,就會回收這些對象的內存。可用來實現內存敏感的高速緩存。
軟引用可以和一個ReferenceQueue(引用隊列)聯合使用,如果軟引用引用的對象被垃圾回收器回收,JVM會把這個軟引用加入與之關聯的引用隊列中。
3、弱引用
在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間是否足夠,都會回收它的內存。不過,由于垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。
這里要注意,可能需要運行多次GC,才能找到并釋放弱引用對象。
4、虛引用
只能用于跟蹤即將對被引用對象進行的收集。虛擬機必須與ReferenceQueue類聯合使用。因為它能夠充當通知機制。
2、減少不必要的內存開銷
1、AutoBoxing
自動裝箱的核心就是把基礎數據類型轉換成對應的復雜類型。在自動裝箱轉化時,都會產生一個新的對象,這樣就會產生更多的內存和性能開銷。如int只占4字節,而Integer對象有16字節,特別是HashMap這類容器,進行增、刪、改、查操作時,都會產生大量的自動裝箱操作。
檢測方式
使用TraceView查看耗時,如果發現調用了大量的integer.value,就說明發生了AutoBoxing。
2、內存復用
對于內存復用,有如下四種可行的方式:
- 資源復用:通用的字符串、顏色定義、簡單頁面布局的復用。
- 視圖復用:可以使用ViewHolder實現ConvertView復用。
- 對象池:顯示創建對象池,實現復用邏輯,對相同的類型數據使用同一塊內存空間。
- Bitmap對象的復用:使用inBitmap屬性可以告知Bitmap解碼器嘗試使用已經存在的內存區域,新解碼的bitmap會嘗試使用之前那張bitmap在heap中占據的pixel data內存區域。
3、使用最優的數據類型
1、HashMap與ArrayMap
HashMap是一個散列鏈表,向HashMap中put元素時,先根據key的HashCode重新計算hash值,根據hash值得到這個元素在數組中的位置,如果數組該位置上已經存放有其它元素了,那么這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最后加入的放在鏈尾。如果數組該位置上沒有元素,就直接將該元素放到此數組中的該位置上。也就是說,向HashMap插入一個對象前,會給一個通向Hash陣列的索引,在索引的位置中,保存了這個Key對象的值。這意味著需要考慮的一個最大問題是沖突,當多個對象散列于陣列相同位置時,就會有散列沖突的問題。因此,HashMap會配置一個大的數組來減少潛在的沖突,并且會有其他邏輯防止鏈接算法和一些沖突的發生。
ArrayMap提供了和HashMap一樣的功能,但避免了過多的內存開銷,方法是使用兩個小數組,而不是一個大數組。并且ArrayMap在內存上是連續不間斷的。
總體來說,在ArrayMap中執行插入或者刪除操作時,從性能角度上看,比HashMap還要更差一些,但如果只涉及很小的對象數,比如1000以下,就不需要擔心這個問題了。因為此時ArrayMap不會分配過大的數組。
此外,Android自身還提供了一系列優化過后的數據集合工具類,如 SparseArray、SparseBooleanArray、LongSparseArray,使用這些API可以讓我們的程序更加高效。HashMap 工具類會相對比較 低效,因為它 需要為每一個鍵值對都提供一個對象入口,而 SparseArray 就 避免 掉了 基本數據類型轉換成對象數據類型的時間。
2、使用 IntDef和StringDef 替代枚舉類型
使用枚舉類型的dex size是普通常量定義的dex size的13倍以上,同時,運行時的內存分配,一個enum值的聲明會消耗至少20bytes。
枚舉最大的優點是類型安全,但在Android平臺上,枚舉的內存開銷是直接定義常量的三倍以上。所以Android提供了注解的方式檢查類型安全。目前提供了int型和String型兩種注解方式:IntDef和StringDef,用來提供編譯期的類型檢查。
注意
使用IntDef和StringDef需要在Gradle配置中引入相應的依賴包:
compile ‘com.android.support:support-annotations:22.0.0’
3、LruCache
最近最少使用緩存,使用強引用保存需要緩存的對象,它內部維護了一個由LinkedHashMap組成的雙向列表,不支持線程安全,LruCache對它進行了封裝,添加了線程安全操作。當其中的一個值被訪問時,它被放到隊列的尾部,當緩存將滿時,隊列頭部的值(最近最少被訪問的)被丟棄,之后可以被GC回收。
除了普通的get/set方法之外,還有sizeOf方法,它用來返回每個緩存對象的大小。此外,還有entryRemoved方法,當一個緩存對象被丟棄時調用的方法,當第一個參數為true:表明緩存對象是為了騰出空間而被清理。否則,表明緩存對象的entry是被remove移除或者被put覆蓋。
注意
分配LruCache大小時應考慮應用剩余內存有多大。
4、圖片內存優化
在Android默認情況下,當圖片文件解碼成位圖時,會被處理成32bit/像素。紅色、綠色、藍色和透明通道各8bit,即使是沒有透明通道的圖片,如JEPG隔世是沒有透明通道的,但然后會處理成32bit位圖,這樣分配的32bit中的8bit透明通道數據是沒有任何用處的,這完全沒有必要,并且在這些圖片被屏幕渲染之前,它們首先要被作為紋理傳送到GPU,這意味著每一張圖片會同時占用CPU內存和GPU內存。下面,我總結了減少內存開銷的幾種常用方式,如下所示:
1、設置位圖的規格:當顯示小圖片或對圖片質量要求不高時可以考慮使用RGB_565,用戶頭像或圓角圖片一般可以嘗試ARGB_4444。通過設置inPreferredConfig參數來實現不同的位圖規格,代碼如下所示:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
BitmapFactory.decodeStream(is, null, options);
2、inSampleSize:位圖功能對象中的inSampleSize屬性實現了位圖的縮放功能,代碼如下所示:
BitampFactory.Options options = new BitmapFactory.Options();
// 設置為4就是寬和高都變為原來1/4大小的圖片
options.inSampleSize = 4;
BitmapFactory.decodeSream(is, null, options);
3、inScaled,inDensity和inTargetDensity實現更細的縮放圖片:當inScaled設置為true時,系統會按照現有的密度來劃分目標密度,代碼如下所示:
BitampFactory.Options options = new BitampFactory.Options();
options.inScaled = true;
options.inDensity = srcWidth;
options.inTargetDensity = dstWidth;
BitmapFactory.decodeStream(is, null, options);
上述三種方案的缺點:使用了過多的算法,導致圖片顯示過程需要更多的時間開銷,如果圖片很多的話,就影響到圖片的顯示效果。最好的方案是結合這兩個方法,達到最佳的性能結合,首先使用inSampleSize處理圖片,轉換為接近目標的2次冪,然后用inDensity和inTargetDensity生成最終想要的準確大小,因為inSampleSize會減少像素的數量,而基于輸出密碼的需要對像素重新過濾。但獲取資源圖片的大小,需要設置位圖對象的inJustDecodeBounds值為true,然后繼續解碼圖片文件,這樣才能生產圖片的寬高數據,并允許繼續優化圖片。總體的代碼如下所示:
BitmapFactory.Options options = new BitampFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
options.inScaled = true;
options.inDensity = options.outWidth;
options.inSampleSize = 4;
Options.inTargetDensity = desWith * options.inSampleSize;
options.inJustDecodeBounds = false;
BitmapFactory.decodeStream(is, null, options);
5、inBitmap
可以結合LruCache來實現,在LruCache移除超出cache size的圖片時,暫時緩存Bitamp到一個軟引用集合,需要創建新的Bitamp時,可以從這個軟引用集合中找到最適合重用的Bitmap,來重用它的內存區域。
需要注意,新申請的Bitmap與舊的Bitmap必須有相同的解碼格式,并且在Android 4.4之前,只能重用相同大小的Bitamp的內存區域,而Android 4.4之后可以重用任何bitmap的內存區域。
6、圖片放置優化
只需要UI提供一套高分辨率的圖,圖片建議放在drawable-xxhdpi文件夾下,這樣在低分辨率設備中圖片的大小只是壓縮,不會存在內存增大的情況。如若遇到不需縮放的文件,放在drawable-nodpi文件夾下。
7、在App可用內存過低時主動釋放內存
在App退到后臺內存緊張即將被Kill掉時選擇重寫 onTrimMemory/onLowMemory 方法去釋放掉圖片緩存、靜態緩存來自保。
8、item被回收不可見時釋放掉對圖片的引用
- ListView:因此每次item被回收后再次利用都會重新綁定數據,只需在ImageView onDetachFromWindow的時候釋放掉圖片引用即可。
- RecyclerView:因為被回收不可見時第一選擇是放進mCacheView中,這里item被復用并不會只需bindViewHolder來重新綁定數據,只有被回收進mRecyclePool中后拿出來復用才會重新綁定數據,因此重寫Recycler.Adapter中的onViewRecycled()方法來使item被回收進RecyclePool的時候去釋放圖片引用。
9、避免創作不必要的對象
例如,我們可以在字符串拼接的時候使用StringBuffer,StringBuilder。
10、自定義View中的內存優化
例如,在onDraw方法里面不要執行對象的創建,一般來說,都應該在自定義View的構造器中創建對象。
11、其它的內存優化注意事項
除了上面的一些內存優化點之外,這里還有一些內存優化的點我們需要注意,如下所示:
- 盡使用static final 優化成員變量。
- 使用增強型for循環語法。
- 在沒有特殊原因的情況下,盡量使用基本數據類型來代替封裝數據類型,int比Integer要更加有效,其它數據類型也是一樣。
- 在合適的時候適當采用軟引用和弱引用。
- 采用內存緩存和磁盤緩存。
- 盡量采用靜態內部類,可避免潛在由于內部類導致的內存泄漏。
五、圖片管理模塊的設計與實現
在設計一個模塊時,需要考慮以下幾點:
- 1、單一職責
- 2、避免不同功能之間的耦合
- 3、接口隔離
在編寫代碼前先畫好UML圖,確定每一個對象、方法、接口的功能,首先盡量做到功能單一原則,在這個基礎上,再明確模塊與模塊的直接關系,最后使用代碼實現。
1、實現異步加載功能
1.實現網絡圖片顯示
ImageLoader是實現圖片加載的基類,其中ImageLoader有一個內部類BitmapLoadTask是繼承AsyncTask的異步下載管理類,負責圖片的下載和刷新,MiniImageLoader是ImageLoader的子類,維護類一個ImageLoader的單例,并且實現了基類的網絡加載功能,因為具體的下載在應用中有不同的下載引擎,抽象成接口便于替換。代碼如下所示:
public abstract class ImageLoader {
private boolean mExitTasksEarly = false; //是否提前結束
protected boolean mPauseWork = false;
private final Object mPauseWorkLock = new Object();
protected ImageLoader() {
}
public void loadImage(String url, ImageView imageView) {
if (url == null) {
return;
}
BitmapDrawable bitma 《Android學習筆記總結+最新移動架構視頻+大廠安卓面試真題+項目實戰源碼講義》無償開源 徽信搜索公眾號【編程進階路】 pDrawable = null;
if (bitmapDrawable != null) {
imageView.setImageDrawable(bitmapDrawable);
} else {
final BitmapLoadTask task = new BitmapLoadTask(url, imageView);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private class BitmapLoadTask extends AsyncTask<Void, Void, Bitmap> {
private String mUrl;
private final WeakReference imageViewWeakReference;
public BitmapLoadTask(String url, ImageView imageView) {
mUrl = url;
imageViewWeakReference = new WeakReference(imageView);
}
@Override
protected Bitmap doInBackground(Void… params) {
Bitmap bitmap = null;
BitmapDrawable drawable = null;
synchronized (mPauseWorkLock) {
while (mPauseWork && !isCancelled()) {
try {
mPauseWorkLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (bitmap == null
&& !isCancelled()
&& imageViewWeakReference.get() != null
&& !mExitTasksEarly) {
bitmap = downLoadBitmap(mUrl);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled() || mExitTasksEarly) {
bitmap = null;
}
ImageView imageView = imageViewWeakReference.get();
if (bitmap != null && imageView != null) {
setImageBitmap(imageView, bitmap);
}
}
@Override
protected void onCancelled(Bitmap bitmap) {
super.onCancelled(bitmap);
synchronized (mPauseWorkLock) {
mPauseWorkLock.notifyAll();
}
}
}
public void setPauseWork(boolean pauseWork) {
synchronized (mPauseWorkLock) {
mPauseWork = pauseWork;
if (!mPauseWork) {
mPauseWorkLock.notifyAll();
}
}
}
public void setExitTasksEarly(boolean exitTasksEarly) {
mExitTasksEarly = exitTasksEarly;
setPauseWork(false);
}
private void setImageBitmap(ImageView imageView, Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
}
protected abstract Bitmap downLoadBitmap(String mUrl);
}
setPauseWork方法是圖片加載線程控制接口,pauseWork控制圖片模塊的暫停和繼續工作,一般在listView等控件中,滑動時停止加載圖片,保證滑動流暢。另外,具體的圖片下載和解碼是和業務強相關的,因此在ImageLoader中不做具體的實現,只是定義類一個抽象方法。
MiniImageLoader是一個單例,保證一個應用只維護一個ImageLoader,減少對象開銷,并管理應用中所有的圖片加載。MiniImageLoader代碼如下所示:
public class MiniImageLoader extends ImageLoader {
private volatile static MiniImageLoader sMiniImageLoader = null;
private ImageCache mImageCache = null;
public static MiniImageLoader getInstance() {
if (null == sMiniImageLoader) {
synchronized (MiniImageLoader.class) {
MiniImageLoader tmp = sMiniImageLoader;
if (tmp == null) {
tmp = new MiniImageLoader();
}
sMiniImageLoader = tmp;
}
}
return sMiniImageLoader;
}
public MiniImageLoader() {
mImageCache = new ImageCache();
}
@Override
protected Bitmap downLoadBitmap(String mUrl) {
HttpURLConnection urlConnection = null;
InputStream in = null;
try {
final URL url = new URL(mUrl);
urlConnection = (HttpURLConnection) url.openConnection();
in = urlConnection.getInputStream();
Bitmap bitmap = decodeSampledBitmapFromStream(in, null);
return bitmap;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
urlConnection = null;
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
public Bitmap decodeSampledBitmapFromStream(InputStream is, BitmapFactory.Options options) {
return BitmapFactory.decodeStream(is, null, options);
}
}
其中,volatile保證了對象從主內存加載。并且,上面的try …cache層級太多,Java中有一個Closeable接口,該接口標識類一個可關閉的對象,因此可以寫如下的工具類:
public class CloseUtils {
public static void closeQuietly(Closeable closeable) {
if (null != closeable) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
改造后如下所示:
finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
CloseUtil.closeQuietly(in);
}
同時,為了使ListView在滑動過程中更流暢,在滑動時暫停圖片加載,減少系統開銷,代碼如下所示:
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
if (scorllState == AbsListView.OnScrollListener.SCROLL_STAE_FLING) {
MiniImageLoader.getInstance().setPauseWork(true);
} else {
MiniImageLoader.getInstance().setPauseWork(false);
}
}
2 單個圖片內存優化
這里使用一個BitmapConfig類來實現參數的配置,代碼如下所示:
public class BitmapConfig {
private int mWidth, mHeight;
private Bitmap.Config mPreferred;
public BitmapConfig(int width, int height) {
this.mWidth = width;
this.mHeight = height;
this.mPreferred = Bitmap.Config.RGB_565;
}
public BitmapConfig(int width, int height, Bitmap.Config preferred) {
this.mWidth = width;
this.mHeight = height;
this.mPreferred = preferred;
}
public BitmapFactory.Options getBitmapOptions() {
return getBitmapOptions(null);
}
// 精確計算,需要圖片is流現解碼,再計算寬高比
public BitmapFactory.Options getBitmapOptions(InputStream is) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
if (is != null) {
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
options.inSampleSize = calculateInSampleSize(options, mWidth, mHeight);
}
options.inJustDecodeBounds = false;
return options;
}
private static int calculateInSampleSize(BitmapFactory.Options options, int mWidth, int mHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > mHeight || width > mWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > mHeight
&& (halfWidth / inSampleSize) > mWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
然后,調用MiniImageLoader的downLoadBitmap方法,增加獲取BitmapFactory.Options的步驟:
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = urlConnection.getInputStream();
final BitmapFactory.Options options = mConfig.getBitmapOptions(in);
in.close();
urlConnection.disconnect();
urlConnection = (HttpURLConnection) url.openConnection();
in = urlConnection.getInputStream();
Bitmap bitmap = decodeSampledBitmapFromStream(in, options);
優化后仍存在一些問題:
- 1.相同的圖片,每次都要重新加載;
- 2.整體內存開銷不可控,雖然減少了單個圖片開銷,但是在片非常多的情況下,沒有合理管理機制仍然對性能有嚴重影的。
為了解決這兩個問題,就需要有內存池的設計理念,通過內存池控制整體圖片內存,不重新加載和解碼已經顯示過的圖片。
2、實現三級緩存
內存–本地–網絡
1、內存緩存
使用軟引用和弱引用(SoftReference or WeakReference)來實現內存池是以前的常用做法,但是現在不建議。從API 9起(Android 2.3)開始,Android系統垃圾回收器更傾向于回收持有軟引用和弱引用的對象,所以不是很靠譜,從Android 3.0開始(API 11)開始,圖片的數據無法用一種可遇見的方式將其釋放,這就存在潛在的內存溢出風險。 使用LruCache來實現內存管理是一種可靠的方式,它的主要算法原理是把最近使用的對象用強引用來存儲在LinkedHashMap中,并且把最近最少使用的對象在緩存值達到預設定值之前從內存中移除。使用LruCache實現一個圖片的內存緩存的代碼如下所示:
public class MemoryCache {
private final int DEFAULT_MEM_CACHE_SIZE = 1024 * 12;
private LruCache<String, Bitmap> mMemoryCache;
private final String TAG = “MemoryCache”;
public MemoryCache(float sizePer) {
init(sizePer);
}
private void init(float sizePer) {
int cacheSize = DEFAULT_MEM_CACHE_SIZE;
if (sizePer > 0) {
cacheSize = Math.round(sizePer * Runtime.getRuntime().maxMemory() / 1024);
}
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
final int bitmapSize = getBitmapSize(value) / 1024;
return bitmapSize == 0 ? 1 : bitmapSize;
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
super.entryRemoved(evicted, key, oldValue, newValue);
}
};
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public int getBitmapSize(Bitmap bitmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return bitmap.getAllocationByteCount();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
return bitmap.getByteCount();
}
return bitmap.getRowBytes() * bitmap.getHeight();
}
public Bitmap getBitmap(String url) {
Bitmap bitmap = null;
if (mMemoryCache != null) {
bitmap = mMemoryCache.get(url);
}
if (bitmap != null) {
Log.d(TAG, “Memory cache exiet”);
}
return bitmap;
}
public void addBitmapToCache(String url, Bitmap bitmap) {
if (url == null || bitmap == null) {
return;
}
mMemoryCache.put(url, bitmap);
}
public void clearCache() {
if (mMemoryCache != null) {
mMemoryCache.evictAll();
}
}
}
上述代碼中cacheSize百分比占比多少合適?可以基于以下幾點來考慮:
- 1.應用中內存的占用情況,除了圖片以外,是否還有大內存的數據需要緩存到內存。
- 2.在應用中大部分情況要同時顯示多少張圖片,優先保證最大圖片的顯示數量的緩存支持。
- 3.Bitmap的規格,計算出一張圖片占用的內存大小。
- 4.圖片訪問的頻率。
在應用中,如果有一些圖片的訪問頻率要比其它的大一些,或者必須一直顯示出來,就需要一直保持在內存中,這種情況可以使用多個LruCache對象來管理多組Bitmap,對Bitmap進行分級,不同級別的Bitmap放到不同的LruCache中。
2、bitmap內存復用
從Android3.0開始Bitmap支持內存復用,也就是BitmapFactoy.Options.inBitmap屬性,如果這個屬性被設置有效的目標用對象,decode方法就在加載內容時重用已經存在的bitmap,這意味著Bitmap的內存被重新利用,這可以減少內存的分配回收,提高圖片的性能。代碼如下所示:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
mReusableBitmaps = Collections.synchronizedSet(newHashSet<SoftReference>());
}
因為inBitmap屬性在Android3.0以后才支持,在entryRemoved方法中加入軟引用集合,作為復用的源對象,之前是直接刪除,代碼如下所示:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
mReusableBitmaps.add(new SoftReference(oldValue));
}
同樣在3.0以上判斷,需要分配一個新的bitmap對象時,首先檢查是否有可復用的bitmap對象:
public static Bitmap decodeSampledBitmapFromStream(InputStream is, BitmapFactory.Options options, ImageCache cache) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
addInBitmapOptions(options, cache);
}
return BitmapFactory.decodeStream(is, null, options);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
options.inMutable = true;
if (cache != null) {
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
if (inBitmap != null) {
options.inBitmap = inBitmap;
}
}
}
接著,我們使用cache.getBitmapForResubleSet方法查找一個合適的bitmap賦值給inBitmap。代碼如下所示:
// 獲取inBitmap,實現內存復用
public Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
Bitmap bitmap = null;
if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
final Iterator<SoftReference> iterator = mReusableBitmaps.iterator();
Bitmap item;
while (iterator.hasNext()) {
item = iterator.next().get();
if (null != item && item.isMutable()) {
if (canUseForInBitmap(item, options)) {
Log.v(“TEST”, “canUseForInBitmap!!!”);
bitmap = item;
// Remove from reusable set so it can’t be used again
iterator.remove();
break;
}
} else {
// Remove from the set if the reference has been cleared.
iterator.remove();
}
}
}
return bitmap;
}
上述方法從軟引用集合中查找規格可利用的Bitamp作為內存復用對象,因為使用inBitmap有一些限制,在Android 4.4之前,只支持同等大小的位圖。因此使用了canUseForInBitmap方法來判斷該Bitmap是否可以復用,代碼如下所示:
@TargetApi(Build.VERSION_CODES.KITKAT)
private static boolean canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
}
3、磁盤緩存
由于磁盤讀取時間是不可預知的,所以圖片的解碼和文件讀取都應該在后臺進程中完成。DisLruCache是Android提供的一個管理磁盤緩存的類。
1、首先調用DiskLruCache的open方法進行初始化,代碼如下:
public static DiskLruCache open(File directory, int appVersion, int valueCou9nt, long maxSize)
directory一般建議緩存到SD卡上。appVersion發生變化時,會自動刪除前一個版本的數據。valueCount是指Key與Value的對應關系,一般情況下是1對1的關系。maxSize是緩存圖片的最大緩存數據大小。初始化DiskLruCache的代碼如下所示:
private void init(final long cacheSize,final File cacheFile) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (mDiskCacheLock) {
if(!cacheFile.exists()){
cacheFile.mkdir();
}
MLog.d(TAG,“Init DiskLruCache cache path:” + cacheFile.getPath() + “\r\n” + “Disk Size:” + cacheSize);
try {
mDiskLruCache = DiskLruCache.open(cacheFile, MiniImageLoaderConfig.VESION_IMAGELOADER, 1, cacheSize);
mDiskCacheStarting = false;
// Finished initialization
mDiskCacheLock.notifyAll();
// Wake any waiting threads
}catch(IOException e){
MLog.e(TAG,“Init err:” + e.getMessage());
}
}
}
}).start();
}
如果在初始化前就要操作寫或者讀會導致失敗,所以在整個DiskCache中使用的Object的wait/notifyAll機制來避免同步問題。
2、寫入DiskLruCache
首先,獲取Editor實例,它需要傳入一個key來獲取參數,Key必須與圖片有唯一對應關系,但由于URL中的字符可能會帶來文件名不支持的字符類型,所以取URL的MD4值作為文件名,實現Key與圖片的對應關系,通過URL獲取MD5值的代碼如下所示:
private String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance(“MD5”);
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append(‘0’);
}
sb.append(hex);
}
return sb.toString();
}
然后,寫入需要保存的圖片數據,圖片數據寫入本地緩存的整體代碼如下所示:
public void saveToDisk(String imageUrl, InputStream in) {
// add to disk cache
synchronized (mDiskCacheLock) {
try {
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (InterruptedException e) {}
}
String key = hashKeyForDisk(imageUrl);
MLog.d(TAG,“saveToDisk get key:” + key);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (in != null && editor != null) {
// 當 valueCount指定為1時,index傳0即可
OutputStream outputStream = editor.newOutputStream(0);
MLog.d(TAG, “saveToDisk”);
if (FileUtil.copyStream(in,outputStream)) {
MLog.d(TAG, “saveToDisk commit start”);
editor.commit();
MLog.d(TAG, “saveToDisk commit over”);
} else {
editor.abort();
MLog.e(TAG, “saveToDisk commit abort”);
}
總結
以上是生活随笔為你收集整理的Android性能优化之内存优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022最新最全MyBatis(简单全面
- 下一篇: 三角函数π/2转化_高中数学:三角函数知