zoukankan      html  css  js  c++  java
  • Bitmap基本概念及在Android4.4系统上使用BitmapFactory的注意事项

    本文首先总结一下Bitmap的相关概念,然后通过一个实际的问题来分析设置BitmapFactory.options的注意事项,以减少不必要的内存占用率,避免发生OOM。

    一、 Bitmap的使用trick

    尽量不要使用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中配置相应的图片资源, 否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。    

    BitmapFactory.Options.inPreferredConfig 

         * ALPHA_8:数字为8,图形参数应该由一个字节来表示,应该是一种8位的位图 
         * ARGB_4444:4+4+4+4=16,图形的参数应该由两个字节来表示,应该是一种16位的位图. 
         * ARGB_8888:8+8+8+8=32,图形的参数应该由四个字节来表示,应该是一种32位的位图. 
         * RGB_565:5+6+5=16,图形的参数应该由两个字节来表示,应该是一种16位的位图. 
         *  
         * ALPHA_8,ARGB_4444,ARGB_8888都是透明的位图,也就是所字母A代表透明。 
         * ARGB_4444:意味着有四个参数,即A,R,G,B,每一个参数由4bit表示. 
         * ARGB_8888:意味着有四个参数,即A,R,G,B,每一个参数由8bit来表示. 
         * RGB_565:意味着有三个参数,R,G,B,三个参数分别占5bit,6bit,5bit. 
         *  
         *  
         * BitmapFactory.Options.inPurgeable; 
         *  
         * 如果 inPurgeable 设为True的话表示使用BitmapFactory创建的Bitmap 
         * 用于存储Pixel的内存空间在系统内存不足时可以被回收, 
         * 在应用需要再次访问Bitmap的Pixel时(如绘制Bitmap或是调用getPixel), 
         * 系统会再次调用BitmapFactory decoder重新生成Bitmap的Pixel数组。  
         * 为了能够重新解码图像,bitmap要能够访问存储Bitmap的原始数据。 
         *  
         * 在inPurgeable为false时表示创建的Bitmap的Pixel内存空间不能被回收, 
         * 这样BitmapFactory在不停decodeByteArray创建新的Bitmap对象, 
         * 不同设备的内存不同,因此能够同时创建的Bitmap个数可能有所不同, 
         * 200个bitmap足以使大部分的设备重新OutOfMemory错误。 
         * 当isPurgable设为true时,系统中内存不足时, 
         * 可以回收部分Bitmap占据的内存空间,这时一般不会出现OutOfMemory 错误。 
    下面给出一段读取Bitmap的代码:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public Bitmap readBitmap(Context context, int resId) {    
    2.        BitmapFactory.Options opts = new BitmapFactory.Options();    
    3.        opts.inPreferredConfig = Config.RGB_565;    
    4.        opts.inPurgeable = true;    
    5.        opts.inInputShareable = true;    
    6.        InputStream is = context.getResources().openRawResource(resId);    
    7.        return BitmapFactory.decodeStream(is, null, opts);    
    8.    }   

    二、在Android4.4系统上使用BitmapFactory.options的注意事项
    前段时间将手机的Android系统升级到4.4之后,发现之前开发的App运行起来非常的卡,严重影响了用户体验。后来发现跟Bitmap.decodeByteArray的底层实现有关。本文将对问题原因进行总结,希望大家写代码时能留意一下,因为这种问题一旦遇到,要花很多时间才能发现原因。

    我们的代码能根据屏幕的密度对图片进行缩放,因此我们使用最大的图片资源,这样的话对于任何的手机屏幕,都会对图像进行压缩,不会造成视觉上的问题。图片解码前需要对BitmapFactory.Options进行设置,部分代码如下:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. BitmapFactory.Options options = new BitmapFactory.Options();  
    2. DisplayMetrics displayMetrics = context.getResources.getDisplayMetrics();  
    3. ......  
    4. options.inTargetDensity = displayMetrics.densityDpi;  
    5. options.inScaled = true;  
    6. //getBitmapDensity()用于设置图片将要被显示的密度。  
    7. options.inDensity = getBitmapDensity();  
    8. ......  
    9. Bitmap bitmap = getBitmapFromPath(loadPath, options);  

    options.inTargetDensity表示的是目标Bitmap即将被画到屏幕上的像素密度(每英寸有多少个像素)。这个属性往往会和options.inDensity和options.inScaled一起来觉得目标bitmap是否需要进行缩放。若果这个值为0,则BitmapFactory.decodeResource(Resources, int)和BitmapFactory.decodeResource(Resources, int, android.graphics.BitmapFactory.Options)decodeResourceStream(Resources, TypedValue, InputStream, Rect, BitmapFactory.Options) 将inTargetDensity用DisplayMetrics.densityDpi来设置,其它函数则不会对bitmap进行任何缩放。
    options.inDensity表示的是bitmap所使用的像素密度。如果这个值和options.inTargetDensity不一致,则会对图像进行缩放。 如果被设置成0,则 decodeResource(Resources, int), decodeResource(Resources, int, android.graphics.BitmapFactory.Options), 和decodeResourceStream(Resources, TypedValue, InputStream, Rect, BitmapFactory.Options)将用屏幕密度值来设定这个参数,其它函数将不进行缩放。

    图片的缩放倍数是根据inTargetDensity/inDensity来计算得到的。
      我们使用的图片是640 * 1136,编码格式ARGB_8888,则大小为 640*1136*4=291K。手机屏幕密度为480,则options.inTargtetDensity为480;inDensity被设置成160. 安照以上的设置,bitmap的大小将被放大9倍,图片编码后的大小应为640*1136*4=26M。我们的App中总共加载了3张这样的图片,故运行起来非常的卡。
     但为何Android4.4之前的版本没有这样的问题,为此我们分析了Bitmap.decodeByteArray()的源码。

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. /** 
    2.   * Decode an immutable bitmap from the specified byte array. 
    3.   * 
    4.   * @param data byte array of compressed image data 
    5.   * @param offset offset into imageData for where the decoder should begin 
    6.   *               parsing. 
    7.   * @param length the number of bytes, beginning at offset, to parse 
    8.   * @param opts null-ok; Options that control downsampling and whether the 
    9.   *             image should be completely decoded, or just is size returned. 
    10.   * @return The decoded bitmap, or null if the image data could not be 
    11.   *         decoded, or, if opts is non-null, if opts requested only the 
    12.   *         size be returned (in opts.outWidth and opts.outHeight) 
    13.   */  
    14.  public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {  
    15.      if ((offset | length) < 0 || data.length < offset + length) {  
    16.          throw new ArrayIndexOutOfBoundsException();  
    17.      }  
    18.   
    19.      Bitmap bm;  
    20.   
    21.      Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");  
    22.      try {  
    23.          bm = nativeDecodeByteArray(data, offset, length, opts);  
    24.   
    25.          if (bm == null && opts != null && opts.inBitmap != null) {  
    26.              throw new IllegalArgumentException("Problem decoding into existing bitmap");  
    27.          }  
    28.          setDensityFromOptions(bm, opts);  
    29.      } finally {  
    30.          Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);  
    31.      }  
    32.   
    33.      return bm;  
    34.  }  


    我们发现,该函数会调用本地函数 nativeDecodeByteArray(byte[] data, int offset, int length, Options opts)来解析图片。
    android4.4以前的BitmapFactory.cpp中nativeDecodeByteArray调用doDecode函数时不会根据density进行缩放处理(没有查到所有的4.4以前的所有代码,以4.2为例):

    willscale这个参数决定了它是否能被缩放,由于没有传入scale值到doDecode中,scale一直使用默认值1.0f,所以willScale根据默认值计算将始终为false,即bitmap不会被缩放。

    android4.4平台nativeDecodeByteArray对doDecode的调用方式没有改变,但改变了doDecode函数的实现,特别是对willScale的计算方式进行了修改

    其中全局变量gOptions_scaledFieldID为java文件中BitmapFactory.Options的inScale变量在native层的id,


    因为scale = (float) targetDensity / density;所以缩放倍速由inTargetDensity和inDensity两个值确定。

    可知在native层,当scale不等于1.0时会对图片进行缩放,长宽缩放方式如下:



    好了,就写到这儿,大家可以查查以前写的代码,如果有根据屏幕密度加载Bitmap的部分,请将App在4.4的系统上跑跑看,观察内存占用的情况。解决上面的问题办法其实也很简单,大家可以想想看。

  • 相关阅读:
    Atitit 人员成本优化 实习生制度 attilax总结 1.1. 适合领域 于测试 与 轻度运维领域 轻度研发开发领域 1 1.2. 适合领域 行政领域 1 1.3. 要不要适当发放点生活补贴
    Atitit dataindex rootindex cyarindex diaryindex meatindex v8 s99 recently data up dir s
    Atitit 前端与ui开发的技术道术与艺术 attilax著 1. 概述 2 1.1. 适用领域: ui相关领域(包括h5 web ios android安卓 cs桌面程序 游戏程序 等
    Atitit nosql的概念与attilax的理解 目录 1. 常见的nosql 二、Redis,Memcache,MongoDb的特点 1 HBase 1 2. Nosql的核心nosql 1
    Atitit 艾提拉博士带来“深度?广度?高度 人员的职业发展之路 ”的主题分享。 目录 1.1. 技术团队气氛的区别 开发架构模式 2 1.2. 技术人员的职业发展有哪些路线? 3 1.3. 主
    Atitit js通讯技术 jsbridge ajax bomext Atitit jsbridge 与jsrpc 的联系与区别 JSBridge——Web与Native交互 侧重本
    Atitit 微服务的优点和拆分 目录 1. 微服务架构五大优势 崛起势头不可挡 4 1 1.1. 1、复杂度可控 6避免“盲人摸象” 7 2 1.2. 2、灵活可扩展 7 2 1.3. 3、独立部
    Atitit外包优缺点 提升开发效率 外包模式 1.一般来说外包优点 1.1.更加方便快捷 时间成本降低了 1.2.会导致 经济成本高,,时间成本降低了, 2.缺点 2.1.成本高 2.2.
    Atitit 演讲常用肢体语言与手势总结 目录 1. 原则 ,哑语一样,手势不只是补充。。。 1 2. 比拟实际物体与抽象物体 1 2.1. 三个实用的手势: 1 2.2. (五)、演讲中忌讳的动作
    Atitit 前端 dom 的艺术 attilax著 目录 1. 概念 1 2. 发展历程 1 2.1. 厂商各自为政 2 2.2. 1.4 制定标准 标准化 w3cdom 2 2.3. 1.4.
  • 原文地址:https://www.cnblogs.com/dongweiq/p/4724394.html
Copyright © 2011-2022 走看看