资源描述
·
Title:Bitmap解码使用范例-ImageView
· Date:2011.11.23
· Author:Tianyang Lu
· Content:
· 知道了Bitmap的工作原理,那实际是怎样使用的呢?一个图片资源是怎样显示在屏幕上的呢?
拿ImageView做例子,看看ImageView.java,这里有四种装载资源的方法,先看前两种:
/**
* Sets a drawable as the content of this ImageView.
*
* This does Bitmap reading and decoding on the UI
* thread, which can cause a latency hiccup. If that's a concern,
* consider using {@link #setImageDrawable} or
* {@link #setImageBitmap} and
* {@link android.graphics.BitmapFactory} instead.
*
* @param resId the resource identifier of the the drawable
*
* @attr ref android.R.styleable#ImageView_src
*/
@android.view.RemotableViewMethod
public void setImageResource(int resId) {
if (mUri != null || mResource != resId) {
updateDrawable(null);
mResource = resId;
mUri = null;
resolveUri(); //意思是处理Uri,就在这个方法里载入资源的
requestLayout();
invalidate();
}
}
/**
* Sets the content of this ImageView to the specified Uri.
*
* This does Bitmap reading and decoding on the UI
* thread, which can cause a latency hiccup. If that's a concern,
* consider using {@link #setImageDrawable} or
* {@link #setImageBitmap} and
* {@link android.graphics.BitmapFactory} instead.
*
* @param uri The Uri of an image
*/
@android.view.RemotableViewMethod
public void setImageURI(Uri uri) {
if (mResource != 0 ||
(mUri != uri &&
(uri == null || mUri == null || !uri.equals(mUri)))) {
updateDrawable(null);
mResource = 0;
mUri = uri;
resolveUri();
requestLayout();
invalidate();
}
}
这两个方法其实是差不多的,都是调用resolveUri()方法载入的,这个方法明显可以
通过int resID和Uri两个任意一个来载入资源,跳到这个方法看看:
private void resolveUri() {
if (mDrawable != null) {
return;
}
Resources rsrc = getResources();
if (rsrc == null) {
return;
}
Drawable d = null;
if (mResource != 0) { //如果int mResouce 资源ID不为0
try {
d = rsrc.getDrawable(mResource); //那么该ID的Drawable就赋给Drawable d
} catch (Exception e) {
Log.w("ImageView", "Unable to find resource: " + mResource, e);
// Don't try again.
mUri = null;
}
} else if (mUri != null) //否则,当mUri不为null
String scheme = mUri.getScheme();
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) { //如果是资源类型的,SCHEME_ANDROID_RESOURCE = "android.resource"
try {
// Load drawable through Resources, to get the source density information
ContentResolver.OpenResourceIdResult r =
mContext.getContentResolver().getResourceId(mUri);
d = r.r.getDrawable(r.id); //用读Resouce的方法载入
} catch (Exception e) {
Log.w("ImageView", "Unable to open content: " + mUri, e);
}
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme) //如果是Content或文件类型的,SCHEME_CONTENT = "content"
|| ContentResolver.SCHEME_FILE.equals(scheme)) { //SCHEME_FILE = "file"
try {
d = Drawable.createFromStream( //以流的方式创建一个Drawable
mContext.getContentResolver().openInputStream(mUri),
null);
} catch (Exception e) {
Log.w("ImageView", "Unable to open content: " + mUri, e);
}
} else { //其他类型
d = Drawable.createFromPath(mUri.toString());
}
if (d == null) {
System.out.println("resolveUri failed on bad bitmap uri: "
+ mUri);
// Don't try again.
mUri = null;
}
} else {
return;
}
updateDrawable(d);
}
我们先只看和图片相关的,用到了Drawable.createFromStream和Drawable.createFromPath这两个方法
而这两个方法,前者调用了BitmapFactory.decodeResourceStream(),后者调用了BitmapFactory.decodeFile()
创建了Bitmap对象实例,最后都调用了Drawable.drawableFromBitmap()返回了一个BitmapDrawable实例,见下:
private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np,
Rect pad, String srcName) {
if (np != null) {
return new NinePatchDrawable(res, bm, np, pad, srcName);
}
return new BitmapDrawable(res, bm); //返回了一个BitmapDrawable
}
}
其实到这里就知道了ImageView载入图片时实际是怎么使用解码器创建Bitmap对象的了,然后接下来ImageView
的onDraw方法调用Drawable.draw(Canvas canvas)的抽象方法(Drawable的子类必须实现draw方法)将成员变量
mDrawable画出来。
· Title:Bitmap解码过程
· Date:2011.11.22
· Author:Tianyang Lu
· Content:
· 我们先来看BitmapFactory.java,主要的解码方法decodeStream()和decodeByteArray()、decodeFileDescriptor(),
· 其他的都是调用这两个方法实现的。而这三个方法的实现分别基于这三个native方法,nativeDecodeStream()和
· nativeDecodeByteArray()、nativeDecodeFileDescriptor(),我们在BitmapFactory.cpp里找到这三个方法,
· 发现三个方法其实都是调用doDecode()实现的。static jobject doDecode(JNIEnv* env, SkStream* stream, jobject
padding, jobject options, bool allowPurgeable, bool forcePurgeable = false) 在这里有几句关键代码
SkImageDecoder* decoder = SkImageDecoder::Factory(stream); //创建解码器
...
if (!decoder->decode(stream, bitmap, prefConfig, decodeMode)) { //解码
return nullObjectReturn("decoder->decode returned false");
}
...
return GraphicsJNI::createBitmap(env, bitmap, false, ninePatchChunk); //返回java Bitmap对象实例,在上一节分析过
那我们再去看看创建SkImageDecoder对象的方法SkImageDecoder::Factory,在SkImageDecoder_Factory.cpp中看到:
typedef SkTRegistry<SkImageDecoder*, SkStream*> DecodeReg; //解码器,链表形式
template DecodeReg* DecodeReg::gHead;
SkImageDecoder* SkImageDecoder::Factory(SkStream* stream) {
const DecodeReg* curr = DecodeReg::Head();
while (curr) {
SkImageDecoder* codec = curr->factory()(stream); //创建解码器,没找到curr->factort()(stream)这个??
// we rewind here, because we promise later when we call "decode", that
// the stream will be at its beginning.
stream->rewind();
if (codec) {
return codec;
}
curr = curr->next();
}
return NULL;
}
看看SkTRegistry.h,这里不太懂,先贴着
/** Template class that registers itself (in the constructor) into a linked-list
and provides a function-pointer. This can be used to auto-register a set of
services, e.g. a set of image codecs.
*/
template <typename T, typename P> class SkTRegistry : SkNoncopyable {
public:
typedef T (*Factory)(P);
SkTRegistry(Factory fact) {
#ifdef ANDROID
// work-around for double-initialization bug
{
SkTRegistry* reg = gHead;
while (reg) {
if (reg == this) {
return;
}
reg = reg->fChain;
}
}
#endif
fFact = fact;
fChain = gHead;
gHead = this;
}
static const SkTRegistry* Head() { return gHead; }
const SkTRegistry* next() const { return fChain; }
Factory factory() const { return fFact; }
private:
Factory fFact;
SkTRegistry* fChain;
static SkTRegistry* gHead;
};
而这些解码器的注册过程是怎样的呢?去SkImageDecoder_libbmp.cpp看看,先看看SkBMPImageDecoder类定义:
class SkBMPImageDecoder : public SkImageDecoder { //继承SkImageDecoder
public:
SkBMPImageDecoder() {}
virtual Format getFormat() const {
return kBMP_Format;
}
protected:
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode);
};
然后下面有个static SkImageDecoder* Factory(SkStream* stream)方法(见下),哦~~应该就是上面调用
curr->factort()(stream)的具体实现,引用湛哥的话:(当然这里是BMP解码器,但是原理一样的)
DFactory就是jpeg解码器需要注册的入口函数,这个函数接受的流参数,首先会检查是否符合文件规范,
是就创建一个SkJPEGImageDecoder,那么在SkImageDecoder::Factory就会有个非Null的codec,否则,
继续下一个解码器查询。static SkTRegistry gDReg(DFactory);是注册函数。
很明显,这是一个静态的变量,将会在libskia.so装载的时候首先执行。
static SkImageDecoder* Factory(SkStream* stream) {
static const char kBmpMagic[] = { 'B', 'M' };
size_t len = stream->getLength();
char buffer[sizeof(kBmpMagic)];
if (len > sizeof(kBmpMagic) &&
stream->read(buffer, sizeof(kBmpMagic)) == sizeof(kBmpMagic) &&
!memcmp(buffer, kBmpMagic, sizeof(kBmpMagic))) {
return SkNEW(SkBMPImageDecoder); //创建并返回SkBMPImageDecoder对象实例
}
return NULL;
}
static SkTRegistry<SkImageDecoder*, SkStream*> gReg(Factory);
好吧,有了解码器,那解码器是怎么工作的呢?回到第一个代码块,
decoder->decode(stream, bitmap, prefConfig, decodeMode),看看SkImageDecoder.cpp:
bool SkImageDecoder::decode(SkStream* stream, SkBitmap* bm,
SkBitmap::Config pref, Mode mode) {
// pass a temporary bitmap, so that if we return false, we are assured of
// leaving the caller's bitmap untouched.
SkBitmap tmp;
// we reset this to false before calling onDecode
fShouldCancelDecode = false;
// assign this, for use by getPrefConfig(), in case fUsePrefTable is false
fDefaultPref = pref;
if (!this->onDecode(stream, &tmp, mode)) {
return false;
}
bm->swap(tmp);
return true;
}
恩,调用了onDecode(),有没有很熟悉?看上面的SkBMPImageDecoder类的例子,里面就有
protected:
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode);
看看onDecode的具体实现,比如jpeg里,可以看到调用了jpeglib里解码函数,至此,已经到最底层了
· Title:Bitmap(Java对象)构造过程
· Date:2011.11.22
· Author:Tianyang Lu
· Content:
从Bitmap.java这个类来看,有多个createBitmap方法,例如:
/**
* Returns a mutable bitmap with the specified width and height. Its
* initial density is as per {@link #getDensity}.
*
* @param width The width of the bitmap
* @param height The height of the bitmap
* @param config The bitmap config to create.
* @throws IllegalArgumentException if the width or height are <= 0
*/
public static Bitmap createBitmap(int width, int height, Config config) {
Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);
bm.eraseColor(0); // start with black/transparent pixels
return bm;
}
这里调用了nativeCreate方法,这是个native mothod,在Bitmap.cpp中看到:
static JNINativeMethod gBitmapMethods[] = {
{ "nativeCreate", "([IIIIIIZ)Landroid/graphics/Bitmap;",
(void*)Bitmap_creator},
...
}
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
int offset, int stride, int width, int height,
SkBitmap::Config config, jboolean isMutable) {
if (width <= 0 || height <= 0) {
doThrowIAE(env, "width and height must be > 0");
return NULL;
}
if (NULL != jColors) {
size_t n = env->GetArrayLength(jColors);
if (n < SkAbs32(stride) * (size_t)height) {
doThrowAIOOBE(env);
return NULL;
}
}
SkBitmap bitmap;
bitmap.setConfig(config, width, height);
if (!GraphicsJNI::setJavaPixelRef(env, &bitmap, NULL, true)) {
return NULL;
}
if (jColors != NULL) {
GraphicsJNI::SetPixels(env, jColors, offset, stride,
0, 0, width, height, bitmap);
}
return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), isMutable,
NULL);
}
这里new了一个SkBitmap,返回了GraphicsJNI::createBitmap的返回值,继续往下找:(Graphics.cpp)
jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable,
jbyteArray ninepatch, int density)
{
SkASSERT(bitmap != NULL);
SkASSERT(NULL != bitmap->pixelRef());
jobject obj = env->AllocObject(gBitmap_class); //obj转化成了Bitmap类
if (obj) {
env->CallVoidMethod(obj, gBitmap_constructorMethodID,
(jint)bitmap, isMutable, ninepatch, density);//构造方法
if (hasException(env)) {
obj = NULL;
}
}
return obj;
}
...
gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I");
gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>",
"(IZ[BI)V");
env->CallVoidMethod(obj, gBitmap_constructorMethodID,(jint)bitmap, isMutable, ninepatch, density);
这一句将转换成jint型的SkBitmap的指针变量bitmap传递给了Bitmap的构造方法,回看Bitmap.java中该方法:
/**
* @noinspection UnusedDeclaration
*/
/* Private constructor that must received an already allocated native
bitmap int (pointer).
This can be called from JNI code.
*/
private Bitmap(int nativeBitmap, boolean isMutable, byte[] ninePatchChunk,
int density) {
if (nativeBitmap == 0) {
throw new RuntimeException("internal error: native bitmap is 0");
}
// we delete this in our finalizer
mNativeBitmap = nativeBitmap;
mIsMutable = isMutable;
mNinePatchChunk = ninePatchChunk;
if (density >= 0) {
mDensity = density;
}
}
总的来看,GraphicsJNI::createBitmap()新建一个jobject型变量obj,关联了Bitmap.java类,
用Bitmap构造方法创建了一个实例并返回。实际上是用SkBitmap*类型传入来构造Bitmap对象的,
通过调用GraphicsJNI::createBitmap(),Bitmap_creator()(也就是Bitmap.nativeCreate()方法),
Bitmap.createBitmap()一层一层返回Bitmap实例,完成了创建Bitmap实例的方法
展开阅读全文