美文网首页
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