內存泄漏彙總
1.基本介紹 ---來自caoweic
1.1什麼是內存泄露?
Android進程中某些對象(垃圾對象)已經沒有使用價值了,但是它們卻可以直接或間接地被引用到,導致無法被GC回收。無用的對象佔據着內存空間,使得實際可使用內存變小,導致應用所需要的內存超過這個系統分配的內存限額,這就造成了內存溢出而導致應用卡頓、最終Crash。
1.2爲什麼會產生內存泄漏?
Android的每個應用程序都會使用一個專有的Dalvik虛擬機實例來運行,它是由Zygote服務進程孵化出來的,也就是說每個應用程序都是在屬於自己的進程中運行的。Android爲不同類型的進程分配了不同的內存使用上限(在不同的設備上會因爲RAM大小不同而各有差異),如果程序在運行過程中出現了內存泄漏的而造成應用進程使用的內存超過了這個上限,則會被系統視爲內存泄漏,從而被kill掉,這使得僅僅自己的進程被kill掉,而不會影響其他進程(如果是system_process等系統進程出問題的話,則會引起系統重啓)。
1.3內存泄露的危害
1.導致用戶手機可用內存變少
2.程序出現卡頓
3.導致應用莫名退出
4.應用程序Force Close
5.用戶流失
2.Android中常見的內存泄漏
2.1資源使用了爲關閉
BroadcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者註銷,否則這些資源將不會被回收,造成內存泄漏。
2.2構造Adapter時,沒有使用緩存的 convertView
在BaseAdapter中提供了方法:
public View getView(intposition, View convertView, ViewGroup parent)
來向ListView提供每一個item所需要的view對象。初始時ListView會從BaseAdapter中根據當前的屏幕布局實例化一定數量的view對象,同時ListView會將這些view對象緩存起來。當向上滾動ListView時,原先位於最上面的list item的view對象會被回收,然後被用來構造新出現的最下面的list item。這個構造過程就是由getView()方法完成的,getView()的第二個形參 View convertView就是被緩存起來的list item的view對象(初始化時緩存中沒有view對象則convertView是null)。
由此可以看出,如果我們不去使用convertView,而是每次都在getView()中重新實例化一個View對象的話,即浪費時間,也造成內存垃圾,給垃圾回收增加壓力,如果垃圾回收來不及的話,虛擬機將不得不給該應用進程分配更多的內存,造成不必要的內存開支。ListView回收list item的view對象的過程可以查看:
android.widget.AbsListView.Java--> void addScrapView(View scrap) 方法。
2.3註冊沒取消造成的內存泄漏
一些Android程序可能引用我們的Anroid程序的對象(比如註冊機制)。即使我們的Android程序已經結束了,但是別的引用程序仍然還有對我們的Android程序的某個對象的引用,泄漏的內存依然不能被垃圾回收。調用registerReceiver後未調用unregisterReceiver。
比如:假設我們希望在鎖屏界面(LockScreen)中,監聽系統中的電話服務以獲取一些信息(如信號強度等),則可以在LockScreen中定義一個PhoneStateListener的對象,同時將它註冊到TelephonyManager服務中。對於LockScreen對象,當需要顯示鎖屏界面的時候就會創建一個LockScreen對象,而當鎖屏界面消失的時候LockScreen對象就會被釋放掉。
但是如果在釋放 LockScreen對象的時候忘記取消我們之前註冊的PhoneStateListener對象,則會導致LockScreen無法被垃圾回收。如果不斷的使鎖屏界面顯示和消失,則最終會由於大量的LockScreen對象沒有辦法被回收而引起OutOfMemory,使得system_process 進程掛掉。
雖然有些系統程序,它本身好像是可以自動取消註冊的(當然不及時),但是我們還是應該在我們的程序中明確的取消註冊,程序結束時應該把所有的註冊都取消掉
2.4單例造成的內存泄漏
單例模式非常受開發者的喜愛,不過使用的不恰當的話也會造成內存泄漏,由於單例的靜態特性使得單例的生命週期和應用的生命週期一樣長,這就說明了如果一個對象已經不需要使用了,而單例對象還持有該對象的引用,那麼這個對象將不能被正常回收,這就導致了內存泄漏。
創建這個單例的時候,由於需要傳入一個Context,所以這個Context的生命週期的長短至關重要:
1、傳入的是Application的Context:這將沒有任何問題,因爲單例的生命週期和Application的一樣長
2、傳入的是Activity的Context:當這個Context所對應的Activity退出時,由於該Context和Activity的生命週期一樣長(Activity間接繼承於Context),所以當前Activity退出時它的內存並不會被回收,因爲單例對象持有該Activity的引用。
3.實例代碼:
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}
4.解決方案
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}
這樣不管傳入什麼Context最終將使用Application的Context,而單例的生命週期和應用的一樣長,這樣就防止了內存泄漏
2.5非靜態內部類創建靜態實例造成的內存泄漏
有的時候我們可能會在啓動頻繁的Activity中,爲了避免重複創建相同的數據資源,可能會出現這種寫法:
public class MainActivity extends Activity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}
}
class TestResource {
}
}
這樣就在Activity內部創建了一個非靜態內部類的單例,每次啓動Activity時都會使用該單例的數據,這樣雖然避免了資源的重複創建,不過這種寫法卻會造成內存泄漏,因爲非靜態內部類默認會持有外部類的引用,而又使用了該非靜態內部類創建了一個靜態的實例,該實例的生命週期和應用的一樣長,這就導致了該靜態實例一直會持有該Activity的引用,導致Activity的內存資源不能正常回收。正確的做法爲:
將該內部類設爲靜態內部類或將該內部類抽取出來封裝成一個單例,如果需要使用Context,請使用ApplicationContext
2.6 Handler造成的內存泄漏
這種創建Handler的方式會造成內存泄漏,由於mHandler是Handler的非靜態匿名內部類的實例,所以它持有外部類Activity的引用,我們知道消息隊列是在一個Looper線程中不斷輪詢處理消息,那麼當這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,mHandler又持有Activity的引用,所以導致該Activity的內存資源無法及時回收,引發內存泄漏
public class MainActivity extends Activity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
private void loadData(){
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
解決方案:
public class MainActivity extends Activity {
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
//創建一個靜態Handler內部類,然後對Handler持有的對象使用弱引用
//這樣在回收時也可以回收Handler持有的對象,這樣雖然避免了Activity泄漏
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
//Looper線程的消息隊列中還是可能會有待處理的消息,
//所以我們在Activity的Destroy時或者Stop時應該移除消息隊列中的消息
@Override
protected void onDestroy() {
super.onDestroy();
//移除消息隊列中所有消息和所有的Runnable
mHandler.removeCallbacksAndMessages(null);
}
}
創建一個靜態Handler內部類,然後對Handler持有的對象使用弱引用,這樣在回收時也可以回收Handler持有的對象,這樣雖然避免了Activity泄漏,不過Looper線程的消息隊列中還是可能會有待處理的消息,所以我們在Activity的Destroy時或者Stop時應該移除消息隊列中的消息,使用mHandler.removeCallbacksAndMessages(null);是移除消息隊列中所有消息和所有的Runnable。當然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();來移除指定的Runnable和Message。
2.7線程造成的內存泄漏
//——————test1
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}.execute();
//——————test2
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
上面的異步任務和Runnable都是一個匿名內部類,因此它們對當前Activity都有一個隱式引用。如果Activity在銷燬之前,任務還未完成,那麼將導致Activity的內存資源無法回收,造成內存泄漏。
正確的做法還是使用靜態內部類的方式,如下:
//test1
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
//test2
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
//——————
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
這樣就避免了Activity的內存資源泄漏,當然在Activity銷燬時候也應該取消相應的任務AsyncTask.cancel(),避免任務在後臺執行浪費資源。
2.8集合中對象沒清理造成的內存泄漏
Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
Object o = new Object();
v.add(o);
o = null;
}
通常把一些對象的引用加入到了集合中,當我們不需要該對象時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。
循環申請Object對象,並將所申請的對象放入一個Vector中,如果僅僅釋放對象本身,但因爲Vector仍然引用該對象,所以這個對象對GC來說是不可回收的。因此,如果對象加入到Vector後,還必須從Vector中刪除,最簡單的方法就是將Vector對象設置爲null。