Android系统源码分析-bitmap的加载

引导

在Android的开发中,有图片是非常常见的了,但是对于图片的加载 处理遇到问题也是经常出现的,对于开发者而言,加载图片的时候产生OOM,应该都有出现过吧.本文,通过阅读bitmap源码的方式,熟悉Android中图片的加载工作流程,以便能从工作流程上去解决一些bitmap加载的问题.

1.Bitmap.java阅读

bitmap是Android里面的图片对象类,Android开发中,接触bitmap是必不可少的.java的代码阅读的话,首先肯定是构造函数,那么看一下bitmap的构造函数:

Bitmap(String name, int width, int height, Bitmap.NativeWrapper nativeData) {
    mName = name;
    mWidth = width;
    mHeight = height;
    mNativeWrapper = nativeData;
}

可以看到,bitmap的构造函数是默认的访问权限,即外部不可以访问.那么我们要怎么样去得到一个bitmap的对象呢?

常规来说,java类的对象获得一般步骤如下:

a) new一个对象,及调用构造方法
b) getInstance()类似的静态方法,方法内部调用了构造函数
c) 工厂类调用工厂方法

但是,在bitmap类中,并没有静态的获得对象的方法,所以我们只能把希望寄托在工厂类了.

2.BitmapFactory.java阅读

BitmapFactory中,有提供一些静态的方法去获得一个bitmap对象,主要方法如下:
主要有五对方法:
a) 从文件加载

public static Bitmap decodeFile(String pathName){}
public static Bitmap decodeFile(String pathName, Options opts){}

b) 从资源文件加载

public static Bitmap decodeResource(Resources res, int id){}
public static Bitmap decodeResource(Resources res, int id, Options opts) {}

c) 从二进制数组加载

public static Bitmap decodeByteArray(byte[] data, int offset, int length){}
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) {}

d) 从流加载

public static Bitmap decodeStream(InputStream is) {}
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {}

f) 从文件描述符加载

public static Bitmap decodeFileDescriptor(FileDescriptor fd) {}
public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) {}

上述五对方法,都会有相对应一组中参数少的一个调用参数多的一个方法,最后转到调用几个native的方法:

a) private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,Rect padding, Options opts);
b) private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,Rect padding, Options opts);
c) private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);
d) private static native Bitmap nativeDecodeByteArray(byte[] data, int offset,int length, Options opts);    

由于已经是在调用本地方法了,但是却没有加载so库的代码,那就只能找找同名文件了, BitmapFactory.cpp

3.BitmapFactory.cpp阅读

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

进入到 BitmapFactory.cpp后,代码并不多,很容易就看到了BitmapFactory.java中的native方法,接下来一一解读

a) nativeDecodeStream方法

static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
        jobject padding, jobject options) {
    //创建一个bitmap对象
    jobject bitmap = NULL;
    //创建一个输入流适配器,(SkAutoTUnref)自解引用
    SkAutoTUnref<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));

    if (stream.get()) {
        SkAutoTUnref<SkStreamRewindable> bufferedStream(
                SkFrontBufferedStream::Create(stream, BYTES_TO_BUFFER));
        SkASSERT(bufferedStream.get() != NULL);
        //图片解码
        bitmap = doDecode(env, bufferedStream, padding, options);
    }
    //返回图片对象,加载失败的时候返回空
    return bitmap;
}

b) nativeDecodeFileDescriptor

static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor,
            jobject padding, jobject bitmapFactoryOptions) {

        ...

        return doDecode(env, stream, padding, bitmapFactoryOptions);
}

其余几个方法也一样,最终都调用了doDecode()方法,此处对doDecode()详细说明

4.doDecode方法阅读

/**
    * JNIEnv* env jni指针
    *   SkStreamRewindable* stream 流对象
    * jobject padding 边距对象
    * jobject options 图片选项参数对象
    */
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
//缩放值,默认不缩放
    int sampleSize = 1;

    //图片解码模式,像素点模式
    SkImageDecoder::Mode decodeMode = SkImageDecoder::kDecodePixels_Mode;

    ...//省略参数初始化

    //javabitmap对象
    jobject javaBitmap = NULL;

    //对于options的参数选项初始化
    if (options != NULL) {
        //获得参数中是否需要缩放
        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
        if (optionsJustBounds(env, options)) {
            //确定现在的图片解码模式
            //在java中可以设置inJustDecodeBounds参数
            //public boolean inJustDecodeBounds;true的时候,只会去加载bitmap的大小
            decodeMode = SkImageDecoder::kDecodeBounds_Mode;
        }


        //图片的配置相关参数
        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);

        //判断是否需要缩放
        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) {
                //计算出缩放比列
                scale = (float) targetDensity / density;
            }
        }
    }

    //通过缩放比例判断是否需要缩放
    const bool willScale = scale != 1.0f;

    //解码器创建
    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
    if (decoder == NULL) {
        return nullObjectReturn("SkImageDecoder::Factory returned null");
    }
    //解码器参数设置
    decoder->setSampleSize(sampleSize);
    decoder->setDitherImage(doDither);
    decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
    decoder->setRequireUnpremultipliedColors(requireUnpremultiplied);

    //加载像素的分配器
    JavaPixelAllocator javaAllocator(env);

    if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) {
        //用于取消的时候使用
        return nullObjectReturn("gOptions_mCancelID");
    }

    //解码
    SkBitmap decodingBitmap;
    if (decoder->decode(stream, &decodingBitmap, prefColorType, decodeMode)
                != SkImageDecoder::kSuccess) {
        return nullObjectReturn("decoder->decode returned false");
    }

    //缩放后的大小,decodingBitmap.width()是默认是图片大小
    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) {
          //更新选项参数
    }

    // justBounds mode模式下,直接返回,不继续加载
    if (decodeMode == SkImageDecoder::kDecodeBounds_Mode) {
        return NULL;
    }

    //点九图片相关
    jbyteArray ninePatchChunk = NULL;
    if (peeker.mPatch != NULL) {
        env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
    }

    jobject ninePatchInsets = NULL;
    if (peeker.mHasInsets) {
        if (javaBitmap != NULL) {
            env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets);
        }
    }

    //缩放操作
    if (willScale) {
        canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
    } else {
        outputBitmap->swap(decodingBitmap);
        //swap交换,底层实现是交换对象 指针,并不是深拷贝
    }

    //边距处理
    if (padding) {
        if (peeker.mPatch != NULL) {
            GraphicsJNI::set_jrect(env, padding,
                    peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
                    peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
        } else {
            GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
        }
    }

    //如果已经可以 了 就直接返回
    if (javaBitmap != NULL) {
        bool isPremultiplied = !requireUnpremultiplied;
        GraphicsJNI::reinitBitmap(env, javaBitmap, outputBitmap, isPremultiplied);
        outputBitmap->notifyPixelsChanged();
        // If a java bitmap was passed in for reuse, pass it back
        return javaBitmap;
    }

    //创建bitmap对象返回
    return GraphicsJNI::createBitmap(env, outputBitmap, javaAllocator.getStorageObj(),
            bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
}

5.GraphicsJNI.h阅读

路径:F:\Android5\android5.1\frameworks\base\core\jni\android\graphics
//返回bitmap对象

static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags,
        jbyteArray ninePatch, int density = -1) {
    return createBitmap(env, bitmap, NULL, bitmapCreateFlags, ninePatch, NULL, density);
}

并没有找到有GraphicsJNI.cpp,按照套路的话找一下Graphics.cpp,查看Graphics.cpp是否是实现GraphicsJNI.h的.

6.Graphics.cpp阅读

路径:F:\Android5\android5.1\frameworks\base\core\jni\android\graphics
//发现这个方法异常的简单呢

jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer,
            int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets, int density)
    {
        SkASSERT(bitmap);
        //像素参考值
        SkASSERT(bitmap->pixelRef());
        //异常检测
        SkASSERT(!env->ExceptionCheck());
        bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
        bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;

        // The caller needs to have already set the alpha type properly, so the
        // native SkBitmap stays in sync with the Java Bitmap.
        assert_premultiplied(*bitmap, isPremultiplied);
            //调用java方法创建对象
        jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
                reinterpret_cast<jlong>(bitmap), buffer,
                bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
                ninePatchChunk, ninePatchInsets);
        hasException(env); // For the side effect of logging.
        //返回对象,至此完成了一个bitmap对象
        return obj;
}

7.释放内存

从上面可以看到,其实图片的解码是由C++代码实现的,而对于C++和C的话,有经验的程序员都会特别的注重内存的管理,毕竟C/C++不像java一样,有gc机制,栈区的内存都需要程序员手动管理(对象生命周期中,析构函数管理的除外),因此,对于bitmap的使用,在不需要的时候手动释放是一个很好的习惯.

 public void recycle() {
    //bitmap的释放方法
    if (!mRecycled && mNativePtr != 0) {
        //因为图片是从cpp加载的,所以释放的时候也要交给cpp去处理
        if (nativeRecycle(mNativePtr)) {
            mBuffer = null;
            mNinePatchChunk = null;
        }
        mRecycled = true;
    }
}

//对应的回收代码Bitmap.cpp中
static jboolean Bitmap_recycle(JNIEnv* env, jobject, jlong bitmapHandle) {

SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
#ifdef USE_OPENGL_RENDERER //条件编译,是否用了opengl渲染,单独释放opengl中的数据
if (android::uirenderer::ResourceCache::hasInstance()) {
    bool result;
    result = android::uirenderer::ResourceCache::getInstance().recycle(bitmap);
    return result ? JNI_TRUE : JNI_FALSE;
}
#endif // USE_OPENGL_RENDERER
//回收数据
bitmap->setPixels(NULL, NULL);
return JNI_TRUE;

}

结语

相对来说bitmap的加载流程源码是比较简单的,经常阅读源码可以学习优秀的程序设计思想,好好学习.哈哈哈!多多关注.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章