Leakcanary檢測(cè)內(nèi)存泄漏匯總
目錄介紹:
1.什么是內(nèi)存泄漏
2.內(nèi)存泄漏造成什么影響
3.內(nèi)存泄漏檢測(cè)的工具有哪些
4.關(guān)于Leakcanary使用介紹
5.Leakcanary捕捉常見(jiàn)的內(nèi)存泄漏及解決辦法
5.1 錯(cuò)誤使用單例造成的內(nèi)存泄漏
5.2 錯(cuò)誤使用靜態(tài)變量,導(dǎo)致引用后無(wú)法銷(xiāo)毀【工具類(lèi)使用不當(dāng)導(dǎo)致內(nèi)存泄漏】
5.3 Handler造成的內(nèi)存泄漏
5.4 線(xiàn)程造成的內(nèi)存泄漏
5.5 由WebView引起的內(nèi)存泄漏
5.6 資源未關(guān)閉造成的內(nèi)存泄漏
5.7 未注銷(xiāo)EventBus導(dǎo)致的內(nèi)存泄漏
5.8 靜態(tài)集合使用不當(dāng)導(dǎo)致的內(nèi)存泄漏
5.9 使用弱引用避免內(nèi)存泄漏
6.其他
好消息
- 博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時(shí)開(kāi)發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長(zhǎng)期更新維護(hù)并且修正,持續(xù)完善……開(kāi)源的文件是markdown格式的!同時(shí)也開(kāi)源了生活博客,從12年起,積累共計(jì)47篇[近20萬(wàn)字],轉(zhuǎn)載請(qǐng)注明出處,謝謝!
- 鏈接地址:https://github.com/yangchong211/YCBlogs
- 如果覺(jué)得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬(wàn)事起于忽微,量變引起質(zhì)變!
1.什么是內(nèi)存泄漏?
一些對(duì)象有著有限的聲明周期,當(dāng)這些對(duì)象所要做的事情完成了,我們希望它們會(huì)被垃圾回收器回收掉。但是如果有一系列對(duì)這個(gè)對(duì)象的引用存在,那么在我們期待這個(gè)對(duì)象生命周期結(jié)束時(shí)被垃圾回收器回收的時(shí)候,它是不會(huì)被回收的。它還會(huì)占用內(nèi)存,這就造成了內(nèi)存泄露。持續(xù)累加,內(nèi)存很快被耗盡。
比如:當(dāng)Activity的onDestroy()方法被調(diào)用后,Activity以及它涉及到的View和相關(guān)的Bitmap都應(yīng)該被回收掉。但是,如果有一個(gè)后臺(tái)線(xiàn)程持有這個(gè)Activity的引用,那么該Activity所占用的內(nèi)存就不能被回收,這最終將會(huì)導(dǎo)致內(nèi)存耗盡引發(fā)OOM而讓?xiě)?yīng)用crash掉。
2.內(nèi)存泄漏會(huì)造成什么影響?
它是造成應(yīng)用程序OOM的主要原因之一。由于android系統(tǒng)為每個(gè)應(yīng)用程序分配的內(nèi)存有限,當(dāng)一個(gè)應(yīng)用中產(chǎn)生的內(nèi)存泄漏比較多時(shí),就難免會(huì)導(dǎo)致應(yīng)用所需要的內(nèi)存超過(guò)這個(gè)系統(tǒng)分配的內(nèi)存限額,這就造成了內(nèi)存溢出而導(dǎo)致應(yīng)用Crash。
3.內(nèi)存泄漏檢測(cè)的工具有哪些
最常見(jiàn)的是:Leakcanary
4.關(guān)于Leakcanary使用介紹
leakCanary是Square開(kāi)源框架,是一個(gè)Android和Java的內(nèi)存泄露檢測(cè)庫(kù),如果檢測(cè)到某個(gè) activity 有內(nèi)存泄露,LeakCanary 就是自動(dòng)地顯示一個(gè)通知,所以可以把它理解為傻瓜式的內(nèi)存泄露檢測(cè)工具。通過(guò)它可以大幅度減少開(kāi)發(fā)中遇到的oom問(wèn)題,大大提高APP的質(zhì)量。
關(guān)于如何配置,這個(gè)就不說(shuō)呢,網(wǎng)上有步驟
5.Leakcanary捕捉常見(jiàn)的內(nèi)存泄漏及解決辦法
- 5.1 錯(cuò)誤使用單例造成的內(nèi)存泄漏
在平時(shí)開(kāi)發(fā)中單例設(shè)計(jì)模式是我們經(jīng)常使用的一種設(shè)計(jì)模式,而在開(kāi)發(fā)中單例經(jīng)常需要持有Context對(duì)象,如果持有的Context對(duì)象生命周期與單例生命周期更短時(shí),或?qū)е翪ontext無(wú)法被釋放回收,則有可能造成內(nèi)存泄漏,錯(cuò)誤寫(xiě)法如下: - 問(wèn)題引起內(nèi)存泄漏代碼
public class LoginManager {
private static LoginManager mInstance;
private Context mContext;
private LoginManager(Context context) {
this.mContext = context; //修改代碼:**this.mContext = context.getApplicationContext();**
}
public static LoginManager getInstance(Context context) {
if (mInstance == null) {
synchronized (LoginManager.class) {
if (mInstance == null) {
mInstance = new LoginManager(context);
}
}
}
return mInstance;
}
public void dealData() {}
}
- 在一個(gè)Activity中調(diào)用的,然后關(guān)閉該Activity則會(huì)出現(xiàn)內(nèi)存泄漏。
LoginManager.getInstance(this).dealData();
- 看看報(bào)錯(cuò)
- 解決辦法:
要保證Context和AppLication的生命周期一樣,修改后代碼如下:
this.mContext = context.getApplicationContext();
原因分析
創(chuàng)建單例對(duì)象,并且在創(chuàng)建的時(shí)候需要傳入一個(gè)Context對(duì)象,而這時(shí)候如果我們使用Activity、Service等Context對(duì)象,由于單例對(duì)象的生命周期與進(jìn)程的生命周期相同,會(huì)造成我們傳入的Activity、Service對(duì)象無(wú)法被回收,這時(shí)候就需要我們傳入Application對(duì)象,或者在方法中使用Application對(duì)象5.2 錯(cuò)誤使用靜態(tài)變量,導(dǎo)致引用后無(wú)法銷(xiāo)毀
在平時(shí)開(kāi)發(fā)中,有時(shí)候我們創(chuàng)建了一個(gè)工具類(lèi)。比如分享工具類(lèi),十分方便多處調(diào)用,因此使用靜態(tài)方法是十分方便的。但是創(chuàng)建的對(duì)象,建議不要全局化,全局化的變量必須加上static。這樣會(huì)引起內(nèi)存泄漏!問(wèn)題代碼
- Image.png
在Activity中引用后,關(guān)閉該Activity會(huì)導(dǎo)致內(nèi)存泄漏
DoShareUtil.showFullScreenShareView(PNewsContentActivity.this, title, title, shareurl, logo);
查看報(bào)錯(cuò)
- Image.png
解決辦法
靜態(tài)方法中,創(chuàng)建對(duì)象或變量,不要全局化,全局化后的變量或者對(duì)象會(huì)導(dǎo)致內(nèi)存泄漏;popMenuView和popMenu都不要全局化知識(shí)延伸
**非靜態(tài)內(nèi)部類(lèi),靜態(tài)實(shí)例化**
public class MyActivity extends AppCompatActivity {
//靜態(tài)成員變量
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 doSomeThing() {}
}
}
這里內(nèi)部類(lèi)InnerClass隱式的持有外部類(lèi)MyActivity的引用,而在MyActivity的onCreate方法中調(diào)用了。
這樣innerClass就會(huì)在MyActivity創(chuàng)建的時(shí)候是有了他的引用,而innerClass是靜態(tài)類(lèi)型的不會(huì)被垃圾回收,
MyActivity在執(zhí)行onDestory方法的時(shí)候由于被innerClass持有了引用而無(wú)法被回收,所以這樣MyActivity就總是被innerClass持有而無(wú)法回收造成內(nèi)存泄露。
- 5.3 Handler造成的內(nèi)存泄漏【輪播圖無(wú)限循環(huán)輪播,一定要關(guān)閉,否則內(nèi)存泄漏】
handler是工作線(xiàn)程與UI線(xiàn)程之間通訊的橋梁,只是現(xiàn)在大量開(kāi)源框架對(duì)其進(jìn)行了封裝,我們這里模擬一種常見(jiàn)使用方式來(lái)模擬內(nèi)存泄漏情形。 - 問(wèn)題代碼
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler();
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.text); //模擬內(nèi)存泄露
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mTextView.setText("yangchong");
}
}, 2000);
}
}
造成內(nèi)存泄漏原因分析
上述代碼通過(guò)內(nèi)部類(lèi)的方式創(chuàng)建mHandler對(duì)象,此時(shí)mHandler會(huì)隱式地持有一個(gè)外部類(lèi)對(duì)象引用這里就是MainActivity,當(dāng)執(zhí)行postDelayed方法時(shí),該方法會(huì)將你的Handler裝入一個(gè)Message,并把這條Message推到MessageQueue中,MessageQueue是在一個(gè)Looper線(xiàn)程中不斷輪詢(xún)處理消息,那么當(dāng)這個(gè)Activity退出時(shí)消息隊(duì)列中還有未處理的消息或者正在處理消息,而消息隊(duì)列中的Message持有mHandler實(shí)例的引用,mHandler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無(wú)法及時(shí)回收,引發(fā)內(nèi)存泄漏。-
查看報(bào)錯(cuò)結(jié)果如下:
Image.png 解決辦法
要想避免Handler引起內(nèi)存泄漏問(wèn)題,需要我們?cè)贏ctivity關(guān)閉退出的時(shí)候的移除消息隊(duì)列中所有消息和所有的Runnable。
上述代碼只需在onDestroy()函數(shù)中調(diào)用mHandler.removeCallbacksAndMessages(null);就行了。
@Override
protected void onDestroy() {
super.onDestroy();
if(handler!=null){
handler.removeCallbacksAndMessages(null);
handler = null;
}
}
- 5.4 線(xiàn)程造成的內(nèi)存泄漏
早時(shí)期的時(shí)候處理耗時(shí)操作多數(shù)都是采用Thread+Handler的方式,后來(lái)逐步被AsyncTask取代,直到現(xiàn)在采用RxJava的方式來(lái)處理異步。這里以AsyncTask為例,可能大部分人都會(huì)這樣處理一個(gè)耗時(shí)操作然后通知UI更新結(jié)果: - 問(wèn)題代碼
public class MainActivity extends AppCompatActivity {
private AsyncTask<Void, Void, Integer> asyncTask;
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.text);
testAsyncTask();
finish();
}
private void testAsyncTask() {
asyncTask = new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... params) {
int i = 0;
//模擬耗時(shí)操作
while (!isCancelled()) {
i++;
if (i > 1000000000) {
break;
}
Log.e("LeakCanary", "asyncTask---->" + i);
}
return i;
}
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
mTextView.setText(String.valueOf(integer));
}
};
asyncTask.execute();
}
}
造成內(nèi)存泄漏原因分析
在處理一個(gè)比較耗時(shí)的操作時(shí),可能還沒(méi)處理結(jié)束MainActivity就執(zhí)行了退出操作,但是此時(shí)AsyncTask依然持有對(duì)MainActivity的引用就會(huì)導(dǎo)致MainActivity無(wú)法釋放回收引發(fā)內(nèi)存泄漏-
查看報(bào)錯(cuò)結(jié)果如下:
Image.png 解決辦法
在使用AsyncTask時(shí),在Activity銷(xiāo)毀時(shí)候也應(yīng)該取消相應(yīng)的任務(wù)AsyncTask.cancel()方法,避免任務(wù)在后臺(tái)執(zhí)行浪費(fèi)資源,進(jìn)而避免內(nèi)存泄漏的發(fā)生
private void destroyAsyncTask() {
if (asyncTask != null && !asyncTask.isCancelled()) {
asyncTask.cancel(true);
}
asyncTask = null;
}
@Override
protected void onDestroy() {
super.onDestroy();
destroyAsyncTask();
}
- 5.5 由WebView引起的內(nèi)存泄漏
WebView解析網(wǎng)頁(yè)時(shí)會(huì)申請(qǐng)Native堆內(nèi)存用于保存頁(yè)面元素,當(dāng)頁(yè)面較復(fù)雜時(shí)會(huì)有很大的內(nèi)存占用。如果頁(yè)面包含圖片,內(nèi)存占用會(huì)更嚴(yán)重。并且打開(kāi)新頁(yè)面時(shí),為了能快速回退,之前頁(yè)面占用的內(nèi)存也不會(huì)釋放。有時(shí)瀏覽十幾個(gè)網(wǎng)頁(yè),都會(huì)占用幾百兆的內(nèi)存。這樣加載網(wǎng)頁(yè)較多時(shí),會(huì)導(dǎo)致系統(tǒng)不堪重負(fù),最終強(qiáng)制關(guān)閉應(yīng)用,也就是出現(xiàn)應(yīng)用閃退或重啟 - 問(wèn)題代碼
public class MainActivity5 extends AppCompatActivity {
private WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web);
mWebView = (WebView) findViewById(R.id.web);
mWebView.loadUrl("http://www.cnblogs.com/whoislcj/p/5720202.html");
}
}
造成內(nèi)存泄漏原因分析
加載網(wǎng)頁(yè)有緩存,當(dāng)加載了許多網(wǎng)頁(yè),并且手機(jī)配置比較低時(shí),造成的內(nèi)存泄漏就對(duì)手機(jī)影響很大查看報(bào)錯(cuò)結(jié)果如下:
解決辦法
@Override
protected void onDestroy() {
if (mWebView != null) {
mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
mWebView.clearHistory();
ViewGroup parent = (ViewGroup) mWebView.getParent();
if(parent!=null){
parent.removeView(mWebView);
}
mWebView.destroy();
mWebView = null;
}
super.onDestroy();
}
5.6 資源未關(guān)閉造成的內(nèi)存泄漏
對(duì)于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷(xiāo)毀時(shí)及時(shí)關(guān)閉或者注銷(xiāo),否則這些資源將不會(huì)被回收,造成內(nèi)存泄漏。5.7 未注銷(xiāo)EventBus導(dǎo)致的內(nèi)存泄漏
直接展示代碼
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_common);
EventBus.getDefault().register(this);
}
@Subscribe
public void onEvent(MessageEvent msg) {
}
@Override
protected void onDestroy() {
super.onDestroy();
//未移除注冊(cè)的EventBus
//EventBus.getDefault().unregister(this);
}
- 5.8 靜態(tài)集合使用不當(dāng)導(dǎo)致的內(nèi)存泄漏
添加Activity到棧,或者移除Activity出棧。導(dǎo)致內(nèi)存泄漏
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<Activity>();
public static void addActivity(Activity activity) {
activities.add(activity);
}
public static void removeActivity(Activity activity) {
activities.remove(activity);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_common);
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
//靜態(tài)集合沒(méi)有移除元素
//ActivityCollector.removeActivity(this);
}
- 5.9 使用弱引用避免內(nèi)存泄漏
在 Activity 中避免使用非靜態(tài)內(nèi)部類(lèi),比如上面我們將 Handler 聲明為靜態(tài)的,則其存活期跟 Activity 的生命周期就無(wú)關(guān)了。同時(shí)通過(guò)弱引用的方式引入 Activity,避免直接將 Activity 作為 context 傳進(jìn)去
public class WeakReferenceActivity extends AppCompatActivity {
//將Handler聲明為靜態(tài) 沒(méi)有了Activity的引用, 無(wú)法直接引用其變量或方法,
//使用弱引用WeakReference來(lái)解決這個(gè)問(wèn)題
private static class DBHandler extends Handler {
//弱引用, 而不是使用外部類(lèi)this或者傳進(jìn)來(lái)
private final WeakReference<WeakReferenceActivity> mActivity;
public DBHandler(WeakReferenceActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
WeakReferenceActivity activity = mActivity.get();
if (activity != null) {
Toast.makeText(activity, "what: " + msg.what, Toast.LENGTH_SHORT).show();
}
}
}
private final DBHandler mHandler = new DBHandler(this);
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() {
Toast.makeText(App.getInstance(), "sRunnable run()", Toast.LENGTH_SHORT).show();
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weak_reference);
mHandler.postDelayed(sRunnable, 1000 * 20);
}
public void onClick(View view) {
mHandler.sendEmptyMessage(new Random().nextInt(10));
}
}
后續(xù):
平時(shí)喜歡寫(xiě)寫(xiě)文章,筆記。別人建議我把筆記,以前寫(xiě)的東西整理,然后寫(xiě)成博客,所以我會(huì)陸續(xù)整理文章,只發(fā)自己寫(xiě)的東西,敬請(qǐng)期待。如果有什么問(wèn)題或者需要筆記,可以直接聯(lián)系我,可以發(fā)送印象筆記文檔給你!
知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
領(lǐng)英:https://www.linkedin.com/in/chong-yang-049216146/
簡(jiǎn)書(shū):http://www.lxweimin.com/u/b7b2c6ed9284
csdn:http://my.csdn.net/m0_37700275
網(wǎng)易博客:http://yangchong211.blog.163.com/
新浪博客:http://blog.sina.com.cn/786041010yc
github:https://github.com/yangchong211
喜馬拉雅聽(tīng)書(shū):http://www.ximalaya.com/zhubo/71989305/
脈脈:yc930211
開(kāi)源中國(guó):https://my.oschina.net/zbj1618/blog
郵箱:yangchong211@163.com