在AS上用C语言(JNI方式)播放gif动图

2022-07-27,,,,

Android中gif播放一般是比较耗内存的操作,Android中的ImageView不能直接播放gif,使用Java方式实现的gif播放的是非常耗内存的,就算是使用Glide这种优秀的三方库,也是一样的,所以项目中有gif播放需求,尤其是列表中有gif播放的,建议使用JNI的实现方式。

创建工程

先将基本的功能,例如申请权限,按钮点击等 用Java实现,activity_main.xml 如下:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >
    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="C方式加载Gif"
        android:gravity="center"
        android:onClick="ndkLoadGif"
        />
</LinearLayout>

接下来将android源码下的gif加载文件拷贝过来,底层的渲染加载都由这些库完成,我们主要实现获取gif的路径、宽高和渲染等逻辑

除了箭头两个是AS生成的,其他都是来自系统源码,Android源码中的版本号\external\giflib路径下。

 

 

另外还有一点要注意的是要将引用到的文件加到中CMakeLists.txt,不然有些方法拿不到会报错

接着在 GifHandler 类中放获取加载gif、获取宽高、渲染图片的方法 代码如下:

package com.xifei.gifdemo;

import android.graphics.Bitmap;

public class GifHandler {
    long gifHander;//地址   指针类型

    static {
        System.loadLibrary("native-lib");
    }

    public int getWidth() {
        return getWidth(gifHander);
    }

    public int getHeight() {
        return getHeight(gifHander);
    }

    public int updateFrame(Bitmap bitmap) {
        return updateFrame(gifHander, bitmap);
    }

    private GifHandler(long gifHander) {
        this.gifHander = gifHander;
    }

    public static GifHandler load(String path) {
        long gifHander = loadGif(path);
        GifHandler gifHandler = new GifHandler(gifHander);
        return gifHandler;
    }

    // 开始加载gif文件  Java+包名+类名+方法名  中间分隔用下划线
    public static native long loadGif(String path);

    // 宽
    public static native int getWidth(long gifHander);

    // 高
    public static native int getHeight(long gifPoint);

    // 渲染图片
    public static native int updateFrame(long gifPoint, Bitmap bitmap);

}
static {
    System.loadLibrary("native-lib");
}

将GifHandler 的方法和native-lib.cpp文件关联起来,

public static native long loadGif(String path);   对应  Java_com_xifei_gifdemo_GifHandler_loadGif(JNIEnv *env, jclass clazz, jstring path_) 方法

// 宽
public static native int getWidth(long gifHander);  对应    Java_com_xifei_gifdemo_GifHandler_getWidth(JNIEnv *env, jclass clazz, jlong gif_hander)

// 高
public static native int getHeight(long gifPoint);  对应   Java_com_xifei_gifdemo_GifHandler_getHeight(JNIEnv *env, jclass clazz, jlong gif_hander)

// 渲染图片
public static native int updateFrame(long gifPoint, Bitmap bitmap);   对应  Java_com_xifei_gifdemo_GifHandler_updateFrame(JNIEnv *env, jclass clazz, jlong gif_point,jobject bitmap)

主要的实现逻辑也是要在native-lib.cpp的方法中完成的,代码如下:

#include <jni.h>
#include <string>
#include <android/bitmap.h>
#include <malloc.h>
#include <string.h>

extern "C" {
#include "gif_lib.h"
}
//16个字节
struct GifBean {
    int current_frame;
    int total_frame;
    int *delays;
};

//解析图像
#define  argb(a, r, g, b) ( ((a) & 0xff) << 24 ) | ( ((b) & 0xff) << 16 ) | ( ((g) & 0xff) << 8 ) | ((r) & 0xff)
extern "C"
JNIEXPORT jlong JNICALL
Java_com_xifei_gifdemo_GifHandler_loadGif(JNIEnv *env, jclass clazz, jstring path_) {
    const char *path = env->GetStringUTFChars(path_, 0);
    int Error;//打开失败还是成功
    GifFileType *gifFileType = DGifOpenFileName(path, &Error);

    //初始化缓冲区 数组SaveImages
    DGifSlurp(gifFileType);
    GifBean *gifBean = static_cast<GifBean *>(malloc(sizeof(GifBean)));
    //重置,防止有脏数据
    memset(gifBean, 0, sizeof(GifBean));
    //赋值
    gifFileType->UserData = gifBean;
    gifBean->current_frame = 0;
    //总帧数
    gifBean->total_frame = gifFileType->ImageCount;

    //释放空间
    env->ReleaseStringUTFChars(path_, path);
    return reinterpret_cast<jlong>(gifFileType);

}

extern "C"
JNIEXPORT jint JNICALL
Java_com_xifei_gifdemo_GifHandler_getWidth(JNIEnv *env, jclass clazz, jlong gif_hander) {
    GifFileType *gifFileType = reinterpret_cast<GifFileType *>(gif_hander);
    return gifFileType->SWidth;
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_xifei_gifdemo_GifHandler_getHeight(JNIEnv *env, jclass clazz, jlong gif_hander) {
    GifFileType *gifFileType = reinterpret_cast<GifFileType *>(gif_hander);
    return gifFileType->SHeight;
}

void drawFrame1(GifFileType *gifFileType, AndroidBitmapInfo info, void *pixels) {
    GifBean *gifBean = static_cast<GifBean *>(gifFileType->UserData);
    //获取到当前帧
    SavedImage savedImage = gifFileType->SavedImages[gifBean->current_frame];
    //图像分成两部分  像素   一部分是 描述
    GifImageDesc frameInfo = savedImage.ImageDesc;
    ColorMapObject *colorMapObject = frameInfo.ColorMap;
    //记录每一行的首地址
    int *px = (int *) pixels;
    //临时 索引
    int *line;
    //索引
    int pointPixel;
    GifByteType gifByteType;
    //解压
    GifColorType gifColorType;
    for (int y = frameInfo.Top; y < frameInfo.Top + frameInfo.Height; ++y) {
        //每次遍历行将首地址 传给line
        line = px;
        for (int x = frameInfo.Left; x < frameInfo.Left + frameInfo.Width; ++x) {
            // 定位像素  索引
            pointPixel = (y - frameInfo.Top) * frameInfo.Width + (x - frameInfo.Left);
            // 压缩的像素
            gifByteType = savedImage.RasterBits[pointPixel];
            gifColorType = colorMapObject->Colors[gifByteType];
            //line 进行复制   0  255  屏幕有颜色 line
            line[x] = argb(255, gifColorType.Red, gifColorType.Green, gifColorType.Blue);
        }
        //遍历条件 转到下一行
        px = (int *) ((char *) px + info.stride);
    }
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_xifei_gifdemo_GifHandler_updateFrame(JNIEnv *env, jclass clazz, jlong gif_point,
                                              jobject bitmap) {
    //获取bitmap
    GifFileType *gifFileType = reinterpret_cast<GifFileType *>(gif_point);
    //第一种获取bitmap、宽高的方法
    int width = gifFileType->SWidth;
    int height = gifFileType->SHeight;
    //第二种获取bitmap、宽高的方法
    AndroidBitmapInfo info;
    AndroidBitmap_getInfo(env, bitmap, &info);
    width = info.width;
    height = info.height;

    //获取bitmap 相当于二维数组(万物皆为数组)
    void *pixels;
    //锁住当前bitmap
    AndroidBitmap_lockPixels(env, bitmap, &pixels);
    //绘制
    drawFrame1(gifFileType, info, pixels);
    AndroidBitmap_unlockPixels(env, bitmap);

    GifBean *gifBean = static_cast<GifBean *>(gifFileType->UserData);
    //帧数移动
    gifBean->current_frame++;
    if (gifBean->current_frame >= gifBean->total_frame - 1) {
        gifBean->current_frame = 0;
    }
    return 100;
}

MainActivity的代码如下:
package com.xifei.gifdemo;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.app.Activity;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import java.io.File;

public class MainActivity extends AppCompatActivity {
    Bitmap bitmap;
    GifHandler gifHandler;
    ImageView image;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        verifyStoragePermissions(this);
        image= (ImageView) findViewById(R.id.image);
    }

    Handler myHandler = new Handler() {
        public void handleMessage(Message msg) {
            int delay=gifHandler.updateFrame(bitmap);
            myHandler.sendEmptyMessageDelayed(1,delay);
            image.setImageBitmap(bitmap);
//            Object
        }
    };
    public void verifyStoragePermissions(Activity activity) {
        int REQUEST_EXTERNAL_STORAGE = 1;
        String[] PERMISSIONS_STORAGE = {
                "android.permission.READ_EXTERNAL_STORAGE",
                "android.permission.WRITE_EXTERNAL_STORAGE" };
        try {
            //检测是否有写的权限
            int permission = ActivityCompat.checkSelfPermission(activity,
                    "android.permission.WRITE_EXTERNAL_STORAGE");
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // 没有写的权限,去申请写的权限,会弹出对话框
                ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void ndkLoadGif(View view) {
        File file=new File(Environment.getExternalStorageDirectory(),"demo.gif");
        Log.e("xifei >>","-------"+file.getAbsolutePath());
        gifHandler = GifHandler.load(file.getAbsolutePath());
        Log.e("xifei >>","gifHandler -------"+gifHandler);

        int width=gifHandler.getWidth();
        int height=gifHandler.getHeight();
        Log.i("xifei >>","宽   "+width+"   高  "+height);
        bitmap = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
        //C  通知C渲染完成
        int delay= gifHandler.updateFrame(bitmap);
        Log.e("xifei >>","delay  "+delay);
        image.setImageBitmap(bitmap);

        myHandler.sendEmptyMessageDelayed(1, delay);
        View view1;

    }
}

项目源码下载:

https://download.csdn.net/download/xifei66/13191665

 

 

本文地址:https://blog.csdn.net/xifei66/article/details/110163884

《在AS上用C语言(JNI方式)播放gif动图.doc》

下载本文的Word格式文档,以方便收藏与打印。