Bitmap内存问题

来源:互联网 发布:ubuntu命令行启动软件 编辑:程序博客网 时间:2024/06/14 14:19
Bitmap内存问题

在Android应用里,最耗费内存的就是图片资源。而且在Android系统中,读取位图Bitmap时,分给虚拟机中的图片的堆栈大小只有8M,如果超出了,就会出现OutOfMemory异常。
 OOM 两个原因:(1)图片太大;(2)创建太多的bitmap对象而又没有及时释放。

释放一个bitmap对象标准方法
    if(!bmp.isRecycle() ){
              bmp.recycle()   //回收图片所占的内存
                 system.gc()  //提醒系统及时回收
      }
  
bmp=null;
解决方法:
《一》图片预先缩放
这种方法只是对图片做了一个缩放处理,降低了图片的分辨率,在需要保证图片质量的应用中不可取。
尽量不使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图, 
因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存. 
    因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source,decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间. 
    如果在读取时加上图片的Config参数,可以跟有效减少加载的内存,从而跟有效阻止抛out of Memory异常.另外,decodeStream直接拿的图片来读取字节码了,不会根据机器的各种分辨率来自动适应,使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源,否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了. 

       
  1.  //使用BitmapFactory.Options的inSampleSize参数来缩放  
  2.     public static Drawable resizeImage2(String path,  
  3.             int width,int height)   
  4.     {  
  5.         BitmapFactory.Options options = new BitmapFactory.Options();  
  6.         options.inJustDecodeBounds = true;//不加载bitmap到内存中  
  7.         BitmapFactory.decodeFile(path,options);   
  8.         int outWidth = options.outWidth;  
  9.         int outHeight = options.outHeight;  
  10.         options.inDither = false;  
  11.         options.inPreferredConfig = Bitmap.Config.ARGB_8888;  
  12.         options.inSampleSize = 1;  
  13.           
  14.         if (outWidth != 0 && outHeight != 0 && width != 0 && height != 0)   
  15.         {  
  16.             int sampleSize=(outWidth/width+outHeight/height)/2;  
  17.             Log.d(tag, "sampleSize = " + sampleSize);  
  18.             options.inSampleSize = sampleSize;  
  19.         }  
  20.       
  21.         options.inJustDecodeBounds = false;  
  22.         return new BitmapDrawable(BitmapFactory.decodeFile(path, options));       
  23.     }  
  

《二》普通的图片缩放方法
这种方法代码相对简单,即先把源图加载到内存再处理,适合于源图已经在内存的情况。当从网络上直接读取图片的时候可以用这个方法缩放到指定的大小再显示。
  1. //使用Bitmap加Matrix来缩放  
  2.     public static Drawable resizeImage(Bitmap bitmap, int w, int h)   
  3.     {    
  4.         Bitmap BitmapOrg = bitmap;    
  5.         int width = BitmapOrg.getWidth();    
  6.         int height = BitmapOrg.getHeight();    
  7.         int newWidth = w;    
  8.         int newHeight = h;    
  9.   
  10.         float scaleWidth = ((float) newWidth) / width;    
  11.         float scaleHeight = ((float) newHeight) / height;    
  12.   
  13.         Matrix matrix = new Matrix();    
  14.         matrix.postScale(scaleWidth, scaleHeight);    
  15.         // if you want to rotate the Bitmap     
  16.         // matrix.postRotate(45);     
  17.         Bitmap resizedBitmap = Bitmap.createBitmap(BitmapOrg, 00, width,    
  18.                         height, matrix, true);    
  19.         return new BitmapDrawable(resizedBitmap);    
  20.     }  

《三》Dalvik虚拟机的堆内存分配
堆(HEAP)是VM中占用内存最多的部分,通常是动态分配的。堆的大小不是一成不变的,当堆内存实际的利用率偏离设定的值的时候,虚拟机会在GC的时候调整堆内存大小,让实际占用率向个百分比靠拢。比如初始的HEAP是4M大小,当4M的空间被占用超过75%的时候,重新分配堆为8M大;当8M被占用超过75%,分配堆为16M大。倒过来,当16M的堆利用不足30%的时候,缩减它的大小为8M大。重新设置堆的大小,尤其是压缩,一般会涉及到内存的拷贝,所以变更堆的大小对效率有不良影响。
  1. int newSize = 4 * 1024 * 1024 ; //设置最小堆内存大小为4MB  
  2. VMRuntime.getRuntime().setMinimumHeapSize(newSize);  
  3. VMRuntime.getRuntime().setTargetHeapUtilization(0.75); // 设置堆内存的利用率为75%  

《四》Bitmap对象及时释放
Bitmap类的构造方法都是私有的,所以开发者不能直接new出一个Bitmap对象,只能通过BitmapFactory类的各种静态方法来实例化一个Bitmap。仔细查看BitmapFactory的源代码可以看到,生成Bitmap对象最终都是通过JNI调用方式实现的。所以,加载Bitmap到内存里以后,是包含两部分内存区域的。简单的说,一部分是Java部分的,一部分是C部分的。这个Bitmap对象是由Java部分分配的,不用的时候系统就会自动回收了,但是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。所以需要调用recycle()方法来释放C部分的内存。从Bitmap类的源代码也可以看到,recycle()方法里也的确是调用了JNI方法了的。

《五》创建图片缓冲
有时候,可能需要在一个Activity里多次用到同一张图片。比如一个Activity会展示一些用户的头像列表,而如果用户没有设置头像的话,则会显示一个默认头像,而这个头像是位于应用程序本身的资源文件中的。
如果有类似上面的场景,就可以对同一Bitmap进行缓存。如果不进行缓存,尽管看到的是同一张图片文件,但是使用BitmapFactory类的方法来实例化出来的Bitmap,是不同的Bitmap对象。缓存可以避免新建多个Bitmap对象,避免内存的浪费。

《六》优化Adapter
ListView,GridView,Gallery等可以使用缓存的View,我们可以只保留屏幕可见区域需要显示的Bitmap,而释放其他不需要显示的。
当ListView列表里的每一项显示时,都会调用Adapter的getView方法返回一个View,
来向ListView提供所需要的View对象。
下面是一个完整的getView()方法的代码示例。
public View getView(int position, View convertView, ViewGroup parent) {
  ViewHolder holder;
if (convertView == null) {
      convertView = mInflater.inflate(R.layout.list_item, null);
      holder = new ViewHolder();
      holder.text = (TextView) convertView.findViewById(R.id.text);
      convertView.setTag(holder);
  } else {
      holder = (ViewHolder) convertView.getTag();
  }
  holder.text.setText("line" + position);
  return convertView;
}
private class ViewHolder {
  TextView text;
}
当向上滚动ListView时,getView()方法会被反复调用。getView()的第二个参数convertView是被缓存起来的List条目中
的View对象。当ListView滑动的时候,getView可能会直接返回旧的convertView。这里使用了convertView和ViewHolder,可以充分利用缓存,避免反复创建View对象和TextView对象。如果ListView的条目只有几个,这种技巧并不能带来多少性能的提升。但是如果条目有几百甚至几千个,使用这种技巧只会创建几个convertView和ViewHolder(取决于当前界面能够显示的条目数),性能的差别就非常非常大了。

《七》使用软引用




0 0