Android 優(yōu)化一: Leakcanary檢測內存泄漏匯總

Leakcanary檢測內存泄漏匯總
目錄介紹:
1.什么是內存泄漏
2.內存泄漏造成什么影響
3.內存泄漏檢測的工具有哪些
4.關于Leakcanary使用介紹
5.Leakcanary捕捉常見的內存泄漏及解決辦法
5.1 錯誤使用單例造成的內存泄漏
5.2 錯誤使用靜態(tài)變量,導致引用后無法銷毀【工具類使用不當導致內存泄漏】
5.3 Handler造成的內存泄漏
5.4 線程造成的內存泄漏
5.5 由WebView引起的內存泄漏
5.6 資源未關閉造成的內存泄漏
5.7 未注銷EventBus導致的內存泄漏
5.8 靜態(tài)集合使用不當導致的內存泄漏
5.9 使用弱引用避免內存泄漏
6.其他

好消息

  • 博客筆記大匯總【16年3月到至今】,包括Java基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發(fā)中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉載請注明出處,謝謝!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質變!

1.什么是內存泄漏?
一些對象有著有限的聲明周期,當這些對象所要做的事情完成了,我們希望它們會被垃圾回收器回收掉。但是如果有一系列對這個對象的引用存在,那么在我們期待這個對象生命周期結束時被垃圾回收器回收的時候,它是不會被回收的。它還會占用內存,這就造成了內存泄露。持續(xù)累加,內存很快被耗盡。
比如:當Activity的onDestroy()方法被調用后,Activity以及它涉及到的View和相關的Bitmap都應該被回收掉。但是,如果有一個后臺線程持有這個Activity的引用,那么該Activity所占用的內存就不能被回收,這最終將會導致內存耗盡引發(fā)OOM而讓應用crash掉。

2.內存泄漏會造成什么影響?
它是造成應用程序OOM的主要原因之一。由于android系統(tǒng)為每個應用程序分配的內存有限,當一個應用中產生的內存泄漏比較多時,就難免會導致應用所需要的內存超過這個系統(tǒng)分配的內存限額,這就造成了內存溢出而導致應用Crash。

3.內存泄漏檢測的工具有哪些
最常見的是:Leakcanary

4.關于Leakcanary使用介紹
leakCanary是Square開源框架,是一個Android和Java的內存泄露檢測庫,如果檢測到某個 activity 有內存泄露,LeakCanary 就是自動地顯示一個通知,所以可以把它理解為傻瓜式的內存泄露檢測工具。通過它可以大幅度減少開發(fā)中遇到的oom問題,大大提高APP的質量。
關于如何配置,這個就不說呢,網(wǎng)上有步驟

5.Leakcanary捕捉常見的內存泄漏及解決辦法

  • 5.1 錯誤使用單例造成的內存泄漏
    在平時開發(fā)中單例設計模式是我們經(jīng)常使用的一種設計模式,而在開發(fā)中單例經(jīng)常需要持有Context對象,如果持有的Context對象生命周期與單例生命周期更短時,或導致Context無法被釋放回收,則有可能造成內存泄漏,錯誤寫法如下:
  • 問題引起內存泄漏代碼
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() {}
}
  • 在一個Activity中調用的,然后關閉該Activity則會出現(xiàn)內存泄漏。
LoginManager.getInstance(this).dealData();
  • 看看報錯
Image.png
  • 解決辦法:
要保證Context和AppLication的生命周期一樣,修改后代碼如下:
this.mContext = context.getApplicationContext();
  • 原因分析
    創(chuàng)建單例對象,并且在創(chuàng)建的時候需要傳入一個Context對象,而這時候如果我們使用Activity、Service等Context對象,由于單例對象的生命周期與進程的生命周期相同,會造成我們傳入的Activity、Service對象無法被回收,這時候就需要我們傳入Application對象,或者在方法中使用Application對象

  • 5.2 錯誤使用靜態(tài)變量,導致引用后無法銷毀
    在平時開發(fā)中,有時候我們創(chuàng)建了一個工具類。比如分享工具類,十分方便多處調用,因此使用靜態(tài)方法是十分方便的。但是創(chuàng)建的對象,建議不要全局化,全局化的變量必須加上static。這樣會引起內存泄漏!

  • 問題代碼

  • Image.png
  • 在Activity中引用后,關閉該Activity會導致內存泄漏

DoShareUtil.showFullScreenShareView(PNewsContentActivity.this, title, title, shareurl, logo);
  • 查看報錯

  • Image.png
  • 解決辦法
    靜態(tài)方法中,創(chuàng)建對象或變量,不要全局化,全局化后的變量或者對象會導致內存泄漏;popMenuView和popMenu都不要全局化

  • 知識延伸

**非靜態(tài)內部類,靜態(tài)實例化**
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() {}
    }
}

這里內部類InnerClass隱式的持有外部類MyActivity的引用,而在MyActivity的onCreate方法中調用了。
這樣innerClass就會在MyActivity創(chuàng)建的時候是有了他的引用,而innerClass是靜態(tài)類型的不會被垃圾回收,
MyActivity在執(zhí)行onDestory方法的時候由于被innerClass持有了引用而無法被回收,所以這樣MyActivity就總是被innerClass持有而無法回收造成內存泄露。
  • 5.3 Handler造成的內存泄漏【輪播圖無限循環(huán)輪播,一定要關閉,否則內存泄漏】
    handler是工作線程與UI線程之間通訊的橋梁,只是現(xià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);        //模擬內存泄露
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("yangchong");
            }
        }, 2000);
    }
}
  • 造成內存泄漏原因分析
    上述代碼通過內部類的方式創(chuàng)建mHandler對象,此時mHandler會隱式地持有一個外部類對象引用這里就是MainActivity,當執(zhí)行postDelayed方法時,該方法會將你的Handler裝入一個Message,并把這條Message推到MessageQueue中,MessageQueue是在一個Looper線程中不斷輪詢處理消息,那么當這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,mHandler又持有Activity的引用,所以導致該Activity的內存資源無法及時回收,引發(fā)內存泄漏。

  • 查看報錯結果如下:


    Image.png
  • 解決辦法
    要想避免Handler引起內存泄漏問題,需要我們在Activity關閉退出的時候的移除消息隊列中所有消息和所有的Runnable。
    上述代碼只需在onDestroy()函數(shù)中調用mHandler.removeCallbacksAndMessages(null);就行了。

@Override
protected void onDestroy() {
    super.onDestroy();
    if(handler!=null){
        handler.removeCallbacksAndMessages(null);
        handler = null;
    }
}
  • 5.4 線程造成的內存泄漏
    早時期的時候處理耗時操作多數(shù)都是采用Thread+Handler的方式,后來逐步被AsyncTask取代,直到現(xiàn)在采用RxJava的方式來處理異步。這里以AsyncTask為例,可能大部分人都會這樣處理一個耗時操作然后通知UI更新結果:
  • 問題代碼
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;
                //模擬耗時操作
                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();
    }
}
  • 造成內存泄漏原因分析
    在處理一個比較耗時的操作時,可能還沒處理結束MainActivity就執(zhí)行了退出操作,但是此時AsyncTask依然持有對MainActivity的引用就會導致MainActivity無法釋放回收引發(fā)內存泄漏

  • 查看報錯結果如下:


    Image.png
  • 解決辦法
    在使用AsyncTask時,在Activity銷毀時候也應該取消相應的任務AsyncTask.cancel()方法,避免任務在后臺執(zhí)行浪費資源,進而避免內存泄漏的發(fā)生

private void destroyAsyncTask() {
    if (asyncTask != null && !asyncTask.isCancelled()) {
        asyncTask.cancel(true);
    }
    asyncTask = null;
}

@Override
protected void onDestroy() {
    super.onDestroy();
    destroyAsyncTask();
}
  • 5.5 由WebView引起的內存泄漏
    WebView解析網(wǎng)頁時會申請Native堆內存用于保存頁面元素,當頁面較復雜時會有很大的內存占用。如果頁面包含圖片,內存占用會更嚴重。并且打開新頁面時,為了能快速回退,之前頁面占用的內存也不會釋放。有時瀏覽十幾個網(wǎng)頁,都會占用幾百兆的內存。這樣加載網(wǎng)頁較多時,會導致系統(tǒng)不堪重負,最終強制關閉應用,也就是出現(xià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");
    }
}
  • 造成內存泄漏原因分析
    加載網(wǎng)頁有緩存,當加載了許多網(wǎng)頁,并且手機配置比較低時,造成的內存泄漏就對手機影響很大

  • 查看報錯結果如下:

  • 解決辦法

@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 資源未關閉造成的內存泄漏
    對于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者注銷,否則這些資源將不會被回收,造成內存泄漏。

  • 5.7 未注銷EventBus導致的內存泄漏
    直接展示代碼

@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();
    //未移除注冊的EventBus
    //EventBus.getDefault().unregister(this);
}
  • 5.8 靜態(tài)集合使用不當導致的內存泄漏
    添加Activity到棧,或者移除Activity出棧。導致內存泄漏
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)集合沒有移除元素
    //ActivityCollector.removeActivity(this);
}
  • 5.9 使用弱引用避免內存泄漏
    在 Activity 中避免使用非靜態(tài)內部類,比如上面我們將 Handler 聲明為靜態(tài)的,則其存活期跟 Activity 的生命周期就無關了。同時通過弱引用的方式引入 Activity,避免直接將 Activity 作為 context 傳進去
public class WeakReferenceActivity extends AppCompatActivity {

    //將Handler聲明為靜態(tài) 沒有了Activity的引用, 無法直接引用其變量或方法,
    //使用弱引用WeakReference來解決這個問題
    private static class DBHandler extends Handler {
        //弱引用, 而不是使用外部類this或者傳進來
        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ù):
平時喜歡寫寫文章,筆記。別人建議我把筆記,以前寫的東西整理,然后寫成博客,所以我會陸續(xù)整理文章,只發(fā)自己寫的東西,敬請期待。如果有什么問題或者需要筆記,可以直接聯(lián)系我,可以發(fā)送印象筆記文檔給你!
知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
領英:https://www.linkedin.com/in/chong-yang-049216146/
簡書:http://www.itdecent.cn/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
喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
脈脈:yc930211
開源中國:https://my.oschina.net/zbj1618/blog
郵箱:yangchong211@163.com

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容