內(nèi)存泄露發(fā)生的原因
一個不再被使用的對象令哟,被另外一個使用著的對象持有該對象的引用猛计,導(dǎo)致該對象無法被回收史隆,而停留在堆內(nèi)存當(dāng)中略吨。通常使用著的對象是生命周期更長的對象攒发。
內(nèi)存泄露的危害
1、輕則造成app使用卡頓晋南,因為內(nèi)存泄露會造成app使用時所占用的內(nèi)存越來越多惠猿,在內(nèi)存空間不足時,就會頻繁的引起GC,jvm在進(jìn)行垃圾回收時會暫停當(dāng)前正在活動著的線程偶妖,等垃圾回收完畢之后線程再繼續(xù)姜凄。所以頻繁的GC會導(dǎo)致用戶用起來一卡一卡的,用戶體驗會非常差趾访。
2态秧、嚴(yán)重的就造成app直接崩掉,在分配的內(nèi)存耗盡時扼鞋,引發(fā)OOM申鱼。
內(nèi)存泄露的檢測工具
1、使用android studio自帶的profiler
2云头、使用mat工具
3捐友、使用Android LeakCanary 工具,可參考我的另一篇文章對LeakCanary的介紹:Android內(nèi)存泄露檢測之LeakCanary的使用
內(nèi)存泄露常見的原因
1溃槐、類的靜態(tài)變量持有大數(shù)據(jù)對象匣砖。一般出現(xiàn)在單例模式、靜態(tài)的view或者其他靜態(tài)的變量申明昏滴;
2猴鲫、非靜態(tài)的內(nèi)部類、匿名內(nèi)部類谣殊。例如Handler拂共;
3、監(jiān)聽器姻几。例如各種需要注冊的Listener匣缘,Watcher等;
4鲜棠、資源對象沒有關(guān)閉肌厨;
5、容器中的對象沒清理造成的內(nèi)存泄漏豁陆;
6柑爸、webView;
1盒音、靜態(tài)的變量:靜態(tài)變量的生命周期和應(yīng)用的生命周期一樣長表鳍。如果靜態(tài)變量持有某個Activity的context,則會引發(fā)對應(yīng)Activity無法釋放祥诽,導(dǎo)致內(nèi)存泄漏譬圣。如果持有application的context,就沒有問題雄坪。
這里列舉自己的項目中曾經(jīng)出現(xiàn)過的因為靜態(tài)變量使用不當(dāng)導(dǎo)致的問題:
public class ToastUtil {
private static Toast mToast;
public static void showToast(Context context, String text, int gravity){
if (StringUtil.isStringNull(text)){
return;
}
cancelToast();
if (context != null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.toast_layout, null);
((TextView) layout.findViewById(R.id.tv_toast_text)).setText(text);
mToast = new Toast(context);
mToast.setView(layout);
mToast.setGravity(gravity, 0, 20);
mToast.setDuration(Toast.LENGTH_LONG);
mToast.show();
}
}
public static void cancelToast() {
if (mToast != null){
mToast.cancel();
}
}
}
這里寫了一個ToastUtil來管理彈出框厘熟,類中申明了一個靜態(tài)的Toast變量,然后在showToast()創(chuàng)建了這個靜態(tài)的對象,這里每調(diào)一次showToast方法绳姨,就會創(chuàng)建一個靜態(tài)的toast對象登澜,而靜態(tài)變量的生命周期是存在于整個應(yīng)用期間的,所以在toast對象創(chuàng)建后飘庄,一直沒辦法回收脑蠕,導(dǎo)致內(nèi)存泄露,這里要想避免內(nèi)存泄露需要在toast調(diào)用show方法之后跪削,將toast對象置為null谴仙。當(dāng)然不斷的創(chuàng)建靜態(tài)對象又銷毀很影響性能,這里可以考慮用單例模式碾盐,但是單例模式如果使用不當(dāng)晃跺,也很容易產(chǎn)生內(nèi)存泄露,下面舉兩個容易產(chǎn)生內(nèi)存泄露的例子:
private volatile static SingleInstance mSingleInstance = null;
private SingleInstance (Context context) {}
public static SingleInstance getInstance(Context context) {
if (mSingleInstance == null) {
synchronized (SingleInstance.class) {
if (mSingleInstance == null) {
mSingleInstance = new SingleInstance(context);
}
}
}
return mSingleInstance;
}
public class MyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//這樣就出問題了, 單例中持有了activity對象,導(dǎo)致activity無法被回收
SingleInstance singleInstance = SingleInstance.getInstance(this);
}
}
上面這段代碼廓旬,我想有些初學(xué)者肯定這樣寫過哼审,把當(dāng)前的activity對象傳入到單例中谐腰,一旦單例持有了activity孕豹,那么activity在整個應(yīng)用期間將無法被釋放。正確的做法應(yīng)該是傳入applicationContext十气。
當(dāng)然在單例模式中励背,我們可能還需要設(shè)置View或者其他的對象。那么如果我們像下面這么寫也是會出問題的:
private volatile static SingleInstance mSingleInstance = null;
private SingleInstance (Context context) {}
public static SingleInstance getInstance(Context context) {
if (mSingleInstance == null) {
synchronized (SingleInstance.class) {
if (mSingleInstance == null) {
mSingleInstance = new SingleInstance(context);
}
}
}
return mSingleInstance;
}
//單例模式中這樣持有View的引用會導(dǎo)致內(nèi)存泄漏
private View myView = null;
public void setMyView(View myView) {
this.myView = myView;
}
這里原因和上面的一樣砸西,那么我們應(yīng)該怎么解決這種問題呢叶眉,我們可以采用弱引用(WeakReference)的方式:
private volatile static SingleInstance mSingleInstance = null;
private SingleInstance (Context context) {}
public static SingleInstance getInstance(Context context) {
if (mSingleInstance == null) {
synchronized (SingleInstance.class) {
if (mSingleInstance == null) {
mSingleInstance = new SingleInstance(context);
}
}
}
return mSingleInstance;
}
//用弱引用
private WeakReference<View> myView = null;
public void setMyView(View myView) {
this.myView = new WeakReference<View>(myView);
}
弱引用指向的對象,當(dāng)gc運行的時候芹枷,無論是否被其他對象引用都會被回收衅疙。
2、非靜態(tài)內(nèi)部類和匿名內(nèi)部類容易造成內(nèi)存泄露的原因是因為這兩者都會持有外部類的引用鸳慈。下面這個例子中饱溢,handler延遲一秒執(zhí)行,當(dāng)界面從HandlerActivity跳轉(zhuǎn)到SecondActivity時走芋,HandlerActivity會進(jìn)入到后臺绩郎,此時如果內(nèi)存緊張,有可能會觸發(fā)gc翁逞,那么SecondActivity就將被回收肋杖,但是因為Handler持有了持有了外部類HandlerActivity,所以HandlerActivity無法被回收挖函,造成內(nèi)存泄露状植。解決辦法是
public class HandlerActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
TextView tv_test = findViewById(R.id.tv_test);
tv_test.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(HandlerActivity.this, SecondActivity.class);
startActivity(intent);
}
});
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mHandler.sendEmptyMessage(0);
}
}, 1000);
}
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0: {
}
}
}
};
@Override
protected void onDestroy() {
super.onDestroy();
}
}
上面問題的解決辦法是將Handler聲明為靜態(tài)內(nèi)部類,就不會持有外部類SecondActivity的引用,其生命周期就和外部類無關(guān)浅萧,如果Handler里面需要context的話逐沙,可以通過弱引用方式引用外部類闭专。
public class HandlerActivity extends AppCompatActivity {
private Handler mHandler;
private Runnable mRunnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
TextView tv_test = findViewById(R.id.tv_test);
tv_test.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(HandlerActivity.this, SecondActivity.class);
startActivity(intent);
}
});
mHandler = new MyHandler(this);
mRunnable = new Runnable() {
@Override
public void run() {
mHandler.sendEmptyMessage(0);
}
};
mHandler.postDelayed(mRunnable, 1000);
}
private static final class MyHandler extends Handler {
private final WeakReference<HandlerActivity> mActivity;
private MyHandler(HandlerActivity mActivity) {
this.mActivity = new WeakReference<>(mActivity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// TODO
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 頁面銷毀之后,移除消息隊列中待處理的消息
mHandler.removeCallbacks(mRunnable);
}
}
3题山、當(dāng)我們需要使用系統(tǒng)服務(wù)時,比如執(zhí)行某些后臺任務(wù)媒惕、為硬件訪問提供接口等等系統(tǒng)服務(wù)帝簇。我們需要把自己注冊到服務(wù)的監(jiān)聽器中徘郭。然而,這會讓服務(wù)持有 activity 的引用丧肴,如果程序員忘記在 activity 銷毀時取消注冊残揉,那就會導(dǎo)致 activity 泄漏了。
??例如:EditText的一個addTextChangeListener芋浮,如果在回調(diào)方法里有耗時操作抱环,可能會造成內(nèi)存泄露。這種情況下, 我們需要在onDestory時纸巷,取消注冊镇草,editText.removeTextChangedListener。
public class MainActivity extends AppCompatActivity {
EditText et_test;
TextWatcher textWatcher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_layout);
TextView tv_test = findViewById(R.id.tv_test);
tv_test.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, LeakActivity.class);
startActivity(intent);
}
});
et_test = findViewById(R.id.et_test);
textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//
}
@Override
public void afterTextChanged(Editable s) {
}
};
et_test.addTextChangedListener(textWatcher);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 如果TextWatcher里面執(zhí)行了耗時任務(wù), 那么在onDestroy方法中需要把監(jiān)聽取消掉
et_test.removeTextChangedListener(textWatcher);
}
}
4瘤旨、在android中梯啤,資源性對象比如Cursor、File存哲、Bitmap因宇、視頻等,系統(tǒng)都用了一些緩沖技術(shù)祟偷,在使用這些資源的時候察滑,如果我們確保自己不再使用這些資源了,要及時關(guān)閉修肠,否則可能引起內(nèi)存泄漏贺辰。因為有些操作不僅僅只是涉及到Dalvik虛擬機(jī),還涉及到底層C/C++等的內(nèi)存管理氛赐,不能完全寄希望虛擬機(jī)幫我們完成內(nèi)存管理魂爪。
??在這些資源不使用的時候,記得調(diào)用相應(yīng)的類似close()艰管、destroy()滓侍、recycler()、release()等函數(shù)牲芋,這些函數(shù)往往會通過jni調(diào)用底層C/C++的相應(yīng)函數(shù)撩笆,完成相關(guān)的內(nèi)存釋放捺球。
5、我們通常會把一些對象裝入到集合中夕冲,當(dāng)不使用的時候一定要記得及時清理集合氮兵,讓相關(guān)對象不再被引用。如果集合是static歹鱼、不斷的往里面添加?xùn)|西泣栈、又忘記去清理,肯定會引起內(nèi)存泄漏弥姻。
6南片、webView內(nèi)部的一些線程持有activity對象,導(dǎo)致activity無法釋放庭敦,繼而內(nèi)存泄漏疼进。網(wǎng)上多采用新開一個進(jìn)程來解決⊙砹可參考:http://www.reibang.com/p/aa5a99b565e7