原始问题是这样

然后扔到了很多Android开发交流群里。
接着产生了很多的见解,我感觉比较靠谱的有以下:

网友对我问题的回答

1、onDestroy被回调代不代表Activity被回收了?
官方是这么说的
Perform any final cleanup 【before】 an activity is destroyed.
x
 
1
Perform any final cleanup 【before】 an activity is destroyed.
众多网友:不代表!
网友1:代表【将】被系统回收,具体什么时候回收看系统
网友2:app退出时,并不清理其所占用的内存,你调gc只是建议,干不干还得看gc自己(意思是:onDestroy调用时和app退出时一样)
网友3:GC统一回收,要看GC判断你这个对象是不是不可达了
网友4:那只是个AMS流程回调

2、上述情况Activity有没有被回收?
网友1:Receiver一直持有Activity的引用怎么被回收
网友2:activity也是GC负责回收的,如果被强引用,没法回收

3、如果Activity被销毁了,Receiver是否还有引用?
网友1:Receiver明显不止被Activity持有,Receiver会注册到系统管理的的ams中
网友2:如果receiver被static修饰,即使activity被销毁,receiver也不会被回收,指向这个receiver的指针变成了野指针,没法主动销毁,从而造成内存泄露
网友3:你在Activity中注册了广播,如果不取消注册,这个广播会一直存在在系统中,这个广播会一直持有ACtivity的引用,肯定会内存泄漏。就跟非静态内部类一样
网友4:activity回收后receiver还在运行

测试代码

测试不取消注册广播导致内存泄漏的问题
/**
* 测试不取消注册广播导致内存泄漏的问题
*/
public class MemoryLeaksActivity extends Activity {
MyBRReceiver myReceiver;
TextView textView; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
textView = new TextView(this);
textView.setText("音量变化\n有线耳机插入或拔下\n");
setContentView(textView); myReceiver = new MyBRReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.media.VOLUME_CHANGED_ACTION");//音量变化
intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);//有线耳机插入或拔下
registerReceiver(myReceiver, intentFilter);
} private class MyBRReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("bqt", "【context】" + context.getClass().getSimpleName());//MemoryLeaksActivity
String action = intent.getAction();
switch (action) {
case Intent.ACTION_HEADSET_PLUG:
int state = intent.getIntExtra("state", 0);
if (state == 1) textView.append("\n耳机模式");
else if (state == 0) textView.append("\n外放模式");
break;
case "android.media.VOLUME_CHANGED_ACTION":
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int musicVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
int ringVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
textView.append("\n当前音量:musicVolume=" + musicVolume + " ringVolume=" + ringVolume);
break;
}
}
} @Override
protected void onDestroy() {
super.onDestroy();
Log.i("bqt", "【onDestroy被回调了,并不表明Activity被回收了,Receiver更是没有被回收】");
}
}
x
 
1
/**
2
 * 测试不取消注册广播导致内存泄漏的问题
3
 */
4
public class MemoryLeaksActivity extends Activity {
5
    MyBRReceiver myReceiver;
6
    TextView textView;
7
    
8
    @Override
9
    protected void onCreate(Bundle savedInstanceState) {
10
        super.onCreate(savedInstanceState);
11
        textView = new TextView(this);
12
        textView.setText("音量变化\n有线耳机插入或拔下\n");
13
        setContentView(textView);
14
        
15
        myReceiver = new MyBRReceiver();
16
        IntentFilter intentFilter = new IntentFilter();
17
        intentFilter.addAction("android.media.VOLUME_CHANGED_ACTION");//音量变化
18
        intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);//有线耳机插入或拔下
19
        registerReceiver(myReceiver, intentFilter);
20
    }
21
    
22
    private class MyBRReceiver extends BroadcastReceiver {
23
        @Override
24
        public void onReceive(Context context, Intent intent) {
25
            Log.i("bqt", "【context】" + context.getClass().getSimpleName());//MemoryLeaksActivity
26
            String action = intent.getAction();
27
            switch (action) {
28
                case Intent.ACTION_HEADSET_PLUG:
29
                    int state = intent.getIntExtra("state", 0);
30
                    if (state == 1) textView.append("\n耳机模式");
31
                    else if (state == 0) textView.append("\n外放模式");
32
                    break;
33
                case "android.media.VOLUME_CHANGED_ACTION":
34
                    AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
35
                    int musicVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
36
                    int ringVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
37
                    textView.append("\n当前音量:musicVolume=" + musicVolume + "  ringVolume=" + ringVolume);
38
                    break;
39
            }
40
        }
41
    }
42
    
43
    @Override
44
    protected void onDestroy() {
45
        super.onDestroy();
46
        Log.i("bqt", "【onDestroy被回调了,并不表明Activity被回收了,Receiver更是没有被回收】");
47
    }
48
}
2017-8-24

个人总结

如果我们在Activity中使用了registerReceiver()方法注册了一个BroadcastReceiver,如果没在Activity的生命周期内调用unregisterReceiver()方法取消注册此BroadcastReceiver,由于BroadcastReceiver不止被Activity引用,还可能会被AMS等系统服务、管理器等之类的引用,导致BroadcastReceiver无法被回收,而BroadcastReceiver中又持有着Activity的引用(即:onReceive方法中的参数Context),会导致Activity也无法被回收(虽然Activity回调了onDestroy方法,但并不意味着Activity被回收了),从而导致严重的内存泄漏。

一网友专门写了一篇博客解释这个问题


今天,在Q群中有网友发出了网上的一个相对经典的问题,问题具体见下图(白注:就是上面那张图)。

本来是无意写此文的,但群里多个网友热情不好推却,于是,撰此文予以分析。

从这个问题的陈述中,我们发现,提问者明显对Android中的几个基本概念在理解上是存在误区的(或直接称之为理解错误)。且这种误区,我发现是较为广泛的存在于不少Android开发心中的。

理解误区主要体现在对以下几个概念没有区分清:

  • 1,Activity的onDestory回调方法;
  • 2,Activity的销毁;
  • 3,Activity的内存回收;
  • 4,内存泄露;
  • 5,Activity中动态注册的BroadcastReceiver与Activity的引用持有关系。

下面我们来一个个具体分析下。

1,Activity的onDestory回调方法

onDestory作为Activity中生命周期中的一个常见的方法,我们先来看一下官方文档中的描述。

从这个定义中,我们得出如下几点细节:

  • a,onDestory回调方法是Activity被销毁前的最后一个Activity中回调方法;
  • b,onDestory回调方法的触发时机是Activity被外部主动调用了finish()方法,或因系统内存空间不足而导致的临行性的销毁该Activity实例,以便获得内存空间。

在实际操作中,onDestory回调方法的触发时机(或称之为Activity销毁的触发时机)主要表现在如下四种情况:

  • a,人为的主动的调用了finish()方法,以期望去销毁当前的Activity;
  • b,人为的主动操作导致的系统去销毁当前Activity,如常见的按下手机上的返回键;
  • c,系统因内存不足导致的临行性的销毁该Activity实例,如从A Activity跳转到B Activity后,系统内存不足的情况下可能会销毁掉A Activity;
  • d,打开手机上的“开发者选项”中的“不保留活动”选项,其中,这个更多的是为了模拟出C场景,效果同C。

另外,上述的b中的按下手机上的返回键,系统源码中也是调用了finish()方法。

区分上述的ab与cd两种方式可以通过isFinishing()方法的返回值来判断。

为了行文方便,且从ab与cd的人为主观性角度出发,本文将ab情形称之为“主动销毁”,cd情形称之为“被动销毁”。

2,Activity的销毁

相较于onDestory作为的Activity生命周期中的回调方法,“销毁”一词在Activity中更多的表示的是Activity所处声明周期中的一种“状态”。

处于此种状态的Activity实例,对于User Interface层来说是不再可见的(无论是当前界面还是按返回键等各种情况)。

实践中,处于“销毁状态”的Activity与上述的Activity销毁的触发时机具有一致的逻辑关系,这种逻辑关系具体体现为:

  • a,对于主动销毁,除却Activity实例不再可见外,当前Activity实例也直接被Activity栈中移除,直接表位为对用户操作导航路径的影响;
  • b,对于被动销毁,当前Activity实例依然不再可见,但与主动销毁不同的是,Activity实例的对应关系在Activity栈中依然存在,此时,对用户操作导航路径并无影响。如B Activity中,A Activity虽然被动销毁,但未改变栈结构,按下返回键依然看到A,不过此时的A与之前的A并非一个Activity实例。

需要注意的是,处于“销毁状态”的Activity,严格意义上与当前Activity的真实内存占用是否释放没有直接的对应关系。也就是说,Activity的销毁,并不意味着Activity的内存就已经被回收。

3,Activity的内存回收

Android是基于Java基础之上,虽然在内存回收机制等方面做了一定的处理与优化(主要是基于Dalvik/ART),但是基本的GC原理上并无差别。主要表现在:

  • a,对于一个堆内存中的对象空间,一旦还有其他的强引用可达,该内存空间就处于不可回收状态;
  • b,GC的触发时机依然具有不可确定性,取决于系统依据当前的运行状态与其系统本身的GC机制判定进行。

也就是说,即使堆内存中对象已经处于可回收状态,但只要GC未被触发,内存依然被占用。

在此,需要区分下GC的不可回收状态与可回收状态的区别,严格意义上来说,其并非对立面,因为针对可回收状态,还有可能对应的软引用与弱引用需要加以考虑。

4,内存泄露

Android中,内存泄露作为一个基本的概念,常常被提及且实践中也需尽量掌握。网上关于内存泄露的文章林林总总。

终究内存泄露的本质,是指当前对象在实际运行中超出了其本身意义上生命周期范围的,从而导致本该处于内存可回收状态的但实际上却一直处于不可回收状态的内存占用非正常现象。

内存泄露在出现,常常见于如下两种情况(为行文方便,下述将发生了内存泄露的对象称之为M):

  • a,因异步回调中持有M,异步回调本身的生命时长长于M本身而导致的M发生内存泄露(如最常见的是Activity中的Handler以及异步线程导致Activity本身发生内存泄露);
  • b,因静态属性所指向的对象中持有了M而导致的M一直被强引用可达,使得M发生内存泄露(如最常见的单例对象中强引用了外部的非静态对象)。

内存泄露过多会导致应用内存的不断上升,达到一定程度会直接导致内存溢出(OOM)。具体解决内存泄露时,主要都是针对上述AB两种情况分析排查即可。

5,Activity中动态注册的BroadcastReceiver与Activity的引用持有关系

对于Android中的广播机制,可以先参考文章:《Android总结篇系列:Android广播机制》

Activity中动态注册的广播接收器,一般性写法都是此Activity中持有创建的广播接收器的对象引用,并指明广播接收器对应的接收广播类型(IntentFilter)。

Activity中调用registerReceiver(mBroadcastReceiver, intentFilter)方法进行广播接收器的注册。此时,通过Binder机制向AMS(Activity Manager Service)进行注册。

AMS会对应的记录Activity上下文、广播接收器以及对应的IntentFilter等内容,并形成类似于消息的发布-订阅存储模式与结构。

当对应的广播发出时,在定义的广播接收器的onReceive(context, intent)方法回调中,对于Activity中动态注册的广播接收器,onReceive方法回调中的context指的是Activity Context!

也就是说,Activity与mBroadcastReceiver此时实际上是通过AMS相互持有强引用的。因此,对于Activity中动态注册的广播接收器,一定要在对应的声明周期回调方法中去unregisterReceiver,以斩断此关联。

否则,就会出现当前Activity的内存泄露。

为什么不取消注册BroadcastReceiver会导致内存泄漏的更多相关文章

  1. objective-c strong导致内存泄漏简单案例

    例如: @interface Test:NSObject{ id __strong obj_; } -(void) setObject:(id __strong)obj; @end @implemen ...

  2. MSDN官方XmlSerializer类导致内存泄漏和性能低

    MSDN官方XmlSerializer类使用说明链接: http://msdn.microsoft.com/zh-CN/library/system.xml.serialization.xmlseri ...

  3. Android 非静态内部类导致内存泄漏原因深入剖析

    背景 上周发现蘑菇街IM-Android代码里面.一些地方代码编写不当.存在内存泄漏的问题.在和疯紫交流的过程中.发现加深了一些理解,所以决定写一下分析思路,相互学习. 内存泄漏 一个不会被使用的对象 ...

  4. python中循环引用导致内存泄漏小案例

    首先定义一个Person类和一个Dog类,然后分别实例化对象p和d,给p对象添加一个pet属性 给d对象添加一个master属性此时Person和Dog的应用计数都为2,当del p 和del d后P ...

  5. 面试官:小伙子,你给我说一下Java中什么情况会导致内存泄漏呢?

    概念 内存泄露:指程序中动态分配内存给一些临时对象,但对象不会被GC回收,它始终占用内存,被分配的对象可达但已无用.即无用对象持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间浪费. 可达 ...

  6. Android一般什么情况下会导致内存泄漏

    资料参考:https://blog.csdn.net/u011479990/article/details/78480091 内存泄漏的原因在于生命周期长的对象持有了生命周期短的对象的引用 内存泄漏形 ...

  7. 重写hashCode方法,导致内存泄漏

    package com.nchu.learn.base.reflect; import org.junit.Test; import java.util.Collection; import java ...

  8. 引用计数gc机制使用不当导致内存泄漏

    上一篇文章找同事review了一下,收到的反馈是铺垫太长了,我尽量直入正题,哈哈 最近dbd压测时发现内存泄漏,其实这个问题去年已经暴露了,参见这篇博客[压测周].当时排查不够仔细,在此检讨下.关于d ...

  9. 连续改变Chrome浏览器窗口大小,可以导致内存泄漏

    最近在做响应式布局的页面,在开发测试过程中,为了看到页面在不同尺寸的窗口中的表现,因此要不停的拖动浏览器来改变其窗口大小:开始在Chrome浏览器下查看页面,拖动了几次,感觉电脑明显的卡了下来,刚开没 ...

随机推荐

  1. Java 初相识

    Java是如何出现的呢?这就要回到1991年,那时候随着单片机的发展,出现了很多微型的系统,Sun公司在这个时候就成立的一个项目组,成员就有我们熟知的“Java之父” 詹姆斯·高斯林,起初的目标是为了 ...

  2. Java 中的浮点数取精度方法

    Java 中的浮点数取精度方法 一.内容 一般在Java代码中取一个double类型的浮点数的精度,四舍五入或者直接舍去等的方式,使用了4种方法,推荐使用第一种,我已经封装成工具类了. 二.代码实现 ...

  3. Java 的类加载顺序

    Java 的类加载顺序 一.加载顺序:先父类后子类,先静态后普通 1.父类的静态成员变量初始化 2.父类的静态代码块 3.子类的静态成员变量初始化 4.子类的静态代码块 5.父类的普通成员变量初始化 ...

  4. [BZOJ4832]抵制克苏恩

    [BZOJ4832]抵制克苏恩 思路: \(f[i][j][k][l]\)表示打了\(i\)次,血量为\(1\sim 3\)的随从有\(j,k,l\)个的期望.转移时注意避免重复. 源代码: #inc ...

  5. PYQT控件使用

    QtGui.QComboBox .addItem(string)#添加字符串项到Item.addItems(list)#添加列表或元组元素到Item.clear()#清除所有Item.clearEdi ...

  6. C#情怀与未来,怨天尤人还是抓住机会,能否跟上dnc新时代浪潮?

    C#情怀与未来,怨天尤人还是抓住机会,能否跟上dnc新时代浪潮?   经常看到有.NET圈子在讨论是否应该转其它语言   C#情怀是一方面,如果觉得C#未来没前途,光靠情怀是撑不住的, 建议对C#未来 ...

  7. 力特ZE398C驱动光盘-USB转RS232-支持Windows 10/Mac

    这个工具是USB1.1的,相对来说比较老,一开始做小白鼠不知道买了USB1.1的,所以我不建议买这个,还有其它的型号,支持USB2.0和USB3.0,不过价格也相对来说比较贵,这个才30块钱左右. 关 ...

  8. hdu 2112 HDU Today (floyd算法)

    这道题貌似在原来学长给我们的搞的小比赛中出过! 这次又让我遇到,果断拿下! 不过方法很蠢,跑了1000多ms,虽然要求5000ms以内! 题目就是给你一些位置之间的距离,然后再让你求特定的两点之间的距 ...

  9. Android MMC/EMMC/MTD Partition Layout

    Android devices have a couple of partitions to store different data. The common ones are the recover ...

  10. JTAG Level Translation

    http://www.freelabs.com/~whitis/electronics/jtag/ One of the big issues in making a JTAG pod is leve ...