内存优化(二)如何避免内存泄漏

一、不同生命周期导致的内存泄漏

前面有分析了内存泄漏的原因,本该被回收的对象被占用,得不到回收便会内存泄漏。总归到底的原因还是对象引用在类之间传递,它们的生命周期不同,导致回收时发生问题。

举个简单的例子:

当单列模式中传入的Activity是,ToastRouter便持有了MainActivity的强引用,当MainActivity结束时,便得不到回收,这是内存泄漏发生了

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ToastRouter.getInstance(this);
    }
}


...


public class ToastRouter {
    private static final ToastRouter ourInstance = new ToastRouter();
    private Context mContext;

    public static ToastRouter getInstance(Context context) {
        this.mContext=context;
        return ourInstance;
    }

    private ToastRouter() {
    }

}

解决办法

  • 尽量避免生命周期不对等的对象引用传递,如果避免不了,应用对等生命周期的对象替代。如上Activity传入,可以用ApplicationContext传递代替,因为ApplicationContext的生命周期与APP对等。
  • 与对象生命周期绑定释放,如上例子,应该在MainActivity onDestary()方法中释放传入对象引用

二、非静态内部类持有对象导致的内存泄漏

非静态内部类持有对象导致的内存泄漏也很好理解,就是内部类持有了外部类的对象,导致的外部类回收失败造成的内存泄漏

1. 非静态内部类调用外部类的方法的

  • 看一个简单的JAVA代码案例
    • 通过匿名内部类,内部类分别打印一个延时log日志。
    • 其中分别用的AsyncTask和Handler举例
        /**
         * 通过匿名内部类,打印一个1.5s延时log
         */
        public void doAnonymousInnerClassTask() {
            new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... voids) {
                    //睡眠1.5s
                    try {
                        Thread.sleep(1500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    logMsg(" AsyncTask 匿名内部类 doInBackground");
                    return null;
                }
            }.execute();
        }
    
        /**
         * 通过内部类,打印一个1.5s延时log
         */
        public void doNormalInnerTask() {
            TestAsyncTask testAsyncTask = new TestAsyncTask();
            testAsyncTask.execute();
        }
        
        private class TestAsyncTask extends AsyncTask<Void, Integer, Void> {
    
            @Override
            protected Void doInBackground(Void... voids) {
    
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                logMsg(" TestAsyncTask内部类 doInBackground");
                return null;
            }
        }
        
        private void logMsg(String msg) {
            Log.d(TAG, msg);
        }
  • 我们通过.class文件,查看它的对象引用发现,在匿名内部类和内部类中,调用MainActivity的logMsg方法时,已持有了MainActivity.this的引用,因此当非静态内部类生命周期比MainActivity长时,即发生了内存泄漏。

      public void doAnonymousInnerClassTask() {
          (new AsyncTask<Void, Void, Void>() {
              protected Void doInBackground(Void... voids) {
                  try {
                      Thread.sleep(1500L);
                  } catch (InterruptedException var3) {
                      var3.printStackTrace();
                  }
    
                  MainActivity.this.logMsg(" AsyncTask 匿名内部类 doInBackground");
                  return null;
              }
          }).execute(new Void[0]);
      }
    
      private class TestAsyncTask extends AsyncTask<Void, Integer, Void> {
          private TestAsyncTask() {
          }
    
          protected Void doInBackground(Void... voids) {
              try {
                  Thread.sleep(1500L);
              } catch (InterruptedException var3) {
                  var3.printStackTrace();
              }
    
              MainActivity.this.logMsg(" TestAsyncTask内部类 doInBackground");
              return null;
          }
      }
    

2. 内部类是如何持有外部类对象?

上述通过.class文件可以看到内部类在调用外部类的方法时,通过持有的外部类对象去调用。那么外部类对象的引用是什么时候传入的呢。通过
参考文章:深入理解Java中为什么内部类可以访问外部类的成员得到结论:

  • 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象的引用;
  • 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为1中添加的成员变量赋值;
  • 在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用。

由此,即使内部类并没有调用外部类的方法或者变量,也一样会持有外部类的引用。

3. 如何处理非静态内部类内存泄漏问题

  1. 可以使用,但是前提是确保非静态内部类的生命周期超过外部类

  2. 使用静态内部类,在静态内部类需要持有外部类引用时,通过关联外部类的弱引用去调用。

     /**
      * 通过静态内部类打印一个1.5s延时log
      */
     public void doStaticInnerClassTask() {
         mHandler.sendEmptyMessageDelayed(0, 1500);
     }
    
     private final StaticHandler mHandler = new StaticHandler(this);
    
     private static class StaticHandler<T> extends Handler {
    
         protected WeakReference<T> ref;
    
         public StaticHandler(T cls) {
             ref = new WeakReference<T>(cls);
         }
    
         public T getRef() {
             return ref != null ? ref.get() : null;
         }
    
         @Override
         public void handleMessage(Message msg) {
             super.handleMessage(msg);
             MainActivity _Activity = (MainActivity) ref.get();
             _Activity.logMsg("StaticHandler 静态内部类log ");
         }
     }
    
  3. 在外部类生命周期结束前自主释放外部类的引用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏...
    _痞子阅读 1,622评论 0 8
  • 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,...
    宇宙只有巴掌大阅读 2,360评论 0 12
  • Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏...
    apkcore阅读 1,216评论 2 7
  • 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,...
    DreamFish阅读 790评论 0 5
  • 内存泄漏 Java 内存分配策略 Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,...
    酷酷de熊阅读 330评论 0 0