欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Android內(nèi)存泄露及優(yōu)化方案

 更新時(shí)間:2021年09月10日 11:33:00   作者:我唱著黑色毛衣  
這篇文章主要介紹了詳解Android內(nèi)存泄露及優(yōu)化方案,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

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

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

單例是我們開(kāi)發(fā)中最常見(jiàn)和使用最頻繁的設(shè)計(jì)模式之一,所以如果使用不當(dāng)就會(huì)導(dǎo)致內(nèi)存泄露。因?yàn)閱卫撵o態(tài)特性使得它的生命周期同應(yīng)用的生命周期一樣長(zhǎng),如果一個(gè)對(duì)象已經(jīng)沒(méi)有用處了,但是單例還持有它的引用,那么在整個(gè)應(yīng)用程序的生命周期這個(gè)對(duì)象都不能正常被回收,從而導(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)方法時(shí)傳入的上下文如果為當(dāng)前活動(dòng)Activity或者當(dāng)前服務(wù)的Service以及當(dāng)前fragment的上下文,當(dāng)他們銷(xiāo)毀時(shí),這個(gè)靜態(tài)單例sIntance還會(huì)持用他們的引用,從而導(dǎo)致當(dāng)前活動(dòng)、服務(wù)、fragment等對(duì)象不能被回收釋放,從而導(dǎo)致內(nèi)存泄漏。這種上下文的使用很多時(shí)候處理不當(dāng)就會(huì)導(dǎo)致內(nèi)存泄漏,需要我們多注意編碼規(guī)范。

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

靜態(tài)變量存儲(chǔ)在方法區(qū),它的生命周期從類(lèi)加載開(kāi)始,到整個(gè)進(jìn)程結(jié)束。一旦靜態(tài)變量初始化后, 它所持有的引用只有等到進(jìn)程結(jié)束才會(huì)釋放。

如下代碼:

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 長(zhǎng)。所以當(dāng) Activity 退出后,sInfo 仍然引用了 Activity,Activity 不能被回收, 這就導(dǎo)致了內(nèi)存泄露。 在 Android 開(kāi)發(fā)中,靜態(tài)持有很多時(shí)候都有可能因?yàn)槠涫褂玫纳芷诓灰恢露鴮?dǎo)致內(nèi)存泄露, 所以我們?cè)谛陆o態(tài)持有的變量的時(shí)候需要多考慮一下各個(gè)成員之間的引用關(guān)系,并且盡量少地 使用靜態(tài)持有的變量,以避免發(fā)生內(nèi)存泄露。當(dāng)然,我們也可以在適當(dāng)?shù)臅r(shí)候講靜態(tài)量重置為 null, 使其不再持有引用,這樣也可以避免內(nèi)存泄露。

3、非靜態(tài)內(nèi)部類(lèi)導(dǎo)致內(nèi)存泄露

非靜態(tài)內(nèi)部類(lèi)(包括匿名內(nèi)部類(lèi))默認(rèn)就會(huì)持有外部類(lèi)的引用,當(dāng)非靜態(tài)內(nèi)部類(lèi)對(duì)象的生命周期 比外部類(lèi)對(duì)象的生命周期長(zhǎng)時(shí),就會(huì)導(dǎo)致內(nèi)存泄露。這類(lèi)內(nèi)存泄漏很典型的Handler的使用,這么一說(shuō)大家應(yīng)該就很熟悉了吧,大家都知道怎么處理這類(lèi)內(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 消息機(jī)制,mHandler 會(huì)作為成員變量保存在發(fā)送的消息 msg 中,即 msg 持有 mHandler 的引用,而 mHandler 是 Activity 的非靜態(tài)內(nèi)部類(lèi)實(shí)例,即 mHandler 持有 Activity 的引 用,那么我們就可以理解為 msg 間接持有 Activity 的引用。msg 被發(fā)送后先放到消息隊(duì)列 MessageQueue 中,然后等待 Looper 的輪詢(xún)處理(MessageQueue 和 Looper 都是與線程相關(guān)聯(lián)的, MessageQueue 是 Looper 引用的成員變量,而 Looper 是保存在 ThreadLocal 中的)。那么當(dāng) Activity 退出后,msg 可能仍然存在于消息對(duì)列 MessageQueue 中未處理或者正在處理,那么這樣就會(huì)導(dǎo)致 Activity 無(wú)法被回收,以致發(fā)生 Activity 的內(nèi)存泄露。

如何避免:
1、采用靜態(tài)內(nèi)部類(lè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 通過(guò)弱引用的方式持有 Activity,當(dāng) GC 執(zhí)行垃圾回收時(shí),遇到 Activity 就會(huì)回收并釋 放所占據(jù)的內(nèi)存單元。這樣就不會(huì)發(fā)生內(nèi)存泄露了。但是 msg 還是有可能存在消息隊(duì)列 MessageQueue 中。

2、Activity 銷(xiāo)毀時(shí)就將 mHandler 的回調(diào)和發(fā)送的消息給移除掉。

  @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }

非靜態(tài)內(nèi)部類(lè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)部類(lèi)對(duì)象,默認(rèn)就隱式的持有外部 Activity 的引用, 導(dǎo)致 Activity 內(nèi)存泄露。要避免內(nèi)存泄露的話還是需要像上面 Handler 一樣使用采用靜態(tài)內(nèi)部類(lèi)+弱引用的方式(如上面Hanlder采用靜態(tài)內(nèi)部類(lèi)+弱引用的方式)。

4、未取消注冊(cè)或回調(diào)導(dǎo)致內(nèi)存泄露

比如我們?cè)?Activity 中注冊(cè)廣播,如果在 Activity 銷(xiāo)毀后不取消注冊(cè),那么這個(gè)剛播會(huì)一直存在 系統(tǒng)中,同上面所說(shuō)的非靜態(tài)內(nèi)部類(lèi)一樣持有 Activity 引用,導(dǎo)致內(nèi)存泄露。因此注冊(cè)廣播后在 Activity 銷(xiāo)毀后一定要取消注冊(cè)。

this.unregisterReceiver(mReceiver);

在注冊(cè)觀察則模式的時(shí)候,如果不及時(shí)取消也會(huì)造成內(nèi)存泄露。比如使用 Retrofit+RxJava 注冊(cè)網(wǎng)絡(luò)請(qǐng)求的觀察者回調(diào),同樣作為匿名內(nèi)部類(lèi)持有外部引用,所以需要記得在不用或者銷(xiāo)毀的時(shí)候 取消注冊(cè)。

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

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

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

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

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

在使用 IO、File 流或者 Sqlite、Cursor 等資源時(shí)要及時(shí)關(guān)閉。這些資源在進(jìn)行讀寫(xiě)操作時(shí)通常都 使用了緩沖,如果及時(shí)不關(guān)閉,這些緩沖對(duì)象就會(huì)一直被占用而得不到釋放,以致發(fā)生內(nèi)存泄露。 因此我們?cè)诓恍枰褂盟鼈兊臅r(shí)候就及時(shí)關(guān)閉,以便緩沖能及時(shí)得到釋放,從而避免內(nèi)存泄露。

8、動(dòng)畫(huà)造成內(nèi)存泄露

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

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

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

關(guān)于 WebView 的內(nèi)存泄露,因?yàn)?WebView在加載網(wǎng)頁(yè)后會(huì)長(zhǎng)期占用內(nèi)存而不能被釋放,因此我 們?cè)?Activity 銷(xiāo)毀后要調(diào)用它的 destory()方法來(lái)銷(xiāo)毀它以釋放內(nèi)存。
另外在查閱 WebView 內(nèi)存泄露相關(guān)資料時(shí)看到這種情況: Webview 下面的 Callback 持有 Activity 引用,造成 Webview 內(nèi)存無(wú)法釋放,即使是調(diào)用了 Webview.destory()等方法都無(wú)法解決問(wèn)題(Android5.1 之后)

最終的解決方案是:在銷(xiāo)毀 WebView 之前需要先將 WebView 從父容器中移除,然后在銷(xiāo)毀 WebView。

   @Override
    protected void onDestroy() {
        super.onDestroy();
        // 先從父控件中移除
        WebView mWebViewContainer.removeView(mWebView);
        mWebView.stopLoading();
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.removeAllViews();
        mWebView.destroy();
    }

總結(jié)

構(gòu)造單例的時(shí)候盡量別用 Activity 的引用;
靜態(tài)引用時(shí)注意應(yīng)用對(duì)象的置空或者少用靜態(tài)引用;
使用靜態(tài)內(nèi)部類(lèi)+軟引用代替非靜態(tài)內(nèi)部類(lèi);
及時(shí)取消廣播或者觀察者注冊(cè);
耗時(shí)任務(wù)、屬性動(dòng)畫(huà)在 Activity 銷(xiāo)毀時(shí)記得 cancel;
文件流、Cursor 等資源及時(shí)關(guān)閉; Activity 銷(xiāo)毀時(shí) WebView 的移除和銷(xiāo)毀。

下一篇繼續(xù):詳解Android內(nèi)存優(yōu)化策略

ps:內(nèi)存泄漏是開(kāi)發(fā)中的一個(gè)痛點(diǎn),需要我們有很好的良好編碼習(xí)慣。奧里給?。。。。。。。?!

到此這篇關(guān)于詳解Android內(nèi)存泄露及優(yōu)化方案一的文章就介紹到這了,更多相關(guān)Android內(nèi)存優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論