溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android常見的內(nèi)存泄露應(yīng)用場景有哪些

發(fā)布時間:2021-09-10 13:21:48 來源:億速云 閱讀:155 作者:小新 欄目:開發(fā)技術(shù)

這篇文章給大家分享的是有關(guān)Android常見的內(nèi)存泄露應(yīng)用場景有哪些的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

    一、常見的內(nèi)存泄露應(yīng)用場景?

    1、單例的不恰當(dāng)使用

    單例是我們開發(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ī)范。

    2、靜態(tài)變量導(dǎo)致內(nèi)存泄露

    靜態(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)存泄露。

    3、非靜態(tài)內(nèi)部類導(dǎo)致內(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)部類+弱引用的方式)。

    4、未取消注冊或回調(diào)導(dǎo)致內(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)部類持有外部引用,所以需要記得在不用或者銷毀的時候 取消注冊。

    5、定時器Timer 和 TimerTask 導(dǎo)致內(nèi)存泄露

    當(dāng)我們 Activity 銷毀的時,有可能 Timer 還在繼續(xù)等待執(zhí)行 TimerTask,它持有 Activity 的引用不 能被回收,因此當(dāng)我們 Activity 銷毀的時候要立即 cancel 掉 Timer 和 TimerTask,以避免發(fā)生內(nèi)存 泄漏。

    6、集合中的對象未清理造成內(nèi)存泄露

    這個比較好理解,如果一個對象放入到 ArrayList、HashMap 等集合中,這個集合就會持有該對象 的引用。當(dāng)我們不再需要這個對象時,也并沒有將它從集合中移除,這樣只要集合還在使用(而 此對象已經(jīng)無用了),這個對象就造成了內(nèi)存泄露。并且如果集合被靜態(tài)引用的話,集合里面那 些沒有用的對象更會造成內(nèi)存泄露了。所以在使用集合時要及時將不用的對象從集合 remove,或 者 clear 集合,以避免內(nèi)存泄漏。

    7、資源未關(guān)閉或釋放導(dǎo)致內(nèi)存泄露

    在使用 IO、File 流或者 Sqlite、Cursor 等資源時要及時關(guān)閉。這些資源在進(jìn)行讀寫操作時通常都 使用了緩沖,如果及時不關(guān)閉,這些緩沖對象就會一直被占用而得不到釋放,以致發(fā)生內(nèi)存泄露。 因此我們在不需要使用它們的時候就及時關(guān)閉,以便緩沖能及時得到釋放,從而避免內(nèi)存泄露。

    8、動畫造成內(nèi)存泄露

    動畫同樣是一個耗時任務(wù),比如在 Activity 中啟動了屬性動畫(ObjectAnimator),但是在銷毀 的時候,沒有調(diào)用 cancle 方法,雖然我們看不到動畫了,但是這個動畫依然會不斷地播放下去, 動畫引用所在的控件,所在的控件引用 Activity,這就造成 Activity 無法正常釋放。因此同樣要 在 Activity 銷毀的時候 cancel 掉屬性動畫,避免發(fā)生內(nèi)存泄漏。

      @Override
        protected void onDestroy() {
            super.onDestroy();
            mAnimator.cancel();
        }

    9、WebView 造成內(nèi)存泄露

    關(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é)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

    向AI問一下細(xì)節(jié)

    免責(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)容。

    AI