美文网首页
源码学习->14Bitmap

源码学习->14Bitmap

作者: 冉桓彬 | 来源:发表于2018-05-09 09:27 被阅读17次

    F:\Android5\android5.1\frameworks\base\core\jni\android\graphics

    参考文章:

    一、Bitmap像素存储的方式配置:

    格式 解释 内存占用
    ALPHA_8 一个像素占8位 1字节
    RGB_565 一个像素占5+6+5 = 16位 2字节
    ARGB_4444 一个像素占4 * 4 = 16位 2字节
    ARGB_8888 一个像素占8 * 4 = 32 4字节

    二、图片所在文件夹对应的密度:

    图片所在文件夹 密度density
    mdpi 160
    hdpi 240
    xhdpi 320
    xxhdpi 480
    xxxhdpi 640

    三、Bitmap内存占用方面:

    • 不考虑压缩, 加载一张Bitmap, 它占用的内存 = 图片width * 图片height * 一个像素所占的内存 * (nTargetDensity/inDensity)2;
    • 假设使用一张128 * 128的图片, 放在drawable-xhdpi目录下, 默认像素模式采用Bitmap.Config.ARGB_8888;
    public static void bitmapTest() {
        BitmapFactory.Options options = new BitmapFactory.Options();
        Bitmap bitmap = BitmapFactory.decodeResource(Utils.getContext().getResources(), R.drawable.logo, options);
        LogUtils.log("ByteCount:" + bitmap.getByteCount() + ",AllocationByteCount:" + bitmap.getAllocationByteCount());
        LogUtils.log("width:" + bitmap.getWidth() + ",height:" + bitmap.getHeight());
        LogUtils.log("inDensity:" + options.inDensity + ",inTargetDensity:" + options.inTargetDensity);
    }
    

    打印结果如下所示:

    V/AndroidTest: BitmapTest->getByteCount:36864,getAllocationByteCount:36864
    V/AndroidTest: BitmapTest->width:96,height:96
    V/AndroidTest: BitmapTest->inDensity:320,inTargetDensity:240
    

    注意:

    36864 = 128 * 128 * 4 * (240/320) * (240/320);
    36864 = 96 * 96 * 4;
    注意这里的结果, 与前面的内存计算公式, 宽高指的是图片本身的宽高, 图片被加载进Bitmap之后, 
    通过Bitmap获取的width, height是缩放之后的宽高, 并不是图片本身的宽高, 此时要过要计算
    内存, 直接使用bitmap.width * bitmap*height * (一个像素所占内存)即可;
    

    至于为何会出现上面的计算情况, 从源码入手:

    3.1 BitmapFactory.decodeResource:
    public static Bitmap decodeResource(Resources res, int id, Options opts) {
        ...
        bm = decodeResourceStream(res, value, is, null, opts);
        return bm;
    }
    
    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
                InputStream is, Rect pad, Options opts) {
        validate(opts);
        if (opts == null) {
            opts = new Options();
        }
    
        if (opts.inDensity == 0 && value != null) {
            final int density = value.density;
            if (density == TypedValue.DENSITY_DEFAULT) {
                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
            } else if (density != TypedValue.DENSITY_NONE) {
                opts.inDensity = density;
            }
        }
        /**
         * inTargetDensity为当前系统密度, 与设备分辨率有关;
         */    
        if (opts.inTargetDensity == 0 && res != null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }        
        return decodeStream(is, pad, opts);
    }
    
    public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
        Bitmap bm = null;
        if (is instanceof AssetManager.AssetInputStream) {
            final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
            /**
             * 最终通过native将流数据加载进Bitmap中;
             */
            bm = nativeDecodeAsset(asset, outPadding, opts);
        } else {
            bm = decodeStreamInternal(is, outPadding, opts);
        }
        setDensityFromOptions(bm, opts);
        return bm;
    }
    
    3.2 BitmapFactory.nativeDecodeAsset
    static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jlong native_asset,
            jobject padding, jobject options) {
    
        Asset* asset = reinterpret_cast<Asset*>(native_asset);
        SkAutoTDelete<SkStreamRewindable> stream(new AssetStreamAdaptor(asset));
        /**
         * 给Bitmap创建分配堆内存, 并且将数据流读入到Bitmap, 并且对Bitmap的宽高进行初始化;
         */
        return doDecode(env, stream, padding, options);
    }
    
    3.3 BitmapFactory.nativeDecodeAsset:
    static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
    
        int sampleSize = 1;
        ...
        float scale = 1.0f;
        ...
        if (options != NULL) {
            ...
            if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
                const int density = env->GetIntField(options, gOptions_densityFieldID);
                const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
                const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
                if (density != 0 && targetDensity != 0 && density != screenDensity) {
                    /**
                     * 1. 根据density以及targetDensity算出缩放比例;
                     * 2. density对应java层的Options.inDensity, 图片所在文件夹对应的密度;
                     * 3. targetDensity对应java层的Options.inTargetDensity, 系统当前密度;
                     */
                    scale = (float) targetDensity / density;
                }
            }
        }
    
        const bool willScale = scale != 1.0f;
    
        SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
    
        SkBitmap decodingBitmap;
        /**
         * 流数据解码进SkBitmap中;
         */
        if (decoder->decode(stream, &decodingBitmap, prefColorType, decodeMode)
                    != SkImageDecoder::kSuccess) {
            ...
        }
        /**
         * 获取解码后的Bitmap原始宽高;
         */
        int scaledWidth = decodingBitmap.width();
        int scaledHeight = decodingBitmap.height();
    
        if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
            /**
             * 通过这里算出缩放之后的图片的宽高;
             */
            scaledWidth = int(scaledWidth * scale + 0.5f);
            scaledHeight = int(scaledHeight * scale + 0.5f);
        }
        if (options != NULL) {
            jstring mimeType = getMimeTypeString(env, decoder->getFormat());
            /**
             * 算出缩放后的图片宽高之后, 再回传给java层的Bitmap.mWidth, Bitmap.mHeight;
             */
            env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
            env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
            env->SetObjectField(options, gOptions_mimeFieldID, mimeType);
        }
        SkBitmap outputBitmap;
        if (willScale) {
            /**
             * 结合前面描述可知这里的sx, sy实际等于scale;
             */
            const float sx = scaledWidth / float(decodingBitmap.width());
            const float sy = scaledHeight / float(decodingBitmap.height());
    
            SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType());
    
            outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
                    colorType, decodingBitmap.alphaType()));
    
            SkPaint paint;
            paint.setFilterQuality(kLow_SkFilterQuality);
    
            SkCanvas canvas(outputBitmap);
            canvas.scale(sx, sy);
            canvas.drawARGB(0x00, 0x00, 0x00, 0x00);
            canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
        } else {
            outputBitmap.swap(decodingBitmap);
        }
        /**
         * 创建Bitmap对象, 并将Bitmap对象返回给java层;
         */
        return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),
                bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
    }
    
    • 1、结合上面代码可知, 图片在加载进Bitmap时, 会对原始尺寸进行缩放, 缩放系数 scale = targetDensity / density;图片被加载进Bitmap之后, 宽高分别为OriWidth * scale, OriHeight * scale, 因此图片被加载进Bitmap之后, 内存占用为OriWidth * OriHeight * (targetDensity / density)2 * 一个像素占用;
    3.4 关于内存占用:
    • 1、通过模块<3.1> ~ <3.3>的分析可知, 每次加载一张图片, 都需要创建一个Bitmap, Bitmap需要的内存与图片的分辨率有关, 越高清默认占用的内存也就越多;
    • 2、通过源代码分析可知, 减小scale, 可以减少Bitmap占用的内存, scale实际与图片所在drawable文件夹有关, 而且影响也不是太明显, 通过模块<二>可知, 比例范围最大也就是4倍;
    • 3、还可以通过采样率sampleSize的方式对图片进行压缩达到减少Bitmap内存占用;
    • 4、第三个就是Bitmap复用, 关于Bitmap复用, 直接以Glide源码为例进行学习(https://www.jianshu.com/p/eaadeacd3848);

    相关文章

      网友评论

          本文标题:源码学习->14Bitmap

          本文链接:https://www.haomeiwen.com/subject/mllirftx.html