1.單例造成的內存泄露
單例的生命周期與應用一樣長,因此當創建出來后就會一直存在,如果在創建的時候持有了某個對象的引用,就會一直持有它導致內存泄露。如下面的例子,在創建ActivityManager單例的時候我們傳入了一個上下文Context參數,包含了一個對Activity的引用,那么在這種情況下就會造成該Activity無法回收,發生內存泄露
public class ActivityManager
{
private Context mContext;
private static ActivityManager manager;
private ActivityManager(Context mContext)
{
this.mContext = mContext; //持有了Activity的Context
}
public static ActivityManager getInstance(Context mContext)
{
if (manager!=null)
{
manager = new ActivityManager(mContext);
}
return manager;
}
}
修正方案為,通過傳入的Activity的Context參數獲取到ApplicationContext,這樣這個單例持有的就是應用本身的引用,本身單例就與應用生命周期相同,因此就不會有內存泄露發生。
2.Handler造成的內存泄漏
public class DemoActivity extends AppCompatActivity
{
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...更新UI操作
}
};
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
initDatas();
}
private void initDatas()
{
//...子線程獲取數據,在主線程中更新UI
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
Handler是Activity中的非靜態匿名內部類,因此創建的mHandler會持有外部對象DemoActivity的引用,looper會在Activity同一線程不斷循環查詢消息并進行處理,當Activity退出時如果還有未處理完成的消息,消息隊列會一直持有handler的引用,而handler又一直存在并持有Activity的引用,導致Activity無法被回收,導致內存泄露。
public class DemoActivity extends AppCompatActivity
{
private MyHandler mHandler = new MyHandler(this);
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null)
{
//...更新UI操作
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
initDatas();
}
private void initDatas()
{
//...子線程獲取數據,在主線程中更新UI
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
//移除消息隊列中所有消息和所有的Runnable
mHandler.removeCallbacksAndMessages(null);
}
}
3.匿名內部類造成的內存泄漏(實際上是第二點的泛化情況)
匿名內部類會持有外部對象(如Activity)的引用,當這個匿名內部類對象生命周期與Activity一致時不會出現問題,但是當匿名內部類生命周期超出外部對象(如啟動了一個新的線程),則會出現外部對象無法被回收,而導致內存泄露。
例如AscynTask:
void startAsyncTask() {
new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}.execute();
}
解決方案就是將匿名內部類改為一個靜態內部類。
private static class NimbleTask extends AsyncTask<Void, Void, Void> {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}
void startAsyncTask() {
new NimbleTask().execute();
}
如果一定要持有外部對象,請將其設置為弱引用。
另外一種解決方案是在將其生命周期與外部對象同步,如匿名內部類啟動了一個新的線程,那么我們在外部對象被銷毀時終止該線程
private Thread thread;
@Override
public void onDestroy() {
super.onDestroy();
if (thread != null) {
thread.interrupt();
}
}
void spawnThread() {
thread = new Thread() {
@Override public void run() {
while (!isInterrupted()) {
}
}
}
thread.start();
}
4.靜態變量導致的內存泄露
public class DemoActivity extends AppCompatActivity
{
private static Context mContext;
@Override
private void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.mContext = this; //靜態變量與類的生命周期相同,但持有該Activity實例的引用,造成泄漏
}
}
public class DemoActivity extends AppCompatActivity
{
private static View sView;
@Override
private void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sView = new View(this); //靜態變量與類的生命周期相同,但持有該Activity實例的引用,造成泄漏
}
}
該問題主要為類聲明的靜態變量持有了某個實例的引用,若類不被卸載,則該靜態變量不會被回收,同樣的,其持有的實例引用也不會被回收,造成內存泄露。
5.雜七雜八的東西
在Activity生命周期技術的時候完成結束動畫、置空bitmap、關閉流Stream、關閉游標Cursor、注銷BroadcastReceiver等操作。
6.小結
內存泄露的核心實際上都是由于某長生命周期對象持有了較短生命周期對象的引用,所以需要著重注意單例、靜態變量、會啟動長周期任務的匿名內部類等長周期對象,注意不要讓其持有Activity實例的引用。