美文网首页Android面试
Android 内存泄露总结

Android 内存泄露总结

作者: 1a473fcb13b0 | 来源:发表于2019-03-19 16:55 被阅读214次

    什么是内存泄露?

    内存泄露即某个对象已经不需要再用了,但是它却没有被系统所回收,一直在内存中占用着空间,而导致它无法被回收的原因大多是由于它被一个生命周期更长的对象所引用。其实要分析 Android 中的内存泄漏的原因非常简单,只要理解一句话,那就是生命周期较长的对象持有生命周期较短的对象的引用。

    内存泄露的危害?

    过多的内存泄露就会造成内存溢出。

    内存泄露案例

    例如:我们MVP模式(如果没有使用RxLifecycle或者AutoDipose做相应的内存泄露处理的话),由于Presenter经常性的需要执行一些耗时操作,那么当我们在操作未完成时候关闭了Activity,会导致Presenter一直持有Activity的对象,造成内存泄漏。如果对这个Activity重复进行多次关闭操作就会有可能出现内存溢出的情况。

    了解和熟练一些出现内存泄露的问题的原因,在以后我们进行内存优化和开发中尽量避免内存泄露的问题,有很好的作用。

    1、单例对象持有外部的引用

    public class SingleTon {
        
        private static SingleTon singleTon;
    
        private Context context;
    
        private SingleTon(Context context) {
            this.context = context;
        }
    
        public static SingleTon getInstance(Context context) {
            if (singleTon == null) {
                synchronized (SingleTon.class) {
                    if (singleTon == null) {
                        singleTon = new SingleTon(context);
                    }
                }
            }
            return singleTon;
        }
    
    }
    

    如果 Activity 中调用 getInstance 方法并传入 this 时,singleTon 就持有了此 Activity 的引用,当退出 Activity 时,Activity 就无法回收,造成内存泄漏

    private SingleTon(Context context) {
        this.context = context.getApplicationContext();
    }
    

    通过 getApplicationContext 来获取 Application 的 Context,让它被单例持有,这样退出 Activity 时,Activity 对象就能正常被回收了,而 Application 的 Context 的生命周期和单例的生命周期是一致的,所有再整个 App 运行过程中都不会造成内存泄漏。

    2.非静态内部类、匿名内部类

    非静态内部类、匿名内部类 都会持有外部类的一个引用,如果有一个静态变量引用了非静态内部类或者匿名内部类,导致非静态内部类或者匿名内部类的生命周期比外部类(Activity)长,就会导致外部类在该被回收的时候,无法被回收掉,引起内存泄露。

    静态内部类和非静态内部类对比图

    常见的非静态内部类、匿名内部类,例如:Adapter(写在Activity内的Adapter,注意使用static关键字),Runnable,Handler,AsyncTask,TimerTask等。

    ①、匿名内部类:Runnable
    内存泄露代码
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //模拟耗时操作
                        Thread.sleep(15000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
    优化代码示例:

    将 非静态内部类 改为 静态非匿名内部类

      new Thread(new MyRunnable()).start();
    
      private static class MyRunnable implements Runnable {
    
            @Override
            public void run() {
                try {
                    Thread.sleep(15000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
    ②、成员内部类:Handler
    内存泄露代码
       private final static int MESSAGECODE = 1;
    
        private Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("mmmmmmmm", "handler " + msg.what);
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    handler.sendEmptyMessage(MESSAGECODE);
                    try {
                        Thread.sleep(8000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    handler.sendEmptyMessage(MESSAGECODE);
                }
            }).start();
    
    
        }
    
    优化代码示例:

    1、使用静态内部类
    2、使用弱引用
    3、在onDestroy() 里面取消异步任务。(注意:单纯的取消还是会内存泄漏);

       private final static int MESSAGECODE = 1;
        private static Handler handler;//静态
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //创建Handler
            handler = new MyHandler(this);
    
            //创建线程并且启动线程
            new Thread(new MyRunnable()).start();
        }
    
    
        //1、避免Handler引用activity造成的内存泄漏:使用静态内部类+ 使用弱引用
        private static class MyHandler extends Handler {
            WeakReference<HandlerActivity> weakReference;
    
            public MyHandler(HandlerActivity activity) {
                weakReference = new WeakReference<HandlerActivity>(activity);
            }
    
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (weakReference.get() != null) {
                    // update android ui
                    Log.d("mmmmmmmm", "handler " + msg.what);
                }
            }
        }
    
        //2、避免非静态Runnable内部类引用activity造成的内存泄漏
        private static class MyRunnable implements Runnable {
    
            @Override
            public void run() {
                handler.sendEmptyMessage(MESSAGECODE);
                try {
                    Thread.sleep(8000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.sendEmptyMessage(MESSAGECODE);
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //3、如果参数为null的话,会将所有的Callbacks和Messages全部清除掉。
            handler.removeCallbacksAndMessages(null);
        }
    
    ③、AsyncTask
    内存泄露代码
    new AsyncTask<String,Integer,String>(){
    
                @Override
                protected String doInBackground(String... params) {
                    try {
                        Thread.sleep( 6000 );
                    } catch (InterruptedException e) {
    
                    }
                    return "ssss";
                }
    
    
                @Override
                protected void onPostExecute(String s) {
                    super.onPostExecute(s);
                    Log.d( "mmmmmm activity2 " , "" + s ) ;
                }
    
    }.execute();
    
    优化代码示例:

    1、自定义静态AsyncTask类
    2、AsyncTask的周期和Activity周期应该保持一致。也就是在Activity生命周期结束时要将AsyncTask cancel掉。

        private static MyTask myTask;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            myTask = new MyTask();
            myTask.execute();
        }
    
        //1、创建静态内部类
        private static class MyTask extends AsyncTask {
    
            @Override
            protected Object doInBackground(Object[] params) {
                try {
                    //模拟耗时操作
                    Thread.sleep(15000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "";
            }
        }
    
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //2、取消异步任务
            if (myTask != null) {
                myTask.cancel(true);
            }
        }
    
    ④、TimerTask
    内存泄露代码
            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
                    while (true) ;
                }
            }, 1000);  // 1秒后启动一个任务
    
    优化代码示例:

    1、在适当的时机进行Cancel。
    2、TimerTask用静态内部类

     private TimerTask timerTask ;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            timerTask = new MyTimerTask() ;
            new Timer().schedule( timerTask ,1000 );  // 1秒后启动一个任务
        }
    
    
        private static class MyTimerTask extends TimerTask {
    
            @Override
            public void run() {
                while(true){
                    Log.d( "ttttttttt" , "timerTask" ) ;
                }
            }
        }
    
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //取消定时任务
            if ( timerTask != null ){
                timerTask.cancel() ;
            }
    
        }
    
    3、静态变量导致内存泄露

    静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。一旦静态变量初始化后,它所持有的引用只有等到进程结束才会释放。

    比如下面这样的情况,在Activity中为了避免重复的创建info,将sInfo作为静态变量:

    public class MainActivity extends AppCompatActivity {
    
        private static Info sInfo;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            if (sInfo != null) {
                sInfo = new Info(this);
            }
        }
    }
    
    class Info {
        public Info(Activity activity) {
        }
    }
    

    Info作为Activity的静态成员,并且持有Activity的引用,但是sInfo作为静态变量,生命周期肯定比Activity长。所以当Activity退出后,sInfo仍然引用了Activity,Activity不能被回收,这就导致了内存泄露。
    在Android开发中,静态持有很多时候都有可能因为其使用的生命周期不一致而导致内存泄露,所以我们在新建静态持有的变量的时候需要多考虑一下各个成员之间的引用关系,并且尽量少地使用静态持有的变量,以避免发生内存泄露。当然,我们也可以在适当的时候将静态量重置为null,使其不再持有引用,这样也可以避免内存泄露。

    4、未取消注册或回调导致内存泄露,例如:BroadcastReceiver,EventBus等

    比如我们在Activity中注册广播,如果在Activity销毁后不取消注册,那么这个刚播会一直存在系统中,同上面所说的非静态内部类一样持有Activity引用,导致内存泄露。因此注册广播后在Activity销毁后一定要取消注册。

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            this.registerReceiver(mReceiver, new IntentFilter());
        }
    
        private BroadcastReceiver mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                // 接收到广播需要做的逻辑
            }
        };
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            this.unregisterReceiver(mReceiver);
        }
    }
    

    在注册观察则模式的时候,如果不及时取消也会造成内存泄露。比如使用Retrofit+RxJava注册网络请求的观察者回调,同样作为匿名内部类持有外部引用,所以需要记得在不用或者销毁的时候取消注册。

    5、集合中的对象未清理造成内存泄露

    这个比较好理解,如果一个对象放入到ArrayList、HashMap等集合中,这个集合就会持有该对象的引用。当我们不再需要这个对象时,也并没有将它从集合中移除,这样只要集合还在使用(而此对象已经无用了),这个对象就造成了内存泄露。并且如果集合被静态引用的话,集合里面那些没有用的对象更会造成内存泄露了。所以在使用集合时要及时将不用的对象从集合remove,或者clear集合,以避免内存泄漏。

    6、资源未关闭或释放导致内存泄露

    在使用IO、File流或者Sqlite、Cursor,Bitmap,TypeArray,Watcher等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。

    7、属性动画造成内存泄露

    动画同样是一个耗时任务,比如在Activity中启动了属性动画(ObjectAnimator),但是在销毁的时候,没有调用cancle方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。因此同样要在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏。

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mAnimator.cancel();
    }
    
    8、WebView造成内存泄露

    关于WebView的内存泄露,因为WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的destory()方法来销毁它以释放内存。

    另外在查阅WebView内存泄露相关资料时看到这种情况:

    Webview下面的Callback持有Activity引用,造成Webview内存无法释放,即使是调用了Webview.destory()等方法都无法解决问题(Android5.1之后)。

    最终的解决方案是:在销毁WebView之前需要先将WebView从父容器中移除,然后在销毁WebView。详细分析过程请参考这篇文章:WebView内存泄漏解决方法

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 先从父控件中移除WebView
        mWebViewContainer.removeView(mWebView);
        mWebView.stopLoading();
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.removeAllViews();
        mWebView.destroy();
    }
    
    9、静态的View

    有时,当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。这确实是一个提高Activity启动速度的好方法!但是要注意,一旦View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View有事一个静态变量,所以导致Activity不被回收。

    解决办法:
      在使用静态View时,需要确保在资源回收时,将静态View detach掉。

    10、RxJava

    在使用RxJava时,如果在发布了一个订阅后,由于没有及时取消,导致Activity/Fragment无法销毁,导致的内存泄露
    解决办法:RxLifecycle或者AutoDipose做相应的内存泄露处理;

    11、成员方法中使用完对象未及时释放 example:mHandler.post(run()) run里面使用完成员变量应该可以被释放,但包含成员变量的对象还未释放;
    12、Adapter中没有使用缓存的convertView;
    总结:

    构造单例的时候尽量别用Activity的引用;
    静态引用时注意应用对象的置空或者少用静态引用;
    使用静态内部类+软引用代替非静态内部类;
    及时取消广播或者观察者注册;
    耗时任务、属性动画在Activity销毁时记得cancel;
    文件流、Cursor等资源及时关闭;
    Activity销毁时WebView的移除和销毁。

    一些建议:

    1、对 Activity 等组件的引用应该控制在 Activity 的生命周期之内;
    如果不能就考虑使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。

    2、尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。

    3、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
    a)将内部类改为静态内部类
    b)静态内部类中使用弱引用来引用外部类的成员变量

    4、Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空 Handler 里面的消息。
    比如在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable.

    5、在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。

    6、正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。

    7、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。

    参考文章
    https://blog.csdn.net/sinat_31057219/article/details/74533647
    https://blog.csdn.net/u010878994/article/details/51553415
    https://www.jianshu.com/p/ab4a7e353076
    https://blog.csdn.net/unicorn97/article/details/81009204#2-%E9%9D%99%E6%80%81%E7%9A%84view

    相关文章

      网友评论

        本文标题:Android 内存泄露总结

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