[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的分析最后一行。

    (未完待续)

 

 

 

分类:  Android Memory
本文转自demoblog博客园博客,原文链接http://www.cnblogs.com/0616--ataozhijia/p/3755894.html如需转载请自行联系原作者

demoblog
相关文章
|
2月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
|
3月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
141 6
|
5月前
|
JavaScript 前端开发 Android开发
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
155 13
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
2月前
|
缓存 编解码 Android开发
Android内存优化之图片优化
本文主要探讨Android开发中的图片优化问题,包括图片优化的重要性、OOM错误的成因及解决方法、Android支持的图片格式及其特点。同时介绍了图片储存优化的三种方式:尺寸优化、质量压缩和内存重用,并详细讲解了相关的实现方法与属性。此外,还分析了图片加载优化策略,如异步加载、缓存机制、懒加载等,并结合多级缓存流程提升性能。最后对比了几大主流图片加载框架(Universal ImageLoader、Picasso、Glide、Fresco)的特点与适用场景,重点推荐Fresco在处理大图、动图时的优异表现。这些内容为开发者提供了全面的图片优化解决方案。
|
3月前
|
数据采集 JSON 网络安全
移动端数据抓取:Android App的TLS流量解密方案
本文介绍了一种通过TLS流量解密技术抓取知乎App热榜数据的方法。利用Charles Proxy解密HTTPS流量,分析App与服务器通信内容;结合Python Requests库模拟请求,配置特定请求头以绕过反爬机制。同时使用代理IP隐藏真实IP地址,确保抓取稳定。最终成功提取热榜标题、内容简介、链接等信息,为分析热点话题和用户趋势提供数据支持。此方法也可应用于其他Android App的数据采集,但需注意选择可靠的代理服务。
128 11
移动端数据抓取:Android App的TLS流量解密方案
|
2月前
|
Arthas 监控 Java
Arthas mc(Memory Compiler/内存编译器 )
Arthas mc(Memory Compiler/内存编译器 )
54 6
|
4月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
5月前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
135 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
5月前
|
数据采集 JavaScript Android开发
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
148 7
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
5月前
|
缓存 Java 测试技术
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
407 3
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了

热门文章

最新文章