Android 內(nèi)存泄漏總結(jié)

Android 性能相關(guān)知識(shí)

  1. Android性能之 內(nèi)存泄漏總結(jié)
  2. Android性能之 卡頓分析解決
  3. Android性能之 ANR 分析解決
  4. Android性能之 OOM 產(chǎn)生和解決
  5. Android性能之布局優(yōu)化

android 是基于java語言的,所以對(duì)于內(nèi)存回收也是用到j(luò)ava 的 GC垃圾回收機(jī)制。

垃圾回收原理

垃圾回收器通常是作為一個(gè)單獨(dú)的低級(jí)別的線程運(yùn)行,不可預(yù)知的情況下對(duì)內(nèi)存堆中已經(jīng)死亡的或者長時(shí)間沒有使用的對(duì)象進(jìn)行清除和回收,程序員不能實(shí)時(shí)的調(diào)用垃圾回收器對(duì)某個(gè)對(duì)象或所有對(duì)象進(jìn)行垃圾回收。
回收機(jī)制有分代復(fù)制垃圾回收和標(biāo)記垃圾回收,增量垃圾回收

在Android中主要用到標(biāo)注并清理回收法,程序在運(yùn)行的過程中不停的創(chuàng)建新的對(duì)象并消耗內(nèi)存,直到內(nèi)存用光,這時(shí)再要?jiǎng)?chuàng)建新對(duì)象時(shí),系統(tǒng)暫停其它組件的運(yùn)行,觸發(fā)GC線程啟動(dòng)垃圾回收過程。內(nèi)存回收的原理很簡單,就是從所謂的"GC Roots"集合開始,將內(nèi)存整個(gè)遍歷一次,保留所有可以被GC Roots直接或間接引用到的對(duì)象,而剩下的對(duì)象都當(dāng)作垃圾對(duì)待并回收。

內(nèi)存泄漏根本原因

內(nèi)存泄露的根本原因就是保存了不可能再被訪問的變量類型的引用。
即在進(jìn)程中某些對(duì)象(垃圾對(duì)象)已經(jīng)沒有使用價(jià)值了,但是它們卻可以直接或間接地引用到gc roots導(dǎo)致無法被GC回收。無用的對(duì)象占據(jù)著內(nèi)存空間,使得實(shí)際可使用內(nèi)存變小,形象地說法就是內(nèi)存泄漏了。

常見內(nèi)存泄漏以及解決辦法

一、錯(cuò)誤使用單例造成的內(nèi)存泄漏

單例模式或者靜態(tài)方法長期持有Context對(duì)象,如果持有的Context對(duì)象生命周期與單例生命周期更短時(shí),或?qū)е翪ontext無法被釋放回收,則有可能造成內(nèi)存泄漏。

例如:

public class LoginManager {

    private static LoginManager mInstance;
    private Context mContext;

    private LoginManager(Context context) {
        //持有一般的context
        this.mContext = context;
    }


    public static LoginManager getInstance(Context context) {
        if (mInstance == null) {
            synchronized (LoginManager.class) {
                if (mInstance == null) {
                    mInstance = new LoginManager(context);
                }
            }
        }
        return mInstance;
    }

    public void dealData() {

    }
}

我們?cè)谝粋€(gè)Activity中調(diào)用的,然后關(guān)閉該Activity則會(huì)出現(xiàn)內(nèi)存泄漏。

LoginManager.getInstance(this).dealData();

用 LeakCanary檢測結(jié)果如下:

singleton.png

LeakCanary可以檢測到所有泄漏的調(diào)用鏈。

解決 辦法要保證Context和AppLication的生命周期一樣

public class LoginManagerRepair {

    private static LoginManagerRepair mInstance;
    private Context mContext;

    private LoginManagerRepair(Context context) {
        //用 aplicationContext
        this.mContext = context.getApplicationContext();
    }


    public static LoginManagerRepair getInstance(Context context) {
        if (mInstance == null) {
            synchronized (LoginManagerRepair.class) {
                if (mInstance == null) {
                    mInstance = new LoginManagerRepair(context);
                }
            }
        }
        return mInstance;
    }

    public void dealData() {

    }
}

二、Handler造成的內(nèi)存泄漏

通過內(nèi)部類的方式創(chuàng)建mHandler對(duì)象,此時(shí)mHandler會(huì)隱式地持有一個(gè)外部類對(duì)象引用這里就是HandlerActivity,當(dāng)執(zhí)行postDelayed方法時(shí),該方法會(huì)將你的Handler裝入一個(gè)Message,并把這條Message推到MessageQueue中,MessageQueue是在一個(gè)Looper線程中不斷輪詢處理消息,那么當(dāng)這個(gè)Activity退出時(shí)消息隊(duì)列中還有未處理的消息或者正在處理消息,而消息隊(duì)列中的Message持有mHandler實(shí)例的引用,mHandler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無法及時(shí)回收,引發(fā)內(nèi)存泄漏。

public class HandlerActivity 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);

        //模擬內(nèi)存泄漏.
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("ceshi");
            }
        },60*1000);
        finish();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ExampleApplication.getRefWatcher(this).watch(this);
    }
}

LeakCanary檢測結(jié)果如下:


handler.png

要想避免Handler引起內(nèi)存泄漏問題,需要我們?cè)贏ctivity關(guān)閉退出的時(shí)候的移除消息隊(duì)列中所有消息和所有的Runnable。上述代碼只需在onDestroy()函數(shù)中調(diào)用mHandler.removeCallbacksAndMessages(null);就行了。

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
        mHandler=null;
        LApplication.getRefWatcher().watch(this);
    }

三、AsyncTask 異步

處理一個(gè)比較耗時(shí)的操作時(shí),可能還沒處理結(jié)束AsynTaskActivity就執(zhí)行了退出操作,但是此時(shí)AsyncTask依然持有對(duì)AsynTaskActivity的引用就會(huì)導(dǎo)致AsynTaskActivity無法釋放回收引發(fā)內(nèi)存泄漏。

例如:

public class AsynTaskActivity 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);
        testAsynTask();

        finish();
    }

    private void testAsynTask(){
        asyncTask = new AsyncTask<Void, Void, Integer>() {
            @Override
            protected Integer doInBackground(Void... voids) {
                int i=0;
                while (!isCancelled()){
                    i++;
                    if(i>10000000000l){
                        break;
                    }
                    Log.e("LeakCanary", "asyncTask---->" + i);
                }

                return i;
            }

            @Override
            protected void onPostExecute(Integer integer) {
                super.onPostExecute(integer);
                mTextView.setText(String.valueOf(integer));
            }
        };

        asyncTask.execute();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ExampleApplication.getRefWatcher(this).watch(this);
    }
}

LeakCanary檢測結(jié)果:


asyntask.png

解決:在使用AsyncTask時(shí),在Activity銷毀時(shí)候也應(yīng)該取消相應(yīng)的任務(wù)AsyncTask.cancel()方法,避免任務(wù)在后臺(tái)執(zhí)行浪費(fèi)資源,進(jìn)而避免內(nèi)存泄漏的發(fā)生。

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        destroyAsyncTask();
        LApplication.getRefWatcher().watch(this);
    }

AsyncTask 還有一種內(nèi)部類的寫法:

public class AsynTask2Activity extends AppCompatActivity {

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = (TextView) findViewById(R.id.text);

        ScheduleTask scheduleTask = new ScheduleTask();
        scheduleTask.execute();

        finish();
    }

    class ScheduleTask extends AsyncTask<Void, Void, Integer> {

        @Override
        protected Integer doInBackground(Void... voids) {
            int i=0;
            while (!isCancelled()){
                i++;
                if(i>10000000000l){
                    break;
                }
                Log.e("LeakCanary", "asyncTask---->" + i);
            }

            return i;
        }

        @Override
        protected void onPostExecute(Integer integer) {
            super.onPostExecute(integer);
            mTextView.setText(String.valueOf(integer));
        }
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        ExampleApplication.getRefWatcher(this).watch(this);
    }
}

一樣會(huì)導(dǎo)致內(nèi)存泄漏,可以使用弱引用:

class ScheduleTask extends AsyncTask<Void, Void, Integer> {

        private final WeakReference<Activity> mTarget;

        public ScheduleTask(Activity activity){
            mTarget = new WeakReference<Activity>(activity);
        }

        @Override
        protected Integer doInBackground(Void... voids) {
            int i=0;
            while (!isCancelled()){
                i++;
                if(i>10000000000l){
                    break;
                }
                Log.e("LeakCanary", "asyncTask---->" + i);
            }

            return i;
        }

        @Override
        protected void onPostExecute(Integer integer) {
            super.onPostExecute(integer);
            Activity activity = mTarget.get();
            if (activity == null
                    || activity.isFinishing()) {
                // activity沒了,就結(jié)束可以了
                return;
            }

            mTextView.setText(String.valueOf(integer));
        }
    }

四、非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏

public class InnerClassActivity extends AppCompatActivity {

    private static Config config;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(config == null) {
            config = new Config();
            config.setSize(11);
        }

        finish();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ExampleApplication.getRefWatcher(this).watch(this);
    }

    class Config {
        private int size;

        public int getSize() {
            return size;
        }

        public void setSize(int size) {
            this.size = size;
        }

    }

}

內(nèi)部類都會(huì)持有一個(gè)外部類引用,這里這個(gè)外部類就是InnerClassActivity,然而內(nèi)部類實(shí)例又是static靜態(tài)變量其生命周期與Application生命周期一樣,所以在InnerClassActivity關(guān)閉的時(shí)候,內(nèi)部類靜態(tài)實(shí)例依然持有對(duì)InnerClassActivity的引用,導(dǎo)致InnerClassActivity無法被回收釋放,引發(fā)內(nèi)存泄漏。

inner.png

對(duì)于這種泄漏的解決辦法就是將內(nèi)部類改成靜態(tài)內(nèi)部類,不再持有InnerClassActivity的引用即可,修改后的代碼如下:

static class Config {
        private int size;

        public int getSize() {
            return size;
        }

        public void setSize(int size) {
            this.size = size;
        }

    }

五、由WebView引起的內(nèi)存泄漏

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.baidu.com");
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        LApplication.getRefWatcher().watch(this);
    }

}

WebView解析網(wǎng)頁時(shí)會(huì)申請(qǐng)Native堆內(nèi)存用于保存頁面元素,當(dāng)頁面較復(fù)雜時(shí)會(huì)有很大的內(nèi)存占用。如果頁面包含圖片,內(nèi)存占用會(huì)更嚴(yán)重。并且打開新頁面時(shí),為了能快速回退,之前頁面占用的內(nèi)存也不會(huì)釋放。有時(shí)瀏覽十幾個(gè)網(wǎng)頁,都會(huì)占用幾百兆的內(nèi)存。這樣加載網(wǎng)頁較多時(shí),會(huì)導(dǎo)致系統(tǒng)不堪重負(fù),最終強(qiáng)制關(guān)閉應(yīng)用,也就是出現(xiàn)應(yīng)用閃退或重啟。及時(shí)Activity關(guān)閉時(shí)在onDestroy中調(diào)用如下代碼也是沒有任何作用。

private void destroyWebView() {
        if (mWebView != null) {
            mLinearLayout.removeView(mWebView);
            mWebView.pauseTimers();
            mWebView.removeAllViews();
            mWebView.destroy();
            mWebView = null;
        }
    }

網(wǎng)上一種解決方法是使用getApplicationgContext作為參數(shù)構(gòu)建WebView,然后動(dòng)態(tài)添加到一個(gè)ViewGroup中,最后退出的時(shí)候調(diào)用webView的銷毀的函數(shù),雖然也達(dá)到了防止內(nèi)存溢出的效果,但是在有些網(wǎng)頁彈出時(shí)候需要記住密碼的對(duì)話框的時(shí)候,會(huì)出現(xiàn)Unable to add window -- token null is not for an application 的錯(cuò)誤。

所以這里采用的解決辦法是通過把使用了WebView的Activity(或者Service)放在單獨(dú)的進(jìn)程里。然后在檢測到應(yīng)用占用內(nèi)存過大有可能被系統(tǒng)干掉或者它所在的Activity(或者Service)結(jié)束后,調(diào)用android.os.Process.killProcess(android.os.Process.myPid());,主動(dòng)Kill掉進(jìn)程。由于系統(tǒng)的內(nèi)存分配是以進(jìn)程為準(zhǔn)的,進(jìn)程關(guān)閉后,系統(tǒng)會(huì)自動(dòng)回收所有內(nèi)存。

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");
    }

    @Override
    protected void onDestroy() {
        destroyWebView();
        android.os.Process.killProcess(android.os.Process.myPid());
        super.onDestroy();
        LApplication.getRefWatcher().watch(this);

    }

    private void destroyWebView() {
        if (mWebView != null) {
            mWebView.pauseTimers();
            mWebView.removeAllViews();
            mWebView.destroy();
            mWebView = null;
        }
    }

}

放在單獨(dú)的進(jìn)程。manifest中對(duì)應(yīng)的activity配置如下:

<activity
   android:name=".MainActivity5"
   android:process="com.whoislcj.webview"/>

六、資源未關(guān)閉造成的內(nèi)存泄漏

對(duì)于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷,否則這些資源將不會(huì)被回收,造成內(nèi)存泄漏。例如獲取媒體庫圖片地址代碼在查詢結(jié)束的時(shí)候一定要調(diào)用Cursor 的關(guān)閉方法防止造成內(nèi)存泄漏。

String columns[] = new String[]{
                MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID, MediaStore.Images.Media.TITLE, MediaStore.Images.Media.DISPLAY_NAME
        };
        Cursor cursor = this.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, null);
        if (cursor != null) {
            int photoIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            //顯示每張圖片的地址,但是首先要判斷一下,Cursor是否有值
            while (cursor.moveToNext()) {
                String photoPath = cursor.getString(photoIndex); //這里獲取到的就是圖片存儲(chǔ)的位置信息
                Log.e("LeakCanary", "photoPath---->" + photoPath);
            }
            cursor.close();
        }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容