防范不良代码

2015/04/29 00:44
阅读数 15

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


以构造ListViewBaseAdapter为例,在BaseAdapter中提高了方法:

public View getView(int position,Viewconvert View, ViewGroup parent)

来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view

象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的listitemview对象会被回收,然后被用来

构造新出现的最下面的listitem。这个构造过程就是由getView()方法完成的,getView()的第二个形参View convertView就是被缓存起

来的listitemview对象(初始化时缓存中没有view对象则convertViewnull)


    由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时

间,也会使得内存占用越来越大。ListView回收listitemview对象的过程可以查看:


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



3Bitmap对象释放内存


    有时我们会手工的操作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

的引用。



5Context的使用


Android应用程序堆最大为16MB,至少在G1之前是这样即便没有将这些内存用完的打算,开发者也应尽量减少内存开销以便其他

应用能够在后台运行而不会被强制关闭。这样的话,Android在内存中保存的应用越多,用户在应用间的切换就越快。Android应用

程序的内存泄露问题,大部分时间里,这些问题都是源自同一个错误:对Context(上下文环境)的长时间引用。


Android上,Context用于多种操作,但最多的还是用来加载和访问资源。这也是为什么所有的Widges在其构造函数中都有一

Context参数。常规的Android应用中,有两类ContextActivityContextApplicationContext,通常前者被开发者传递给需要

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又引用了ActivityContextActivityContext又进一步引用了更多的东西(依赖与你的代码)。


上面这段示例是最简单的泄露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部分。

 


6线程

线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

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

}

}

}

这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThreadrun函数是一个很费时的操作,当我们开启

该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才

对,然而事实上并非如此。

由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThreadrun函数没有结束时,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()方法中需要适

当的释放资源的情况。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部