您的位置:首页 > 移动开发 > Android开发

Android 常见内存泄漏之四大元凶

2015-12-17 10:04 513 查看
对于内存泄漏,我想大家应该都有碰到过,常见的表现就是异常程序退出。

到了程序强制关闭的时候,那已经到了一定的程度了。一般时候内存泄漏了我们是看不见的。因为它在堆中活动。

所以常常我们会通过一些工具来检测。例如:LeakCanary、MAT等工具。

MAT是一款强大的内存分析工具,功能繁多而复杂,而LeakCanary则是由Square开源的一款轻量第三方内存泄漏检测工具,当它检测到程序中有内存泄漏的产生时,它将以最直观的方式告诉我们该内存泄漏是由谁产生的和该内存泄漏导致谁泄漏了而不能回收,供我们复查。

下面是一个内存检测是图:

data boject变化的值,一般在1.5M到2.5M左右,过大肯定是不行的。另外就是我们可以在手机上点击各个位置看这个值的变化情况。如果我们点击了一个页面这个值上去了,然后我们退出了这个界面的时候,这个值久久不能退下来,那么初步判断这个页面可能造成了内存泄漏。

个人总结最常见的内存泄漏,可以说是几大元凶:

资源没有关闭造成的内存泄漏

常见的BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap
。其中Bitmap是我们最常用碰到的,也是很厉害的。在Activity销毁时及时关闭或者注销,不然这些资源是不会被回收的,也就造成了内存的泄漏。

不妨可以试试看咯。用Mat检测一下。你会发现内存上去了下不来。

Handler造成的内存泄漏

Handler在应用中的表现我就不说了。但是它为什么会造成内存泄漏呢?没错,就是它,而且还很常见。

public class MainActivity extends AppActivity {
		 
	        private Handler mHandler = new Handler() {
	            @Override
	            public void handleMessage(Message msg) {
	                //...do some thing
	            }
	        };

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

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


这种是我们常见的Handler创建方式,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,消息队列是在一个Looper线程中不断处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以有这样一种写法:
public class MainActivity extends AppCompatActivity {
        private MyHandler mHandler = new MyHandler(this);
        private TextView mTextView;

        private static class MyHandlerextendsHandler {
            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) {
                    activity.mTextView.setText("");
                }
            }
        }

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mTextView = (TextView) findViewById(R.id.textview);
            loadData();
        }

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

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


创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象。
同时我们在Activity的Destroy或Stop中移除消息队列中的消息,mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。

线程造成的内存泄漏

线程造成的内存泄漏和Handler的过程你可以看成是差不多的。它们都是在Activity关闭的时候可能还未处理完造成的。

同样我们做法是这样的

static class MyAsyncTask extends AsyncTask<Void,Void,Void>{

        private WeakReference<Context>weakReference;

        public MyAsyncTask(Context context){

            weakReference=new WeakReference<>(context);

        }

        @Override
        protected Void doInBackground(Void...params){

            SystemClock.sleep(10000);

            return null;

        }

        @Override
        protected void onPostExecute(VoidaVoid){

            super.onPostExecute(aVoid);
            MainActivity activity=(MainActivity)weakReference.get();
            if(activity!=null){

            }
        }

    }
在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),如果我们没有这样在后台继续请求的需求,那么就可以避免任务在后台执行浪费资源。

Context造成的内存泄漏

这个说的大家可能不大相信了,上下文会造成内存泄漏吗?

看你这个context传到了什么地方,如果你传过去的地方在你关闭的时候都还一直占用着,必然会造成对内存的占用,造成内存的泄漏。下面简单举个例子,就拿单例来说,它也是会造成内训泄漏的。

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进去,它的生命周期很重要,单例的生命周期和Application的一样长。

传入的是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;
        }
    }


单例是生命周期应该和应用的生命周期是一样的,所以传入Application的context是可以防止内存泄漏的。

全局静态变量不会造成内存泄漏,但是会造成内存无法回收。给内存很大的压力,尽量少用全局静态变量。

单例、静态对象、全局性的集合等,全局静态用起来很爽,但是在爽的时候请注意戴好保险!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: