您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)Android常見的內(nèi)存泄露應(yīng)用場景有哪些的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
單例是我們開發(fā)中最常見和使用最頻繁的設(shè)計模式之一,所以如果使用不當(dāng)就會導(dǎo)致內(nèi)存泄露。因為單例的靜態(tài)特性使得它的生命周期同應(yīng)用的生命周期一樣長,如果一個對象已經(jīng)沒有用處了,但是單例還持有它的引用,那么在整個應(yīng)用程序的生命周期這個對象都不能正常被回收,從而導(dǎo)致內(nèi)存泄露。
如:
public class App { private static App sInstance; private Context mContext; private App(Context context) { this.mContext = context; } public static App getInstance(Context context) { if (sInstance == null) { sInstance = new App(context); } return sInstance; } }
調(diào)用getInstance(Context context)方法時傳入的上下文如果為當(dāng)前活動Activity或者當(dāng)前服務(wù)的Service以及當(dāng)前fragment的上下文,當(dāng)他們銷毀時,這個靜態(tài)單例sIntance還會持用他們的引用,從而導(dǎo)致當(dāng)前活動、服務(wù)、fragment等對象不能被回收釋放,從而導(dǎo)致內(nèi)存泄漏。這種上下文的使用很多時候處理不當(dāng)就會導(dǎo)致內(nèi)存泄漏,需要我們多注意編碼規(guī)范。
靜態(tài)變量存儲在方法區(qū),它的生命周期從類加載開始,到整個進(jìn)程結(jié)束。一旦靜態(tài)變量初始化后, 它所持有的引用只有等到進(jìn)程結(jié)束才會釋放。
如下代碼:
public class MainActivity extends AppCompatActivity { private static Info sInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (sInfo != null) { sInfo = new Info(this); } } } class Info { public Info(Activity activity) { } }
Info 作為 Activity 的靜態(tài)成員,并且持有 Activity 的引用,但是 sInfo 作為靜態(tài)變量,生命周期 肯定比 Activity 長。所以當(dāng) Activity 退出后,sInfo 仍然引用了 Activity,Activity 不能被回收, 這就導(dǎo)致了內(nèi)存泄露。 在 Android 開發(fā)中,靜態(tài)持有很多時候都有可能因為其使用的生命周期不一致而導(dǎo)致內(nèi)存泄露, 所以我們在新建靜態(tài)持有的變量的時候需要多考慮一下各個成員之間的引用關(guān)系,并且盡量少地 使用靜態(tài)持有的變量,以避免發(fā)生內(nèi)存泄露。當(dāng)然,我們也可以在適當(dāng)?shù)臅r候講靜態(tài)量重置為 null, 使其不再持有引用,這樣也可以避免內(nèi)存泄露。
非靜態(tài)內(nèi)部類(包括匿名內(nèi)部類)默認(rèn)就會持有外部類的引用,當(dāng)非靜態(tài)內(nèi)部類對象的生命周期 比外部類對象的生命周期長時,就會導(dǎo)致內(nèi)存泄露。這類內(nèi)存泄漏很典型的Handler的使用,這么一說大家應(yīng)該就很熟悉了吧,大家都知道怎么處理這類內(nèi)存泄漏。
Handler的使用示例:
private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg); } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { // ui更新 } } };
Handler 消息機制,mHandler 會作為成員變量保存在發(fā)送的消息 msg 中,即 msg 持有 mHandler 的引用,而 mHandler 是 Activity 的非靜態(tài)內(nèi)部類實例,即 mHandler 持有 Activity 的引 用,那么我們就可以理解為 msg 間接持有 Activity 的引用。msg 被發(fā)送后先放到消息隊列 MessageQueue 中,然后等待 Looper 的輪詢處理(MessageQueue 和 Looper 都是與線程相關(guān)聯(lián)的, MessageQueue 是 Looper 引用的成員變量,而 Looper 是保存在 ThreadLocal 中的)。那么當(dāng) Activity 退出后,msg 可能仍然存在于消息對列 MessageQueue 中未處理或者正在處理,那么這樣就會導(dǎo)致 Activity 無法被回收,以致發(fā)生 Activity 的內(nèi)存泄露。
如何避免:
1、采用靜態(tài)內(nèi)部類+弱引用的方式
private static class MyHandler extends Handler { private WeakReference<MainActivity> activityWeakReference; public MyHandler(MainActivity activity) { activityWeakReference = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { MainActivity activity = activityWeakReference.get(); if (activity != null) { if (msg.what == 1) { // 做相應(yīng)邏輯 } } } }
mHandler 通過弱引用的方式持有 Activity,當(dāng) GC 執(zhí)行垃圾回收時,遇到 Activity 就會回收并釋 放所占據(jù)的內(nèi)存單元。這樣就不會發(fā)生內(nèi)存泄露了。但是 msg 還是有可能存在消息隊列 MessageQueue 中。
2、Activity 銷毀時就將 mHandler 的回調(diào)和發(fā)送的消息給移除掉。
@Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }
非靜態(tài)內(nèi)部類造成內(nèi)存泄露還有一種情況就是使用 Thread 或者 AsyncTask異步調(diào)用:
如示例:
Thread :
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }
AsyncTask:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { // UI線程處理 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } }.execute(); } }
以上新建的子線程 Thread 和 AsyncTask 都是匿名內(nèi)部類對象,默認(rèn)就隱式的持有外部 Activity 的引用, 導(dǎo)致 Activity 內(nèi)存泄露。要避免內(nèi)存泄露的話還是需要像上面 Handler 一樣使用采用靜態(tài)內(nèi)部類+弱引用的方式(如上面Hanlder采用靜態(tài)內(nèi)部類+弱引用的方式)。
比如我們在 Activity 中注冊廣播,如果在 Activity 銷毀后不取消注冊,那么這個剛播會一直存在 系統(tǒng)中,同上面所說的非靜態(tài)內(nèi)部類一樣持有 Activity 引用,導(dǎo)致內(nèi)存泄露。因此注冊廣播后在 Activity 銷毀后一定要取消注冊。
this.unregisterReceiver(mReceiver);
在注冊觀察則模式的時候,如果不及時取消也會造成內(nèi)存泄露。比如使用 Retrofit+RxJava 注冊網(wǎng)絡(luò)請求的觀察者回調(diào),同樣作為匿名內(nèi)部類持有外部引用,所以需要記得在不用或者銷毀的時候 取消注冊。
當(dāng)我們 Activity 銷毀的時,有可能 Timer 還在繼續(xù)等待執(zhí)行 TimerTask,它持有 Activity 的引用不 能被回收,因此當(dāng)我們 Activity 銷毀的時候要立即 cancel 掉 Timer 和 TimerTask,以避免發(fā)生內(nèi)存 泄漏。
這個比較好理解,如果一個對象放入到 ArrayList、HashMap 等集合中,這個集合就會持有該對象 的引用。當(dāng)我們不再需要這個對象時,也并沒有將它從集合中移除,這樣只要集合還在使用(而 此對象已經(jīng)無用了),這個對象就造成了內(nèi)存泄露。并且如果集合被靜態(tài)引用的話,集合里面那 些沒有用的對象更會造成內(nèi)存泄露了。所以在使用集合時要及時將不用的對象從集合 remove,或 者 clear 集合,以避免內(nèi)存泄漏。
在使用 IO、File 流或者 Sqlite、Cursor 等資源時要及時關(guān)閉。這些資源在進(jìn)行讀寫操作時通常都 使用了緩沖,如果及時不關(guān)閉,這些緩沖對象就會一直被占用而得不到釋放,以致發(fā)生內(nèi)存泄露。 因此我們在不需要使用它們的時候就及時關(guān)閉,以便緩沖能及時得到釋放,從而避免內(nèi)存泄露。
動畫同樣是一個耗時任務(wù),比如在 Activity 中啟動了屬性動畫(ObjectAnimator),但是在銷毀 的時候,沒有調(diào)用 cancle 方法,雖然我們看不到動畫了,但是這個動畫依然會不斷地播放下去, 動畫引用所在的控件,所在的控件引用 Activity,這就造成 Activity 無法正常釋放。因此同樣要 在 Activity 銷毀的時候 cancel 掉屬性動畫,避免發(fā)生內(nèi)存泄漏。
@Override protected void onDestroy() { super.onDestroy(); mAnimator.cancel(); }
關(guān)于 WebView 的內(nèi)存泄露,因為 WebView在加載網(wǎng)頁后會長期占用內(nèi)存而不能被釋放,因此我 們在 Activity 銷毀后要調(diào)用它的 destory()方法來銷毀它以釋放內(nèi)存。
另外在查閱 WebView 內(nèi)存泄露相關(guān)資料時看到這種情況: Webview 下面的 Callback 持有 Activity 引用,造成 Webview 內(nèi)存無法釋放,即使是調(diào)用了 Webview.destory()等方法都無法解決問題(Android5.1 之后)
最終的解決方案是:在銷毀 WebView 之前需要先將 WebView 從父容器中移除,然后在銷毀 WebView。
@Override protected void onDestroy() { super.onDestroy(); // 先從父控件中移除 WebView mWebViewContainer.removeView(mWebView); mWebView.stopLoading(); mWebView.getSettings().setJavaScriptEnabled(false); mWebView.clearHistory(); mWebView.removeAllViews(); mWebView.destroy(); }
感謝各位的閱讀!關(guān)于“Android常見的內(nèi)存泄露應(yīng)用場景有哪些”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。