美文网首页
Android 性能优化之内存泄露

Android 性能优化之内存泄露

作者: 伊泽瑞额 | 来源:发表于2019-03-26 11:50 被阅读0次

什么是内存泄露

内存泄露:程序在向系统申请分配内存空间后(new),在使用完毕后未释放。通俗讲,没有用的对象一直无法回收的现象就是内存泄露,值得注意的是,我们App随着内存泄露的不断积累,最终会导致内存溢出OOM(Out Of Memory),更严重的导致程序崩溃
内存溢出:程序向系统申请的内存空间超出了系统能给的,比如内存只能分配一个int类型,我却要塞给他一个long类型,系统就出现oom。

在Android中造成内存泄露的原因

  • 1 单例
public class TestManager
{
    private  static TestManager mTestManager;
    private static Context mContext;
    private TestManager(Context context){
        this.mContext=context;
    }

    public static  TestManager getmTestManager(Context context){
        if(mTestManager !=null){
            synchronized (TestManager.class){
                if(mTestManager !=null){
                    mTestManager=new TestManager(context);
                }
            }
        }
        return  mTestManager;
    }
}

然后在MainActivity中使用

  private void initData()
    {
         TestManager testManager = TestManager.getmTestManager(this);
    }

需要外部传入一个 Context 来获取该类的实例,如果此时传入的 Context 是 Activity 的话,此时单例就有持有该 Activity 的强引用(直到整个应用生命周期结束)。这样的话,即使该 Activity 退出,该 Activity 的内存也不会被回收,这样就造了内存泄露,

解决办法——单例模式引用的对象的生命周期 = 应用生命周期。将 context.getApplicationContext() 赋值给 mContext,此时单例引用的对象是 Application,而 Application 的生命周期本来就跟应用程序是一样的,也就不存在内存泄露。

public class TestManager
{

    private  static TestManager mTestManager;
    private static Context mContext;

    private TestManager(Context context){

        this.mContext=context.getApplicationContext();

    }


    public static  TestManager getmTestManager(Context context){
        if(mTestManager !=null){
            synchronized (TestManager.class){
                if(mTestManager !=null){
                    mTestManager=new TestManager(context);
                }
            }
        }
        return  mTestManager;
    }
}
  • 2.匿名内部类/非静态内部类(匿名类和非静态内部类最大的共同点就是 都持有外部类的引用)

匿名内部类,在Java当中,非静态内部类默认将会有持有外部类的引用,当在内部类实例化一个静态的对象,那么,这个对象将会与App的生命周期一样长,又因为非静态内部类一直持有外部的MainActivity的引用,导致MainActivity无法被回收,内存泄露的代码如下:

public class MainActivity extends AppCompatActivity {

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

    class MyAscnyTask extends AsyncTask<Void, Integer, String>{
        @Override
        protected String doInBackground(Void... params) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "";
        }
    }
}

可以看到我们在 Activity 中继承 AsyncTask 自定义了一个非静态内部类,在 doInbackground() 方法中做了耗时的操作,然后在 onCreate() 中启动 MyAsyncTask。如果在耗时操作结束之前,Activity 被销毁了,这时候因为 MyAsyncTask 持有 Activity 的强引用,便会导致 Activity 的内存无法被回收,这时候便会产生内存泄露。

  • 解决办法 :将 MyAsyncTask 变成静态内部类
public class MainActivity extends AppCompatActivity {

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

    static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
        @Override
        protected String doInBackground(Void... params) {
            try {
                Thread.sleep(50000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "";
        }
    }
}

匿名类和非静态内部类最大的共同点就是 都持有外部类的引用,因此,匿名类造成内存泄露的原因也跟静态内部类基本是一样的,下面举个几个比较常见的例子:

public class MainActivity extends AppCompatActivity {

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // ① 匿名线程持有 Activity 的引用,进行耗时操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(50000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        // ② 使用匿名 Handler 发送耗时消息
        Message message = Message.obtain();
        mHandler.sendMessageDelayed(message, 60000);
    }

上面举出了两个比较常见的例子:

  • new 出一个匿名的 Thread,进行耗时的操作,如果 MainActivity 被销毁而 Thread 中的耗时操作没有结束的话,便会产生内存泄露
  • new 出一个匿名的 Handler,这里我采用了 sendMessageDelayed() 方法来发送消息,这时如果 MainActivity 被销毁,而 Handler 里面的消息还没发送完毕的话,Activity 的内存也不会被回收

解决办法:

  • 继承 Thread 实现静态内部类
  • 继承 Handler 实现静态内部类,以及在 Activity 的 onDestroy() 方法中,移除所有的消息 mHandler.removeCallbacksAndMessages(null);
private MyHandler mHandler = new MyHandler(this);

private static class MyHandler extends Handler{
    private WeakReference<Context> reference;
    public MyHandler(Context context){
        reference = new WeakReference<Context>(context);
    }

    @Override
    public void handleMessage(Message msg) {
        MainActivity mainActivity = (MainActivity) reference.get();
        if(mainActivity != null){
            //业务处理逻辑
        }
    }
}

3.其他情况

除了上述 3 种常见情况外,还有其他的一些情况

1、需要手动关闭的对象没有关闭

网络、文件等流忘记关闭
手动注册广播时,退出时忘记 unregisterReceiver()
Service 执行完后忘记 stopSelf()
EventBus 等观察者模式的框架忘记手动解除注册

2、static 关键字修饰的成员变量
3、ListView 的 Item 泄露

相关文章

网友评论

      本文标题:Android 性能优化之内存泄露

      本文链接:https://www.haomeiwen.com/subject/fztgvqtx.html