引導
在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的加載流程源碼是比較簡單的,經常閱讀源碼可以學習優秀的程序設計思想,好好學習.哈哈哈!多多關注.