• (转载) Android常见的几种内存泄漏小结


    转载: http://www.jb51.net/article/109261.htm

    在Android程序开发中,当一个对象已经不需要再使用了,本该被回收时,而另外一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致本该被回收的对象不能被回收而停留在堆内存中,内存泄漏就产生了。内存泄漏有什么影响呢?它是造成应用程序OOM的主要原因之一。由于Android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash。了解了内存泄漏的原因及影响后,我们需要做的就是掌握常见的内存泄漏,并在以后的Android程序开发中,尽量避免它。

    1、单例造成的内存泄漏

    Android的单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏。因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class SingleInstance {
     
      private static SingleInstance instance;
      private Context context;
     
      private SingleInstance(Context context) {
        this.context = context;
      }
     
      public synchronized static SingleInstance getInstance(Context context) {
        if (instance != null) {
          instance = new SingleInstance(context);
        }
        return instance;
      }
    }

    这是一个普通的单例模式,大家都知道,静态变量最大的特点是什么,常驻内存,也就是说如果你的APP的进程没有没杀死,它就一直在内存中。当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:如果传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。

    所以正确的单例应该为这种姿势:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class SingleInstance {
     
      private static SingleInstance instance;
      private Context context;
     
      private SingleInstance(Context context) {
        this.context = context.getApplicationContext();
      }
     
      public synchronized static SingleInstance getInstance(Context context) {
        if (instance != null) {
          instance = new SingleInstance(context);
        }
        return instance;
      }
    }

    这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏。

    2、非静态内部类(比如内部类、匿名内部类)创建静态实例造成的内存泄漏

    有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,会出现这种写法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class MainActivity extends AppCompatActivity {
     
      private InnerClass innerClass;
     
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
     
        if(innerClass == null){
          innerClass = new InnerClass();
        }
      }
     
      class InnerClass{
     
      }
    }

    这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。

    正确的做法为:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext 。

    3、Handler造成的内存泄漏

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为,用软引用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    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);
      }
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    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。

    当然简单点,也可以直接这样

    1
    2
    3
    4
    5
    6
    @Override
     protected void onDestroy() {
       super.onDestroy();
       //移除消息队列和回调
       mHandler.removeCallbacksAndMessages(null);
     }

    亲测试,有效。

    4、线程造成的内存泄漏

    对于线程造成的内存泄漏,也是平时比较常见的,如下这两个示例可能每个人都这样写过:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //——————test1
        new AsyncTask<Void, Void, Void>() {
          @Override
          protected Void doInBackground(Void... params) {
            SystemClock.sleep(10000);
            return null;
          }
        }.execute();
     
    //——————test2
        new Thread(new Runnable() {
          @Override
          public void run() {
            SystemClock.sleep(10000);
          }
        }).start();

    上面的异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。正确的做法还是使用静态内部类的方式,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    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(),避免任务在后台执行浪费资源。

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

    对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

    6、WebView、或百度、高德地图的MapView引起的内存泄漏,可以参考我的这篇文章

    WebView引起的内存泄漏:http://www.jb51.net/article/79372.htm

  • 相关阅读:
    warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
    Windows10+CLion+OpenCV4.5.2开发环境搭建
    Android解决部分机型WebView播放视频全屏按钮灰色无法点击、点击全屏白屏无法播放等问题
    MediaCodec.configure Picture Width(1080) or Height(2163) invalid, should N*2
    tesseract
    Caer -- a friendly API wrapper for OpenCV
    Integrating OpenCV python tool into one SKlearn MNIST example for supporting prediction
    Integrating Hub with one sklearn mnist example
    What is WSGI (Web Server Gateway Interface)?
    Hub --- 机器学习燃料(数据)的仓库
  • 原文地址:https://www.cnblogs.com/bruce-he/p/8893072.html
Copyright © 2020-2023  润新知