編輯:關於Android編程
最近工作中,遇到了幾個內存優化的問題,1.應用退出後,此應用進程保持了不少內存得不到釋放,用工具強制gc也無法釋放。2.應用進入某些頁面瞬間請求分配內存過大。此兩個問題對於有經驗的開發者很容易猜測一個是內存洩露,一個是圖片之類的資源問題。下面來寫一個例子分析一下這兩個問題
第一個例子是Volley加載圖片的app,當此app退出時緩存釋放問題
Application類
package demo.memory.com.memorydemo;
import android.app.Application;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
public class MyApplication extends Application{
RequestQueue mRequestQueue;
private static MyApplication mInstance;
public static MyApplication getInstance(){
return mInstance;
}
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
mRequestQueue = Volley.newRequestQueue(this);
}
return mRequestQueue;
}
}
主Activity簡單的跳轉功能
package demo.memory.com.memorydemo;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void jump(View view){
Intent intent = new Intent(this,ShowImageActivity.class);
startActivity(intent);
}
}
package demo.memory.com.memorydemo;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ImageView;
import com.android.volley.toolbox.ImageLoader;
public class ShowImageActivity extends Activity{
private final static String IMAGE1_URL = "http://o6lxzg30h.bkt.clouddn.com/7375cd24ee25d29c81dff09a7375fff1.jpg";
private final static String IMAGE2_URL = "http://o6lxzg30h.bkt.clouddn.com/223412884cpmc7m4j1gof1.jpg";
ImageLoader mImageLoader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.show_image);
mImageLoader = new ImageLoader(MyApplication.getInstance().getRequestQueue(), MyImageCache.getInstance());
ImageView image1Iv = (ImageView)findViewById(R.id.image1_iv);
ImageView image2Iv = (ImageView)findViewById(R.id.image2_iv);
mImageLoader.get(IMAGE1_URL,new MyImageListener(image1Iv));
mImageLoader.get(IMAGE2_URL,new MyImageListener(image2Iv));
}
}
其他工具類
package demo.memory.com.memorydemo;
import android.graphics.Bitmap;
import android.util.LruCache;
import com.android.volley.toolbox.ImageLoader;
public class MyImageCache implements ImageLoader.ImageCache {
private LruCache mMemoryCache;
private static MyImageCache mImageCache;
private MyImageCache() {
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
}
public static MyImageCache getInstance() {
if (mImageCache == null)
mImageCache = new MyImageCache();
return mImageCache;
}
@Override
public void putBitmap(String key, Bitmap value) {
mMemoryCache.put(key, value);
}
@Override
public Bitmap getBitmap(String key) {
return mMemoryCache.get(key);
}
public void clearCache(){
if(mMemoryCache != null){
mMemoryCache.evictAll();
}
}
}
package demo.memory.com.memorydemo;
import android.widget.ImageView;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
public class MyImageListener implements ImageLoader.ImageListener {
private ImageView view;
public MyImageListener(ImageView view){
this.view = view;
}
@Override
public void onErrorResponse(VolleyError error) {}
@Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
view.setImageBitmap(response.getBitmap());
}
}

此時app進程占用內存是10M

退出app,點擊
進行強制gc回收,但是發現此app雖然退出了,但是還占用9M內存,我們知道一個空的應用進程如果沒有被殺死,它占用1.4M左右內存才算正常的,現在是9M顯然是有問題。接下來用工具分析一下這個問題。
首先抓取此時Heap中的信息,因為Java中的內存占用主要在Heap中。點擊
進行抓取,大約等幾十秒中,等待抓取完成會生成一個hprof文件,在Android studio 中的 captures選項夾中可以看到,此文件會被android studio 自動打開如圖

Retained Size表示內存總占用,從圖中可以看出byte[]占用了8M多,從這個大小中我們大約可以可以猜測應該是這個的問題,點擊byte[],從右側的Instance中可以找到兩個特別大的對象,如圖

這兩個對象都占用了4M內存,點擊其中一個對象,在下方的Preference Tree中我們可以定位到 com.memorydemo.MyImageCache類的mMemoryCache成員,看來是這個類的問題,從上面代碼中我們可以看到,mMemoryCache是我們定義的一個圖片緩存對象,為什麼不能被gc回收呢?是因為它是靜態的。
再來通過另一個遠古神器MAT來分析一下,MAT下載地址http://www.eclipse.org/mat/downloads.php
MAT啟動界面如圖

此工具是讀取hprof文件的,和上面的一樣,但是Android studio生成的不是標准的需要轉換一下,在Captures裡右鍵選擇 Export to standard.hprof

轉換後的文件,可以被MAT識別,在MAT的File->Open Heap Dump打開轉換後的文件,選擇Leak Suspects Report ,進入如圖所示界面

我們點擊
此按鈕生成一個histogram,如圖

從圖中可以看到同樣是byte[]占用高,右鍵byte[],--> List objects --> with incoming references

可見是前兩個對象占用過高,右鍵其中一個對象 --> Path to GC Roots --> exclude all phantom/weak/soft etc. references,進入如下界面點開調用棧發現最終定位到了mImageCache,此對象前面有個小黃點,表示它不能被gc回收,前面說了它是個靜態成員

好了通過這兩個工具清晰的說明了,之所以app退出後仍然沒有釋放內存,是因為我們的圖片緩存沒有釋放導致,那麼我們在何時釋放圖片緩存了?當然是在程序的全部UI都退出後。Android 提供了 public void onTrimMemory(int level)方法來監聽此過程,在https://developer.android.com/training/articles/memory.html文檔中的Release memory as memory becomes tight中講解了 此方法各參數的用法,我們這裡只堅挺 level =TRIM_MEMORY_UI_HIDDEN 的情況,此level表示app所有界面已不可見,在Application類中重寫方法onTrimMemory釋放緩存文件
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if(level == TRIM_MEMORY_UI_HIDDEN){
MyImageCache.getInstance().clearCache();
}
}
此時app如果退出,此app進程占用內存值就回到了正常狀態
下一篇來說明一下一個內存洩露問題
Android-支付寶快捷支付
支付寶的快捷支付Android版業務流程比較麻煩,出現的意外情況比較多.在此,簡單說下開發流程以及出現錯誤的解決方案; 1.注冊支付業務.這裡不在贅述.建立數據安全傳輸所
詳解Android中Handler的內部實現原理
本文主要是對Handler和消息循環的實現原理進行源碼分析,如果不熟悉Handler可以參見博文《詳解Android中Handler的使用方法》,裡面對Android為何
Android-Universal-Image-Loader學習筆記(一)
Android-Universal-Image-Loader是一個開源項目,負責處理圖片的加載和緩存。閒暇之時看了一些源代碼,特記錄之。 說道圖片文件(磁盤)緩存,需要
Android Camera探究之路——起步
Android Camera探究之路——起步Camera在手機中有著舉足輕重的地位,不管是二維碼還是照片、識別,都離不開攝像頭,本文將對Andro