安卓开发中防止内存溢出浅析

安卓的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M。但是安卓采用的是Java语言编写,所以在很大程度上,安卓的内存机制等同于Java的内存机制,在刚开始开发的时候,内存的限制问题会给我们带来内存溢出等严重问题。在我们不使用一些内存的时候,我们要尽量在Android或者其他平台上避免在运行其他程序时,保存必要的状态,使得一些死进程所带来的内存问题,应该尽量在关闭程序或者保存状态的时候释放掉,这样能提高系统在运行方面的流畅性。

  安卓的内存主要表现在:

  1. 在Android平台上,长期保持一些资源的引用,造成一些内存不能释放,带来的内存泄露问题很多。比如:Context(下文中提到的Activity都是Context),在一些你需要保持你的首个类对象状态,并且把状态传入其他类对象中时,这样消除掉首个类对象之前,你必须先把接收类对象释放掉。需要注意一点的是:因为在Java或者Android内存机制中,顶点的结点释放前必须保证其他对象没有调用才能被系统GC回收释放。我们来看一段代码:

  @Override

  protected void onCreate(Bundle state) {

  super.onCreate(state);

  TextViewlabel = new TextView(this);

  label.setText("Leaksare bad");

  setContentView(label);

  }

  这个代码的意思就是我们把一个TextView的实例加载到了我们正在运行的Activity(Context)当中,因此,通过GC回收机制,我们知道,要释放Context,就必须先释放掉引用他的一些对象。如果没有,那在要释放Context的时候,你会发现会有大量的内存溢出。所以在你不小心的情况下内存溢出是一件非常容易的事情。 保存一些对象时,同时也会造成内存泄露。最简单的比如说位图(Bitmap),比如说:在屏幕旋转时,会破坏当前保持的一个Activity状态,并且重新申请生成新的Activity,直到新的Activity状态被保存。我们再看一段代码:

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

  }

  这个代码是非常快的同时也是错误的。它的内存泄露很容易出在屏幕转移的方向上。虽然我们会发现没有显示的保存Context这个实例,但是当我们把绘制的图连接到一个视图的时候,Drawable就会将被View设置为回调,这就说明,在上述的代码中,其实在绘制TextView到活动中的时候,我们已经引用到了这个Activity。链接情况可以表现为:Drawable->TextView->Context。

  所以在想要释放Context的时候,其实还是保存在内存中,并没有得到释放。

  如何避免这种情况:主要在于。线程最容易出错。大家不要小看线程,在Android里面线程最容易造成内存泄露。线程产生内存泄露的主要原因在于线程生命周期的不可控。下面有一段代码:

  publicclass MyTest extends Activity {

  @Override

  publicvoid onCreate(BundlesavedInstanceState) {

  super.onCreate(savedInstanceState);

  setContentView(R.layout.main);

  new MyThread().start();

  }

  privateclass MyThread extends Thread{

  @Override

  public void run() {

  super.run();

  //do somthing

  }

  }

  }

  代码很简单,但是在Android上又来新问题了,当我们在切换视图屏幕的时候(横竖屏),就会重新建立横屏或者竖屏的Activity。我们形象的认为之前建立的Activity会被回收,但是事实如何呢?Java机制不会给你同样的感受,在我们释放Activity之前,因为run函数没有结束,这样MyThread并没有销毁,因此引用它的Activity(Mytest)也有没有被销毁,因此也带来的内存泄露问题。

  有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。

  线程问题的改进方式主要有:

  l 将线程的内部类,改为静态内部类。

  l 在程序中尽量采用弱引用保存Context。

  2. 万恶的bitmap。。。

  Bitmap是一个很万恶的对象,对于一个内存对象,如果该对象所占内存过大,在超出了系统的内存限制时候,内存泄露问题就很明显了。。

  解决bitmap主要是要解决在内存尽量不保存它或者使得采样率变小。在很多场合下,因为我们的图片像素很高,而对于手机屏幕尺寸来说我们并不用那么高像素比例的图片来加载时,我们就可以先把图片的采样率降低在做原来的UI操作。

  如果在我们不需要保存bitmap对象的引用时候,我们还可以用软引用来做替换。具体的实例代码google上面也有很多。

  综上所述,要避免内存泄露,主要要遵循以下几点:

  第一:不要为Context长期保存引用(要引用Context就要使得引用对象和它本身的生命周期保持一致)。

  第二:如果要使用到Context,尽量使用ApplicationContext去代替Context,因为ApplicationContext的生命周期较长,引用情况下不会造成内存泄露问题

  第三:在你不控制对象的生命周期的情况下避免在你的Activity中使用static变量。尽量使用WeakReference去代替一个static。

  第四:垃圾回收器并不保证能准确回收内存,这样在使用自己需要的内容时,主要生命周期和及时释放掉不需要的对象。尽量在Activity的生命周期结束时,在onDestroy中把我们做引用的其他对象做释放,比如:cursor.close()。

  其实我们可以在很多方面使用更少的代码去完成程序。比如:我们可以多的使用9patch图片等。有很多细节地方都可以值得我们去发现、挖掘更多的内存问题。我们要是能做到C/C++对于程序的“谁创建,谁释放”原则,那我们对于内存的把握,并不比Java或Android本身的GC机制差,而且更好的控制内存,能使我们的手机运行得更流畅。

 

下面列举出我所遇到的问题及解决方法(注:所有测试都是基于android 4.0.4系统)。

1).百度地图刷新得不到及时响应,出现灰色区域

   解决方法:在manifest文件中的application标签下添加 android:hardwareAccelerated="false",但添加之后还是不行。最后在 非主activity(主activity:启动软件显示的第一个activity)中添加 android:hardwareAccelerated="true",启用硬件加速,然后地图响应就很流畅了。

2).用腾讯微博开放平台发表带图片的微博时抛NetWorkOnMainThreadException

  解决方法:在onCreate方法中添加如下代码就OK了:

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
				.detectDiskReads().detectDiskWrites().detectNetwork()
				.penaltyLog().build());

在2.3以前的系统中不会出现此问题。或者通过异步方式进行此操作,因为它说的是**OnMainThread**,那么不在主线程中进行这种耗时的网络操作,就不会有问题了。

3).Context的使用问题

  大家都知道,context容易引发内存溢出,而且很隐蔽,所以大多数情况都尽量使用Application context,因为这种context和Application有相同的生命周期,不会引起内存泄露。但当我要显示一个AlertDialog时,使用Application context就会抛出异常:android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application,必须使用Activity context。查看google提供的文档,Activity->ContextThemeWrapper->ContextWrapper->Context;Application->ContextWrapper->Context,可以看出Context是Activity和Application的父类,通过Activity和Application都可以获得Context对象。但问题就是,显示dialog相当于显示一个自定义窗口,Application context是无法获得Window对象的,而Activity可以通过getWindow()方法获得Window对象。为了防止context引发的内存溢出,请记住以下3点:

  1. 不要让生命周期长的对象引用activity context,即保证引用activity的对象要与activity本身生命周期是一样的
  2. 对于生命周期长的对象,可以使用application context
  3. 避免非静态的内部类,尽量使用静态内部类,避免生命周期问题,注意内部类对外部对象引用导致的生命周期变化

4).Bitmap内存溢出问题

  这是我遇到的最头疼的一个问题,在网上也查了很多资料,大多数都一样。如弱引用(WeakReference),软引用(SoftReference)等,这些在一定程度上的确可以优化,但并不能彻底解决问题,图片很多的时候,还是会出现内存溢出。最主要的还是要及时回收bitmap,这个及时,,如何及时?在网上看到一些方法:

      originalImage = BitmapFactory.decodeFile(path, options);
        width = originalImage.getWidth();
        height = originalImage.getHeight();
        Bitmap bitmapWithReflection = Bitmap.createBitmap(dw,
                dh, Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmapWithReflection);
        canvas.drawBitmap(originalImage, 0, 0, null);
      ImageView imageView = new ImageView(mContext);
      imageView.setImageBitmap(originalImage);
      imageView.setLayoutParams(new GalleryFlow.LayoutParams(width, height));
      mImages[index++]=imageView;
        bitmapWithReflection.recycle();
        bitmapWithReflection = null;
        System.gc();

这样如果马上回收originalImage,图片就无法显示;显示后再回收由于每初始化一张图片,originalImage对象就会指向另一块内存,而指向原来的bitmap内存的对象则被覆盖了,当我们要回收该bitmap时,却发现找不到指向该内存的对象,自然也就无法回收了。

        我的解决方法是:延迟加载图片,每初始化一张图片,就把bitmap对象存储在一个Bitmap数组中,ImageView再从该数组中取得对应的bitmap。需要回收的时候,可以通过数组中bitmap对象来释放所占用的内存。不管有多少图片,都可以保证在内存中的图片数量不超过M张(M可由自己控制,如8张。通过option参数可以控制加载到内存的图片的大小,一般800*600*4=1920000byte=1.8M,8张一般不会超出android单个程序内存限制)。部分代码如下:

public View getView(int position, View convertView, ViewGroup parent) {
		if(convertView==null){
			if(mImages[position]==null && bitmaps[position]==null){
				int high = position+mem_size;
				/**
				 * 当图片的索引位置大于或等于mem_size时,要考虑回收两头的bitmap内存;
				 * 否则只需考虑回收后面的内存。
				 */
				if(position>=mem_size){
					int low=position-mem_size;
					if(bitmaps[low]!=null){
						this.destory(low);
					}
					if(high<size && bitmaps[high]!=null){
						this.destory(high);
					}
				}else{
					if(high<size && bitmaps[high]!=null){
						this.destory(high);
					}
				}
				System.gc();
				try {
					ImageView imageView = new ImageView(mContext);
					bitmaps[position]=this.createView(paths.get(position));
					imageView.setImageBitmap(bitmaps[position]);
					imageView.setLayoutParams(new GalleryFlow.LayoutParams(width, height));
					mImages[position]=imageView;
				} catch (Exception e) {
					e.printStackTrace();
				}finally{
					convertView=mImages[position];
				}
			}else{
				convertView=mImages[position];
			}			
		}
		return convertView;
	}
	
	 private void destory(int index){
	        bitmaps[index].recycle();
	        bitmaps[index]=null;
	        mImages[index]=null;
	 }


 

算是解决了bitmap内存溢出问题,但是性能上还有待优化

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值