1.查询数据库没有关闭游标
程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存
的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。
示例代码:
Cursor cursor=getContentResolver().query(uri...);
if(cursor.moveToNext()){
......
}
修正示例代码:
Cursor cursor = null;
try{
cursor=getContentResolver().query(uri...);
if(cursor!=null && cursor.moveToNext()){
......
}
}finally{
if(cursor != null){
try{
cursor.close();
}catch(Exception e){
}
}
}
2.缓存 convertView
以构造ListView的BaseAdapter为例,在BaseAdapter中提高了方法:
public View getView(int position,Viewconvert View, ViewGroup parent)
来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对
象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的listitem的view对象会被回收,然后被用来
构造新出现的最下面的listitem。这个构造过程就是由getView()方法完成的,getView()的第二个形参View convertView就是被缓存起
来的listitem的view对象(初始化时缓存中没有view对象则convertView是null)。
由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时
间,也会使得内存占用越来越大。ListView回收listitem的view对象的过程可以查看:
android.widget.AbsListView.java-->void addScrapView(Viewscrap)方法。
示例代码:
public View getView(int position,Viewconvert View,ViewGroup parent){
View view = new Xxx(...);
......
return view;
}
修正示例代码:
public View getView(int position,Viewconvert View,ViewGroup parent){
View view = null;
if(convertView != null){
view = convertView;
populate(view , getItem(position));
...
}else{
view = new Xxx(...);
...
}
return view;
}
3.Bitmap对象释放内存
有时我们会手工的操作Bitmap对象,如果一个Bitmap对象比较占内存,当它不在被使用的时候,可以调用Bitmap.recycle()方法回
收此对象的像素所占用的内存,但这不是必须的,视情况而定。
4.释放对象的引用
这种情况描述起来比较麻烦,举两个例子进行说明。
示例A:
假设有如下操作
public class DemoActivity extends Activity{
......
private Handler mHandler=...
private Object obj;
public void operation(){
obj = init Obj();
...
[Mark]
mHandler.post(new Runnable(){
public void run(){
use Obj(obj);
}
});
}
}
我们有一个成员变量obj,在operation()中我们希望能够将处理obj实例的操作post到某个线程的MessageQueue中。在以上的代码
中,即便是mHandler所在的线程使用完了obj所引用的对象,但这个对象仍然不会被垃圾回收掉,因为DemoActivity.obj还保有这个对
象的引用。所以如果在DemoActivity中不再使用这个对象了,可以在[Mark]的位置释放对象的引用,而代码可以修改为:
......
public void operation(){
obj = init Obj();
...
final Object o= obj;
obj = null;
mHandler.post(new Runnable(){
public void run(){
useObj(o);
}
}
}
......
示例B:
假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以在LockScreen中定义一
个PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。对于LockScreen对象,当需要显示锁屏界面的时候就会
创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。
但是如果在释放LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen无法被垃圾回收。
如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process
进程挂掉。
总之当一个生命周期较短的对象A,被一个生命周期较长的对象B保有其引用的情况下,在A的生命周期结束时,要在B中清除掉对A
的引用。
5.Context的使用
Android应用程序堆最大为16MB,至少在G1之前是这样,即便没有将这些内存用完的打算,开发者也应尽量减少内存开销以便其他
应用能够在后台运行而不会被强制关闭。这样的话,Android在内存中保存的应用越多,用户在应用间的切换就越快。Android应用
程序的内存泄露问题,大部分时间里,这些问题都是源自同一个错误:对Context(上下文环境)的长时间引用。
在Android上,Context用于多种操作,但最多的还是用来加载和访问资源。这也是为什么所有的Widges在其构造函数中都有一
个Context参数。常规的Android应用中,有两类Context:ActivityContext和ApplicationContext,通常前者被开发者传递给需要
Context的类和方法。
1. @Override
2. Protected void onCreate(Bundle state){
3. super.onCreate(state);
4.
5. TextView label = new TextView(this);
6. label.setText("Leaks are bad");
7.
8. setContentView(label);
9. }
这就意味着那些视图引用了整个Activity及其所拥有的一切:一般是整个视图层和所有资源。因此,如果泄露了这类Context(这里的
泄露指的是引用Context,从而阻止了GC(垃圾回收)操作),就泄露了很多内存空间。如果不小心,泄露整个Activity是非常容易
的事。
在进行屏幕方向改变的时候,系统默认做法是保持状态不变的情况下,销毁当前Activity并重新创建一个新的Activity。这样
做,Android会从资源文件中重新装载当前应用的UI。现在假设你写了一个带有很大一幅位图的应用,但你不想在每次屏幕旋转时都
装载一次位图,最简单的做法就是将其保存在一个静态区域中:
1. Private static Drawables Background;
2.
3. @Override
4. Protected void onCreate(Bundle state){
5. super.onCreate(state);
6.
7. TextView label = new TextView(this);
8. label.setText("Leaks are bad");
9.
10. if(sBackground == null){
11. sBackground=getDrawable(R.drawable.large_bitmap);
12. }
13. label.setBackgroundDrawable(sBackground);
14.
15. setContentView(label);
16. }
这段代码执行的快,同时也很有问题:在进行第一次屏幕方向改变的时候泄露了第一个Activity所占的内存空间。当一个Drawable连
接到一个视图上时,视图被设置为Drawable上的一个回调,在上面的代码片段中,这就意味着Drawable引用了TextView,
而TextView又引用了ActivityContext,ActivityContext又进一步引用了更多的东西(依赖与你的代码)。
上面这段示例是最简单的泄露ActivityContext的情况,你可以到HomeScreen'sSourceCode(查看unbindDrawables()方法)中看看我们
是如何通过在Acitivity销毁时将存储Drawable的回调置为null来解决该问题的。如果再有兴趣的话,某些情况下会产生一个由泄露的
Context形成的链,这很糟糕,会很快使得内存耗尽。
有两种方法可以避免ActivityContext相关的内存泄露:最明显的一种是避免在ActivityContext自身的范围之外对其进行引用。上面这段示
例展示了静态引用的情况,但对内部类和外部类的隐式引用同样都是危险的。第二种解决方法是用ApplicationContext,因为该Context
与应用的生命周期一样长,并不依赖Activity的生命周期。如果想拥有一个生命期足够长的object(对象),但却需要给其一个必须的
Context的话,别忘了ApplicationObject。获取ApplicationContext的方法很简单:执行Context.getApplicationContext()
或Activity.getApplication()。
总之,为了避免ActivityContext相关的内存泄露,记住下面几条:
a) 不要长时间引用一个ActivityContext(引用周期应与Acitivity的生命周期一样长)
b) 尝试使用ApplicationContext代替AcitivityContext
c) 在Activity中,避免使用你无法控制其生命周期的非静态的内部类,使用静态的内部类,并对Activity内部进行弱引用。就是在静态的
内部类中对外部类进行弱引用,就如在ViewRoot及其W内部类中的做法那样
d) 垃圾回收(GC)无法保证内存泄露
e) 使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;
该部分的详细内容也可以参考Android文档中Article部分。
线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。
Public class MyActivity extends Activity{
Public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
newMyThread().start();
}
Private class MyThread extends Thread{
@Override
Public void run(){
super.run();
//dosomthing
}
}
}
这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启
该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才
对,然而事实上并非如此。
由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不
会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。
有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内存泄露
问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法
控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。
这种线程导致的内存泄露问题应该如何解决呢?
第一、将线程的内部类,改为静态内部类。
第二、在线程内部采用弱引用保存Context引用。
解决的模型如下:
Public abstract class WeakAsyncTask<Params,Progress,Result,WeakTarget> extends
AsyncTask<Params,Progress,Result>{
Protected WeakReference<WeakTarget> mTarget;
Public WeakAsyncTask(WeakTarget target){
mTarget = new WeakReference<WeakTarget>(target);
}
@Override
Protected final void onPreExecute(){
Final WeakTarget target=mTarget.get();
if(target != null){
this.onPreExecute(target);
}
}
@Override
Protected final Result doInBackground(Params...params){
Final WeakTarget target=mTarget.get();
if(target!=null){
return this.doInBackground(target,params);
}else{
Return null;
}
}
@Override
Protected final void onPostExecute(Result result){
Final WeakTarget target=mTarget.get();
if(target != null){
this.onPostExecute(target,result);
}
}
Protected void onPreExecute(WeakTarget target){
//Nodefaultaction
}
Protected abstract Result doInBackground(WeakTarget target,Params...params);
Protected void onPostExecute(WeakTarget target ,Result result){
//Nodefaultaction
}
}
7.其他
Android应用程序中最典型的需要注意释放资源的情况是在Activity的生命周期中,在onPause()、onStop()、onDestroy()方法中需要适
当的释放资源的情况。