android 內(nèi)存泄漏(多篇文章摘錄)

什么叫內(nèi)存泄漏呢?

gc沒(méi)有辦法回收activity的內(nèi)存

那什么情況下會(huì)GC呢?

  • 屏幕旋轉(zhuǎn)的時(shí)候
  • 內(nèi)存不夠用的時(shí)候。

什么情況會(huì)發(fā)生內(nèi)存泄漏呢?

靜態(tài)資源問(wèn)題

靜態(tài)變量在整個(gè)應(yīng)用的內(nèi)存里只保存一份,一旦創(chuàng)建就不會(huì)釋放該變量的內(nèi)存,直到整個(gè)應(yīng)用都銷(xiāo)毀才會(huì)釋放static靜態(tài)變量的內(nèi)存.

例子:

public class MyCustomResource {
    //靜態(tài)變量drawable
    private static Drawable drawable;
    private View view;

    public MyCustomResource(Context context) {
        Resources resources = context.getResources();
        drawable = resources.getDrawable(R.drawable.ic_launcher);
        view = new View(context);
        view.setBackgroundDrawable(drawable);
    }
}
//查看setBackgroundDrawable的源代碼:
public  void  setBackgroundDrawable(Drawable background) { 
..........
/**此處的this就是當(dāng)前View對(duì)象,而View對(duì)象又是有Context對(duì)象獲得 因此,變量background持有View對(duì)象的引用,View持有Context的引用, 所有background間接持有Context對(duì)象的引用了*/
background.setCallback(this);
.......
}

分析:
此處的background.setCallback(this);this是當(dāng)前的view的對(duì)象。由于background是一個(gè)靜態(tài)變量,會(huì)一直持有View對(duì)象的引用,而然View對(duì)象又是由Context對(duì)象創(chuàng)建出來(lái)的,因此background會(huì)間接持有Context的對(duì)象的引用
所以:
該Context對(duì)應(yīng)的Activity退出finish掉的時(shí)候其實(shí)該Activity是不能完全釋放內(nèi)存的
值得注意的是:
代碼是由于靜態(tài)資源drawable持有View對(duì)象的引用導(dǎo)致內(nèi)存泄漏隱患的,并不是由于context.getResource導(dǎo)致內(nèi)存泄漏,因此如果你想通過(guò)context.getApplicaitonContext來(lái)獲取getResource是解決不了內(nèi)存泄漏的.
另外提一點(diǎn):(使用getApplicaitonContext的錯(cuò)誤)
android.app.Application cannot be cast to android.app.Activity

因此:在android 3.0 中:
修改了setBackgroundDrawable內(nèi)部方法中的 background.setCallback(this);方法。里面的實(shí)現(xiàn)使用了弱引用來(lái)持有View對(duì)象的引用,從而避免了內(nèi)存泄漏隱患

總結(jié):以后代碼中避免使用靜態(tài)資源,或者使用弱引用來(lái)解決相應(yīng)的問(wèn)題也是可以的。

單例模式導(dǎo)致內(nèi)存泄漏(單例中的類(lèi)是靜態(tài)的,引用context將導(dǎo)致以上的問(wèn)題)

public class CustomManager {
    private static CustomManager sInstance;
    public static CustomManager getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new CustomManager(context);//使用context的引用。
        }
        return sInstance;
    }

    private Context mContext;
    private CustomManager(Context context) {
        mContext = context;
    }
}

單例模式使用的是靜態(tài)類(lèi)的方式,讓該對(duì)象在整個(gè)應(yīng)用的內(nèi)存中保持一份該對(duì)象,從而減少對(duì)多次創(chuàng)建對(duì)象帶來(lái)的資源浪費(fèi)
同樣的問(wèn)題:
在創(chuàng)建該單例的時(shí)候使用了生命周期端的Context對(duì)象的引用,如果你是在Application中創(chuàng)建以上單例的話是木有任何問(wèn)題的。因?yàn)?strong>Application的Context生命周期是整個(gè)應(yīng)用,和單例的生命周期一樣,因此不會(huì)導(dǎo)致內(nèi)存泄漏。但是,如果你是在Activity中創(chuàng)建以上單例的話,將一樣導(dǎo)致跟上一個(gè)一樣的內(nèi)存問(wèn)題。
所以講以上的代碼改成:

if (sInstance == null) { 
sInstance = new CustomManager(context.getApplicationContext()); }//注意這里的不同。
return sInstance;

以上全摘自:Android Context 是什么?

通過(guò)上面大家可能對(duì)Context比較模糊。請(qǐng)閱讀:Android Context 是什么?
記錄幾點(diǎn):

  • getApplication和getApplicationContext返回同一個(gè)Application對(duì)象,只是里面方法調(diào)用的成員返回不同。
  • 所有Context都是在應(yīng)用的主線程ActivityThread中創(chuàng)建的
  • 盡量少用Context對(duì)象去獲取靜態(tài)變量,靜態(tài)方法,以及單例對(duì)象。以免導(dǎo)致內(nèi)存泄漏
  • 在創(chuàng)建與UI相關(guān)的地方,比如創(chuàng)建一個(gè)Dialog,或者在代碼中創(chuàng)建一個(gè)TextView,都用Activity的Context去創(chuàng)建。然而在引用靜態(tài)資源,創(chuàng)建靜態(tài)方法,單例模式等情況下,使用生命周期更長(zhǎng)的Application的Context才不會(huì)導(dǎo)致內(nèi)存泄漏

內(nèi)部類(lèi)Handler類(lèi)引起內(nèi)存泄漏

在Activity中定義了一個(gè)內(nèi)部Handler類(lèi):

public class MainActivity extends Activity {
    private  Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //TODO handle message...
        }
    };
    @TargetApi(11)
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.sendMessageDelayed(Message.obtain(), 60000);//延遲一分鐘執(zhí)行
        //just finish this activity
        finish();//關(guān)閉activity
    }
}

以上代碼將會(huì)導(dǎo)致內(nèi)存泄漏的問(wèn)題(原因):

  • 在Java中,非靜態(tài)(匿名)內(nèi)部類(lèi)會(huì)引用外部類(lèi)對(duì)象。而靜態(tài)內(nèi)部類(lèi)不會(huì)引用外部類(lèi)對(duì)象

當(dāng)Activity finish后,延時(shí)消息會(huì)繼續(xù)存在主線程消息隊(duì)列中1分鐘,然后處理消息。而該消息引用了Activity的Handler對(duì)象,然后這個(gè)Handler又引用了這個(gè)Activity(從以上原因可知道)。這些引用對(duì)象會(huì)保持到該消息被處理完,這樣就導(dǎo)致該Activity對(duì)象無(wú)法被回收,從而導(dǎo)致了上面說(shuō)的 Activity泄露。

修改:將Handler改為靜態(tài)類(lèi),并使用WeakReference來(lái)保持外部的activity對(duì)象。

private Handler mHandler = new MyHandler(this);
private static class MyHandler extends Handler{
    private final WeakReference<Activity> mActivity;
    public MyHandler(Activity activity) {
        mActivity = new WeakReference<Activity>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
        System.out.println(msg);
        if(mActivity.get() == null) {
            return;
        }
    }
}

結(jié)論:當(dāng)你在Activity中使用內(nèi)部類(lèi)的時(shí)候,需要時(shí)刻考慮您是否可以控制該內(nèi)部類(lèi)的生命周期,如果不可以,則最好定義為靜態(tài)內(nèi)部類(lèi)

以上摘自:內(nèi)部Handler類(lèi)引起內(nèi)存泄露

另一種解決的方式:
activtyonDestroy方法對(duì)handler的取消:

public void onDestroy() {
    mHandler.removeMessages(MESSAGE_1);
    mHandler.removeMessages(MESSAGE_2);
    mHandler.removeMessages(MESSAGE_3);
    mHandler.removeMessages(MESSAGE_4);
    // ... ...
    mHandler.removeCallbacks(mRunnable);
    // ... ...
}
或者:
public void onDestroy() {
   mHandler.removeCallbacksAndMessages(null);
}

Thread對(duì)象

同上:Thread的生命周期不一定是和Activity生命周期一致。
同上的解決方式為:

  • 改為靜態(tài)內(nèi)部類(lèi)
  • 采用弱引用來(lái)保存Context引用

正確的使用為:

 public class ThreadAvoidActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyThread(this).start();
    }
    private void dosomthing() {//dosomthing是ThreadAvoidActivity的方法
    }
    private static class MyThread extends Thread {
        WeakReference<ThreadAvoidActivity> mThreadActivityRef;//弱引用
        public MyThread(ThreadAvoidActivity activity) {
            mThreadActivityRef = new WeakReference<ThreadAvoidActivity>(
                    activity);
        }
        @Override
        public void run() {//先判斷是否為null,回收掉
            super.run();
            if (mThreadActivityRef == null)
                return;
            if (mThreadActivityRef.get() != null)
                mThreadActivityRef.get().dosomthing();//通過(guò)弱引用得到外部的activity來(lái)調(diào)用dosomthing方法
            // dosomthing
        }
    }
}

以上切斷兩個(gè)對(duì)象的雙向強(qiáng)引用鏈接

  1. 靜態(tài)內(nèi)部類(lèi):切斷Activity 對(duì)于 MyThread的強(qiáng)引用。
  2. 弱引用: 切斷MyThread對(duì)于Activity 的強(qiáng)引用

AsynTask 內(nèi)部類(lèi)

跟線程一樣的原理。
AsyncTask內(nèi)部的實(shí)現(xiàn)機(jī)制是運(yùn)用了ThreadPoolExcutor, 該類(lèi)產(chǎn)生的Thread對(duì)象的生命周期是不確定的,是應(yīng)用程序無(wú)法控制的。
你可能會(huì)說(shuō):AsynTask不是有cancel的方法么?對(duì)的,是有這個(gè)方法,但還是不一定能取消。通過(guò)源碼可以知道:

public final boolean cancel(boolean mayInterruptIfRunning) {
 mCancelled.set(true); 
 return mFuture.cancel(mayInterruptIfRunning);
 }

問(wèn)題歸因于mFuture.cancel(mayInterruptIfRunning);是否能取消正在執(zhí)行的任務(wù)呢?
通過(guò)線程執(zhí)行者(九)執(zhí)行者取消一個(gè)任務(wù),文章不是重點(diǎn),重點(diǎn)是評(píng)論,提出了是否能真正取消任務(wù)的質(zhì)疑
查閱:Android學(xué)習(xí)系列(37)--App調(diào)試內(nèi)存泄露之Context篇(下),這里引用官方的言語(yǔ):** cancel是不一定成功的。**

總結(jié):只要是使用線程正在進(jìn)行執(zhí)行任務(wù),都不一定能夠取消。大多數(shù)是使用:Thread.interrupt()進(jìn)行中斷,但不一定成功的。所以最好的使用靜態(tài)內(nèi)存類(lèi)或者使用弱引用

官方的例子:

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             // 注意下面這行,如果檢測(cè)到cancel,則及時(shí)退出
             if (isCancelled()) break;
         }
         return totalSize;
     }
 
     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }
 
     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

在后臺(tái)循環(huán)中時(shí)刻監(jiān)聽(tīng)cancel狀態(tài),防止沒(méi)有及時(shí)退出。

所以:使用cancel并不靠譜,那么如何書(shū)寫(xiě)呢?跟Thread的思路是一樣,差不多:

/**
 *
 * 弱引用
 * @version 1.0.0
 * @author Abay Zhuang <br/>
 *         Create at 2014-7-17
 */
public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget>
    extends AsyncTask<Params, Progress, Result> {
protected WeakReference<WeakTarget> mTarget;
public WeakAsyncTask(WeakTarget target) {
    mTarget = new WeakReference<WeakTarget>(target);
}
@Override
protected final void onPreExecute() {
    final WeakTarget target = mTarget.get();
    if (target != null) {
        this.onPreExecute(target);
    }
}
@Override
protected final Result doInBackground(Params... params) {
    final WeakTarget target = mTarget.get();
    if (target != null) {
        return this.doInBackground(target, params);
    } else {
        return null;
    }
}
@Override
protected final void onPostExecute(Result result) {
    final WeakTarget target = mTarget.get();
    if (target != null) {
        this.onPostExecute(target, result);
    }
}
protected void onPreExecute(WeakTarget target) {
    // Nodefaultaction
}
protected abstract Result doInBackground(WeakTarget target,
        Params... params);
protected void onPostExecute(WeakTarget target, Result result) {
    // Nodefaultaction
}
}

BroadcastReceiver對(duì)象

原因:沒(méi)有取消注冊(cè)。
直接:getContext().unregisterReceiver(receiver);即可.
注冊(cè)與反注冊(cè):

addCallback             <==>     removeCallback
registerReceiver        <==>     unregisterReceiver
addObserver             <==>     deleteObserver
registerContentObserver <==>     unregisterContentObserver

TimerTask對(duì)象

TimerTask對(duì)象在和Timer的schedule()方法配合使用的時(shí)候極容易造成內(nèi)存泄露。
要在合適的時(shí)候進(jìn)行Cancel即可。

private void cancelTimer(){ 
        if (mTimer != null) { 
            mTimer.cancel(); 
            mTimer = null; 
        } 
        if (mTimerTask != null) { 
            mTimerTask.cancel(); 
            mTimerTask = null; 
        }
    }

剩下幾個(gè)小點(diǎn):

  • Dialog對(duì)象:使用isFinishing()判斷Activity是否退出。才可以showDialog
  • Bitmap沒(méi)調(diào)用recycle()
  • 資源性對(duì)象比如(Cursor,F(xiàn)ile文件等)往往都用了一些緩沖,我們?cè)诓皇褂玫臅r(shí)候,應(yīng)該及時(shí)關(guān)閉它們
  • 構(gòu)造Adapter時(shí),沒(méi)有使用緩存的 convertView
  • 集合容器對(duì)象沒(méi)清理造成的內(nèi)存泄露(尤其是static的集合,需要clear清理)
  • WebView對(duì)象沒(méi)有銷(xiāo)毀。它的destory()函數(shù)來(lái)銷(xiāo)毀它

總結(jié):

泄漏都是有生命周期和Activity不一定一致,導(dǎo)致無(wú)法進(jìn)行回收。

只要是使用線程正在進(jìn)行執(zhí)行任務(wù),都不一定能夠取消。大多數(shù)是使用:Thread.interrupt()進(jìn)行中斷,但不一定成功的。所以最好的使用靜態(tài)內(nèi)存類(lèi)**或者使用弱引用

實(shí)戰(zhàn)檢測(cè)定位內(nèi)存泄露

1. 使用MAT工具定位Android應(yīng)用內(nèi)存泄漏
# 使用Android studio分析內(nèi)存泄露

資料:

Android Context 是什么?
# Android內(nèi)存泄露之Thread
# Android內(nèi)存泄露之Handler
Android App 內(nèi)存泄露之資源
Android內(nèi)存泄露之DDMS –> Heap工具,這個(gè)系列的文章。
Android學(xué)習(xí)系列(37)--App調(diào)試內(nèi)存泄露之Context篇(下)
Android內(nèi)存泄露的整理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容