您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關Android常見的內存泄露應用場景有哪些的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
單例是我們開發中最常見和使用最頻繁的設計模式之一,所以如果使用不當就會導致內存泄露。因為單例的靜態特性使得它的生命周期同應用的生命周期一樣長,如果一個對象已經沒有用處了,但是單例還持有它的引用,那么在整個應用程序的生命周期這個對象都不能正常被回收,從而導致內存泄露。
如:
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; } }
調用getInstance(Context context)方法時傳入的上下文如果為當前活動Activity或者當前服務的Service以及當前fragment的上下文,當他們銷毀時,這個靜態單例sIntance還會持用他們的引用,從而導致當前活動、服務、fragment等對象不能被回收釋放,從而導致內存泄漏。這種上下文的使用很多時候處理不當就會導致內存泄漏,需要我們多注意編碼規范。
靜態變量存儲在方法區,它的生命周期從類加載開始,到整個進程結束。一旦靜態變量初始化后, 它所持有的引用只有等到進程結束才會釋放。
如下代碼:
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 的靜態成員,并且持有 Activity 的引用,但是 sInfo 作為靜態變量,生命周期 肯定比 Activity 長。所以當 Activity 退出后,sInfo 仍然引用了 Activity,Activity 不能被回收, 這就導致了內存泄露。 在 Android 開發中,靜態持有很多時候都有可能因為其使用的生命周期不一致而導致內存泄露, 所以我們在新建靜態持有的變量的時候需要多考慮一下各個成員之間的引用關系,并且盡量少地 使用靜態持有的變量,以避免發生內存泄露。當然,我們也可以在適當的時候講靜態量重置為 null, 使其不再持有引用,這樣也可以避免內存泄露。
非靜態內部類(包括匿名內部類)默認就會持有外部類的引用,當非靜態內部類對象的生命周期 比外部類對象的生命周期長時,就會導致內存泄露。這類內存泄漏很典型的Handler的使用,這么一說大家應該就很熟悉了吧,大家都知道怎么處理這類內存泄漏。
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 會作為成員變量保存在發送的消息 msg 中,即 msg 持有 mHandler 的引用,而 mHandler 是 Activity 的非靜態內部類實例,即 mHandler 持有 Activity 的引 用,那么我們就可以理解為 msg 間接持有 Activity 的引用。msg 被發送后先放到消息隊列 MessageQueue 中,然后等待 Looper 的輪詢處理(MessageQueue 和 Looper 都是與線程相關聯的, MessageQueue 是 Looper 引用的成員變量,而 Looper 是保存在 ThreadLocal 中的)。那么當 Activity 退出后,msg 可能仍然存在于消息對列 MessageQueue 中未處理或者正在處理,那么這樣就會導致 Activity 無法被回收,以致發生 Activity 的內存泄露。
如何避免:
1、采用靜態內部類+弱引用的方式
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) { // 做相應邏輯 } } } }
mHandler 通過弱引用的方式持有 Activity,當 GC 執行垃圾回收時,遇到 Activity 就會回收并釋 放所占據的內存單元。這樣就不會發生內存泄露了。但是 msg 還是有可能存在消息隊列 MessageQueue 中。
2、Activity 銷毀時就將 mHandler 的回調和發送的消息給移除掉。
@Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }
非靜態內部類造成內存泄露還有一種情況就是使用 Thread 或者 AsyncTask異步調用:
如示例:
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 都是匿名內部類對象,默認就隱式的持有外部 Activity 的引用, 導致 Activity 內存泄露。要避免內存泄露的話還是需要像上面 Handler 一樣使用采用靜態內部類+弱引用的方式(如上面Hanlder采用靜態內部類+弱引用的方式)。
比如我們在 Activity 中注冊廣播,如果在 Activity 銷毀后不取消注冊,那么這個剛播會一直存在 系統中,同上面所說的非靜態內部類一樣持有 Activity 引用,導致內存泄露。因此注冊廣播后在 Activity 銷毀后一定要取消注冊。
this.unregisterReceiver(mReceiver);
在注冊觀察則模式的時候,如果不及時取消也會造成內存泄露。比如使用 Retrofit+RxJava 注冊網絡請求的觀察者回調,同樣作為匿名內部類持有外部引用,所以需要記得在不用或者銷毀的時候 取消注冊。
當我們 Activity 銷毀的時,有可能 Timer 還在繼續等待執行 TimerTask,它持有 Activity 的引用不 能被回收,因此當我們 Activity 銷毀的時候要立即 cancel 掉 Timer 和 TimerTask,以避免發生內存 泄漏。
這個比較好理解,如果一個對象放入到 ArrayList、HashMap 等集合中,這個集合就會持有該對象 的引用。當我們不再需要這個對象時,也并沒有將它從集合中移除,這樣只要集合還在使用(而 此對象已經無用了),這個對象就造成了內存泄露。并且如果集合被靜態引用的話,集合里面那 些沒有用的對象更會造成內存泄露了。所以在使用集合時要及時將不用的對象從集合 remove,或 者 clear 集合,以避免內存泄漏。
在使用 IO、File 流或者 Sqlite、Cursor 等資源時要及時關閉。這些資源在進行讀寫操作時通常都 使用了緩沖,如果及時不關閉,這些緩沖對象就會一直被占用而得不到釋放,以致發生內存泄露。 因此我們在不需要使用它們的時候就及時關閉,以便緩沖能及時得到釋放,從而避免內存泄露。
動畫同樣是一個耗時任務,比如在 Activity 中啟動了屬性動畫(ObjectAnimator),但是在銷毀 的時候,沒有調用 cancle 方法,雖然我們看不到動畫了,但是這個動畫依然會不斷地播放下去, 動畫引用所在的控件,所在的控件引用 Activity,這就造成 Activity 無法正常釋放。因此同樣要 在 Activity 銷毀的時候 cancel 掉屬性動畫,避免發生內存泄漏。
@Override protected void onDestroy() { super.onDestroy(); mAnimator.cancel(); }
關于 WebView 的內存泄露,因為 WebView在加載網頁后會長期占用內存而不能被釋放,因此我 們在 Activity 銷毀后要調用它的 destory()方法來銷毀它以釋放內存。
另外在查閱 WebView 內存泄露相關資料時看到這種情況: Webview 下面的 Callback 持有 Activity 引用,造成 Webview 內存無法釋放,即使是調用了 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(); }
感謝各位的閱讀!關于“Android常見的內存泄露應用場景有哪些”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。