美文网首页Android高阶
android 内存泄漏(多文章摘录)

android 内存泄漏(多文章摘录)

作者: 小浩_w | 来源:发表于2018-09-27 14:45 被阅读537次
    内存泄漏

    gc没有办法回收activity的内存。

    垃圾回收(GC)

    垃圾回收或GC(Garbage Collection),是一种自动的存储管理机制,它是Java语言的一大特性,把内存释放工作的压力都转让到了系统,故而是以消耗系统性能为代价的。C++编码的时候,我们需要自己实现析构函数来进行内存释放,很麻烦,而且非常容易遗漏而最终导致程序崩掉。所以Java语言就引入了自动内存管理的机制,也就是垃圾回收机制,针对的主要的内存的堆区域,关于内存的分配机制。

    一些常见的内存泄漏及解决方案

    一:单例设计模式造成的内存泄漏:

    单例设计模式的静态特性会使他的生命周期和应用程序的生命周期一样长,这就说明了如果一个对象不在使用了,而这时单例对象还在持有该对象的引用,这时GC就会无法回收该对象,造成了内存泄露的情况。
    下面是错误的单例设计模式的代码:

    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;
        }
    }
    

    上面的代码是一个最普通的单例模式,但是需要注意两个问题:
    1、如果我们传入的Context是Application的Context的话,就没有任何问题,因为Application的Context生命周期和应用程序生命周期一样长。
    2、如果我们传入的Context是Activity的Context的话,这时如果我们因为需求销毁了该Activity的话,Context也会随着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;
        }
    }
    

    这样的话不管我们传入什么样的Context,最终使用的都是Application的Context,单例的生命周期和应用一样长,这样就不会造成内存泄漏了。

    二、静态变量导致内存泄露

    静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。一旦静态变量初始化后,它所持有的引用只有等到进程结束才会释放。

    比如下面这样的情况,在Activity中为了避免重复的创建info,将sInfo作为静态变量:

    public class MainActivity extends AppCompatActivity {
    
        private static Info sInfo;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            if (sInfo != null) {
                sInfo = new Info(this);
            }
        }
    }
    
    class Info {
        public Info(Activity activity) {
        }
    }
    

    Info作为Activity的静态成员,并且持有Activity的引用,但是sInfo作为静态变量,生命周期肯定比Activity长。所以当Activity退出后,sInfo仍然引用了Activity,Activity不能被回收,这就导致了内存泄露。

    在Android开发中,静态持有很多时候都有可能因为其使用的生命周期不一致而导致内存泄露,所以我们在新建静态持有的变量的时候需要多考虑一下各个成员之间的引用关系,并且尽量少地使用静态持有的变量,以避免发生内存泄露。当然,我们也可以在适当的时候讲静态量重置为null,使其不再持有引用,这样也可以避免内存泄露。

    三、非静态内部类创建的静态实例造成的内存泄漏

    有时候因为需求我们会去频繁的启动一个Activity,这时为了避免频繁的创建相同的数据源,我们通常会做如下处理:

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

    这样就在Activity中创建了非静态内部类,非静态内部类默认持有Activity类的引用,但是他的生命周期还是和应用程序一样长,所以当Activity销毁时,静态内部类的对象引用不会被GC回收,就会造成了内存溢出,解决办法:
    1、将内部类改为静态内部类。
    2、将这个内部类封装成一个单例,Context使用Application的Context

    四、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也是一个非静态匿名内部类,他跟上面的一样,也会持有Activity的引用,我们知道handler是运行在一个Looper线程中的,而Looper线程是轮询来处理消息队列中的消息的,假设我们处理的消息有十条,而当他执行到第6条的时候,用户点击了back返回键,销毁了当前的Activity,这个时候消息还没有处理完,handler还在持有Activity的引用,这个时候就会导致无法被GC回收,造成了内存泄漏。正确的做法是:

    public class MainActivity extends AppCompatActivity {
    //new一个自定义的Handler
        private MyHandler mHandler = new MyHandler(this);
        private TextView mTextView ;
     
    //自定义静态内部类继承自Handler
        private static class MyHandler extends Handler {
            private WeakReference<Context> reference;
    //在构造函数中使用弱引用来引用context对象
            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();
    //移除队列中所有的Runable和消息
    //这里也可以使用mHandler.removeMessage和mHandler.removeCallBacks来移除指定的Message和Runable
          mHandler.removeCallbacksAndMessages(null);
      }
    }
    
    

    创建一个静态内部类继承自handler,然后再在构造参数中对handler持有的对象做弱引用,这样在回收时就会回收了handler持有的对象,这里还做了一处修改,就是当我们的回收了handler持有的对向,即销毁了该Activity时,这时如果handler中的还有未处理的消息,我们就需要在OnDestry方法中移除消息队列中的消息。

    五、线程造成的内存泄漏

    线程使用不恰当造成的内存泄漏也是很常见的,下面举两个例子:

          //——————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();
    

    上面是两个内部类,当我们的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销毁时也要记得在OnDestry中调用AsyncTask.cancal()方法来取消相应的任务。避免在后台运行浪费资源。

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

    在使用完BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源时,一定要在Activity中的OnDestry中及时的关闭、注销或者释放内存,

    否则这些资源不会被GC回收,就会造成内存泄漏。

    七、Timer和TimerTask导致内存泄露

    Timer和TimerTask在Android中通常会被用来做一些计时或循环任务,比如实现无限轮播的ViewPager:

    public class MainActivity extends AppCompatActivity {
    
        private ViewPager mViewPager;
        private PagerAdapter mAdapter;
        private Timer mTimer;
        private TimerTask mTimerTask;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            init();
            mTimer.schedule(mTimerTask, 3000, 3000);
        }
    
        private void init() {
            mViewPager = (ViewPager) findViewById(R.id.view_pager);
            mAdapter = new ViewPagerAdapter();
            mViewPager.setAdapter(mAdapter);
    
            mTimer = new Timer();
            mTimerTask = new TimerTask() {
                @Override
                public void run() {
                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            loopViewpager();
                        }
                    });
                }
            };
        }
    
        private void loopViewpager() {
            if (mAdapter.getCount() > 0) {
                int curPos = mViewPager.getCurrentItem();
                curPos = (++curPos) % mAdapter.getCount();
                mViewPager.setCurrentItem(curPos);
            }
        }
    
        private void stopLoopViewPager() {
            if (mTimer != null) {
                mTimer.cancel();
                mTimer.purge();
                mTimer = null;
            }
            if (mTimerTask != null) {
                mTimerTask.cancel();
                mTimerTask = null;
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            stopLoopViewPager();
        }
    }
    
    

    当我们Activity销毁的时,有可能Timer还在继续等待执行TimerTask,它持有Activity的引用不能被回收,因此当我们Activity销毁的时候要立即cancel掉Timer和TimerTask,以避免发生内存泄漏。

    集合中的对象未清理造成内存泄露

    这个比较好理解,如果一个对象放入到ArrayList、HashMap等集合中,这个集合就会持有该对象的引用。当我们不再需要这个对象时,也并没有将它从集合中移除,这样只要集合还在使用(而此对象已经无用了),这个对象就造成了内存泄露。并且如果集合被静态引用的话,集合里面那些没有用的对象更会造成内存泄露了。所以在使用集合时要及时将不用的对象从集合remove,或者clear集合,以避免内存泄漏。

    资源未关闭或释放导致内存泄露

    在使用IO、File流或者Sqlite、Cursor等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。

    属性动画造成内存泄露

    动画同样是一个耗时任务,比如在Activity中启动了属性动画(ObjectAnimator),但是在销毁的时候,没有调用cancle方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。因此同样要在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏。

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mAnimator.cancel();
    }
    

    WebView造成内存泄露

    关于WebView的内存泄露,因为WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的destory()方法来销毁它以释放内存。

    另外在查阅WebView内存泄露相关资料时看到这种情况:

    Webview下面的Callback持有Activity引用,造成Webview内存无法释放,即使是调用了Webview.destory()等方法都无法解决问题(Android5.1之后)。

    最终的解决方案是:在销毁WebView之前需要先将WebView从父容器中移除,然后在销毁WebView。详细分析过程请参考这篇文章:WebView内存泄漏解决方法

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 先从父控件中移除WebView
        mWebViewContainer.removeView(mWebView);
        mWebView.stopLoading();
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.removeAllViews();
        mWebView.destroy();
    }
    

    构造单例的时候尽量别用Activity的引用;
    静态引用时注意应用对象的置空或者少用静态引用;
    使用静态内部类+软引用代替非静态内部类;
    及时取消广播或者观察者注册;
    耗时任务、属性动画在Activity销毁时记得cancel;
    文件流、Cursor等资源及时关闭;
    Activity销毁时WebView的移除和销毁。


    本文参考了以下博客:
    https://www.jianshu.com/p/4b6adee12682
    https://blog.csdn.net/qq_35373333/article/details/74909811
    https://www.jianshu.com/p/ab4a7e353076
    https://blog.csdn.net/baidu_33396702/article/details/50337875
    https://blog.csdn.net/lvwenbo0107/article/details/51458885

    相关文章

      网友评论

      本文标题:android 内存泄漏(多文章摘录)

      本文链接:https://www.haomeiwen.com/subject/sdfeoftx.html