Android学习系列(36)--App调试内存泄露之Context篇(上)

举报
aiot_bigbear 发表于 2022/09/25 04:47:35 2022/09/25
【摘要】 Context作为最基本的上下文,承载着Activity,Service等最基本组件。当有对象引用到Activity,并不能被回收释放,必将造成大范围的对象无法被回收释放,进而造成内存泄漏。 下面针对一些常用场景逐一分析。 1. CallBack对象的引用     先看一段代码: ...

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

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

1. CallBack对象的引用

    先看一段代码:

1
2
3
4
5
6
7
8
9
@Override
protectedvoid onCreate(Bundle state){
   super .onCreate(state);
   
   TextView label = new  TextView( this );
   label.setText( "Leaks are bad" );
   
   setContentView(label);
}

    大家看看有什么问题吗?

    没问题是吧,继续看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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各版本发布的历史:

1
2
3
4
5
6
/*
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)方法源码里面有一行代码引起我们的注意:

1
2
3
4
5
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()方法的实现是这样的:

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

    在GingerBread的代码还是如此的。

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

1
2
3
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对象

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

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
//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。

     举个例子吧:

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

 

3. Handler对象

    先看一段代码:

1
2
3
4
5
6
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)引用外部类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 一切都是为了不要让mHandler拖泥带水
@Override
public  void  onDestroy() {
     mHandler.removeMessages(MESSAGE_1);
     mHandler.removeMessages(MESSAGE_2);
     mHandler.removeMessages(MESSAGE_3);
     mHandler.removeMessages(MESSAGE_4);
 
     // ... ...
 
     mHandler.removeCallbacks(mRunnable);
 
     // ... ...
}

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

1
2
3
4
5
@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,我在之前的博文中也提到过。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的分析最后一行。

    (未完待续)

文章来源: blog.csdn.net,作者:悟空胆好小,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/xushx_bigbear/article/details/45008395

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。