本文主要介绍ijkplayer中的jni。Ijkplayer的native层代码是脱离android源码编译的,所以当native层要用到android的Bundle,AudioTrack等类时,需要从native层调用java接口。这里就运用到了jni4android。jni4android是bilibili的一个开源工程。代码路径是https://github.com/Bilibili/jni4android。它的作用是根据java伪代码生成对应的c语言代码,这样就可以直接通过调用c语言接口而调用到对应java类的接口。下面以Parcel这类为例。Parcel这个类Ijkplayer里面没有,如果项目要用到,可以如下操作。
1、先定义Parcel.java的伪代码。如下:
package android.os;
@SimpleCClassName
public final class Parcel {
public static Parcel obtain();
public final void recycle();
public final int dataSize();
public final int dataAvail();
public final int dataPosition();
public final int dataCapacity();
public final void setDataSize(int size);
public final void setDataPosition(int pos);
public final void setDataCapacity(int size);
public final void writeInt(int val);
public final void writeLong(long val);
public final void writeFloat(float val);
public final void writeDouble(double val);
public final void writeString(String val);
public final int readInt();
public final long readLong();
public final float readFloat();
public final double readDouble();
public final String readString();
}
2、执行j4a -c Parcel.java,就会生成四个文件:Parcel.c,Parcel.h,Parcel.include.j4a,Parcel.loader.j4a。其中,Parcel.c,Parcel.h就是我们native code要用到的接口。这里分析一下Parcel.c。它通过jni实现了Parcel.java里面的所有接口,调用它里面的c接口就和java层调用Parcel类的api一样。它里面最重要的函数如下:
int J4A_loadClass__J4AC_android_os_Parcel(JNIEnv *env)
{
int ret = -1;
const char *J4A_UNUSED(name) = NULL;
const char *J4A_UNUSED(sign) = NULL;
jclass J4A_UNUSED(class_id) = NULL;
int J4A_UNUSED(api_level) = 0;
if (class_J4AC_android_os_Parcel.id != NULL)
return 0;
sign = "android/os/Parcel";
class_J4AC_android_os_Parcel.id = J4A_FindClass__asGlobalRef__catchAll(env, sign);
if (class_J4AC_android_os_Parcel.id == NULL)
goto fail;
class_id = class_J4AC_android_os_Parcel.id;
name = "obtain";
sign = "()Landroid/os/Parcel;";
class_J4AC_android_os_Parcel.method_obtain = J4A_GetStaticMethodID__catchAll(env, class_id, name, sign);
if (class_J4AC_android_os_Parcel.method_obtain == NULL)
goto fail;
class_id = class_J4AC_android_os_Parcel.id;
name = "recycle";
sign = "()V";
class_J4AC_android_os_Parcel.method_recycle = J4A_GetMethodID__catchAll(env, class_id, name, sign);
if (class_J4AC_android_os_Parcel.method_recycle == NULL)
goto fail;
class_id = class_J4AC_android_os_Parcel.id;
name = "dataSize";
sign = "()I";
class_J4AC_android_os_Parcel.method_dataSize = J4A_GetMethodID__catchAll(env, class_id, name, sign);
if (class_J4AC_android_os_Parcel.method_dataSize == NULL)
goto fail;
class_id = class_J4AC_android_os_Parcel.id;
name = "dataAvail";
sign = "()I";
class_J4AC_android_os_Parcel.method_dataAvail = J4A_GetMethodID__catchAll(env, class_id, name, sign);
if (class_J4AC_android_os_Parcel.method_dataAvail == NULL)
goto fail;
class_id = class_J4AC_android_os_Parcel.id;
name = "dataPosition";
sign = "()I";
class_J4AC_android_os_Parcel.method_dataPosition = J4A_GetMethodID__catchAll(env, class_id, name, sign);
if (class_J4AC_android_os_Parcel.method_dataPosition == NULL)
goto fail;
......此处省略剩余代码。
可以看出J4A_loadClass__J4AC_android_os_Parcel函数主要就是生成Parcel类的各个methodId并保存下来,供各个c接口使用。所以使用Parcel.c前必须要先执行J4A_loadClass__J4AC_android_os_Parcel。而这个函数是通过J4A_LOAD_CLASS这个宏来执行的。J4A_LOAD_CLASS定义如下:
#define J4A_LOAD_CLASS(class__) \
do { \
ret = J4A_loadClass__J4AC_##class__(env); \
if (ret) \
goto fail; \
} while (0)
所以还需要在j4a_allclasses.loader.h里面添加J4A_LOAD_CLASS(android_os_Parcel);这行代码,这样就能在native层使用Parcel类了。