注册

Android 常见内存泄漏总结、避免踩坑、提供解决方案



对常见的内存泄漏进行一波总结,希望可以帮到大家。

静态实例持有非静态内部类

描述🤦♀️

非静态内部类会持有外部类的实例,所以如果非静态内部类的实例是静态的话,那么它的生命周期就是整个APP的生命周期,而它则会一直持有外部类的引用,阻止外部类实例被系统回收。

举个例子🌰:

public class TestActivity extends AppCompatActivity {

   static InnerClass innerClass;

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);
       setContentView(R.layout.item_layout);
       findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {

           @Override
           public void onClick(View v) {
               innerClass = new InnerClass();
          }
      });
  }

   class InnerClass{
 
    }
}

此时点击button时,会创建InnerClass实例并且赋值给innerClass。因为innerClassstatic修饰,所以InnerClass实例的生命周期会和应用程序一样长,但是它会持有TestActivity的实例,所以就会导致如果系统需要回收不了TestActivity的实例。造成内存泄漏😮。

解决办法🙆♀️

  • 将非静态内部类替换成静态内部类,因为静态内部类不会持有外部类的引用

  • 一定要用非静态内部类的话,要保证内部类的生命周期短于外部类

耗时任务相关的匿名内部类/非静态内部类

描述🤦♀️

这个和上一个类似,非静态内部类持有外部类的实例大家都知道了,这里不在叙述了;匿名内部类也会持有外部类的实例,而且匿名内部类会结合线程使用得多,这里就拉出来讲一下。

同理因为匿名内部类会持有外部类的实例,比如线程的Runable如果在里面做了耗时任务,在外部类对象需要回收的时候,但是线程任务没有执行完,那么就会因为匿名内部类持有外部类的引用,进而阻止系统回收外部类对象了。

简单举个例子

public class TestActivity extends AppCompatActivity {

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);
       setContentView(R.layout.item_layout);
       findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {

           @Override
           public void onClick(View v) {
               ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.MICROSECONDS, new SynchronousQueue(), new ThreadFactory() {
                   @Override
                   public Thread newThread(Runnable r) {
                       return new Thread(new ThreadGroup("test-thread"),r);
                  }
              });
               threadPoolExecutor.submit(new Runnable() {
                   @Override
                   public void run() {
                       while (true){
                           Log.d("TAG", "run: test");
                      }
                  }
              });
          }
      });
  }
}

点击button执行线程任务,提交了一个runable进去,因为里面死循环永远不会结束。所以匿名内部类会一直持有TestActivitty对象。不会被系统回收掉😮。因为匿名内部类这玩意进场使用,所以还是需要注意的!!!🤦♀️

解决方案🙆♀️

  • 将匿名内部类/非静态内部类替换成静态内部类,因为静态内部类不会持有外部类的引用

  • 一定要用匿名内部类/非静态内部类的话,要保证内部类的生命周期短于外部类

Handle内存泄漏

描述🤦♀️

这个就有点老生常谈了😂,但还是的说一下。

Handler发送的Message会存储在MessageQueue里面,但是他们不一定马上就被处理了。

另外我们知道Message的Target会持有记录当前的Handler对象,用于进行消息分发。所以如果Message不被及时处理,那么Handler就无法被回收。

那么如果此时Handler是非静态的,则Handler也会导致引用它的Activity不能被回收😮。

举个例子🌰

public class TestActivity extends AppCompatActivity {

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);
       setContentView(R.layout.item_layout);
       Handler mHandler = new Handler(){

           @Override
           public void handleMessage(Message msg) {
               super.handleMessage(msg);
          }
      };
       findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {

           @Override
           public void onClick(View v) {
   
               mHandler.sendMessageDelayed(new Message(),100000);
               finish();
          }
      });
  }
}

当点击button时,会finish当前activity。但是因为消息没有被及时处理,间接引用了Handler对象,Handler又是匿名内部类实例,持有了activity对象。所以导致内存泄漏🤦♀️。

解决方案🙆♀️

  • 使用静态Handler内部类,handler的持有者用弱引用。

  • 在onDestroy中将未执行的消息和Callbacks清除。

    if (mHandler != null) {
        mHandler.removeCallbacksAndMessages(null);
    }

Context被长期持有

描述🤦♀️

这个也很简单,比如你把Activity的Context传给了一个长期存在的对象,那其实activity的context就是它自身,那么因为被持有就回收不了。造成内存泄漏

解决方案🙆♀️

  • 对于不是必须使用Activity的Context的情况(Dialog的Context必须使用Activity的Context),可以考虑使用Application来代替Activity的Context,因为一般使用Context无非时获取一些资源而已。

  • 一定要传入Activity的Context的话,一定要注意生命周期,不可以被长期引用。

View被静态修饰

描述🤦♀️

这个。。。。我估计没有多少人这么使用吧。如果View被静态修饰的话,因为View会持有Context,所以就会导致当前Activity不会被回收。🤦♀️

举个例子🌰

public class TestActivity extends AppCompatActivity {

   static View button;

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);
       setContentView(R.layout.item_layout);
       button = findViewById(R.id.button);
  }
}

解决方案🙆♀️

  • 在onDestory中将view置null。

大对象/监听器释放

描述🤦♀️

大对象比如Bitmap

Bitmap对象一般比较大,而且好多操作都需要变换产生新的对象。所以需要注意一定要尽快释放临时的Bitmap对象用于节省内存。尽量避免被静态修饰或者其他长生命周期引用。

监听器的释放

很多服务需要register和unregister监听器,需要在合适的时候及时的unregister这些监听器否则容易产生内存泄漏。

解决方案🙆♀️

  • 大对象及时释放

  • 监听器在合适的时候进行释放

资源对象注意关闭

描述🤦♀️

资源对象比如File、Cursor等,如果不进行正常关闭,会造成内存泄漏。

解决方案🙆♀️

  • 通常使用异常代码块捕获,在finally语句中进行关闭,防止出现异常资源没有被正常释放问题。

集合对象

描述🤦♀️

注意一些生命周期很长的集合,比如被static修饰的集合,它的生命周期会时APP的生命周期,那么它里面维持的对象,如果在没用之后要即使清理掉,否则就会造成内存泄漏。

举个例子🌰

public class TestActivity extends AppCompatActivity {

   static List<View> vies;

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);
       setContentView(R.layout.item_layout);
       vies.add(findViewById(R.id.button));
  }
}

view被加入集合,因为集合生命周期为APP的生命周期,所以View、Activity也回收不了,内存泄漏。

解决方案🙆♀️

  • 这个完全需要自己注意,对于长生命周期集合内部对象的管理。

————————————————
作者:pumpkin的玄学
来源:https://blog.csdn.net/weixin_44235109/article/details/122029725

0 个评论

要回复文章请先登录注册