• [Android Memory] App调试内存泄露之Context篇(上)


    转载自:http://www.cnblogs.com/qianxudetianxia/p/3645106.html

    Context作为最基本的上下文,承载着Activity,Service等最基本组件。当有对象引用到Activity,并不能被回收释放,必将造成大范围的对象无法被回收释放,进而造成内存泄漏。

    下面针对一些常用场景逐一分析。

    1. CallBack对象的引用

        先看一段代码:

    @Override
    protectedvoid onCreate(Bundle state){
      super.onCreate(state);
       
      TextView label =new TextView(this);
      label.setText("Leaks are bad");
       
      setContentView(label);
    }

    大家看看有什么问题吗?

        没问题是吧,继续看:

    private static Drawable sBackground;
       
    @Override
    protected void onCreate(Bundle state){
      super.onCreate(state);
       
      TextView label =new TextView(this);
      label.setText("Leaks are bad");
       
      if(sBackground ==null){
        sBackground = getDrawable(R.drawable.large_bitmap);
      }
      label.setBackgroundDrawable(sBackground);
       
      setContentView(label);
    }

    有问题吗?

        哈哈,先Hold住一下,先来说一下android各版本发布的历史:

    /*
    2.2        2010-3-20,Froyo
    2.3        2010-12-6, Gingerbread
    3.0        2011-2-22, Honeycomb
    4.0        2011-10-11 Ice Cream Sandwich
    */

    了解源码的历史,是很有益于我们分析android代码的。

        好,开始分析代码。

        首先,查看setBackgroundDrawable(Drawable background)方法源码里面有一行代码引起我们的注意:

    public void setBackgroundDrawable(Drawable background) {
        // ... ...
        background.setCallback(this);
        // ... ...
    }

        所以sBackground对view保持了一个引用,view对activity保持了一个引用。

        当退出当前Activity时,当前Activity本该释放,但是因为sBackground是静态变量,它的生命周期并没有结束,而sBackground间接保持对Activity的引用,导致当前Activity对象不能被释放,进而导致内存泄露。

        所以结论是:有内存泄露!

        这是Android官方文档的例子:http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html

        到此结束了吗?

        我发现网上太多直接抄或者间接抄这篇文章,一搜一大片,并且吸引了大量的Android初学者不断的转载学习。

        但是经过本人深入分析Drawable源码,事情发生了一些变化。

        Android官方文档的这篇文章是写于2009年1月的,当时的Android Source至少是Froyo之前的。

        Froyo的Drawable的setCallback()方法的实现是这样的:

    public final void setCallback(Callback cb) {
            mCallback = cb;
    }

    在GingerBread的代码还是如此的。

        但是当进入HoneyComb,也就是3.0之后的代码我们发现Drawable的setCallback()方法的实现变成了:

    public final void setCallback(Callback cb) {
            mCallback = new WeakReference<Callback>(cb);
    }

    也就是说3.0之后,Drawable使用了软引用,把这个泄露的例子问题修复了。(至于软引用怎么解决了以后有机会再分析吧)

        所以最终结论是,在android3.0之前是有内存泄露,在3.0之后无内存泄露!

        如果认真比较代码的话,Android3.0前后的代码改进了大量类似代码,前面的Cursor篇里的例子也是在3.0之后修复了。

        从这个例子中,我们很好的发现了内存是怎么通过回调泄露的,同时通过官方代码的update也了解到了怎么修复类似的内存泄露。

    2. System Service对象

        通过各种系统服务,我们能够做一些系统设计好的底层功能:

    //ContextImpl.java
    @Override
    public Object getSystemService(String name) {
        ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
        return fetcher == null ? null : fetcher.getService(this);
    }
     
    static {
        registerService(ACCESSIBILITY_SERVICE, new ServiceFetcher() {
                public Object getService(ContextImpl ctx) {
                return AccessibilityManager.getInstance(ctx);
                }});
     
        registerService(CAPTIONING_SERVICE, new ServiceFetcher() {
                public Object getService(ContextImpl ctx) {
                return new CaptioningManager(ctx);
                }});
     
        registerService(ACCOUNT_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);
                IAccountManager service = IAccountManager.Stub.asInterface(b);
                return new AccountManager(ctx, service);
                }});
        // ... ...
    }

    这些其实就是定义在Context里的,按理说这些都是系统的服务,应该都没问题,但是代码到了各家厂商一改,事情发生了一些变化。

          一些厂商定义的服务,或者厂商自己修改了一些新的代码导致系统服务引用了Context对象不能及时释放,我曾经碰到过Wifi,Storage服务都有内存泄露。

         我们改不了这些系统级应用,我们只能修改自己的应用。

         解决方案就是:使用ApplicationContext代替Context。

         举个例子吧:

    // For example
    mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
    改成:
    mStorageManager = (StorageManager) getApplicationContext().getSystemService(Context.STORAGE_SERVICE);

    3. Handler对象

        先看一段代码:

    public class MainActivity extends QActivity {
            // lint tip: This Handler class should be static or leaks might occur
        class MyHandler extends Handler {
            ... ...
        }
    }

    Handler泄露的关键点有两个:

        1). 内部类

        2). 生命周期和Activity不一定一致

        第一点,Handler使用的比较多,经常需要在Activity中创建内部类,所以这种场景还是很多的。

        内部类持有外部类Activity的引用,当Handler对象有Message在排队,则无法释放,进而导致Activity对象不能释放。

        如果是声明为static,则该内部类不持有外部Acitivity的引用,则不会阻塞Activity对象的释放。

        如果声明为static后,可在其内部声明一个弱引用(WeakReference)引用外部类。

    public class MainActivity extends Activity {
        private CustomHandler mHandler;
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mHandler = new CustomHandler(this);
        }
     
        static class CustomHandlerextends Handler {
            // 内部声明一个弱引用,引用外部类
            private WeakReference<MainActivity > activityWeakReference;
            public MyHandler(MyActivity activity) {
                activityWeakReference= new WeakReference<MainActivity >(activity);
            }
                    // ... ...   
        }
    }

    第二点,其实不单指内部类,而是所有Handler对象,如何解决上面说的Handler对象有Message在排队,而不阻塞Activity对象释放?

        解决方案也很简单,在Activity onStop或者onDestroy的时候,取消掉该Handler对象的Message和Runnable。

        通过查看Handler的API,它有几个方法:removeCallbacks(Runnable r)和removeMessages(int what)等。

    // 一切都是为了不要让mHandler拖泥带水
    @Override
    public void onDestroy() {
        mHandler.removeMessages(MESSAGE_1);
        mHandler.removeMessages(MESSAGE_2);
        mHandler.removeMessages(MESSAGE_3);
        mHandler.removeMessages(MESSAGE_4);
     
        // ... ...
     
        mHandler.removeCallbacks(mRunnable);
     
        // ... ...
    }

      上面的代码太长?好吧,出大招:

    @Override
    public void onDestroy() {
        //  If null, all callbacks and messages will be removed.
        mHandler.removeCallbacksAndMessages(null);
    }

    有人会问,当Activity退出的时候,我还有好多事情要做,怎么办?我想一定有办法的,比如用Service等等.

    4. Thread对象

        同Handler对象可能造成内存泄露的原理一样,Thread的生命周期不一定是和Activity生命周期一致。

        而且因为Thread主要面向多任务,往往会造成大量的Thread实例。

        据此,Thread对象有2个需要注意的泄漏点:

        1). 创建过多的Thread对象

        2). Thread对象在Activity退出后依然在后台执行

        解决方案是:

        1). 使用ThreadPoolExecutor,在同时做很多异步事件的时候是很常用的,这个不细说。

        2). 当Activity退出的时候,退出Thread

        第一点,例子太多,建议大家参考一下afinal中AsyncTask的实现学习。

        第二点,如何正常退出Thread,我在之前的博文中也提到过。示例代码如下:

    // ref http://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
    private volatile Thread blinker;
     
    public void stop() {
        blinker = null;
    }
     
    public void run() {
        Thread thisThread = Thread.currentThread();
        while (blinker == thisThread) {
            try {
                thisThread.sleep(interval);
            } catch (InterruptedException e){
            }
            repaint();
        }
    }

    有人会问,当Activity退出的时候,我还有好多事情要做,怎么办?请看上面Handler的分析最后一行。

        (未完待续)

  • 相关阅读:
    2018-11-13-常用模块1 (time random os sys)
    2018-11-12-递归函数&二分查找
    2018-11-9-匿名函数&递归函数初识
    JS-正则表达式实战篇(Angel著)
    Ubuntu 14.04远程登录服务器
    (37)Spring Boot集成EHCache实现缓存机制【从零开始学Spring Boot】
    (36)Spring Boot Cache理论篇【从零开始学Spring Boot】
    (35)Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】
    (34)Spring Boot的启动器Starter详解【从零开始学Spring Boot】
    Tensorflow 0.8.0 安装配置方法
  • 原文地址:https://www.cnblogs.com/0616--ataozhijia/p/3755894.html
Copyright © 2020-2023  润新知