NDK开发问题合集

  1. 一、介绍
  • JNI是什么
  • 函数命名的规则是什么
    1. 二、基础记录
  • 1 类型
    1. 1.1 基本类型
    2. 1.2 数组类型
    3. 1.3 引用类型
    4. 1.4 属性和方法
    5. 1.5 jvalue(特殊类型)
    6. 1.6 类型签名
    7. 1.7 字符串处理
      1. 1.7.1 创建jstring
      2. 1.7.2 获取字符串长度
      3. 1.7.3 jstring转化为C串及释放jstring串
        1. 简单字符串处理
        2. 复杂字符串处理
        3. 复杂字符串处理2
  • 2.静态方法(jclass)与实例方法(jobject)
    1. 三、类与异常问题
  • 1.类
    1. 1.1 加载类
    2. 1.2 查找一个类
    3. 1.3 获取父类
    4. 1.4 类型转换
  • 2.异常操作
    1. 2.1 抛出异常
    2. 2.2 构造一个新的异常并抛出
    3. 2.3 检查是否发生异常,并抛出异常
    4. 2.4 打印异常的堆栈信息
    5. 2.5 清除异常的堆栈信息
    6. 2.6 致命异常
    7. 2.7 仅仅检查是否发生异常
  • 四、全局引用、局部引用、弱全局引用
  • 全局引用
    1. 创建
    2. 删除
  • 局部引用
    1. 创建
    2. 删除
    3. 生命周期管理函数组
  • 弱全局引用
    1. 创建
    2. 删除
  • 五、对象操作
  • 1 直接创建一个Java对象
  • 2 根据某个构造函数来创建Java对象
  • 3 获取某个对象的类信息
  • 4 获取某个对象的引用类型
  • 5 判断某个对象是否是某个“类”的子类
  • 6 判断两个引用是否指向同一个引用
  • 7 返回属性id
  • 8 返回属性id系列
  • 9 设置属性id系列
  • 10 获取某个类的某个方法id
  • 11 调用Java实例的某个非静态方法“系列”
  • 12 调用某个类的非抽象方法
  • 13 获取静态属性
  • 14 获取静态属性系列
  • 15 设置静态属性系列
  • 16 获取静态函数ID
  • 17 调用静态函数系列
    1. 六、字符串操作
    2. 七、数组操作
    3. 八、系统级别的操作
    4. 九、NIO操作
      1. 打开文件
        1. 操作类型
  • 写入文件
  • 十、反射支持
  • 十一、线程
    1. 线程概念
    2. 相关函数
      1. 创建线程
      2. 等待子线程
  • 十二、JavaVM对象与JNIEvn对象
    1. JavaVM结构体
    2. JNIEnv结构体
  • 二、常见问题
  • GetPrimitiveArrayCritical & ReleasePrimitiveArrayCritical
  • 一、介绍

    目前Android系统支持以下七种不用的CPU架构,每一种对应着各自的应用程序二进制接口ABI:(Application Binary Interface)定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。
    关系如下:

    架构 简称
    ARMv7以下 armeabi
    ARMv7 armeabi-v7a
    ARMV8 arm64-v8a
    x86 x86
    x86_64 x86_64
    MIPS mips
    MIPS64 mips64

    JNI是什么

    全称为Java Native Interface,即Java本地接口。JNI是Java调用Native 语言的一种特性。通过JNI可以使得Java与C/C++机型交互。由于JNI是JVM规范的一部分,因此可以将我们写的JNI的程序在任何实现了JNI规范的Java虚拟机中运行。同时,这个特性使我们可以复用以前用C/C++写的大量代码JNI是一种在Java虚拟机机制下的执行代码的标准机制。代码被编写成汇编程序或者C/C++程序,并组装为动态库。也就允许非静态绑定用法。

    函数命名的规则是什么

    例如在.c文件中:

    JNIExport jstring JNICALL Java_com_yang_demo_MainActivity_stringFromJNI( JNIEnv* env,jobject thiz )

    • jstring为返回类型
    • Java_com_yang_demo是包名
    • MainActivity是类名
    • stringFromJNI是方法名
    • JNIExport和JNICALL是不固定保留的关键字

    二、基础记录

    1 类型

    1.1 基本类型

    Java类型 JNI类型 大小
    boolean jboolean 无符号8位
    byte jbyte 有符号8位
    char jchar 无符号16位
    short jshort 有符号16位
    int jint 有符号32位
    long jlong 有符号64位
    float jfloat 32位
    double jdouble 64位
    void void N/A

    1.2 数组类型

    Java类型 JNI类型 大小
    boolean jboolean 无符号8位
    byte jbyte 有符号8位
    char jchar 无符号16位
    short jshort 有符号16位
    int jint 有符号32位
    long jlong 有符号64位
    float jfloat 32位
    double jdouble 64位
    void void N/A

    1.3 引用类型

    • jobject
    • jclass
    • jstring
    • jarray
    • jobjectArray
    • jbooleanArray
    • jbyteArray
    • jcharArray
    • jshortArray
    • jintArray
    • jlongArray
    • jfloatArray
    • jdoubleArray
    • jthrowable

    1.4 属性和方法

    • jfieldID
    • jmethodID

    1.5 jvalue(特殊类型)

    jvalue共用体可以保存多种类型的数据

    typedef union jvalue {
    jboolean z;
    jbyte b;
    jchar c;
    jshort s;
    jint i;
    jlong j;
    jfloat f;
    jdouble d;
    jobject l;
    } jvalue;

    1.6 类型签名

    用于对java虚拟机内容表示

    类型签名 java类型
    Z boolean
    B byte
    C char
    S short
    I int
    J long
    F float
    D double
    L全类名;
    [type type[]
    (参数类型签名,…)返回值类型签名 方法类型

    例如:

    int func(float a, String b, byte[] c)

    对应的签名为:
    (FLjava/lang/String;[B)I

    1.7 字符串处理

    1.7.1 创建jstring

    创建Unicode格式的jstring串

    jstring NewString(const jchar *unicodeChars, jsize len);

    创建UTF-8格式的jstring串

    jstring NewStringUTF(const char *bytes);

    1.7.2 获取字符串长度

    获取Unicode格式的jstring串长度

    jsize GetStringLength(jstring string);

    获取UTF-8格式的jstring串长度

    jsize GetStringUTFLength(jstring string);

    1.7.3 jstring转化为C串及释放jstring串

    简单字符串处理

    GetStringXXXChars & ReleaseStringXXXChars
    将jstring转为C字符串:

    const jchar* GetStringChars(jstring string, jboolean *isCopy);
    const char* GetStringUTFChars(jstring string, jboolean *isCopy );

    如果生成串的一个副本,isCopy参数将被置为JNI_TRUE,否则置为NULL或者JNI_FALSE

    通知虚拟机平台相关代码无需再访问 chars,释放指针。

    void ReleaseStringChars(jstring string, const jchar *chars);
    void ReleaseStringUTFChars(jstring string, const char *utf);

    复杂字符串处理

    GetStringRegion & GetStringUTFRegion
    GetStringRegion函数将串str的一个子串传送到一个字符缓存器。该子串在位置start处开始,在len-1处结束(这样传送的字符数就是len)

    void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf);
    void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf);

    复杂字符串处理2

    GetStringCritical & ReleaseStringCritical
    该函数返回一个指向特定串中字符的指针。如果需要复制该字符,并且函数返回时将isCopy置为JNI_TRUE,否则置为NULL或JNI_FALSE。

    const jchar* GetStringCritical(jstring string, jboolean *isCopy);
    void ReleaseStringCritical(jstring string,const jchar *carray);

    在调用该函数后,直至调用ReleaseStringCritical之前,所使用的所有函数都无法使当前线程被阻塞。

    2.静态方法(jclass)与实例方法(jobject)

    在添加native方法时有静态方法与实例方法两种方式,但是在生成的C/C++函数会略有不同:

    Java_com_example_myrtmp_YuvLib_init(JNIEnv *env, jobject jobj);
    Java_com_example_myrtmp_YuvLib_init(JNIEnv *env, jclass clazz);

    区别就在于获取的应用类型:实例引用(jobject)和类引用(jclass)。
    实例应用对应于Object,类引用对应于Class。前者为实例操作,后者为类操作。可以通过下面函数看出来:
    // 参数都是jclass
    jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
    jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...);
    jobject (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);

    // 参数都是jobject
    jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);
    jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
    object (*GetObjectField)(JNIEnv*, jobject, jfieldID);

    三、类与异常问题

    1.类

    1.1 加载类

    jclass DefineClass(JNIEnv *env, const char *name, jobject loader,
    const jbyte *buf, jsize bufLen);
    • name:类的全名。
    • loader:类加载器。
    • buf:包含类数据的数据指针。包含.class文件数据的buffer。
    • bufLen:buf长度。
    • return:java类对象。发生错误时返回NULL。

    如果没有指定这个Java类的,则会抛出ClassFormatError
    如果是一个类或者接口是它自己的一个父类/父接口,则会抛出ClassCircularityErrora,类循环错误
    如果内存不足,则会抛出OutOfMemoryError
    如果想尝试在Java包中定义一个类,则会抛出SecurityException

    1.2 查找一个类

    jclass FindClass(JNIEnv *env, const char *name);
    • env:JNI接口指针
    • name:一个完全限定的类名,即包含“包名”+“/”+类名。如果类名以[开头,将返回一个数组类]
    • return:返回对应完全限定类对象,当找不到类时,返回NULL

    如果没有指定这个Java类的,则会抛出ClassFormatError
    如果是一个类/接口是它自己的一个父类/父接口,则会抛出ClassCircularityError
    如果没有找到该类/接口的定义,则抛出NoClassDefFoundError
    如果内存不足,则会抛出OutOfMemoryError

    1.3 获取父类

    jclass GetSuperclass(JNIEnv *env,jclass clazz);
    • env:JNI接口指针
    • clazz:Java的Class类
    • retur:如果clazz有父类则返回其父类,如果没有其父类则返回NULL

    1.4 类型转换

    jboolean IsAssignableFrom(JNIEnv *env,jclass clazz1,jclass clazz2);

    判断clazz1的对象是否可以安全地转化为clazz2的对象

    • env:JNI接口指针
    • clazz1:Java的Class类,即需要被转化的类
    • clazz2:Java的Class类,即需要转化为目标的类
    • return:jni_true or jni_false

    如果满足以下任一条件,则返回JNI_TRUE:

    如果clazz1和clazz2是同一个Java类。
    如果clazz1是clazz2的子类
    如果clazz1是clazz2接口的实现类

    2.异常操作

    2.1 抛出异常

    jint Throw(JNIEnv *env,jthrowable obj);

    传入一个jthrowable对象,并且在JNI并将其抛出。

    • env:JNI接口指针
    • jthrowable:一个Java的java.lang.Throwable对象
    • return:成功为0,失败为负数

    2.2 构造一个新的异常并抛出

    jint ThrowNew(JNIEnv *env,jclass clazz,const char* message);

    传入一个message,并用其构造一个异常并且抛出。

    • env:JNI接口指针
    • jthrowable:一个Java的java.lang.Throwable对象
    • message:用于构造一个java.lang.Throwable对象的消息,该字符串用modified UTF-8编码
    • return:成功为0,失败为负数

    2.3 检查是否发生异常,并抛出异常

    jthrowable ExceptionOccurred(JNIEnv *env);

    检测是否发生了异常,如果发生了,则返回该异常的引用(再调用ExceptionClear()函数前,或者Java处理异常前),如果没有发生异常,则返回NULL。

    • env:JNI接口指针
    • return:jthrowable的异常引用或者NULL

    2.4 打印异常的堆栈信息

    void ExceptionDescribe(JNIEnv *env)

    打印这个异常的堆栈信息

    • env:JNI接口指针

    2.5 清除异常的堆栈信息

    void ExceptionClear(JNIEnv *env);

    清除正在抛出的异常,如果当前没有异常被抛出,这个函数不起作用

    • env:JNI接口指针

    2.6 致命异常

    void FatalError(JNIEnv *env,const char* msg);

    致命异常,用于输出一个异常信息,并终止当前VM实例,即退出程序。

    • env:JNI接口指针
    • msg:异常的错误信息,该字符串用modified UTF-8编码

    2.7 仅仅检查是否发生异常

    jboolean ExceptionCheck(JNIEnv *env);

    检查是否已经发生了异常,如果已经发生了异常,则返回JNI_TRUE,否则返回JNI_FALSE

    • env:JNI接口指针

    四、全局引用、局部引用、弱全局引用

    全局引用

    给对象obj创建一个全局引用,可以跨线程处理。

    创建

    jobject NewGlobalRef(JNIEnv *env,object obj);

    obj可以是任意引用。由于全局引用不再受到JVM统一管理,因此全局引用必须通过DeleteGlobalRef()手动释放。否则引用不会被GC回收。

    • obj:任意类型的引用。
    • return:全局引用。如果内存不足返回NULL。

    删除

    void DeleteGlobalRef(JNIEnv *env,jobject globalRef);

    删除全局引用。

    • globalRef:全局引用。

    局部引用

    局部引用有JVM负责的引用类型,占用JVM资源。在native方法返回后会被自动回收。因此只会在创建他们的线程中有效。

    创建

    jobject NewLocalRef(JNIEnv *env, jobject ref);
    • ref:全局或者局部引用
    • return:局部引用

    删除

    void DeleteLocalRef(JNIEnv *env, jobject localRef);

    删除局部引用

    • localRef:局部引用。

    生命周期管理函数组

    在虚拟机管理局部引用时,JNI为了确保可以创建成功,提供一组生命周期管理函数来帮助管理。

    • 确认局部引用容量

      确认空间是否充足
      jint EnsureLocalCapacity(JNIEnv *env, jint capacity);
      capacity:给定局部引用的数量。
      return:JNI_OK表示当前线程栈可以创建capacity个局部引用。返回其他值表示不可用,并抛出一个OutOfMemoryError异常

    • 局部栈帧的入栈

      创建局部引用的入栈处理
      jint PushLocalFram(JNIEnv *env ,jint capacity);
      capacity:给定局部引用的数量。
      return:JNI_OK表示当前线程栈可以创建capacity个局部引用。返回其他值表示不可用,并抛出一个OutOfMemoryError异常
      注意:当前的局部帧中,前面的局部帧创建的局部引用仍然是有效的

    • 局部栈帧的出栈

      销毁局部引用的出栈处理
      jobject PopLocalFrame(JNIEnv *env, jobject result);
      result:给定保存栈帧的引用,如果不需要前一个栈帧则可以传入NULL。
      return:前一个帧的引用。

    弱全局引用

    弱全局引用是一种特殊的全局引用。不同的是,一个弱全局引用允许Java对象被垃圾回收器回收。一个被回收了的弱引用指向NULL。

    创建

    jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);

    创建一个弱全局引用

    • obj:任意对象。
    • return:返回弱全局引用,如果obj为NULL则返回NULL。

    删除

    void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);

    删除一个弱全局引用

    • obj:弱全局引用。

    五、对象操作

    1 直接创建一个Java对象

    jobject AllocObject(JNIEnv *env,jclass clazz);

    不借助任何构造函数的情况下分配一个新的Java对象,返回对象的一个引用。

    • env:JNI接口指针
    • clazz:Java类对象
    • return:返回一个Java对象,如果该对象无法被创建,则返回NULL。

    注意:

    如果该类是接口或者是抽象类,则抛出InstantiationException
    如果是内存溢出,则抛出OutOfMemoryError

    2 根据某个构造函数来创建Java对象

    jobject NewObject(JNIEnv *env,jclass clazz,jmethodID methodID,...);
    jobject NewObjectA(JNIEnv *env,jclass clazz,jmethodID methodID,const jvalue *args);
    jobject NewObjectV(JNIEnv *env,jclass clazz,jmethodID methodID,va_list args);

    提供了三种创建对象的方式,构造一个新的Java对象。

    • clazz:类
    • methodID:构造器方法ID
    • …:可变参数列表
    • args:这里需要传入参数数组
    • args:指向变参列表的指针

    3 获取某个对象的类信息

    jclass GetObjectClass(JNIEnv *env,object obj);

    返回obj对象对应的类信息

    • env:JNI接口指针
    • obj:Java对象,不能为NULL
    • return:java对象对应的类

    4 获取某个对象的引用类型

    jobjectRefType GetObjectRefType(JNIEnv *env,jobject obj);

    返回obj对象对应的引用类型,可以得到当前对象的引用类型是全局引用、局部引用还是弱全局引用。

    • return:当前对象的引用类型。这些类型在jni.h文件中的定义如下所示。
      typedef enum jobjectRefType {
      JNIInvalidRefType = 0,//无效引用
      JNILocalRefType = 1,//局部引用
      JNIGlobalRefType = 2,//全局引用
      JNIWeakGlobalRefType = 3//弱全局引用
      } jobjectRefType;

    5 判断某个对象是否是某个“类”的子类

    jboolean IsInstanceOf(JNIEnv *env, jobject obj,jclass clazz); 

    测试obj是否是clazz的一个实例

    • env:JNI接口指针
    • obj:一个Java对象
    • clazz:一个Java的类
    • return:是则返回JNI_TRUE;否则则返回JNI_FALSE

    6 判断两个引用是否指向同一个引用

    jboolean IsSampleObject(JNIEnv *env,jobject ref1,jobject ref2);

    判断两个引用是否指向同一个对象

    • env:JNI接口指针
    • ref1:Java对象
    • ref2:Java对象
    • return:同一个类对象,返回JNI_TRUE;否则,返回JNI_FALSE

    7 返回属性id

    jfieldID GetFieldID(JNIEnv *env,jclass clazz,const char *name,const char *sig);

    获取某个类的非静态属性id。通过方法属性名以及属性的签名,来确定对应的是哪个属性。

    • env:JNI接口指针
    • clazz:一个Java类对象
    • name:以”0”结尾的,而且字符类型是”utf-8”的属性名称
    • sig:以”0”结尾的,而且字符类型是”utf-8”的属性签名
    • return:属性对应ID,如果操作失败,则返回NULL

    注意:

    如果找不到指定的属性,则抛出NoSuchFieldError
    如果类初始化失败,则抛出ExceptionInitializerError
    如果内存不足了,则抛出OutOfMemoryError

    8 返回属性id系列

    NativeType GetField(JNIEnv *env,jobject obj,jfieldID fielD);

    返回某个类的非静态属性的值,这是一组函数的简称。

    • env:JNI接口指针
    • obj:Java对象,不能为空
    • fieldID:有效的fieldID
    • return:对应属性的值

    9 设置属性id系列

    void Set<type>Field(JNIEnv *env,jobject obj,jfieldID fieldID,NativeType value)

    设置某个类的的非静态属性的值。其中具体哪个属性通过GetFieldID()来确定哪个属性。这是一组函数的简称。

    • env:JNI接口指针
    • obj:Java对象,不能为空
    • fieldID:有效的属性ID
    • value:属性的新值

    10 获取某个类的某个方法id

    jmethodID GetMethodID(JNIEnv *env,jclass clazz,const char*name,const char* sig);

    返回某个类或者接口的方法ID,该方法可以是被定义在clazz的父类中,然后被clazz继承。

    • env:JNI接口指针
    • clazz:Java类对象
    • name:以0结尾的,并且是”utf-8”的字符串的方法名称
    • sig:以0结尾的,并且是”utf-8”的字符串的方法签名
    • return:方法ID,没有找到指定的方法,则返回NULL

    注意:

    如果找不到指定的方法,则抛出NoSuchMethodError
    如果累初始化失败,则抛出ExceptionInInitializerError
    如果内存不够,则抛出OutOfMemoryError

    11 调用Java实例的某个非静态方法“系列”

    NativeType Call<type>Method(JNIEnv *env,jobject obj,jmethodID methodID,...);
    NativeType Call<type>MethodA(JNIEnv *env,jobjct obj,jmethodID methodID ,const jvalue *args);
    NativeType Call<type>MethodV(JNEnv *env,jobject obj,jmethodID methodID,va_list args);

    这一些列都是在native中调用Java对象的某个非静态方法,它们的不同点在于传参不同。其中的type表示函数返回值类型。切不能是他父类的方法。

    • env:JNI接口指针
    • obj:对应的Java对象
    • methodID:某个方法的方法id

    12 调用某个类的非抽象方法

    NativeType CallNonvirtual<Type>Method(JNIEnv *env,jobject obj,jclass clazz,jmethodID methodID,....);
    NativeType CallNonvirtual<Type>MethodA(JNIEnv *env,jobject obj,jclass clazz,jmethodID methodID,const jvalue *args);
    NativeType CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj,jclass clazz, jmethodID methodID, va_list args);

    调用父类中的实例方法。与Call type Method函数不同的是前者是基于类的对象,而后者是基于类。

    • env:JNI接口指针
    • obj:Java对象
    • clazz:Java类
    • methodID:方法ID
    • return:调用Java方法的结果

    13 获取静态属性

    jfieldID GetStaticFieldID(JNIEnv *env,jclass clazz,const char* name,const char *sig);

    获取某个类的某个静态属性ID,根据属性名以及标签来确定是哪个属性。

    • env:JNI接口指针
    • clazz:Java类
    • name:静态属性的属性名,是一个编码格式”utf-8”并且以0结尾的字符串。
    • sig:属性的签名,是一个编码格式”utf-8”并且以0结尾的字符串。
    • return:返回静态属性ID,如果指定的静态属性无法找则返回NULL

    注意:

    如果指定的静态属性无法找到则抛出NoSuchFieldError
    如果类在初始化失败,则抛出ExceptionInInitializerError
    如果内存不够,则抛出OutOfMemoryError

    14 获取静态属性系列

    NativeType GetStatic<type>Field(JNIEnv *env,jclass clazz,jfieldID fieldID);

    这个系列返回一个对象的静态属性的值。

    • env:JNI接口指针
    • clazz:Java类
    • field:静态属性ID
    • return:返回静态属性

    15 设置静态属性系列

    void SetStatic<type>Field(JNIEnv *env,jclass clazz,jfieldID fieldID,NativeType value);

    这个系列是设置类的静态属性的值。

    • env:JNI接口指针
    • clazz:Java类
    • field:静态属性ID
    • value:设置的值

    16 获取静态函数ID

    jmethodID GetStaticMethodID(JNIEnv *env,jclass clazz,const char *name,const char sig);

    返回类的静态方法ID,通过它的方法名以及签名来确定哪个方法。

    • env:JNI接口指针
    • clazz:Java类
    • name:静态方法的方法名,以”utf-8”编码的,并且以0结尾的字符串
    • sig:方法签名,以”utf-8”编码的,并且以0结尾的字符串
    • return:返回方法ID,如果操作失败,则返回NULL

    注意:

    如果没有找到对应的静态方法,则抛出NoSuchMethodError
    如果类初始化失败,则抛出ExceptionInInitializerError
    如果系统内存不足,则抛出OutOfMemoryError

    17 调用静态函数系列

    NativeType CallStatic<type>Method(JNIEnv *env,jclass clazz,jmethodID methodID,...);
    NativeType CallStatic<type>MethodA(JNIEnv *env,jclass clazz,jmethodID methodID,... jvalue *args);
    NativeType CallStatic<type>MethodV(JNIEnv *env,jclass,jmethodID methodid, va_list args)

    根据指定的方法ID,就可以操作Java对象的静态方法了。

    • env:JNI接口指针
    • clazz:Java类
    • methodID:静态方法ID
    • return:返回静态的Java方法的调用方法

    六、字符串操作

    七、数组操作

    八、系统级别的操作

    九、NIO操作

    打开文件

    #include <stdio.h>
    FILE * fopen(const char * path,const char * mode);

    接收参数为文件路径和操作类型

    操作类型
    • r(rt):打开一个文本文件,文件必须存在,只允许读
    • r+(rt+):打开一个文本文件,文件必须存在,允许读写
    • rb:打开一个二进制文件,文件必须存在,只允许读
    • rb+:打开一个二进制文件,文件必须存在,允许读写
    • w(wt):新建一个文本文件,已存在的文件将内容清空,只允许写
    • w+(wt+):新建一个文本文件,已存在的文件将内容清空,允许读写
    • wb:新建一个二进制文件,已存在的文件将内容清空,只允许写
    • wb+:新建一个二进制文件,已存在的文件将内容清空,允许读写
    • a(at):打开或新建一个文本文件,只允许在文件末尾追写
    • a+(at+):打开或新建一个文本文件,可以读,但只允许在文件末尾追写
    • ab:打开或新建一个二进制文件,只允许在文件末尾追写
    • ab+:打开或新建一个二进制文件,可以读,但只允许在文件末尾追写

    上述说明:

    1.r(read):只读
    2.w(write):只写
    3.a(append):追加
    4.t(text):文本文件,可省略不写
    5.b(binary):二进制文件
    6.+:读和写
    7.用r打开文件时,文件必须存在
    8.用w打开的文件,只能向文件写入。若打开的文件不存在,则以指定的文件名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件。
    9.若要向一个已存在的文件追加新的信息,用a方式打开文件。如果指定文件不存在则尝试创建该文件。
    10.打开文件出错时,返回一个NULL

    写入文件

    size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
    • buffer:是一个指针,对fwrite来说,是要获取数据的地址;
    • size:要写入内容的单字节数;
    • count:要进行写入size字节的数据项的个数;
    • stream:目标文件指针;
      返回实际写入的数据项个数count。

    注意:此时只是将数据推送至缓冲区,并未同步到文件中,所以要将内存与文件同步还需要用fflush(FILE *fp)函数同步。

    十、反射支持

    十一、线程

    Android是基于Linux内核,Linux是遵循POSIX线程标准,而POSIX线程库有一系列Pthread API来操作linux线程。

    #include <pthread.h>

    线程概念

    操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

    相关函数

    • pthread_t :用于声明一个线程对象如:pthread_t thread;
    • pthread_create :用于创建一个实际的线程如:pthread_create(&pthread,NULL,threadCallBack,NULL);其总共接收4个参数,第一个参数为pthread_t对象,第二个参数为线程的一些属性我们一般传NULL就行,第三个参数为线程执行的函数( void* threadCallBack(void *data) ),第四个参数是传递给线程的参数是void*类型的既可以传任意类型。
    • pthread_exit :用于退出线程如:pthread_exit(&thread),参数也可以传NULL。注:线程回调函数最后必须调用此方法。
    • pthread_join :等待一个线程的结束,线程间同步的操作。
    • pthread_mutex_t :用于创建线程锁对象如:pthread_mutex_t mutex;
    • pthread_mutex_init :用于初始化pthread_mutex_t锁对象如:pthread_mutex_init(&mutex, NULL);
    • pthread_mutex_destroy :用于销毁pthread_mutex_t锁对象如:pthread_mutex_destroy(&mutex);
    • pthread_cond_t :用于创建线程条件对象如:pthread_cond_t cond;
    • pthread_cond_init :用于初始化pthread_cond_t条件对象如:pthread_cond_init(&cond, NULL);
    • pthread_cond_destroy :用于销毁pthread_cond_t条件对象如:pthread_cond_destroy(&cond);
    • pthread_mutex_lock :用于上锁mutex,本线程上锁后的其他变量是不能被别的线程操作的如:pthread_mutex_lock(&mutex);
    • pthread_mutex_unlock :用于解锁mutex,解锁后的其他变量可以被其他线程操作如:pthread_mutex_unlock(&mutex);
    • pthread_cond_signal :用于发出条件信号如:pthread_cond_signal(&mutex, &cond);
    • pthread_cond_wait :用于线程阻塞等待,直到pthread_cond_signal发出条件信号后才执行退出线程阻塞执行后面的操作。
    创建线程
    int pthread_create(
    pthread_t* thread,
    pthread_attr_t const* attr,
    void* (*start_routine)(void*),
    void* arg);
    • thread:指向 pthread_t 类型变量的指针,用它代表返回线程的句柄
    • attr:指向 pthread_attr_t 结构的指针形式存在的新线程属性,可以通过该结构来指定新线程的一些属性,比如栈大小、调度优先级等,具体看 pthread_attr_t 结构的内容。如果没有特殊要求,可使用默认值,把该变量取值为 NULL 。
    • 第三个参数是指向启动函数的函数指针,它的函数签名格式如下:void * start_routine(void * args),启动程序将线程参数看成 void 指针,返回 void 指针类型结果。
    • 线程启动程序的参数,也就是函数的参数,如果不需要传递参数,它可以为 NULL 。
    • 函数如果执行成功了则返回 0 ,如果返回其他错误代码。

    注意:当前线程中只能完成一些简单的C层操作,如果需要对Java层做一些操作就不可以来,因为这边没有JVM环境。
    此时需要通过AttachCurrentThread、DetachCurrentThread函数来将当前线程绑定到JVM上面,并且获取到当前线程中的JNIEnv对象。通过他就可以在Java层做一些操作。

    // 获取JVM虚拟环境
    env->GetJavaVM(&g_VM);
    // 获取当前线程中的JNIEnv对象
    g_VM->GetEnv((void **) &env,JNI_VERSION_1_6);
    // 将当前线程绑定到JVM中
    g_VM->AttachCurrentThread(&env, NULL)
    ...
    // 从JVM中分离当前线程
    g_VM->DetachCurrentThread();

    等待子线程
    int pthread_join(pthread_t thread, void **retval);
    • thread:等待退出线程的线程号。
    • retval:退出线程的返回值。

    pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。

    十二、JavaVM对象与JNIEvn对象

    JavaVM结构体

    在Java环境中,每个进程可以有多个VM实例,每个实例会有一个JavaVM结构体实例和他对应。但是在Android环境中,每个进程只能有一个VM示例。所以只有一个JavaVM结构体对应实例。
    通常在VM加载*.so程序库时,会先调用JNI_OnLoad()函数,在该函数中会将JavaVM指针对象保存到C层JNI全局变量中。他在所有线程共享。

    JNIEnv结构体

    当Java线程调用到C层的JNI函数的时候,一定会进入VM,VM会产生相对应的JNIEnv对象。这个对象和线程是一一对应的关系。

    在调用JNIEnv中的函数时,每个线程调用的JNIEnv中的本地函数都是相互独立的,因为VM会为每个线程产生一个JNIEnv对象实体。

    在调用函数的时候如果不方便传递JNIEnv对象,可以先获取JavaVM对象,再使用GetEnv()函数获取JNIEnv对象。

    如果在C层中单独创建线程,就必须用VM创建对应的JNIEnv对象,并回传该对象的指针。

    二、常见问题

    GetPrimitiveArrayCritical & ReleasePrimitiveArrayCritical