在AS上用C语言(JNI方式)播放gif动图
程序员文章站
2023-12-31 14:37:46
Android中gif播放一般是比较耗内存的操作,Android中的ImageView不能直接播放gif,使用Java方式实现的gif播放的是非常耗内存的,就算是使用Glide这种优秀的三方库,也是一样的,所以项目中有gif播放需求,尤其是列表中有gif播放的,建议使用JNI的实现方式。创建工程先将基本的功能,例如申请权限,按钮点击等 用Java实现,activity_main.xml 如下:
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