淺談 Android 記憶體溢位與記憶體洩漏
概念
記憶體溢位(Out of memory):系統會給每個APP分配記憶體,預設16M記憶體,每個手機廠商的預設值不一樣,當APP所需要的記憶體大於了系統分配的記憶體,就會造成記憶體溢位;記憶體溢位就是分配的記憶體被用光了,不夠用了。
記憶體洩漏(Memory leak):當一個物件不再使用了,本應該被垃圾回收器(GC)回收,但是這個物件由於被其他正在使用的物件所持有,造成無法被回收,導致一部分記憶體一直被佔著。
記憶體洩漏和記憶體溢位的關係:記憶體洩露過多會導致記憶體溢位。記憶體洩露導致一部分記憶體沒有被回收,一直被佔著,可利用記憶體變少了,當洩露過多 時,可利用的記憶體越來越少,就會引起記憶體溢位了。
原因及優化
記憶體溢位
- Bitmap使用不當 1、使用Bitmap物件要用recycle釋放 2、使用軟引用、弱引用 3、使用圖片快取技術(例如:LruCache)
記憶體洩漏
在記憶體比較低的系統上對大量的圖片、音訊、視訊處理 優化: 建議使用第三方,或者JNI來進行處理
Handler使用不當 例如: 在日常開發中我們通常都是這樣定義Handler物件:
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
dosomething();
}
};
但是這樣也存在著一個隱藏的問題:在Activity中使用Handler建立匿名內部類會隱式的持有外部Activity物件的引用,當子執行緒使用Handler暫時無法完成非同步任務時,handler物件無法銷燬,同時由於隱式的持有activity物件的引用,造成activity物件以及相關的元件與資原始檔同樣無法銷燬,造成記憶體洩露。我們普通的Handler,平時使用時也是可以不用考慮這些的,但是當你在子執行緒中有耗時操作通知UI時,要考慮到這一點,以免activity結束時子執行緒持有Handler物件,導致Activity無法被釋放。 優化解決方法: 1)使用靜態變數定義Handler
static Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
dosomething();
}
};
這樣的話,handler物件由於是靜態型別無法持有外部activity的引用,但是這樣Handler不再持有外部類的引用,導致程式不允許在Handler中操作activity中的物件了,這時候我們需要在Handler物件中增加一個隊activity的弱引用;
2)Handler使用弱引用
static class MyHandler extends Handler {
WeakReference<Activity > mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}
最後在ondestory方法中將後面的訊息移除 在activity執行onDestory時,判斷是否有handler已經執行完成,否則做相關邏輯操作;
mhanHandler.removeCallbacksAndMessages(null);
- Context物件使用不當 例如自定義單例物件
public class Single {
private static Single instance;
private Context context;
private Object obj = new Object();
private Single(Context context) {
this.context = context;
}
public static Single getInstance(Context context) {
if (instance == null) {
synchronized(obj) {
if (instance == null) {
instance = new Single(context);
}
}
}
return instance;
}
}
優化解決方法:使用全域性的的Context或者context.getApplication(); 單列模式應該儘量少持有生命週期不同的外部物件,一旦持有該物件的時候,必須在該物件的生命週期結束前制null
- 非靜態內部類,靜態例項化 static關鍵字修飾的變數由於生命週期過長,容易造成記憶體洩漏 例如:
public class MyActivity extends AppCompatActivity {
/** 靜態成員變數*/
public static InnerClass innerClass = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
innerClass = new InnerClass();
}
class InnerClass {
public void test() {
//TODO ...
}
}
}
這裡內部類InnerClass隱式的持有外部類MyActivity的引用,而在MyActivity的onCreate方法中呼叫了 innerClass = new InnerClass();
這樣innerClass就會在MyActivity建立的時候是有了他的引用,而innerClass是靜態型別的不會被垃圾回收,MyActivity在執行onDestory方法的時候由於被innerClass持有了引用而無法被回收,所以這樣MyActivity就總是被innerClass持有而無法回收造成記憶體洩露。
優化解決方法:儘量少使用靜態變數,一定要使用要及時進行制null處理
屬性動畫或迴圈動畫使用不當 優化解決方法:在Activity中使用了屬性迴圈動畫,在onDestroy()方法中未正確停止動畫
BraodcastReceiver、File、Cursor等資源的使用未及時關閉 優化解決方法:在銷燬activity時,應該及時銷燬或者回收
框架使用了註冊方法而未反註冊 例如: 使用的事件匯流排框架-EventBus,當我們需要註冊某個Activity時需要在onCreate中:
EventBus.getDefault().register(this);
然後這樣之後就沒有其他操作的話就會出現記憶體洩露的情況,因為EventBus物件會是有該Activity的引用,即使執行了改Activity的onDestory方法,由於被EventBus隱式的持有了該物件的引用,造成其無法被回收。 優化解決方法:需要在onDestory方法中執行反註冊:
EventBus.getDefault().unregister(this);