屬於個人所創,轉載請標明文章出處: https:////www.cnblogs.com/tangZH/p/12356915.html http://77blogs.com/?p=211 背景不多說,反正ndk載入gif比java上載入gif好很多很多,主要體現在記憶體占用與cpu消耗上。使用ndk載入 ...
屬於個人所創,轉載請標明文章出處:
https:////www.cnblogs.com/tangZH/p/12356915.html
背景不多說,反正ndk載入gif比java上載入gif好很多很多,主要體現在記憶體占用與cpu消耗上。使用ndk載入占用記憶體更小,消耗的cpu更少。
要使用ndk載入,需要用到giflib庫,Android源代碼裡面其實也用到了這個庫。
一、下載giflib
https://sourceforge.net/projects/giflib/
二、構建so庫
1、把需要用到的代碼拷貝進來
2、實現我們的CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1) set(SOURCES) file(GLOB_RECURSE SOURCES ${CMAKE_SOURCE_DIR}/*.cpp ${CMAKE_SOURCE_DIR}/*.c) add_library( native-lib SHARED ${SOURCES}) set(LIBS) list(APPEND LIBS log jnigraphics) target_link_libraries( native-lib ${LIBS})
語法上就不多說了。
註意還需要用到log庫和 jnigraphics庫,一個是用來列印log,一個是用來圖片解析的時候用到。
3、新建java類GifHandler,在裡面新建本地方法,方便等會直接用快捷方式在native-lib裡面生成
package com.example.gifndk; import android.graphics.Bitmap; public class GifHandler { static { System.loadLibrary("native-lib"); } private volatile long gifInfo; public GifHandler(String path) { gifInfo = openFile(path); } public synchronized int getWidth() { return getWidthN(gifInfo); } public synchronized int getHeight() { return getHeightN(gifInfo); } public synchronized int getLength() { return getLengthN(gifInfo); } /** * @param bitmap * @param index 第幾幀 * @return */ public long renderFrame(Bitmap bitmap, int index) { return renderFrameN(gifInfo, bitmap, index); } private native int getWidthN(long gifInfo); private native int getHeightN(long gifInfo); private native int getLengthN(long gifInfo); private native long renderFrameN(long gifInfo, Bitmap bitmap, int index); private native long openFile(String path); }
通過這些方法獲取寬度,高度,幀數,渲染,打開文件。
4、native-lib代碼,這裡編寫我們的本地代碼
首先需要引進的頭文件有:
#include <jni.h> #include <string> #include "giflib/gif_lib.h" #include <android/bitmap.h> #include "gif.h" //雙引號是從本地項目找,找不到再從系統找,<>是直接從系統找
<android/bitmap.h>NDK中帶的頭文件,解析圖片要用到。
gif.h我們自己新建的頭文件,等會講
首先看openFile方法
extern "C" JNIEXPORT jlong JNICALL Java_com_example_gifndk_GifHandler_openFile( JNIEnv *env, jobject /* this */, jstring path) { const char *path1 = env->GetStringUTFChars(path, 0); int err; GifFileType *gif = DGifOpenFileName(path1, &err); err = DGifSlurp(gif); env->ReleaseStringUTFChars(path, path1); return reinterpret_cast<jlong>(gif); }
這個方法主要是獲取到GifFileType的指針地址。
GifFileType裡面有gif文件的各種信息。
從註釋也可以看出具體的含義。
順便說一下UserData,這個其實相當於tag,類似於可以給一個view tag標識。
獲取到到GifFileType之後便可以通過它獲取寬度,高度,幀數,方法如下:
extern "C" JNIEXPORT jint JNICALL Java_com_example_gifndk_GifHandler_getWidthN(JNIEnv *env, jobject thiz, jlong gif_info) { return ((GifFileType *) gif_info)->SWidth; } extern "C" JNIEXPORT jint JNICALL Java_com_example_gifndk_GifHandler_getHeightN(JNIEnv *env, jobject thiz, jlong gif_info) { return ((GifFileType *) gif_info)->SHeight; } extern "C" JNIEXPORT jint JNICALL Java_com_example_gifndk_GifHandler_getLengthN(JNIEnv *env, jobject thiz, jlong gif_info) { return ((GifFileType *) gif_info)->ImageCount; }
獲取到相關信息後,我們就要進行渲染了。
先講一下我們的原理,我們的原理是獲取到信息。通過寬高構造一個bitmap,根據每一幀圖片之間的間隔,拿這個bitmap去渲染,也就是通過這個延時時間迴圈去載入每一幀圖片。
renderFrameN方法
extern "C" JNIEXPORT jlong JNICALL Java_com_example_gifndk_GifHandler_renderFrameN(JNIEnv *env, jobject thiz, jlong gif_info, jobject bitmap, jint index) { GifFileType *gifFileType = (GifFileType *) gif_info; //bitmap轉成顏色矩陣 void *pixels; AndroidBitmapInfo info; int err; if ((err = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) { return -1; } //也可以不判斷,這裡的格式需要有透明通道 if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { return -1; } //&pixels為指針的指針,即二維數組 if ((err = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) { return -1; } //渲染 long delay_time = drawFrame(gifFileType, &info, (int *) pixels, index); AndroidBitmap_unlockPixels(env, bitmap); return delay_time; }
AndroidBitmap_lockPixels(env, bitmap, &pixels)這個方法必須調用,它會鎖定圖片記憶體,同時呢,成功的話pixels會指向圖片的地址。
相應的,下麵就必須解除鎖定:AndroidBitmap_unlockPixels(env, bitmap)
核心方法是這一句,當然這一句需要我們自定義頭文件,也就是上面說到的gif.h
long delay_time = drawFrame(gifFileType, &info, (int *) pixels, index);
gif.h如下:
#include "giflib/gif_lib.h" #include <android/bitmap.h> #ifndef GIFNDK_GIF_H #define GIFNDK_GIF_H #endif //GIFNDK_GIF_H extern "C" int drawFrame(GifFileType *gif, AndroidBitmapInfo *info, int *pixels, int frame_no);
drawFrame方法如下,關於gif文件格式,可以自行百度,後面有空再寫:
#define delay(ext) (10 *((ext) -> Bytes[2] << 8 | (ext) ->Bytes[1])) int drawFrame(GifFileType *gif, AndroidBitmapInfo *info, int *pixels, int frame_no) { GifColorType *color; //每一幀圖片 SavedImage *frame; //擴展快,定義一些行為 ExtensionBlock *ext = 0; //描述文件 GifImageDesc *frameInfo; //顏色表 ColorMapObject *colorMap; int *line; int x, y, j, loc; int *px; //獲取這一幀 frame = &(gif->SavedImages[frame_no]); //這一幀相關的描述文件,雖然描述文件已經在GifFileType裡面聲明瞭,但是沒有賦值 //所以需要用這種方式 frameInfo = &(frame->ImageDesc); //這一幀的顏色列表 if (frameInfo->ColorMap) { colorMap = frameInfo->ColorMap; } else { //沒有的話就獲取全局的顏色列表 colorMap = gif->SColorMap; } //遍歷這一幀的擴展塊,找到具有GRAPHICS_EXT_FUNC_CODE標誌位的,這個擴展快存放著對該幀圖片的 //處置方法,是不處理還是其他 for (j = 0; j < frame->ExtensionBlockCount; ++j) { if (frame->ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE) { ext = &(frame->ExtensionBlocks[j]); break; } } //這時候px是二維指針 px = pixels; //frameInfo->Top:從哪個y坐標開始(距離頂部的top),乘以每一行的位元組,就是我們需要開始遍歷的位元組 px = (int *) ((char *) px + info->stride * frameInfo->Top); //遍歷y for (y = frameInfo->Top; y < frameInfo->Top + frameInfo->Height; ++y) { //行 line = px; //遍歷x for (x = frameInfo->Left; x < frameInfo->Left + frameInfo->Width; ++x) { //當前的這一點 loc = (y - frameInfo->Top) * frameInfo->Width + (x - frameInfo->Left); //判斷處置方法,拿到當前幀loc位置的位元組,看是否等於擴展塊中索引為3的位元組,並且數值為1 if (frame->RasterBits[loc] == ext->Bytes[3] && ext->Bytes[0]) { continue; } color = &colorMap->Colors[frame->RasterBits[loc]]; line[x] = ((255 & 0xff)<< 24) | ((color->Blue & 0xff)<< 16) | ((color->Green)<< 8) | (color->Red); } px = (int *) ((char *) px + info->stride); } return delay(ext); }
那麼接下來就是主工程的調用了。
private void load() { gifHandler = new GifHandler(path); int width = gifHandler.getWidth(); int height = gifHandler.getHeight(); bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); maxLength = gifHandler.getLength(); long delayTime = gifHandler.renderFrame(bitmap, currentLength); imageView.setImageBitmap(bitmap); if (handler != null) { handler.sendEmptyMessageDelayed(1, delayTime); } /*Glide.with(this).asGif().load(path).into(imageView);*/ } Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(@NonNull Message msg) { currentLength++; if (currentLength >= maxLength) { currentLength = 0; } long delayTime = gifHandler.renderFrame(bitmap, currentLength); imageView.setImageBitmap(bitmap); handler.sendEmptyMessageDelayed(0, delayTime); return false; } });
到此結束。
源碼地址:https://github.com/TZHANHONG/GifLibSample