Skip to content

Latest commit

 

History

History
370 lines (266 loc) · 15.6 KB

2017-7-11Android 中常见的内存泄漏.md

File metadata and controls

370 lines (266 loc) · 15.6 KB
title date categories tags description
Android中常见的内存泄漏
2017-07-11
Android
性能优化
Android
内存泄漏

单例造成的内存泄漏


由于单例的静态特性使得其生命周期跟应用Application的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。比如:工具类CommonUtil, 当调用getInstance创建这个单例的时候, 由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:

  • 如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。
  • 如果此时传入的是Activity 的 Context, 当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了(Activity泄漏了)。

因此, 使用CommonUtil的时候要传入Application的Context , 或者将下面的getInstance方法改写成 instance = new CommonUtil(Context.getApplicationContext());

public class CommonUtil {

    private static CommonUtil instance;
    private Context context;
    
    private CommonUtil(Context context) {
        this.context = context;
    }
    
    public static CommonUtil getInstance(Context context) {
        if (instance == null) {
            instance = new CommonUtil(context);
        }
        return instance;
    }
}

非静态内部类创建静态实例引起内存泄漏


比如:

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

在Activity内部创建了一个非静态内部类TestResource的static对象(单例), 这样会造成内存泄漏, 因为在sResource对象中中会持有外部类MainActivity的引用, 而该对象又是一个static的, 该实例的生命周期和应用的一样长,这就导致了该静态实例mResource一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为: 将该内部类设为静态内部类将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext。

内部类、匿名内部类、线程造成内存泄漏


Android开发经常会在Activity、Fragment中使用多线程,此时如果你使用了匿名类,也容易造成内存泄漏

public class MainActivity extends Activity {
    ...
	
    @Override
    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);


	    // 代码1
        new (new Runnable() {
            @Override
            public void run() {
				...
            }
        }).start();
		
		// 代码2
		new AsyncTask<Void, Void, Void>() {
				@Override
				protected Void doInBackground(Void... params) {
						SystemClock.sleep(10000);
						return null;
				}
		}.execute();
    }
    
	// 代码3
    Runnable ref2 = new Runnable() {
        @Override
        public void run() {

        }
    };

    ...
}

代码1中的Runnable 和 代码2 中 的 AsyncTask 都是一个匿名内部类,因此它们的匿名对象对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。

代码3中的ref2使用了内部类, 内部类对象ref2会持有外部类的引用, 也就是说当前的Activity实例会被ref2持有, 如果在线程中执行一个很长时间的操作, 当Activity销毁后, 线程还在执行, 这时候因为ref2还持有Activity的引用, 所以Activity占用的内存不会被释放, 造成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(Void aVoid) {
            super.onPostExecute(aVoid);
            MainActivity activity = (MainActivity) weakReference.get();
            if (activity != null) {
                //...
            }
        }
    }
	
    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            SystemClock.sleep(10000);
        }
    }
//——————
    new Thread(new MyRunnable()).start();
    new MyAsyncTask(this).execute();

这样就避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源。

Handler 造成的内存泄漏


Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏,如下示例:

   public class MainActivity extends AppCompatActivity {
        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                //...
            }
        };
        @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退出时消息队列中还有未处理的消息或者正在处理消息(因为handler.postDelay 可以延时发送消息),而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:

    public class MainActivity extends AppCompatActivity {
	
        private MyHandler mHandler = new MyHandler(this);
        private TextView mTextView ;
		
        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){
                    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);
        }
    }

推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。

软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收(上面例子中的Activity),Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。

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

    public class MainActivity extends AppCompatActivity {
	
        private MyHandler mHandler = new MyHandler(this);
        private TextView mTextView ;
		
        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){
                    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);
        }
    }
	

使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。

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


BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。在android中,资源性对象比如Cursor、File、Bitmap、视频等,系统都用了一些缓冲技术,在使用这些资源的时候,如果我们确保自己不再使用这些资源了,要及时关闭,否则可能引起内存泄漏。因为有些操作不仅仅只是涉及到Dalvik虚拟机,还涉及到底层C/C++等的内存管理,不能完全寄希望虚拟机帮我们完成内存管理。 在这些资源不使用的时候,记得调用相应的类似close()、destroy()、recycler()、release()等函数,这些函数往往会通过jni调用底层C/C++的相应函数,完成相关的内存释放。

  • BraodcastReceiver分为静态注册和动态注册, 静态注册这种方式的注册是常驻型的,也就是说当应用关闭后,如果有广播信息传来,MyReceiver也会被系统调用而自动运行。注册方式与静态注册相反,不是常驻型的,也就是说广播会跟随程序的生命周期。在实际应用中,我们在Activity或Service中注册了一个BroadcastReceiver,当这个Activity或Service被销毁时如果没有解除注册,系统会报一个异常,提示我们是否忘记解除注册了。所以,记得在特定的地方执行解除注册操作。

  • File 、Stream 要记得 close()

  • Cursor 要记得 close()

  • Bitmap 要记得recycle()

  • 自定义控件中的自定义属性要记得 recycle()

设置监听但是没有移除监听容易造成内存泄漏


比如:

  • addOnGlobalLayoutListener后要记得remove
       openImageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
           @Override
           public void onGlobalLayout() {
               openImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
           }
       });
  • register 后 要记得unregister , 因为观察者内部可能维护了一个List 或者Map
  • 在andorid开发中,我们经常会在Activity的onCreate中注册广播接受器、EventBus等,如果忘记成对的使用反注册,可能会引起内存泄漏。开发过程中应该养成良好的相关,在onCreate或onResume中注册,要记得相应的在onDestroy或onPause中反注册。
  • setOnClick 中, 监听完成后, 系统自动移除了, 所以不需要再自己移除

动画, 要在onDestory中停止, 否则可能造成泄漏

尽量避免使用 static 成员变量


如果成员变量被声明为 static,那我们都知道其生命周期将与整个app进程生命周期一样。

这会导致一系列问题,如果你的app进程设计上是长驻内存的,那即使app切到后台,这部分内存也不会被释放。按照现在手机app内存管理机制,占内存较大的后台进程将优先回收,如果此app做过进程互保保活,那会造成app在后台频繁重启。当手机安装了你参与开发的app以后一夜时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默。

这里修复的方法是:

不要在类初始时初始化静态成员。可以考虑lazy初始化。 架构设计上要思考是否真的有必要这样做,尽量避免。如果架构需要这么设计,那么此对象的生命周期你有责任管理起来。

避免 override finalize()

1、finalize 方法被执行的时间不确定,不能依赖与它来释放紧缺的资源。时间不确定的原因是: 虚拟机调用GC的时间不确定 Finalize daemon线程被调度到的时间不确定

2、finalize 方法只会被执行一次,即使对象被复活,如果已经执行过了 finalize 方法,再次被 GC 时也不会再执行了,原因是:

含有 finalize 方法的 object 是在 new 的时候由虚拟机生成了一个 finalize reference 在来引用到该Object的,而在 finalize 方法执行的时候,该 object 所对应的 finalize Reference 会被释放掉,即使在这个时候把该 object 复活(即用强引用引用住该 object ),再第二次被 GC 的时候由于没有了 finalize reference 与之对应,所以 finalize 方法不会再执行。

3、含有Finalize方法的object需要至少经过两轮GC才有可能被释放。

对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null

集合对象没有及时清理引起的内存泄漏

我们通常会把一些对象装入到集合中,当不使用的时候一定要记得及时清理集合,让相关对象不再被引用。如果集合是static、不断的往里面添加东西、又忘记去清理,肯定会引起内存泄漏。