User Tools

Site Tools


android:mem-leak:android-traceview-avoid-mem-leak

避免内存泄露, 有效利用内存

一 TraceView 简介

Traceview是android平台配备一个很好的性能分析的工具。它可以通过图形化的方式让我们了解我们要跟踪的程序的性能,并且能具体到method。下面我们看下如何使用这个东东。

首先,我们必须在程序当中加入代码,以便生成trace文件,有了这个trace文件我们才可以将其转化为图形。

要添加的代码如下:

   1. // start tracing to "/sdcard/yourActivityTrace.trace"   
   2. Debug.startMethodTracing("yourActivityTrace");   
   3. // ...   
   4. // stop tracing   
   5. Debug.stopMethodTracing();  
 
    // start tracing to "/sdcard/yourActivityTrace.trace"
 
    Debug.startMethodTracing("yourActivityTrace");
 
    // ...
 
    // stop tracing
 
    Debug.stopMethodTracing();

Google Dev Guide当中说可以在activity的onCreate()中添加Debug.startMethodTracing(), 而在onDestroy()中添加Debug.stopMethodTracing(),但是我在实际的测试时发现这种方式其实并不好用,因为通常情况下我们的activity的onDestroy()是由系统决定何时调用的,因此我们可能等了很长时间都不会得到这个trace文件。因此我决定在onStop()中来调用Debug.stopMethodTracing()。这样当我们切换到其它activity或者点击home键的时候onStop()就会被调用,我们也就可以得到完整的trace file。

在运行我们的程序之前,我们首先要保证我们的AVD是一个带有SD card的AVD,这样才能使trace文件保存到/sdcard/…当中。Google Dev Guide当中是用安装SD card的方式,实际上在SDK1.5以后我们都可以在创建AVD的时候带上SD card, 具体参考http://developer.android.com/guide/developing/tools/avd.html

好的,一切就绪了,开始运行我们的程序吧,运行后可以任意做一些操作,然后点击home键。这是通过DDMS file explore我们就可以看到/sdcard/目录下有一个trace文件,现在我们把这个文件copy到我们的电脑上指定的目录,假设是C:\tracefile 目录下。

现在我们可以通过命令行来执行traceview,进入tools目录后,执行

traceview C:\tracefile\yourActivityTrace.trace

二 避免内存泄露

Android应用在T-Mobile G1上被限制只能使用16MB的内存。这对于手机来说已经是很大的内存了但对于很多开发者来说却仍然有点少。就算你不想把内存耗尽,你也应该尽可能的节约内存来避免其它应用不足以运行。Android保存在内存里的应用越多,用户切换应用的速度也会越快。作为工作的一部分,在开发Android应用的时候我碰到了很多内存泄漏问题,而绝大部分都出自于一个错误:对Context保持了长期的有效引用。

在Android里,context可以有很多用途,但更多的是加载和访问资源。这也就是为什么很多wedget在它们的构造函数里都会接收一个context参数。一般你可能会碰到两种context:Activity和Application,通常开发者都将前者作为需要传入到类或者方法里的context:

@Override
 
protected void onCreate(Bundle state) {
 
    super.onCreate(state);
 
    TextView label = new TextView(this);
 
    label.setText("Leaks are bad");
 
    setContentView(label);
 
}

上面的代码意味着,label这个view持有了整个activity以及activity所拥有的所有资源(通常是整个View层次以及所有的资源)的引用。这就是说,如果你泄漏(意思是你保持了对某一个对象的长期引用使得垃圾回收器无法将其回收)了context,你就泄漏了很多内存,而假如你不加以注意,泄漏整个activity是一件很容易的事情。

当屏幕方向改变时,系统默认会销毁当前activity并创建一个新的保持了之前状态的activity。为此,Android会从资源文件里重新加载应用的UI。现在想象一下假如你的应用里有一张很大的bitmap,而你并不想在每次屏幕旋转的时候都重新加载这张图片,最简单的方式是使用一个静态字段保持对它的引用。

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);
 
}

这段代码很快但是却犯了大错误。它泄漏了第一次屏幕旋转之前创建的第一个activity。当一个Drawable被绑定到一个view时,这个view就被设定成这个drawable的callback了。这意味着这个drawabel拥有了对这个TextView的引用,而这个TextView又拥有对activity的引用。取决于你的代码,activity又会拥有很多其它资源的引用。

这个例子只是context泄漏最简单的一种情况,你可以在源代码里看看我们是怎么通过在activity被销毁的时候将存储的drawables的callbacks置空来解决这个问题。有意思的是,你可以通过很多手段创建一个context泄漏链,那样会更糟糕。他们能让你很快的把内存耗尽。

这里有两个简单的方法来避免Context相关的内存泄漏。最有效的一种方法是避免将context带出它本身的作用域。上个例子说明了,对隐式引用了外部类的内部类的静态引用方式也同样非常危险。(译者:sBackground是静态引用,sBackground对label的隐式引用)第二个解决方案是使用Application上下文。这个context会存活到你的应用终止时,并且不会依存于Activity的生命周期。如果你打算保持一个需要context的长期存活的对象,记住使用Application上下文。可以通过调用Context.getApplicationContext()或者Activity.getApplication()来获得此对象。

总结,避免context相关的内存泄漏,记住以下事项:

不要保持一个对context-activity的长期有效引用(对一个activity的引用的生命周期和这个activity一致)

使用context-application而不是context-activity

如果你不想控制对象的生命周期请避免使用非静态内部类,取而代之是使用内部静态类并保持一个对acticity的弱引用(weak reference)。方法是让内部静态类保持一个对acticity的WeakReference,就像ViewRoot和它的内部类W所做的那样。

垃圾回收器并不是内存泄漏的保险。

三.有效使用内存

  • 尽量避免创建对象
  • 使用自身方法
  • 使用虚拟优于使用接口
  • 使用静态优于使用虚拟
  • 避免内部使用Setter和Getter
  • 缓冲属性调用
  • 声明Final常量
  • 慎重使用增强型的For循环语句
  • 避免列举类型Avoid Enums
  • 通过内联类使用包空间
  • 尽量避免浮点类型
  • 一些标准操作的时间比较

详细请看编写高效的Android代码

http://www.chinaup.org/docs/toolbox/performance.html

/var/www/dokuwiki/wiki/data/pages/android/mem-leak/android-traceview-avoid-mem-leak.txt · Last modified: 2016/05/05 13:07 by 127.0.0.1