什么叫內(nèi)存泄漏呢?
gc沒(méi)有辦法回收activity的內(nèi)存。
那什么情況下會(huì)GC
呢?
- 屏幕旋轉(zhuǎn)的時(shí)候
- 內(nèi)存不夠用的時(shí)候。
什么情況會(huì)發(fā)生內(nèi)存泄漏呢?
靜態(tài)資源問(wèn)題
靜態(tài)變量在整個(gè)應(yīng)用的內(nèi)存里只保存一份,一旦創(chuàng)建就不會(huì)釋放該變量的內(nèi)存,直到整個(gè)應(yīng)用都銷(xiāo)毀才會(huì)釋放static靜態(tài)變量的內(nèi)存.
例子:
public class MyCustomResource {
//靜態(tài)變量drawable
private static Drawable drawable;
private View view;
public MyCustomResource(Context context) {
Resources resources = context.getResources();
drawable = resources.getDrawable(R.drawable.ic_launcher);
view = new View(context);
view.setBackgroundDrawable(drawable);
}
}
//查看setBackgroundDrawable的源代碼:
public void setBackgroundDrawable(Drawable background) {
..........
/**此處的this就是當(dāng)前View對(duì)象,而View對(duì)象又是有Context對(duì)象獲得 因此,變量background持有View對(duì)象的引用,View持有Context的引用, 所有background間接持有Context對(duì)象的引用了*/
background.setCallback(this);
.......
}
分析:
此處的background.setCallback(this);
的this
是當(dāng)前的view
的對(duì)象。由于background
是一個(gè)靜態(tài)變量,會(huì)一直持有View對(duì)象的引用,而然View對(duì)象又是由Context對(duì)象創(chuàng)建出來(lái)的,因此background會(huì)間接持有Context的對(duì)象的引用。
所以:
該Context對(duì)應(yīng)的Activity退出finish掉的時(shí)候其實(shí)該Activity是不能完全釋放內(nèi)存的。
值得注意的是:
代碼是由于靜態(tài)資源drawable
持有View
對(duì)象的引用導(dǎo)致內(nèi)存泄漏隱患的,并不是由于context.getResourc
e導(dǎo)致內(nèi)存泄漏,因此如果你想通過(guò)context.getApplicaitonContext
來(lái)獲取getResource
是解決不了內(nèi)存泄漏的.
另外提一點(diǎn):(使用getApplicaitonContext
的錯(cuò)誤)
android.app.Application cannot be cast to android.app.Activity
因此:在android 3.0 中:
修改了setBackgroundDrawable內(nèi)部方法中的 background.setCallback(this);方法。里面的實(shí)現(xiàn)使用了弱引用來(lái)持有View對(duì)象的引用,從而避免了內(nèi)存泄漏隱患。
總結(jié):以后代碼中避免使用靜態(tài)資源,或者使用弱引用來(lái)解決相應(yīng)的問(wèn)題也是可以的。
單例模式導(dǎo)致內(nèi)存泄漏(單例中的類(lèi)是靜態(tài)的,引用context將導(dǎo)致以上的問(wèn)題)
public class CustomManager {
private static CustomManager sInstance;
public static CustomManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new CustomManager(context);//使用context的引用。
}
return sInstance;
}
private Context mContext;
private CustomManager(Context context) {
mContext = context;
}
}
單例模式使用的是靜態(tài)類(lèi)的方式,讓該對(duì)象在整個(gè)應(yīng)用的內(nèi)存中保持一份該對(duì)象,從而減少對(duì)多次創(chuàng)建對(duì)象帶來(lái)的資源浪費(fèi)。
同樣的問(wèn)題:
在創(chuàng)建該單例的時(shí)候使用了生命周期端的Context對(duì)象的引用,如果你是在Application中創(chuàng)建以上單例的話是木有任何問(wèn)題的。因?yàn)?strong>Application的Context生命周期是整個(gè)應(yīng)用,和單例的生命周期一樣,因此不會(huì)導(dǎo)致內(nèi)存泄漏。但是,如果你是在Activity中創(chuàng)建以上單例的話,將一樣導(dǎo)致跟上一個(gè)一樣的內(nèi)存問(wèn)題。
所以講以上的代碼改成:
if (sInstance == null) {
sInstance = new CustomManager(context.getApplicationContext()); }//注意這里的不同。
return sInstance;
以上全摘自:Android Context 是什么?
通過(guò)上面大家可能對(duì)Context
比較模糊。請(qǐng)閱讀:Android Context 是什么?。
記錄幾點(diǎn):
- getApplication和getApplicationContext返回同一個(gè)
Application
對(duì)象,只是里面方法調(diào)用的成員返回不同。 - 所有Context都是在應(yīng)用的主線程ActivityThread中創(chuàng)建的
- 盡量少用Context對(duì)象去獲取靜態(tài)變量,靜態(tài)方法,以及單例對(duì)象。以免導(dǎo)致內(nèi)存泄漏
- 在創(chuàng)建與UI相關(guān)的地方,比如創(chuàng)建一個(gè)Dialog,或者在代碼中創(chuàng)建一個(gè)TextView,都用Activity的Context去創(chuàng)建。然而在引用靜態(tài)資源,創(chuàng)建靜態(tài)方法,單例模式等情況下,使用生命周期更長(zhǎng)的Application的Context才不會(huì)導(dǎo)致內(nèi)存泄漏
內(nèi)部類(lèi)Handler類(lèi)引起內(nèi)存泄漏
在Activity中定義了一個(gè)內(nèi)部Handler類(lèi):
public class MainActivity extends Activity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//TODO handle message...
}
};
@TargetApi(11)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 60000);//延遲一分鐘執(zhí)行
//just finish this activity
finish();//關(guān)閉activity
}
}
以上代碼將會(huì)導(dǎo)致內(nèi)存泄漏的問(wèn)題(原因):
- 在Java中,非靜態(tài)(匿名)內(nèi)部類(lèi)會(huì)引用外部類(lèi)對(duì)象。而靜態(tài)內(nèi)部類(lèi)不會(huì)引用外部類(lèi)對(duì)象。
當(dāng)Activity finish
后,延時(shí)消息會(huì)繼續(xù)存在主線程消息隊(duì)列中1分鐘,然后處理消息。而該消息引用了Activity的Handler對(duì)象,然后這個(gè)Handler又引用了這個(gè)Activity(從以上原因可知道)。這些引用對(duì)象會(huì)保持到該消息被處理完,這樣就導(dǎo)致該Activity對(duì)象無(wú)法被回收,從而導(dǎo)致了上面說(shuō)的 Activity泄露。
修改:將Handler
改為靜態(tài)類(lèi),并使用WeakReference
來(lái)保持外部的activity
對(duì)象。
private Handler mHandler = new MyHandler(this);
private static class MyHandler extends Handler{
private final WeakReference<Activity> mActivity;
public MyHandler(Activity activity) {
mActivity = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
System.out.println(msg);
if(mActivity.get() == null) {
return;
}
}
}
結(jié)論:當(dāng)你在Activity中使用內(nèi)部類(lèi)的時(shí)候,需要時(shí)刻考慮您是否可以控制該內(nèi)部類(lèi)的生命周期,如果不可以,則最好定義為靜態(tài)內(nèi)部類(lèi)。
以上摘自:內(nèi)部Handler類(lèi)引起內(nèi)存泄露
另一種解決的方式:
在activty
的onDestroy
方法對(duì)handler
的取消:
public void onDestroy() {
mHandler.removeMessages(MESSAGE_1);
mHandler.removeMessages(MESSAGE_2);
mHandler.removeMessages(MESSAGE_3);
mHandler.removeMessages(MESSAGE_4);
// ... ...
mHandler.removeCallbacks(mRunnable);
// ... ...
}
或者:
public void onDestroy() {
mHandler.removeCallbacksAndMessages(null);
}
Thread對(duì)象
同上:Thread的生命周期不一定是和Activity生命周期一致。
同上的解決方式為:
- 改為靜態(tài)內(nèi)部類(lèi)
- 采用弱引用來(lái)保存
Context
引用
正確的使用為:
public class ThreadAvoidActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyThread(this).start();
}
private void dosomthing() {//dosomthing是ThreadAvoidActivity的方法
}
private static class MyThread extends Thread {
WeakReference<ThreadAvoidActivity> mThreadActivityRef;//弱引用
public MyThread(ThreadAvoidActivity activity) {
mThreadActivityRef = new WeakReference<ThreadAvoidActivity>(
activity);
}
@Override
public void run() {//先判斷是否為null,回收掉
super.run();
if (mThreadActivityRef == null)
return;
if (mThreadActivityRef.get() != null)
mThreadActivityRef.get().dosomthing();//通過(guò)弱引用得到外部的activity來(lái)調(diào)用dosomthing方法
// dosomthing
}
}
}
以上切斷兩個(gè)對(duì)象的雙向強(qiáng)引用鏈接
- 靜態(tài)內(nèi)部類(lèi):切斷Activity 對(duì)于 MyThread的強(qiáng)引用。
- 弱引用: 切斷MyThread對(duì)于Activity 的強(qiáng)引用
AsynTask 內(nèi)部類(lèi)
跟線程一樣的原理。
AsyncTask內(nèi)部的實(shí)現(xiàn)機(jī)制是運(yùn)用了ThreadPoolExcutor, 該類(lèi)產(chǎn)生的Thread對(duì)象的生命周期是不確定的,是應(yīng)用程序無(wú)法控制的。
你可能會(huì)說(shuō):AsynTask
不是有cancel
的方法么?對(duì)的,是有這個(gè)方法,但還是不一定能取消。通過(guò)源碼可以知道:
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
問(wèn)題歸因于mFuture.cancel(mayInterruptIfRunning);
是否能取消正在執(zhí)行的任務(wù)呢?
通過(guò)線程執(zhí)行者(九)執(zhí)行者取消一個(gè)任務(wù),文章不是重點(diǎn),重點(diǎn)是評(píng)論,提出了是否能真正取消任務(wù)的質(zhì)疑?
查閱:Android學(xué)習(xí)系列(37)--App調(diào)試內(nèi)存泄露之Context篇(下),這里引用官方的言語(yǔ):** cancel是不一定成功的。**
總結(jié):只要是使用線程正在進(jìn)行執(zhí)行任務(wù),都不一定能夠取消。大多數(shù)是使用:Thread.interrupt()
進(jìn)行中斷,但不一定成功的。所以最好的使用靜態(tài)內(nèi)存類(lèi)或者使用弱引用
。
官方的例子:
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
// 注意下面這行,如果檢測(cè)到cancel,則及時(shí)退出
if (isCancelled()) break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
在后臺(tái)循環(huán)中時(shí)刻監(jiān)聽(tīng)cancel狀態(tài),防止沒(méi)有及時(shí)退出。
所以:使用cancel
并不靠譜,那么如何書(shū)寫(xiě)呢?跟Thread
的思路是一樣,差不多:
/**
*
* 弱引用
* @version 1.0.0
* @author Abay Zhuang <br/>
* Create at 2014-7-17
*/
public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget>
extends AsyncTask<Params, Progress, Result> {
protected WeakReference<WeakTarget> mTarget;
public WeakAsyncTask(WeakTarget target) {
mTarget = new WeakReference<WeakTarget>(target);
}
@Override
protected final void onPreExecute() {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPreExecute(target);
}
}
@Override
protected final Result doInBackground(Params... params) {
final WeakTarget target = mTarget.get();
if (target != null) {
return this.doInBackground(target, params);
} else {
return null;
}
}
@Override
protected final void onPostExecute(Result result) {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPostExecute(target, result);
}
}
protected void onPreExecute(WeakTarget target) {
// Nodefaultaction
}
protected abstract Result doInBackground(WeakTarget target,
Params... params);
protected void onPostExecute(WeakTarget target, Result result) {
// Nodefaultaction
}
}
BroadcastReceiver對(duì)象
原因:沒(méi)有取消注冊(cè)。
直接:getContext().unregisterReceiver(receiver);
即可.
注冊(cè)與反注冊(cè):
addCallback <==> removeCallback
registerReceiver <==> unregisterReceiver
addObserver <==> deleteObserver
registerContentObserver <==> unregisterContentObserver
TimerTask對(duì)象
TimerTask對(duì)象在和Timer的schedule()方法配合使用的時(shí)候極容易造成內(nèi)存泄露。
要在合適的時(shí)候進(jìn)行Cancel
即可。
private void cancelTimer(){
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
if (mTimerTask != null) {
mTimerTask.cancel();
mTimerTask = null;
}
}
剩下幾個(gè)小點(diǎn):
- Dialog對(duì)象:使用isFinishing()判斷Activity是否退出。才可以
showDialog
- Bitmap沒(méi)調(diào)用recycle()
- 資源性對(duì)象比如(Cursor,F(xiàn)ile文件等)往往都用了一些緩沖,我們?cè)诓皇褂玫臅r(shí)候,應(yīng)該及時(shí)關(guān)閉它們
- 構(gòu)造Adapter時(shí),沒(méi)有使用緩存的 convertView
- 集合容器對(duì)象沒(méi)清理造成的內(nèi)存泄露(尤其是static的集合,需要clear清理)
- WebView對(duì)象沒(méi)有銷(xiāo)毀。它的destory()函數(shù)來(lái)銷(xiāo)毀它
總結(jié):
泄漏都是有生命周期和Activity不一定一致,導(dǎo)致無(wú)法進(jìn)行回收。
只要是使用線程正在進(jìn)行執(zhí)行任務(wù),都不一定能夠取消。大多數(shù)是使用:Thread.interrupt()
進(jìn)行中斷,但不一定成功的。所以最好的使用靜態(tài)內(nèi)存類(lèi)**或者使用弱引用
。
實(shí)戰(zhàn)檢測(cè)定位內(nèi)存泄露
1. 使用MAT工具定位Android應(yīng)用內(nèi)存泄漏
# 使用Android studio分析內(nèi)存泄露
資料:
Android Context 是什么?
# Android內(nèi)存泄露之Thread
# Android內(nèi)存泄露之Handler
Android App 內(nèi)存泄露之資源
Android內(nèi)存泄露之DDMS –> Heap工具,這個(gè)系列的文章。
Android學(xué)習(xí)系列(37)--App調(diào)試內(nèi)存泄露之Context篇(下)
Android內(nèi)存泄露的整理