Android-音视频学习系列(一)-JNI-从入门到精通,移动智能终端开发技术第三次作业

boolean b,
byte b1,
char c,
short s,
long l,
float f,
double d,
String name,
int age,
int[] i,
String[] strs,
Person person,
boolean[] bArray
);
}

  1. jni 处理 Java 传递过来的数据

#include <jni.h>
#include
#include <android/log.h>

#include

#define TAG “native-lib”
// VA_ARGS 代表 …的可变参数
#define LOGD(…) __android_log_print(ANDROID_LOG_DEBUG, TAG, VA_ARGS);
#define LOGE(…) __android_log_print(ANDROID_LOG_ERROR, TAG, VA_ARGS);
#define LOGI(…) __android_log_print(ANDROID_LOG_INFO, TAG, VA_ARGS);

extern “C”//支持 C 语言代码
JNIEXPORT void JNICALL
Java_com_devyk_ndk_1sample_MainActivity_test1(JNIEnv *env, jobject instance,
jboolean jboolean1,
jbyte jbyte1,
jchar jchar1,
jshort jshort1,
jlong jlong1,
jfloat jfloat1,
jdouble jdouble1,
jstring name_,
jint age,
jintArray i_,
jobjectArray strs,
jobject person,
jbooleanArray bArray_
) {

//1. 接收 Java 传递过来的 boolean 值
unsigned char b_boolean = jboolean1;
LOGD(“boolean-> %d”, b_boolean);

//2. 接收 Java 传递过来的 boolean 值
char c_byte = jbyte1;
LOGD(“jbyte-> %d”, c_byte);

//3. 接收 Java 传递过来的 char 值
unsigned short c_char = jchar1;
LOGD(“char-> %d”, c_char);

//4. 接收 Java 传递过来的 short 值
short s_short = jshort1;
LOGD(“short-> %d”, s_short);

//5. 接收 Java 传递过来的 long 值
long l_long = jlong1;
LOGD(“long-> %d”, l_long);

//6. 接收 Java 传递过来的 float 值
float f_float = jfloat1;
LOGD(“float-> %f”, f_float);

//7. 接收 Java 传递过来的 double 值
double d_double = jdouble1;
LOGD(“double-> %f”, d_double);

//8. 接收 Java 传递过来的 String 值
const char *name_string = env->GetStringUTFChars(name_, 0);
LOGD(“string-> %s”, name_string);

//9. 接收 Java 传递过来的 int 值
int age_java = age;
LOGD(“int:%d”, age_java);

//10. 打印 Java 传递过来的 int []
jint *intArray = env->GetIntArrayElements(i_, NULL);
//拿到数组长度
jsize intArraySize = env->GetArrayLength(i_);
for (int i = 0; i < intArraySize; ++i) {
LOGD(“intArray->%d:”, intArray[i]);
}
//释放数组
env->ReleaseIntArrayElements(i_, intArray, 0);

//11. 打印 Java 传递过来的 String[]
jsize stringArrayLength = env->GetArrayLength(strs);
for (int i = 0; i < stringArrayLength; ++i) {
jobject jobject1 = env->GetObjectArrayElement(strs, i);
//强转 JNI String
jstring stringArrayData = static_cast(jobject1);

//转 C String
const char *itemStr = env->GetStringUTFChars(stringArrayData, NULL);
LOGD(“String[%d]: %s”, i, itemStr);
//回收 String[]
env->ReleaseStringUTFChars(stringArrayData, itemStr);
}

//12. 打印 Java 传递过来的 Object 对象
//12.1 获取字节码
const char *person_class_str = “com/devyk/ndk_sample/Person”;
//12.2 转 jni jclass
jclass person_class = env->FindClass(person_class_str);
//12.3 拿到方法签名 javap -a
const char *sig = “()Ljava/lang/String;”;
jmethodID jmethodID1 = env->GetMethodID(person_class, “getName”, sig);

jobject obj_string = env->CallObjectMethod(person, jmethodID1);
jstring perStr = static_cast(obj_string);
const char *itemStr2 = env->GetStringUTFChars(perStr, NULL);
LOGD(“Person: %s”, itemStr2);
env->DeleteLocalRef(person_class); // 回收
env->DeleteLocalRef(person); // 回收

//13. 打印 Java 传递过来的 booleanArray
jsize booArrayLength = env->GetArrayLength(bArray_);
jboolean *bArray = env->GetBooleanArrayElements(bArray_, NULL);
for (int i = 0; i < booArrayLength; ++i) {
bool b = bArray[i];
jboolean b2 = bArray[i];
LOGD(“boolean:%d”,b)
LOGD(“jboolean:%d”,b2)
}
//回收
env->ReleaseBooleanArrayElements(bArray_, bArray, 0);

}

输出:

输出:

native-lib: boolean-> 1
native-lib: jbyte-> 1
native-lib: char-> 44
native-lib: short-> 3
native-lib: long-> 4
native-lib: float-> 3.300000
natiAndroid-音视频学习系列(一)-JNI-从入门到精通,移动智能终端开发技术第三次作业
ve-lib: double-> 2.200000
native-lib: string-> DevYK
native-lib: int:28
native-lib: intArray->1:
native-lib: intArray->2:
native-lib: intArray->3:
native-lib: intArray->4:
native-lib: intArray->5:
native-lib: intArray->6:
native-lib: intArray->7:
native-lib: String[0]: 1
native-lib: String[1]: 2
native-lib: String[2]: 4
native-lib: Person: 阳坤
native-lib: boolean:0
native-lib: jboolean:0
native-lib: boolean:1
native-lib: jboolean:1

JNI 处理 Java 对象

  1. 定义一个 Java 对象

public class Person {
private String name;
private int age;

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public void setName(String name){
this.name = name;
}

public String getName(){
return name;
}

@Override
public String toString() {
return “Person{” +
“name=’” + name + ‘’’ +
“, age=” + age +
‘}’;
}
}

  1. 定义 native 接口

public class MainActivity extends AppCompatActivity {

private String TAG = this.getClass().getSimpleName();

/**

  • 1. 加载 native 库
    */
    static {
    System.loadLibrary(“native-lib”);
    }

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView text = findViewById(R.id.sample_text);
/*处理 Java 对象/
String str = getPerson().toString();
text.setText(str);
}
public native Person getPerson();
}

根据上面代码我们知道,如果获取成功,手机屏幕上肯定会打印会显示数据。

  1. JNI 的处理

extern “C”
JNIEXPORT jobject JNICALL
Java_com_devyk_ndk_1sample_MainActivity_getPerson(JNIEnv *env, jobject instance) {

//1. 拿到 Java 类的全路径
const char *person_java = “com/devyk/ndk_sample/Person”;
const char *method = “”; // Java构造方法的标识

//2. 找到需要处理的 Java 对象 class
jclass j_person_class = env->FindClass(person_java);

//3. 拿到空参构造方法
jmethodID person_constructor = env->GetMethodID(j_person_class, method, “()V”);

//4. 创建对象
jobject person_obj = env->NewObject(j_person_class, person_constructor);

//5. 拿到 setName 方法的签名,并拿到对应的 setName 方法
const char *nameSig = “(Ljava/lang/String;)V”;
jmethodID nameMethodId = env->GetMethodID(j_person_class, “setName”, nameSig);

//6. 拿到 setAge 方法的签名,并拿到 setAge 方法
const char *ageSig = “(I)V”;
jmethodID ageMethodId = env->GetMethodID(j_person_class, “setAge”, ageSig);

//7. 正在调用 Java 对象函数
const char *name = “DevYK”;
jstring newStringName = env->NewStringUTF(name);
env->CallVoidMethod(person_obj, nameMethodId, newStringName);
env->CallVoidMethod(person_obj, ageMethodId, 28);

const char *sig = “()Ljava/lang/String;”;
jmethodID jtoString = env->GetMethodID(j_person_class, “toString”, sig);
jobject obj_string = env->CallObjectMethod(person_obj, jtoString);
jstring perStr = static_cast(obj_string);
const char *itemStr2 = env->GetStringUTFChars(perStr, NULL);
LOGD(“Person: %s”, itemStr2);
return person_obj;
}

输出:

Android-音视频学习系列(一)-JNI-从入门到精通,移动智能终端开发技术第三次作业

可以看到 native 返回数据给 Java 了。

5. JNI 动态注册

前面咱们学习的都是静态注册,静态注册虽然简单方便,但是也面临一个较大的问题,如果当前类定义的 native 方法名称改变或者包名改变,那么这一改也就面临在 cpp 中实现的也将改动,如果将要面临这种情况你可以试试 JNI 动态注册,如下代码所示:

public class MainActivity extends AppCompatActivity {

private String TAG = this.getClass().getSimpleName();

/**

  • 1. 加载 native 库
    */
    static {
    System.loadLibrary(“native-lib”);
    }

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView text = findViewById(R.id.sample_text);

/**动态注册的 native */
dynamicRegister(“我是动态注册的”);
}

/**

  • 动态注册
    */
    public native void dynamicRegister(String name);
    }
    复制代码

cpp:

#include <jni.h>
#include
#include <android/log.h>

#include

#define TAG “native-lib”
// VA_ARGS 代表 …的可变参数
#define LOGD(…) __android_log_print(ANDROID_LOG_DEBUG, TAG, VA_ARGS);
#define LOGE(…) __android_log_print(ANDROID_LOG_ERROR, TAG, VA_ARGS);
#define LOGI(…) __android_log_print(ANDROID_LOG_INFO, TAG, VA_ARGS);

/**

  • TODO 动态注册
    */

/**

  • 对应java类的全路径名,.用/代替
    */
    const char *classPathName = “com/devyk/ndk_sample/MainActivity”;

extern “C” //支持 C 语言
JNIEXPORT void JNICALL //告诉虚拟机,这是jni函数
native_dynamicRegister(JNIEnv *env, jobject instance, jstring name) {
const char *j_name = env->GetStringUTFChars(name, NULL);
LOGD(“动态注册: %s”, j_name)
//释放
env->ReleaseStringUTFChars(name, j_name);
}

/* 源码结构体

  • typedef struct {
    const char* name;
    const char* signature;
    void* fnPtr;
    } JNINativeMethod;
    */
    static const JNINativeMethod jniNativeMethod[] = {
    {“dynamicRegister”, “(Ljava/lang/String;)V”, (void *) (native_dynamicRegister)}
    };

/**

  • 该函数定义在jni.h头文件中,System.loadLibrary()时会调用JNI_OnLoad()函数
    */
    JNIEXPORT jint JNICALL
    JNI_OnLoad(JavaVM *javaVm, void *pVoid) {
    //通过虚拟机 创建爱你全新的 evn
    JNIEnv *jniEnv = nullptr;
    jint result = javaVm->GetEnv(reinterpret_cast<void **>(&jniEnv), JNI_VERSION_1_6);
    if (result != JNI_OK) {
    return JNI_ERR; // 主动报错
    }
    jclass mainActivityClass = jniEnv->FindClass(classPathName);
    jniEnv->RegisterNatives(mainActivityClass, jniNativeMethod,
    sizeof(jniNativeMethod) / sizeof(JNINativeMethod));//动态注册的数量

return JNI_VERSION_1_6;
}
复制代码

输出:

动态注册: 我是动态注册的

6. 异常处理

异常处理是 Java 程序设计语言的重要功能, JNI 中的异常行为与 Java 中的有所不同,在 Java 中,当抛出一个异常时,虚拟机停止执行代码块并进入调用栈反向检查能处理特定类型异常的异常处理程序代码块,这也叫捕获异常。虚拟机清除异常并将控制权交给异常处理程序。相比之下, JNI 要求开发人员在异常发生后显式地实现异常处理流。

捕获异常:

JNIEvn 接口提供了一组与异常相关的函数集,在运行过程中可以使用 Java 类查看这些函数,比如代码如下:

public native void dynamicRegister2(String name);

/**

  • 测试抛出异常
  • @throws NullPointerException
    */
    private void testException() throws NullPointerException {
    throw new NullPointerException(“MainActivity testException NullPointerException”);
    }

当调用 testException 方法时,dynamicRegister2 该原生方法需要显式的处理异常信息,JNI 提供了 ExceptionOccurred 函数查询虚拟机中是否有挂起的异常。在使用完之后,异常处理程序需要用 ExceptionClear 函数显式的清除异常,如下代码:

jthrowable exc = env->ExceptionOccurred(); // 检测是否发生异常
if (exc) {//如果发生异常
env->ExceptionDescribe(); // 打印异常信息
env->ExceptionClear(); // 清除掉发生的异常
}

抛出异常:

JNI 也允许原生代码抛出异常。因为异常是 Java 类,应该先用 FindClass 函数找到异常类,用 ThrowNew 函数可以使用化且抛出新的异常,如下代码所示:

jthrowable exc = env->ExceptionOccurred(); // 检测是否发生异常
if (exc) {//如果发生异常
jclass newExcCls = env->FindClass(“java/lang/IllegalArgumentException”);
env->ThrowNew(newExcCls, “JNI 中发生了一个异常信息”); // 返回一个新的异常到 Java
}

因为原生函数的代码执行不受虚拟机的控制,因此抛出异常并不会停止原生函数的执行并把控制权交给异常处理程序。到抛出异常时,原生函数应该释放所有已分配的原生资源,例如内存及合适的返回值等。通过 JNIEvn 接口获得的引用是局部引用且一旦返回原生函数,它们自动地被虚拟机释放。

示例代码:

public class MainActivity extends AppCompatActivity {

private String TAG = this.getClass().getSimpleName();

/**

  • 1. 加载 native 库
    */
    static {
    System.loadLibrary(“native-lib”);
    }

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

dynamicRegister2(“测试异常处理”);
}

public native void dynamicRegister2(String name);

/**

  • 测试抛出异常
  • @throws NullPointerException
    */
    private void testException() throws NullPointerException {
    throw new NullPointerException(“MainActivity testException NullPointerException”);
    }

}

native-lib.cpp 文件

#include <jni.h>
#include
#include <android/log.h>

#include

#define TAG “native-lib”
// VA_ARGS 代表 …的可变参数
#define LOGD(…) __android_log_print(ANDROID_LOG_DEBUG, TAG, VA_ARGS);
#define LOGE(…) __android_log_print(ANDROID_LOG_ERROR, TAG, VA_ARGS);
#define LOGI(…) __android_log_print(ANDROID_LOG_INFO, TAG, VA_ARGS);

/**

  • TODO 动态注册
    */

extern “C” //支持 C 语言
JNIEXPORT void JNICALL //告诉虚拟机,这是jni函数
native_dynamicRegister2(JNIEnv *env, jobject instance, jstring name) {
const char *j_name = env->GetStringUTFChars(name, NULL);
LOGD(“动态注册: %s”, j_name)

jclass clazz = env->GetObjectClass(instance);//拿到当前类的class
jmethodID mid =env->GetMethodID(clazz, “testException”, “()V”);//执行 Java 测试抛出异常的代码
env->CallVoidMethod(instance, mid); // 执行会抛出一个异常
jthrowable exc = env->ExceptionOccurred(); // 检测是否发生异常
if (exc) {//如果发生异常
env->ExceptionDescribe(); // 打印异常信息
env->ExceptionClear(); // 清除掉发生的异常
jclass newExcCls = env->FindClass(“java/lang/IllegalArgumentException”);
env->ThrowNew(newExcCls, “JNI 中发生了一个异常信息”); // 返回一个新的异常到 Java
}

//释放
env->ReleaseStringUTFChars(name, j_name);
}

这里还是使用的动态注册。

最后效果如下:

Android-音视频学习系列(一)-JNI-从入门到精通,移动智能终端开发技术第三次作业

可以看见这里即捕获到了 Java 抛出的异常,也抛出了一个 JNI 新的异常信息。

7. 局部与全局引用

引用在 Java 程序设计中扮演非常重要的角色。虚拟机通过追踪类实例的引用并收回不在引用的垃圾来管理类实例的使用期限。因为原生代码不是一个管理环境,因此 JNI 提供了一组函数允许原生代码显式地管理对象引用及使用期间原生代码。 JNI 支持三种引用: 局部引用、全局引用和弱全局引用。下面将介绍这几类引用。

局部引用:

大多数 JNI 函数返回局部引用。局部应用不能在后续的调用中被缓存及重用,主要是因为它们的使用期限仅限于原生方法,一旦原生方法返回,局部引用即被释放。例如: FindClass 函数返回一个局部引用,当原生方法返回时,它被自动释放,也可以用 DeleteLocalRef 函数显式的释放原生代码。如下代码所示:

jclass personClass;
extern “C” //支持 C 语言
JNIEXPORT void JNICALL //告诉虚拟机,这是jni函数
native_test4(JNIEnv *env, jobject instance) {
LOGD(“测试局部引用”)
if (personClass == NULL) {
const char *person_class = “com/devyk/ndk_sample/Person”;
personClass = env->FindClass(person_class);
LOGD(“personClass == null 执行了。”)
}
//Java Person 构造方法实例化
const char *sig = “()V”;
const char *method = “”;//Java 构造方法标识
jmethodID init = env->GetMethodID(personClass, method, sig);
//创建出来
env->NewObject(personClass, init);
}

效果:

Android-音视频学习系列(一)-JNI-从入门到精通,移动智能终端开发技术第三次作业

跟介绍的一样的吧。局部引用不能再后续的调用中重复使用,那么怎么解决这个问题勒,其实只要把局部引用提升为全局引用或者调用 DeleteLocalRef 显式释放就行了。这个我们在全局引用中演示。

全局引用:

全局引用在原生方法的后续调用过程中依然有效,除非它们被原生代码显式释放。

  1. 创建全局引用

可以使用 NewGlobalRef 函数将局部引用初始化为全局引用,如下代码所示:

jclass personClass;
extern “C” //支持 C 语言
JNIEXPORT void JNICALL //告诉虚拟机,这是jni函数
native_test4(JNIEnv *env, jobject instance) {
LOGD(“测试局部引用”)

if (personClass == NULL) {
//1. 提升全局解决不能重复使用问题
const char *person_class = “com/devyk/ndk_sample/Person”;
jclass jclass1 = env->FindClass(person_class);
personClass = static_cast(env->NewGlobalRef(jclass1));
LOGD(“personClass == null 执行了。”)
}

//Java Person 构造方法实例化
const char *sig = “()V”;
const char *method = “”;//Java 构造方法标识
jmethodID init = env->GetMethodID(personClass, method, sig);
//创建出来
env->NewObject(personClass, init);

//2. 显式释放主动删除全局引用
env->DeleteLocalRef(personClass);
personClass = NULL;
}

Android-音视频学习系列(一)-JNI-从入门到精通,移动智能终端开发技术第三次作业

  1. 删除全局引用

当原生代码不再需要一个全局引用时,可以随时用 DeleteGlobalRef 函数释放它,如下代码所示:

env->DeleteLocalRef(personClass);
personClass = NULL;

弱全局引用

全局引用的另一种类型是弱全局引用。与全局引用一样,弱全局引用在原生方法的后续调用依然有效。与全局引用不同,弱全局引用并不阻止潜在的对象被垃圾收回。

  1. 创建弱全局引用

可以使用 NewWeakGlobalRef 函数对弱全局引用进行初始化,如下所示:

jclass personClass;
extern “C” //支持 C 语言
JNIEXPORT void JNICALL //告诉虚拟机,这是jni函数
native_test4(JNIEnv *env, jobject instance) {
LOGD(“测试局部引用”)

if (personClass == NULL) {
//1. 提升全局解决不能重复使用问题
const char *person_class = “com/devyk/ndk_sample/Person”;
jclass jclass1 = env->FindClass(person_class);
// personClass = static_cast(env->NewGlobalRef(jclass1));
personClass = static_cast(env->NewWeakGlobalRef(jclass1));
LOGD(“personClass == null 执行了。”)
}

//Java Person 构造方法实例化
const char *sig = “()V”;
const char *method = “”;//Java 构造方法标识
jmethodID init = env->GetMethodID(personClass, method, sig);
//创建出来
env->NewObject(personClass, init);

//2. 显式释放主动删除局部引用
// env->DeleteLocalRef(personClass);
env->DeleteWeakGlobalRef(personClass);
personClass = NULL;

}

  1. 弱全局引用的有效性检验

可以用 IsSameObject 函数来检验一个弱全局引用是否仍然指向活动的类实例.

  1. 删除弱全局引用

env->DeleteWeakGlobalRef(personClass);

全局引用显示释放前一直有效,它们可以被其它原生函数及原生线程使用。

8. JNI 线程操作

作为多线程环境的一部分,虚拟机支持运行的原生代码。在开发构件时要记住 JNI 技术的一些约束:

  • 只有再原生方法执行期间及正在执行原生方法的线程环境下局部引用是有效的,局部引用不能再多线程间共享,只有全局可以被多个线程共享。
  • 被传递给每个原生方法的 JNIEvn 接口指针在与方法调用相关的线程中也是有效的,它不能被其它线程缓存或使用。

同步:

同步是多线程程序设计最终的特征。与 Java 同步类似, JNI 的监视器允许原生代码利用 Java 对象同步,虚拟机保证存取监视器的线程能够安全执行,而其他线程等待监视器对象变成可用状态。

jint MonitorEnter(jobject obj)

对 MonitorEnter 函数的调用应该与对 MonitorExit 的调用相匹配,从而避免代码出现死锁。

例子:

public void test4(View view) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
count();
nativeCount();
}
}).start();
}
}

private void count() {
synchronized (this) {
count++;
Log.d(“Java”, “count=” + count);
}
}

public native void nativeCount();

native 代码:

虚拟机支持运行的原生代码。在开发构件时要记住 JNI 技术的一些约束:

  • 只有再原生方法执行期间及正在执行原生方法的线程环境下局部引用是有效的,局部引用不能再多线程间共享,只有全局可以被多个线程共享。
  • 被传递给每个原生方法的 JNIEvn 接口指针在与方法调用相关的线程中也是有效的,它不能被其它线程缓存或使用。

同步:

同步是多线程程序设计最终的特征。与 Java 同步类似, JNI 的监视器允许原生代码利用 Java 对象同步,虚拟机保证存取监视器的线程能够安全执行,而其他线程等待监视器对象变成可用状态。

jint MonitorEnter(jobject obj)

对 MonitorEnter 函数的调用应该与对 MonitorExit 的调用相匹配,从而避免代码出现死锁。

例子:

public void test4(View view) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
count();
nativeCount();
}
}).start();
}
}

private void count() {
synchronized (this) {
count++;
Log.d(“Java”, “count=” + count);
}
}

public native void nativeCount();

native 代码:

上一篇:Android函数抽取壳的实现


下一篇:Linux之poll机制分析