Android内存泄漏

这两天在用LeakCanary查项目的内存泄漏问题,在此记录一下。

内存泄漏的五种常见原因


  • 单例造成的内存泄漏

Android的单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏。因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。常见案例如下:
public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
1、传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长 ;
2、传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。
修改方式如下:

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context.getApplicationContext();
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}


  • 非静态内部类创建静态实例造成的内存泄漏

非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。
常见案例如下:
public class MainActivity extends AppCompatActivity {
    private static TestResource mResource = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mManager == null){
            mManager = new TestResource();
        }
        //...
    }

    class TestResource {
        //...
    }
}

应将内部类设为静态内部类,这样静态内部类就不会持有外部类的引用,修改如下:

public class MainActivity extends AppCompatActivity {
    private static TestResource mResource = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mManager == null){
            mManager = new TestResource();
        }
        //...
    }

    static class TestResource {
        //...
    }
}


  • Handler造成的内存泄漏

Handler的使用造成的内存泄漏问题应该说最为常见,由于Handler的非静态匿名内部类会持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。
常见案例如下:
public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //...
        }
    };
    private void loadData(){
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

解决这类问题的方式是,创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象。这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时还应该移除消息队列中的消息

public class MainActivity 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){
                //...
            }
        }
    }

    private void loadData() {
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }

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


  • 线程造成的内存泄漏

对于线程造成的内存泄漏,也是平时比较常见的,不过其原因也是匿名内部类,内部类会默认持有对外部类的引用,当线程为完成时,可能就会造成外部类的资源无法回收。
常见案例如下:
public class MainActivity extends AppCompatActivity {
    //...
    new Thread(new Runnable() {
        @Override
        public void run() {
            //...
        }
    }).start();
}

解决方案也是将内部类设置为静态内部类

public class MainActivity extends AppCompatActivity {
    //...
    new Thread(new MyRunnable()).start();

    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            //...
        }
    }
}


  • 资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

参考原文

项目中查出的内存泄漏

第一处
这里写图片描述

public static void hideIME(View edit, boolean clearFocus) {
        if (edit == null) {
            return;
        }
        if (clearFocus) {
            edit.clearFocus();
        }
        InputMethodManager imm = (InputMethodManager) edit.getContext()
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(edit.getWindowToken(), 0);
    }

代码分析发现,登录界面点击空白区域隐藏软键盘时,会调用InputMethodManager,查看其源码发现它是一个单例模式(代码如下),所以调用的时候如果是用edit.getContext()就会造成edit及其所在view的资源无法释放,此处应改成Application.getContext()

public final class InputMethodManager {
    //...
    static InputMethodManager sInstance;

    public static InputMethodManager getInstance() {
        synchronized (InputMethodManager.class) {
            if (sInstance == null) {
                IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
                IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
                sInstance = new InputMethodManager(service, Looper.getMainLooper());
            }
            return sInstance;
        }
    }
}

第二处

这里写图片描述

此处是Volley造成的内存泄漏,在查看了Github上的提交记录后,终于找到了原因。先看一下NetwokDispatcher的一段代码如下:

public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    while (true) {
        Request<?> request;
        try {
            // Take a request from the queue.
            request = mQueue.take();
            //...
        }catch{
            //...
        }
        //...
    }
}

从代码中可以看到,线程会不断从mQueue中获取request,获取不到时就会阻塞等待,这是没有问题的,但是我们发现request的声明是在while循环体中的,如果当线程阻塞住时,最后一个request的内存就有可能因为再次分配给Request<?> request而没有释放,这样就会造成request及其内部的listener所引用的资源无法释放。解决方案如下:

public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    Request<?> request;
    while (true) {
        request = null;
        try {
            // Take a request from the queue.
            request = mQueue.take();
            //...
        }catch{
            //...
        }
        //...
    }
}

此外还要将Request及其子类中的listener和errorListener都置为null

public abstract class Request<T> implements Comparable<Request<T>> {
    //...
    private Response.ErrorListener mErrorListener;


    void finish(final String tag) {
        if (mRequestQueue != null) {
            mRequestQueue.finish(this);
            onFinish();
        }
    }

   /**
    * clear listeners when finished 
    */
    protected void onFinish() {
        mErrorListener = null;
    }

    //...
}

子类如ImageRequest

public class ImageRequest extends Request<Bitmap> {
    //...
    private Response.Listener<Bitmap> mListener;

    @Override
    protected void onFinish() {
        super.onFinish();
        mListener = null;
    }

    //...
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值