赞
踩
随着现在5g时代的到来,短视频技术越发的流行了,大家都在争着做短视频的app。当初看到抖音的时候我震惊了,和我以前用支付宝和淘宝一样震惊,用淘宝和支付宝结合,我能在家买到我想要的任何东西,我可以用支付宝充校园卡,交水电费等等,非常的方便。抖音出来的时候我看到的是各种漂亮的小姐姐,有趣的电影短视频剪辑,美食菜谱,生活技巧等应有尽有…,然而最吸引我的还是抖音电商。让大家能在娱乐的时候购物,这是一个双重体验。逛淘宝京东都是目的性的,我想起了啥才去逛,比如我想买衣服了,想买手机了我才想起他们,但是用抖音是在娱乐的过程中发现我还缺啥。真的很方便,我预言,在未来的十年,短视频电商一定会渐渐的抢占淘宝和京东这类的电商的份额。并且在其中占大头。说了这么多其实就是想赶紧抓住这个机会,学习音视频的开发。但是说到音视频的开发就不得不涉及到Android NDK的开发,今天的文章主要是为了介绍Android NDK的开发入门知识,虽说是入门,但是不是零基础入门那种,如果连ndk开发的基本流程都不知道的话请移步其他博客。本篇文章建议坐在电脑旁跟着敲一遍,印象会更深刻。
JNIEnv 可以理解成一个上下文,里面封装了jni的方法指针,JNIenv只在创建它的线程里有效,不能跨线程传递,不同线程之间的JNIEnv相互独立,互不影响
javaVm是虚拟机在JNI层的代表,每个进程只有一个,所有的线程共享一个JavaVM.当我们在native层创建了一个线程,若是想要和Java层通信时,也就是说需要使用JNIEnv对象,但是JNIEnv在不同的线程中不共享,这时候就需要使用到JavaVM的javaVm->AttachCurrentThread(&env,0)
方法把native线程附加到JVM,使用完后使用javaVm->DetachCurrentThread();
解除附加到JVM的native线程
当我们开发ndk的时候,会在Java中声明native方法,在c++文件中实现声明的方法,在 Java中声明native方法很简单,加一个native关键字就行,如下所示:
public native void stringFromJNI(boolean b,
byte b1,
char c,
short s,
long l,
float f,
double d,
String name,
int age,
int []i,
String[] str,
Person person,
boolean[] bArray);
public native Person getPerson();
声明完native方法后的下一步就是实现它,在c++文件中我们需要注册native方法,当Java层调用native的方法时,会执行native中实现的对应方法,这时候注册就分两种,一种是静态注册。
extern "C"
JNIEXPORT jobject JNICALL
Java_com_loveyoung_jnistudy_MainActivity_getPerson(JNIEnv *env, jobject instance) {...}
其实就是把“com.loveyoung.jnistudy.MainActivity”
中的点换成了下划线再加上要调用的方法,这就是静态注册,这种注册方法实现简单,但是缺点显而易见,那就是native方法名要写很长,当Java的native方法所在的类发生改变时,那么修改会很麻烦,比如Java的A类中有100个native方法,再native层的c++文件中使用静态注册的方法注册了这些方法,假如我们的A类改名字了,改成了B类。那么c++文件中的所有方法名字都得改。
和静态注册不同,动态注册更加的优雅,只是实现的过程稍微复杂了那么一点点。当我们在Java层定义完native方法后,在C++文件中按照如下的步骤注册方法:
(1)Java中定义native方法
public native void dynamicRegister(String name);
(2)在c++文件中写一个方法实现你要在native层完成的功能,例如:
extern "C" //支持c
JNIEXPORT void JNICALL //告诉虚拟机这时jni函数
native_dynamicRegister(JNIEnv *env,jobject instance,jstring name){
const char *j_name = env->GetStringUTFChars(name, nullptr);
LOGD("动态注册:%s",j_name)
//释放
env->ReleaseStringUTFChars(name,j_name);
}
(3)用一个静态数组保存你的Java native方法和C++文件中的方法等关联关系
static const JNINativeMethod jniNativeMethod[] = {
{"dynamicRegister","(Ljava/lang/String;)V",(void *) (native_dynamicRegister)}
};
数组中多个方法等关联关系可以在“{}”之间用逗号分隔,其中每个元素各部分的含义为:
{"Java中的native方法名(dynamicRegister)"
,"Java中的native方法名的签名((Ljava/lang/String;)V)"
,(void *) (native_dynamicRegister)(C++文件中定义的方法名)}
(4)重写JNI_OnLoad函数,这个函数定义在jni.h头文件中,当我们在Java层使用System.loadLibrary时会调用它:
static const char *classPathName = "com/loveyoung/jnistudy/MainActivity"; JavaVM *javaVm; jobject instance; extern "C" //支持c JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *javaVm,void *pVoid){ JNIEnv *jniEnv = nullptr; jint result = javaVm->GetEnv(reinterpret_cast<void **>(&jniEnv),JNI_VERSION_1_6); LOGD("result->%d",result); if(result != JNI_OK){ return JNI_ERR; } jclass mainActivityClass = jniEnv->FindClass(classPathName); //这里的jniNativeMethod就是第三步中咱们定义的Java和native层方法关联的数组 jniEnv->RegisterNatives(mainActivityClass,jniNativeMethod,sizeof(jniNativeMethod)/sizeof(JNINativeMethod));//动态注册的数量 return JNI_VERSION_1_6; }
使用动态注册的方法,到时候如果Java层有变动,C++层只需修改classPathName的值为新类的值以及Java native方法和C++文件中的方法等关联关系数组就可以啦,如果我们增加多个动态注册的方法,可以如下设置:
static const JNINativeMethod jniNativeMethod[] = {
{"dynamicRegister","(Ljava/lang/String;)V",(void *) (native_dynamicRegister)},
{"dynamicTestException","(Ljava/lang/String;)V",(void *)(native_dynamicTestException)},
{"test1","(Ljava/lang/String;)V",(void *)(native_test4)},
{"nativeCount","()V",(void *) (native_count)},
{"testThread","()V",(void *)(native_testThread)},
{"releaseThread","()V",(void *) (native_releaseThread)}
};
需要注意的是,方法签名一定要对,否则无法调用到相应的函数
我们都知道Java的类型分为引用类型和基本类型,对于基本类型,咱们在jni层都有相应的对应类型,例如,Java层的int,在jni层会有一个jint对应,同样,float会有个jfloat对应…,具体的大家可以去看下网上的其他博客,今天不多说。引用类型传递过来统一都是一个jobject。
类似于音视频的一些功能,比如编解码、美颜算法,裁剪,瘦脸算法等都会放到native层实现。这时美颜算法我们用的最多的就是调节各种美颜参数,剪辑视频等功能,都是需要和用户交互,而Android开发中交互在Java层,所以就需要把用户调节的参数传递到native层,这时就需要将Java层的参数传递到native层,具体的演示如下所示:
(1)Java层的native方法定义:
public native void stringFromJNI(boolean b, byte b1, char c, short s, long l, float f, double d, String name, int age, int []i, String[] str, Person person, boolean[] bArray); 调用: 在Activity的onCreate()方法中调用 stringFromJNI(true, (byte)1, 'A', (short) 3, 4, 3.3f, 2.2d, "zhongxj", 28, new int[]{1,2,3,4,5,6,7}, new String[]{"I","love","you"}, new Person("walt",27), new boolean[]{false,true} );
(2)native层的接收;
extern "C" JNIEXPORT void JNICALL Java_com_loveyoung_jnistudy_MainActivity_stringFromJNI( JNIEnv* env, jobject instance, jboolean jboolean1, jbyte jbyte1, jchar jchar1, jshort jshort1, jlong jlong1, jfloat jfloat1, jdouble jdouble1, jstring name, jint age, jintArray array, jobjectArray strArr, jobject person, jbooleanArray bArray) { //1.接收Java传递过来的boolean 值 jboolean b_boolean = jboolean1; LOGD("boolean->%d",b_boolean); //2.接收Java传递过来的byte值 jbyte c_byte = jbyte1; LOGD("jbyte->%d",c_byte); //3.接收Java传递过来的char值 jchar j_char = jchar1; LOGD("j_char->%c",j_char); //4.接收Java传递过来的short值 jshort j_short = jshort1; LOGD("j_short->%d",j_short) //5.接收Java传递过来的long值 jlong j_long = jlong1; LOGD("j_long->%lld",j_long) //6. 接收Java传递过来的float值 jfloat j_float = jfloat1; LOGD("j_float->%f",j_float) //7. 接收Java传递过来的double值 jdouble j_double = jdouble1; LOGD("j_double->%f",j_double) //8.接收Java传递过来的String值 const char *j_string = env->GetStringUTFChars(name,nullptr); LOGD("j_string->%s",j_string) //9.接收Java传递过来的int值 jint j_int = age; LOGD("j_int->%d",j_int) //10.打印Java传递过来的int数组 jint *intArray = env->GetIntArrayElements(array,nullptr); //拿到数组长度 jsize intArraySize = env->GetArrayLength(array); for (int i=0;i<intArraySize;i++){ LOGD("intArray:%d",intArray[i]) } //用完记得释放数组 env->ReleaseIntArrayElements(array,intArray,0); //11.打印Java传递过来的String数组 jsize strAttrLength = env->GetArrayLength(strArr); for(int i = 0;i<strAttrLength;i++){ jobject jobject1 = env->GetObjectArrayElement(strArr,i); //强制转成JNI string auto stringArrData = static_cast<jstring> (jobject1); //转 string const char *itemStr = env->GetStringUTFChars(stringArrData,nullptr); LOGD("String[%d]:%s",i,itemStr) //回收String[] env->ReleaseStringUTFChars(stringArrData,itemStr); } //12.打印Java传递过来的Object 对象 //1获取字节码 const char *person_class_str = "com/loveyoung/jnistudy/Person"; //2 转jni class jclass person_class = env->FindClass(person_class_str); //3 拿到方法签名 const char *sig = "()Ljava/lang/String;"; jmethodID jmethodId = env->GetMethodID(person_class,"getName",sig); jobject obj_string = env->CallObjectMethod(person,jmethodId); jstring perStr = static_cast<jstring >(obj_string); const char *itemStr2 = env->GetStringUTFChars(perStr,NULL); LOGD("Person:%s",itemStr2); env->DeleteLocalRef(person_class);//回收 env->DeleteLocalRef(person);//回收 //13.打印Java传递过来的boolean 数组 jsize booleanArratLength = env->GetArrayLength(bArray); jboolean *booleanArray = env->GetBooleanArrayElements(bArray,NULL); for(int i = 0;i<booleanArratLength;++i){ bool b = booleanArray[i]; jboolean b2 = booleanArray[i]; LOGD("boolean:%d",b) LOGD("boolean:%d",b2) } env->ReleaseBooleanArrayElements(bArray,booleanArray,0); }
注释中写得很清楚,就不多说了。照着敲一遍理解会更深刻哦~~~
(1)局部引用
局部应用是通过NewLocalRef和DeleteLocalRef方法创建和释放的。局部引用只在创建它的线程中有效,通常不用删除局部引用,他们会在native方法中返回时全部自动释放,但是建议不再使用的时候手动释放掉局部引用,避免内存的过度使用
1.在 Java层定义测试局部引用的方法
public native void testLocalRef(String name);
2.使用动态注册将方法注册到native层
native_testLocalRef(JNIEnv *env, jobject instance, jstring name) { const char *nameStr = env->GetStringUTFChars(name, nullptr); LOGE("测试局部引用:%s", nameStr) if (personClass == NULL) { const char *person_class = "com/loveyoung/jnistudy/Person"; jclass jclass1 = env->FindClass(person_class); personClass = static_cast<jclass>(env->NewLocalRef(jclass1)); LOGE("personClass == NULL execute") } //java Person 构造方法实例化 const char *sig = "()V"; const char *method = "<init>"; jmethodID init = env->GetMethodID(personClass, method, sig);//这个地方personClass无法使用,因为是局部引用 //创建出来 env->NewObject(personClass, init); }
全局引用
代码中由于是局部引用,所以在后面无法被使用。要解决这个问题,需要使用全局引用,全局引用是通过NewGlobalRef和DeleteGlobalRef方法创建和释放一个全局引用,全局引用能在多个线程中被使用,且不会被GC回收,需要手动释放,而与之对应的是弱全局引用,它是通过newWeakGlobalRef和DeleteWeakGrobalRef,创建和释放一个弱全局引用,弱全局引用类似于全局引用,唯一的区别是他不会阻止被GC回收。
native_testLocalRef(JNIEnv *env, jobject instance, jstring name) { const char *nameStr = env->GetStringUTFChars(name, nullptr); LOGE("测试局部引用:%s", nameStr) if (personClass == NULL) { const char *person_class = "com/loveyoung/jnistudy/Person"; //personClass = env->FindClass(person_class);//局部引用不能再后续的调用中重复使用,所以需要构建全局引用来解决这个问题 //提升全局解决不能重复使用的问题 jclass jclass1 = env->FindClass(person_class); personClass = static_cast<jclass>(env->NewGlobalRef(jclass1)); LOGE("personClass == NULL execute") } //java Person 构造方法实例化 const char *sig = "()V"; const char *method = "<init>"; jmethodID init = env->GetMethodID(personClass, method, sig); //创建出来 env->NewObject(personClass, init); //显示删除全局引用 env->DeleteGlobalRef(personClass); personClass = NULL; }
假如我们有这样一个场景,在native层创建一个线程,然后在创建的线程中调用Java层的方法更新界面UI,这时我们可以像下面这样实现:
Java层的方法声明
public native void testThread();
public native void releaseThread();
定义一个方法用于更新UI
public void updateUI(){ if(Looper.getMainLooper() == Looper.myLooper()){ new AlertDialog.Builder(MainActivity.this) .setTitle("更新UI") .setMessage("Native 线程运行在主线程,可以直接更新UI") .setPositiveButton("OK",null) .show(); }else{ runOnUiThread(new Runnable() { @Override public void run() { new AlertDialog.Builder(MainActivity.this) .setTitle("更新UI") .setMessage("Native 线程运行在子线程切换为主线程更新UI") .setPositiveButton("OK",null) .show(); } }); } }
在C++ 文件中,声明一个函数自定义一个线程
void *customThread(void *pVoid) { //调用的话一定需要jniENV *env //JNIEnv *env 无法跨线程,只有JavaVm才可以跨进程 JNIEnv *env = NULL; int result = javaVm->AttachCurrentThread(&env, 0);//把native线程附加到JVM if (result != 0) { return 0; } jclass mainActivityClass = env->GetObjectClass(instance); //拿到MainActivity的UpdateUI const char *sig = "()V"; jmethodID updateUI = env->GetMethodID(mainActivityClass, "updateUI", sig); env->CallVoidMethod(instance, updateUI); //解除附加到JVM的native线程 javaVm->DetachCurrentThread(); return 0; }
分别调用创建线程和释放线程的方法
extern "C" JNIEXPORT void JNICALL native_testThread(JNIEnv *env, jobject thiz) { instance = env->NewGlobalRef(thiz);//声明成全局的,就不会被释放,所以可以在线程里面用 pthread_t pthread; pthread_create(&pthread, 0, customThread, instance); pthread_join(pthread, 0); } extern "C" JNIEXPORT void JNICALL native_releaseThread(JNIEnv *env, jobject thiz) { if (NULL != instance) { env->DeleteGlobalRef(instance); instance = NULL; } }
本节中主要介绍了如下知识:
(1)jni方法和Java层方法相互传值和类型
(2)jni方法的动态注册和静态注册的区别和使用
(3)三种引用的区别和使用
(4)最后是创建线程然后调用Java层方法更新UI的方法
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。