分享

容易导致内存泄露的场景

 逍遥302 2017-08-10

1.资源性对象未关闭

资源性对象(比如Cursor、File文件等)往往都用了一些缓冲,在不使用的时候,应该及时关闭它们,以便它们的缓存数据能够及时回收。它们的缓存不仅仅在JAVA虚拟机内,还存在JAVA虚拟机外,如果仅仅把它们的引用对象置为null,而不关闭它们,往往会造成内存泄露。因为有些资源性对象,比如SQLite Cursor,如果我们没有关闭它,而仅仅把它置为null,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象不再使用时,应该立刻调用它的close()函数,将其关闭,然后再置为null。在程序退出时,一定要确保其资源性对象已经关闭。

因此,在编写资源文件读写时,都需要在finally中关闭资源性对象,避免在异常情况下资源对象未被释放的隐患。

2.注册对象未注销

如果事件注册后未注销,会导致观察者列表中维持着对象的引用,阻止垃圾回收,一般发生在注册广播接受器、注册观察者等。

假设在Activity中,监听系统中的电话服务,以获取一些信息,可以在Activity中定义一个PhoneStateListener对象,同时将它注册到TelephonyManager服务中。对于Activity对象,理论上要求Activity在退出后该Activity对象就会被释放掉。

但是如果在释放该Activity时,忘记取消之前注册的PhoneStateListener对象,则会导致Activity无法被GC回收。如果不断地进出这个Activity,则最终会由于大量的Activity对象没有办法被回收而引起频繁的GC情况,甚至导致OOM。

3.类的静态变量持有大数据对象

静态变量长期维持着对象的引用,阻止垃圾回收,如果静态变量持有大的数据对象,如Bitmap等,就很容易引起内存不足等问题。

静态变量是在类被load的时候分配内存的,并且存在于方法区。当类被卸载的时候,静态变量被销毁。

1).类在什么时候被加载?

当我们启动一个app的时候,系统会创建一个进程,此进程会加载一个Dalvik VM的实例,

然后代码就运行在DVM之上,类的加载和卸载,垃圾回收等事情都由DVM负责。也就是说在进程启动的时候,类被加载,静态变量被分配内存。

2).类在什么时候被卸载?

在进程结束的时候。

4.非静态内部类的静态实例

非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被系统回收。如下代码是一个非静态内部类的实现。

public class LayoutPerActivity extends Activity {

       private static TestModule mTestModule = null;

       @override

       protected void onCreate(Bundle savedInstanceState) {

           super.onCreate(savedInstanceState);

           this.getWindow().setBackgroundDrawable(null);

           setContentView(R.layout.activity_layout_per);

           if(null == mTestModule) {

               mTestModule = new TestModule(this);

           }

       }

       class TestModule{

           private Context mContext = null;

           public TestModule(Context ctx) {

               mContext = ctx;

           }

       }

   }

在Activity内部创建了一个非静态内部类的静态实例mTestModule,每次启动Activity都会使用TestModule的数据,这样虽然避免了资源的重复创建,但是TestModule为非静态内部类默认持有外部类的引用,而这里又使用该非静态内部类创建了一个静态的实例,该实例的生命周期和应用一样长,这就导致该静态实例一直持有该Activity的引用,Activity的资源不能正常回收。

可以这样避免内存泄露,即将内部类TestModule设为静态内部类或者将内部类TestModule抽取出来封装成一个单例,如果需要使用Context,就在没有特殊要求的情况下,使用Application Context,如果需要使用Activity Context,就记得用完后置空让GC回收,否则还是会内存泄露。

5.Handler临时性内存泄露

Handler通过发送Message与主线程交互是应用开发中非常常见的使用场景之一,Message发出之后存在于MessageQueue中,有些Message也不是马上就被处理到。在Message中存在一个target,它是Handler的一个引用,Message在Queue中存在的时间过长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。Handler发送Message与主线程交互的实现代码如下:

public class LayoutPerActivity extends Activity {

       Handler mHandler = new Handler(){

           @Override

           public void handleMessage(Message msg) {

               super.handleMessage(msg);

           }

       };

       private void doGetDataAsyncTask() {

           Message msg = Message.obtain();

           mHandler.sendMessage(msg);

       }

   }

由于mHandle是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,并且消息队列是在一个Looper线程中不断轮询处理消息,那就有一种情况,当这个Activity退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄露。避免这种内存泄露需要修改以下三个地方;

1.使用一个静态Handler内部类,

2.对Handler持有的对象使用弱引用,这样在回收时,也可以回收Handler持有的对象

3.在Activity的onDestroy或者onStop时,应该移除消息队列中的消息,避免Looper线程的消息队列中有待处理的消息需要处理。

修改后的代码如下:

public class LayoutPerActivity extends Activity {

       private NewHandler mHandler = new NewHandler(this);

       private static class NewHandler extends Handler{

           private WeakReference<Context> mContext = null;

           public NewHandler(Context context) {

               mContext = new WeakReference<Context>(context);

           }

           @Override

           public void handleMessage(Message msg) {

               super.handleMessage(msg);

           }

       }

       private void doGetDataAsyncTask() {

           Message msg = Message.obtain();

           mHandler.sendMessage(msg);

       }

       @Override

       protected void onDestroy() {

           super.onDestroy();

           mHandler.removeCallbacksAndMessages(null);

       }

   }

6.容器中的对象没有清理造成的内存泄露

通常把一些对象的引用加入集合中,在不需要该对象时,如果没有把它的引用从集合中清理掉,这个集合就会越来越大。如果这个集合是static,情况就更严重了。

7.WebView造成的内存泄露

webview的内存泄露的解决方法一般是为此webview单独开一个进程,使用AIDL与应用的主进程进行通信,当不再使用此webView的时候杀死此进程即可。

附:检测内存泄露工具及集成方法

工具:square公司开源的Leakcanary

github地址:https://github.com/square/leakcanary.git

集成方法

添加依赖:

   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'

   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'

   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1

新建一个App类继承Application

image.png

在onCreate方法中添加如下代码

if (LeakCanary.isInAnalyzerProcess(this)) {

     // This process is dedicated to LeakCanary for heap analysis.

     // You should not init your app in this process.

     return;

   }

   LeakCanary.install(this);

   // Normal app init code...

 }

然后在清单文件中配置App类

image.png

就这样就集成好了

下面写一个栗子来验证一下吧:

在工程里新建一个Activity,取名字叫SecondActivity:

在SecondActivity添加如下代码:

public class SecondActivity extends AppCompatActivity {

   private static SecondActivity.TestModule mTestModule = null;

   @Override

   protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       this.getWindow().setBackgroundDrawable(null);

       setContentView(R.layout.activity_main);

       if(null == mTestModule) {

           mTestModule = new SecondActivity.TestModule(this);

       }

   }

   class TestModule{

       private Context mContext = null;

       public TestModule(Context ctx) {

           mContext = ctx;

       }

   }

}

然后在MainActivity中加一个按钮,用于打开SecondActivity

image.png

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

   private Button noStaticInnerClass;

   @Override

   protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       this.getWindow().setBackgroundDrawable(null);

       setContentView(R.layout.activity_main);

       noStaticInnerClass = (Button) findViewById(R.id.no_static_inner_class);

       noStaticInnerClass.setOnClickListener(this);

   }

   @Override

   public void onClick(View view) {

       switch (view.getId()) {

           case R.id.no_static_inner_class:

               startActivity(new Intent(this,SecondActivity.class));

               break;

       }

   }

}

然后一直打开关闭SecondActivity,就会检测到内存泄露:



本文转自:简书

微信号:IdeaofSE


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多