NDK开发之JNI基础

1、JNI 使用场景
(1)、提高代码安全性。因为.so文件反编译困难。(加密一些算法等)
(2)、方便使用已经存在的c/c++库。可以加载 .dll 和 .so 格式的动态库。
(3)、提高某些特定情况下的执行效率。(c/c++的效率比java高。不过并不能明显提升android程序的性能)


2、JNI语法
#ifdef __cplusplus
extern "C"
#endif
// 如果是c++代码,也统一用c的语法去编译。
#ifdef __cplusplus
extern "}"
#endif


extern "C" JNIEXPORT void JNICALL
Java_com_xxx_nPlay(JNIEnv *env, jobject instance, jstring url_) { 
 ...
}

// JNIEXPORT 标记为该方法可以被外部调用

3、JNIEnv
JNIEnv是 JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv;
每个JNIEnv 都是线程专有的, 其它线程不能使用本线程中的 JNIEnv, 线程 A 不能调用 线程 B 的 JNIEnv;
但是,一个本地方法可被不同的 Java 线程所调用,因此可以接受不同的 JNIEnv。

//JNIEnv 作用
(1)、调用Java函数
JNIEnv代表Java运行环境,可以使用JNIEnv调用Java中的代码;

(2)、操作Java对象
Java对象传入JNI层就是Jobject对象, 需要使用JNIEnv来操作这个Java对象;


4、JNI的数据类型
java基本类型            JNI类型            bit    -                
boolean                    jboolean        8        
byte                    jbyte            8        
char                    jchar            16        
short                    jshort            16        
void                    void            无        
int                        jint            32    
long                    jlong            64    
float                    jfloat            32
double                    jdouble            64

java引用类型            JNI类型
Object                    jobject        
Class                    jclass        
String                    jstring        
Object[ ]                jobjectArray        
boolean[ ]                jbooleanArray        
byte[ ]                    jbyteArray        
char[ ]                    jcharArray
short[ ]                jshortArray
int[ ]                    jintArray
long[ ]                    jlongArray
float[ ]                jfloatArray
double[ ]                jdoubleArray
Throwable                jthrowable


5、JNI签名
java中,对于某一个对象来说, 可以用过方法名和参数 就可以确定唯一的一个方法。
在JNI中,通过函数名和 签名信息 可以确定唯一的方法。
(1)、基本数据签名的规则
java类型              JNI类型    
boolean                    Z        
byte                    B        
char                    C    
short                    S        
int                        I
long                    J
float                    F
double                    D
void                    V


(2)、签名详解
a、类的签名(注意末尾的分号)
L+包名+类名+;
java.lang.String  ->   Ljava/lang/String;

b、// 对象签名
跟它所属的类的签名一样。

c、 一维数组签名
格式: [类型
double[ ]  ->  [ D

d、多维数组签名
格式:  n个 [   + 类型签名
int[ ] [ ]   ->  [[I

e、方法签名   (跟方法名无关)
格式: (参数类型签名) + 返回值类型签名
boolean fun( int a , double b , int[ ] c)   ->     (ID[I)Z
void fun(int i)  -> (I)V

6、Java 和 C 函数的映射表
{ java中函数名字 , 参数和返回值 , 对应的c函数}
{
  "setUsbLinkVideoData",
  "(Ljava/nio/ByteBuffer;I)V",
    (void*)com_media_ffmpeg_FFMpegPlayer_setUsbLinkVideoData
}


7、常用函数
(1)、FindClass
(2)、GetMethodID
(3)、CallIntMethod  调用返回值为Int的java方法
(4)、CallVoidMethod
(5)、GetObjectClass

(6)、GetStaticMethodID
(7)、CallStaticIntMethod
(8)、NewObject
(9)、CallVoidMethod
(10)、CallStaticObjectMethod


(11)、NewStringUTF
(12)、GetStringUTFChars   得到一个UTF-8编码的字符串
(13)、GetStringChars     得到UTF-16编码的字符串
(14)、ReleaseStringUTFChars
(15)、JNI_OnLoad    so库加载时调用。如果你的*.so档没有提供JNI_OnLoad()函数,VM会默认使用最老的JNI 1.1版本。

(16)、NewGlobalRef   新建全局变量
(17)、DeleteGlobalRef
(18)、AttachCurrentThread 当前线程获取JNI环境
(19)、DetachCurrentThread
(20)、SetByteArrayRegion 将本地的数组数据拷贝到了 Java 端的数组中
(21)、CallObjectMethod


(21)、env->ThrowNew    抛出异常信息
                        jclass exceptionClazz = env->FindClass("java/lang/RuntimeException");
                        env->ThrowNew(exceptionClazz, "自错误信息");
(22)、reinterpret_cast     类型强制转换
                           MyParcel* parcel = reinterpret_cast <MyParcel*>(nativePtr);
(23)、GetStaticFieldID
(24)、SetStaticFieldID
(25)、DeleteLocalRef


8、JNI_OnLoad(...)作用
System.loadLibrary()函数时,会立即先呼叫JNI_OnLoad()
JavaVM* java_vm = NULL;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv *env;
    if (vm->GetEnv(  (void**) &env,  JNI_VERSION_1_4) != JNI_OK)
    {
        return JNI_ERR ;
    }
    java_vm = vm;
    return JNI_VERSION_1_4;
}

用途有两个:
(1)、告诉VM此C组件使用那一个JNI版本。
  如果你的*.so档没有提供JNI_OnLoad()函数,VM会默认该*.so档是使用最老的JNI 1.1版本。
  由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,
  例如JNI 1.4的java.nio.ByteBuffer,就必须藉由JNI_OnLoad()函数来告知VM。
  
  
(2)、由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(),
  所以C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的初期值之设定(Initialization) 

9、打印日志
#include <android/log.h>
#define LogTag "WK_LOGER"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LogTag ,__VA_ARGS__);

10、缓存策略
直接用全局变量也可以(但是可能会被其他类的程序修改),所以建议用静态static较安全。
Java_com_xxx_staticLocalCache(JNIEnv *env, jclass jclz, jstring name) { 
    static jfieldID j_fid = NULL;  //局部缓存
    if (j_fid == NULL) {
        j_fid = (*env)->GetStaticFieldID(env, jclz, "name", "Ljava/lang/String;")
    }  
}

static jfieldID j_fid = NULL;// 全局缓存
Java_com_xxx_initStaticCache  (JNIEnv *env, jclass jclz){
    j_fid = (*env)->GetStaticFieldID(env, jclz, "name", "Ljava/lang/String;");
}
// 这也叫缓存?乱七八糟。

11、java调用native
注意文件夹路径要和native中的签名一样
static {
    System.loadLibrary("music-player");
}
private native void nPlay(String url);

12、native 调用 java
(1)、C++主线程调用Java方法
a、根据jobject获取jclass
 jclass jlz = env->GetObjectClass(instance);//jobject instance
b、获取 jmethodID
jmethodID jmid_error= jniEnv->GetMethodID(jlz, "onError", "(ILjava/lang/String;)V");
//注意:方法签名的规则 :   (参数类型签名) + 返回值类型签名
//要注意参数的签名,否则会报错 找不到对应的方法
c、调用方法
jniEnv->CallVoidMethod(jobj, jmid_error, code, jmsg);// jobject,jmethodID , 参数...


(2)、C++子线程调用Java方法
注意:JniEnv是线程相关的,所以子线程中不能使用创建线程的JniEnv;
JVM是进程相关的,可以通过JVM来获取当前线程的JniEnv,然后才能调用Java的方法。
JNIEnv *jniEnv;
if (javaVM->AttachCurrentThread(&jniEnv, 0) != JNI_OK)
{
    return;
}
// 如果获取成功,AttachCurrentThread 就会返回一个属于当前线程的JNIEnv指针。
jniEnv->CallVoidMethod(...);
//执行相关操作...
javaVM->DetachCurrentThread();

13、POSIX线程
Android NDK的线程是通过POSIX标准实现的,所以也叫POSIX线程。
POSIX 使用
//头文件
#include <pthread.h>
// 创建线程锁对象
pthread_mutex_t mutex;
// 用来申明一个线程对象
pthread_t thread
//创建线程条件对象
pthread_cond_t cond; 
pthread_attr_t:线程属性
Thread-local storage(或者以Pthreads术语,称作线程特有数据):
常用方法                                    说明
pthread_create(...)                        创建线程.
                                        pthread_create(&playThreadT, NULL, threadPlay, this);
                                        最后一个参数是运行函数的参数。
pthread_detach()                    即主线程与子线程分离,子线程结束后,子线程资源自动回收
pthread_exit()                        终止当前线程
pthread_join()                        阻塞当前的线程,直到另外一个线程运行结束
pthread_attr_init()                    初始化线程的属性
pthread_attr_setdetachstate()        决定这个线程在终止时是否可以被结合
pthread_attr_getdetachstate()        获取脱离状态的属性
pthread_attr_destroy()                删除线程的属性
pthread_kill()                        向线程发送一个信号
pthread_mutex_init(...)                初始化互斥锁
pthread_mutex_destroy(...)            删除互斥锁
pthread_mutex_lock(...)                占有互斥锁(阻塞操作)
pthread_mutex_trylock()                试图占有互斥锁
pthread_mutex_unlock()                释放互斥锁
pthread_cond_init(...)                初始化条件对象
pthread_cond_destroy(...)            销毁条件对象
pthread_cond_signal(...)            发出条件信号,唤醒第一个调用pthread_cond_wait()而进入睡眠的线程
pthread_cond_wait()                    用于线程阻塞等待,直到 pthread_cond_signal 发出条件信号后才执行后续操作
pthread_key_create()                分配用于标识进程中线程特定数据的键
pthread_setspecific()                为指定线程特定数据键设置线程特定绑定
pthread_getspecific()                获取调用线程的键绑定,并将该绑定存储在 value 指向的位置中
pthread_key_delete()                销毁现有线程特定数据键
pthread_attr_getschedparam()        获取线程优先级
pthread_attr_setschedparam()        设置线程优先级
pthread_equal()                        对两个线程的线程标识号进行比较
pthread_self()                        查询线程自身线程标识号
pthread_cancel()                    在安卓NDK中已经被删除。因为比较危险。

14、java与native共享内存
大概原理,在native开辟一块内存。将这块内存的首地址传给java层,以后的相关操作就通过这个地址获得这块内存,用native代码直接操作内存。
暂时用得少,以后再补充。
android中的应用有 Parcel,因为直接操作内存的,所以比操作IO的Serializable 效率高。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

粤M温同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值