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的代码:

     public Bitmap readBitmap(Context context, int resId) {  
            BitmapFactory.Options opts = new BitmapFactory.Options();  
            opts.inPreferredConfig = Config.RGB_565;  
            opts.inPurgeable = true;  
            opts.inInputShareable = true;  
            InputStream is = context.getResources().openRawResource(resId);  
            return BitmapFactory.decodeStream(is, null, opts);  
        } 
    二、在Android4.4系统上使用BitmapFactory.options的注意事项
    前段时间将手机的Android系统升级到4.4之后,发现之前开发的App执行起来非常的卡,严重影响了用户体验。

    后来发现跟Bitmap.decodeByteArray的底层实现有关。

    本文将对问题原因进行总结。希望大家写代码时能留意一下,由于这样的问题一旦遇到,要花非常多时间才干发现原因。


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

    BitmapFactory.Options options = new BitmapFactory.Options();
    DisplayMetrics displayMetrics = context.getResources.getDisplayMetrics();
    ......
    options.inTargetDensity = displayMetrics.densityDpi;
    options.inScaled = true;
    //getBitmapDensity()用于设置图片将要被显示的密度。
    options.inDensity = getBitmapDensity();
    ......
    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()的源代码。


       /**
         * Decode an immutable bitmap from the specified byte array.
         *
         * @param data byte array of compressed image data
         * @param offset offset into imageData for where the decoder should begin
         *               parsing.
         * @param length the number of bytes, beginning at offset, to parse
         * @param opts null-ok; Options that control downsampling and whether the
         *             image should be completely decoded, or just is size returned.
         * @return The decoded bitmap, or null if the image data could not be
         *         decoded, or, if opts is non-null, if opts requested only the
         *         size be returned (in opts.outWidth and opts.outHeight)
         */
        public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {
            if ((offset | length) < 0 || data.length < offset + length) {
                throw new ArrayIndexOutOfBoundsException();
            }
    
            Bitmap bm;
    
            Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
            try {
                bm = nativeDecodeByteArray(data, offset, length, opts);
    
                if (bm == null && opts != null && opts.inBitmap != null) {
                    throw new IllegalArgumentException("Problem decoding into existing bitmap");
                }
                setDensityFromOptions(bm, opts);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
            }
    
            return bm;
        }

    我们发现。该函数会调用本地函数 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的系统上跑跑看。观察内存占用的情况。解决上面的问题办法事实上也非常easy,大家能够想想看。



  • 相关阅读:
    centos6.5 系统乱码解决 i18n --摘自http://blog.csdn.net/yangkai_hudong/article/details/19033393
    openssl pem转cer
    nginx 重装添加http_ssl_module模块
    ios 利用airprint实现无线打印(配合普通打印机)
    centos nginx server_name 配置域名访问规则
    MySQL Innodb数据库性能实践——热点数据性能
    jQuery中的DOM操作
    C++函数学习笔记
    jQuery选择器容易忽视的小知识大问题
    写给自己的话
  • 原文地址:https://www.cnblogs.com/hrhguanli/p/5087769.html
Copyright © 2011-2022 走看看