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

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