浅析Context及可能带来的内存泄漏问题(转载)

本文深入探讨了Android开发中Context的概念及其可能导致的内存泄漏问题。通过示例代码详细解释了为何不当使用Context会引发内存泄漏,并提供了简单有效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

浅析Context及可能带来的内存泄漏问题

分类: Android基础   632人阅读  评论(7)  收藏  举报

目录(?)[+]

什么是 Context

纯英文含义来看,Context 意指上下文、环境、背景等等……那么 Android 中的 Context 的含义和这些英文释义有什么联系呢?不妨看看 Google 给出的定义:

Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.

把它完全翻译下来我觉得没啥必要,那这段话的核心含义是啥呢:

  1. 应用所处环境中所有信息的接口
  2. Context 只是一个抽象类,它的具体实现是由 Android 系统中的实现类提供的
  3. 允许访问系统资源或类,也可以进行应用层的一些操作,例如:启动 Activity、发送广播,接收 Intent 等等……

Context 能干什么

回顾我们使用 Context 的场景来帮助理解吧:我们在使用自定义 View 时,使用 BaseAdapter 时,甚至是访问数据库文件时,都需要传入一个 Context 参数,大家有没有想过这到底是为什么呢?因为我们初始化自定义 View 需要将 View 与某个页面布局关联,因为我们使用 BaseAdapter 时需要某个布局文件作为子 Item,因为我们需要访问应用的数据库文件。此时 Context 就像一个系统信息管理员,你告诉它我想要访问系统的布局资源文件,想要访问应用的数据库文件,它就去给你找,然后提供引用给你使用。

所以大家现在应该能理解了吧,Context 就是用于获得系统资源的,我们所说的系统资源包括设备本身的信息,也有我们开发者提供的信息(类、包、资源文件等等……)。

Context 类继承图

在进行分析之前,我们不妨先看看 Context 的类继承图,了解 Context 的主要实现类有哪些:

如图中所见,Application 类和 Activity、Service 两大 Android 组件都是 Context 的具体实现类,而这三个类确实能访问各自职责内的系统资源,例如 Activity 能访问与界面元素相关的资源、Service 能访问系统服务、Application 能访问应用包名等信息。

知识点:应用中 Context 的数量 = Activity 的数量 + Service 的数量 + 1(Application 的数量)

使用 Context 你不知道的事

前面已经提到,Context 的实现类能够持有各种各样的系统信息,可能有人会想到:使用 Context 不会导致内存泄漏吗?我在这里给大家答案:会!而且我们在开发中经常无意地导致了内存泄漏。有人可能就不服了,我写的代码怎么可能有内存泄漏的问题!

别急,我下面给大家看一段最普通的代码:

例如我们要读取一些 Android 系统的信息,并且需要进行某些操作。那么我们需要传入 Context 来读取系统资源,而创建这个对象的开销可能会很大(如果读取的资源很多),所以我们采用单例模式:

<code class="language-java hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">MyManager</span>{</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> Context mContext;
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> MyManager manager;

    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> MyManager <span class="hljs-title" style="box-sizing: border-box;">getInstance</span>(Context context){
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(manager ==<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>){
            manager = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> MyManager(context);
        }

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> manager;
    }

    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-title" style="box-sizing: border-box;">MyManager</span>(Context context) {
        mContext = context;
    }

省略……
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li></ul>

设计完 MyManager 以后,我们肯定要在 MainActivity 里面使用它了:

<code class="language-java hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onCreate</span>(Bundle savedInstanceState) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        manager = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> MyManager(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);

……省略
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>

一般大家都会写下这样的代码对吧?这样写都不会出现 Bug 对吧?老板还夸你工作效率高对吧?事实上这样的代码会导致内存泄漏哦~为什么呢?大家不妨回顾我在在Activity中使用Thread导致的内存泄漏里提到的内存泄漏原因,博文中说道:非静态匿名内部类将持有外部类的隐式引用,使垃圾回收机制不会回收该对象。事实上,更确切的说法应该是:当对象的持有者生命周期比对象生命周期要宽泛时,就很有可能产生内存泄漏的问题。

还是举例子吧:

  1. Android 系统会持有 Thread 类的引用,这使得 Thread 类的生命周期变得异常地宽泛。那么,在 Thread 未执行完或作为非静态匿名内部类被使用时,传入 Thread 的对象都不会被垃圾回收机制,因为 Thread 类在执行完相应操作前,引用始终由 Android 系统持有,使得 Thread 始终持有传入其中的对象的引用

  2. 我们向某个单例对象传入 Context(某个实例对象),Context 来自 Activity 或 Service (某个具有生命周期的对象)。那么在我们的想象之中,Context 类的对象应该随着 Activity/Serivce 生命周期的完结被垃圾回收机制回收。但现在存在这样一个问题,单例对象是由对应类内部的静态引用管理的,即使 Activity 结束了,单例对象还是存在,这将使得该对象继续持有 Context 的引用,造成内存泄漏问题。

在这段代码中,我们将 Activity 的引用传入 MyManager,使得 MyManager 持有对 Activity 的引用,那么相应地,我们也将持有 Activity 所获得的资源文件的引用。所以这些资源文件将无法被垃圾回收机制回收,造成内存泄漏的问题。

那么有什么解决办法呢?

我们只需要修改一句代码:

<code class="language-java hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background: transparent;">mContext = context.getApplicationContext();

或者

manager = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> MyManager(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>.getApplication());</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>

就可以避免内存泄漏的问题。大家需要记住的是,在这里我们只是演示了一种最常见,最基础的 Context 导致的内存泄漏问题,在实际的开发需求中肯定还有更加复杂的情况,大家应该更多地去思考背后的原因,从根源上解决这些内存泄漏的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值