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学习系列(36)--App调试内存泄露之Context篇(上)的更多相关文章

  1. Android学习系列(37)--App调试内存泄露之Context篇(下)

    接着<Android学习系列(36)--App调试内存泄露之Context篇(上)>继续分析. 5. AsyncTask对象 我N年前去盛大面过一次试,当时面试官极力推荐我使用AsyncT ...

  2. [Android Memory] App调试内存泄露之Context篇(上)

    转载自:http://www.cnblogs.com/qianxudetianxia/p/3645106.html Context作为最基本的上下文,承载着Activity,Service等最基本组件 ...

  3. [Android Memory] App调试内存泄露之Context篇(下)

    转载地址:http://www.cnblogs.com/qianxudetianxia/p/3655475.html 5. AsyncTask对象 我N年前去盛大面过一次试,当时面试官极力推荐我使用A ...

  4. [转] Android学习系列(29)--App调试的几个命令实践

    在Android的应用开发中,我们会用到各种代码调试:其实在Android的开发之后,我们可能会碰到一些随机的问题,如cpu过高,内存泄露等,我们无法简单的进行代码调试,我们需要一个系统日志等等,下面 ...

  5. Android学习系列(7)--App轮询服务器消息

    这篇文章是android开发人员的必备知识. 1.轮询服务器     一般的应用,定时通知消息可以采用轮询的方法从服务器拿取消息,当然实时消息通知的话,建议采用推送服务.    其中需要注意轮询的频率 ...

  6. Android学习系列(15)--App列表之游标ListView(索引ListView)

    游标ListView,提供索引标签,使用户能够快速定位列表项.      也可以叫索引ListView,有的人称也为Tweaked ListView,可能更形象些吧.      一看图啥都懂了: 1. ...

  7. Android学习系列(23)--App主界面实现

    在上篇文章<Android学习系列(22)--App主界面比较>中我们浅略的分析了几个主界面布局,选了一个最大众化的经典布局.今天我们就这个经典布局,用代码具体的实现它. 1.预览图先看下 ...

  8. Android学习系列(17)--App列表之圆角ListView(续)

    http://www.cnblogs.com/qianxudetianxia/archive/2011/09/19/2068760.html   本来这篇文章想并到上篇Android学习系列(16)- ...

  9. Android学习系列(18)--App工程结构搭建

     本文算是一篇漫谈,谈一谈关于Android开发中工程初始化的时候如何在初期我们就能搭建一个好的架构.      关于android架构,因为手机的限制,目前我觉得也确实没什么大谈特谈的,但是从开发的 ...

随机推荐

  1. hdu 1556.Color the ball 解题报告

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1556 题目意思:有 n 个气球从左到右排成一排,编号依次为1,2,3,...,n.给出 n 对 a, ...

  2. codeforces 507B. Amr and Pins 解题报告

    题目链接:http://codeforces.com/problemset/problem/507/B 题目意思:给出圆的半径,以及圆心坐标和最终圆心要到达的坐标位置.问最少步数是多少.移动见下图.( ...

  3. spring mvc 重定向问题

    (1)我在后台一个controller跳转到另一个controller,为什么有这种需求呢,是这样的.我有一个列表页面,然后我会进行新增操作,新增在后台完成之后我要跳转到列表页面,不需要传递参数,列表 ...

  4. 火狐----此地址使用了一个通常用于网络浏览以外的端口。出于安全原因,Firefox 取消了该请求。

    FirFox打开80以外的端口,会弹出以下提示: “此地址使用了一个通常用于网络浏览以外的端口.出于安全原因,Firefox 取消了该请求.”.经网上搜索,解决方法如下: 在Firefox地址栏输入a ...

  5. HTML简历表格

    效果图 <!DOCTYPE > <html> <head> <meta charset="utf-8" /> </head&g ...

  6. Xcode找不到模拟器

    今天新建的工程,突然发现模拟器找不到了,之前遇到过忘记怎么解决了,于是再次记录下解决方法. 首先说下问什么找不到模拟器了,原因就是之前运行的版本和现在xcode的版本不同(的确,我从 Xcode7.3 ...

  7. main方法并发测试

    public static void main(String[] args) throws Exception{ RequestModel r = new RequestModel(); r.setT ...

  8. python基础——使用模块

    python基础——使用模块 Python本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用. 我们以内建的sys模块为例,编写一个hello的模块: #!/usr/bin/env ...

  9. elipse插件整理

    整理一下用过的eclipse插件: 1. WindowBuilder :swing插件,可以拖啊拖啊拖出来一个窗口,可以显著提高开发效率.   官网: http://www.eclipse.org/w ...

  10. 四、优化及调试--网站优化--Yahoo军规下

    21.根据域名划分页面内容 很显然, 是最大限度地实现平行下载 22.尽量减少iframe的个数 考虑即使内容为空,加载也需要时间,会阻止页面加载,没有语意,注意iframe相对于其他DOM元素高出1 ...