引导
在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的加载流程源码是比较简单的,经常阅读源码可以学习优秀的程序设计思想,好好学习.哈哈哈!多多关注.