android NDK 编程学习记录

问题1:c++ string 转 char*

data() 方法:

string str = "hello";
const char* p = str.data();//加const  或者用char * p=(char*)str.data();的形式

c_str()方法:

string str=“world”;
const char *p = str.c_str();//同上,要加const或者等号右边用char*

参考博客

问题2:jstring 转为 char*

const char* GetStringUTFChars(jstring string, jboolean* isCopy)

方法说明:
j_str jstring 类型(Java传递给本地代码的字符串指针)

isCopy: 取值JNI_TRUE和JNI_FALSE,如果值为JNI_TRUE,表示返回JVM内部源字符串的一份拷贝,并为新产生的字符串分配内存空间。如果值为JNI_FALSE,表示返回JVM内部源字符串的指针,意味着可以通过指针修改源字符串的内容,不推荐这么做,因为这样做就打破了Java字符串不能修改的规定。但我们在开发当中,并不关心这个值是多少,通常情况下这个参数填NULL即可。

因为Java默认使用Unicode编码,而C/C++默认使用UTF编码,所以在本地代码中操作字符串的时候,必须使用合适的JNI函数把jstring转换成C风格的字符串。JNI支持字符串在Unicode和UTF-8两种编码之间转换,GetStringUTFChars可以把一个jstring指针(指向JVM内部的Unicode字符序列)转换成一个UTF-8格式的C字符串。在上例中sayHello函数中我们通过GetStringUTFChars正确取得了JVM内部的字符串内容。

问题3:打印Log

  1. 导入头文件:#include <android/log.h>
  2. 方法:
std::string hello = "Hello from C++";
__android_log_print(ANDROID_LOG_ERROR, kTAG, "%s", hello.c_str());
  1. 使用宏定义简化方法的使用
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, kTAG, __VA_ARGS__))

std::string hello = "Hello from C++";
LOGE("%s", hello.data());

问题4:meset 函数

初始化内存,也就是清空内存,防止乱码出现
1. 函数原型:

# include <string.h>
// 虽然参数 c 要求是一个整数,但是整型和字符型是互通的。但是赋值为 '\0' 和 0 是等价的,
// 因为字符 '\0' 在内存中就是 0。所以在 memset 中初始化为 0 也具有结束标志符 '\0' 的作用,
// 所以通常我们就写“0”
void *memset(void *s, int c, unsigned long n);

2. 函数功能:
将s所指向的某一块内存中的前n个字节的内容全部设置为ch指定的ASCII值, 第一个值为指定的内存地址,块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作, 其返回值为指向s的指针,它是对较大的结构体或数组进行清零操作的一种最快方法。

问题5:JavaVM 和 JNIEnv

1. JavaVM
JavaVM 是虚拟机在JNI层的代表,一个进程只有一个JavaVM,所有的线程共用一个JavaVM
2. JNIEnv
JNIEnv 表示Java调用native语言环境,是一个封装了几乎全部JNI方法的指针。
JNIEnv只在创建它的线程生效,不能跨进程传递,不同线程的JNIEnv彼此独立。
native环境创建的线程,如果需要访问JNI,必须要调用AttachCurrentThread关联,并使用DetachCurrentThread 解除连接。

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    jint result = -1;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return result;
    }
    
    // 返回jni的版本
    return JNI_VERSION_1_6;
}

问题6 :JNIEnv* # NewGlobalRef {全局引用}

    全局引用 作用域 :
            空间 : 可以 跨方法 , 跨线程使用
            时间 : 创建后可以使用 , 手动释放后全局引用失效

    全局引用创建 : NewGlobalRef
    全局引用释放 : DeleteGlobalRef
    全局引用会阻止 JVM 回收该引用
        //生成局部引用 , 该局部引用使用完毕后可释放
        jclass tmp_class = env->FindClass("kim/hsl/jni/Teacher");

        //将上述生成的局部引用变成 全局引用
        //全局引用释放时 , env->DeleteGlobalRef(class_teacher_global) 即可释放下面转换的 全局引用
        class_teacher_global = static_cast<jclass>(env->NewGlobalRef(tmp_class));

        //将局部引用释放掉
        env->DeleteLocalRef(tmp_class);

参考博客

问题7:JNI 创建java对象

    jclass cls = env->FindClass("com/example/myapplication/Util");

    //调用无参构造函数
    //构造函数是没有函数名的,<init> 就代表构造函数. ()V 代表无参无返回值
    jmethodID init = env->GetMethodID(cls, "<init>", "()V");
    //调用有参构造函数,两个参数,一个是 String, 一个是 Int
    jmethodID init = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;I)V");
	//创建实例
    jobject util = env->NewObject(cls, init);

参考博客

问题8:JNI 方法签名的类型

对应于Java端的数据类型,我们也可以看一下下面的表: 
Java 类型 类型签名
boolean Z
byte    B
char    C
short   S
int     I
long    L
float   F
double  D
类      L全限定名;,比如String, 其签名为Ljava/lang/util/String;
数组    [类型签名, 比如 [B

参考博客

问题9:JNI 调用 static Java 方法

// 获取 getBuildVersion 方法
// ()Ljava/lang/String; 一定不能少分号
jmethodID versionFun = env->GetStaticMethodID(g_ctx.jniHelperClz, "getBuildVersion", "()Ljava/lang/String;");

问题10:Android native 线程 pthread

  • 创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg); 

函数参数:

  1. 线程句柄 thread:当一个新的线程调用成功之后,就会通过这个参数将线程的句柄返回给调用者,以便对这个线程进行管理。
  2. 入口函数 start_routine(): 当你的程序调用了这个接口之后,就会产生一个线程,而这个线程的入口函数就是start_routine()。如果线程创建成功,这个接口会返回0。
  3. 入口函数参数 *arg : start_routine()函数有一个参数,这个参数就是pthread_create的最后一个参数arg。这种设计可以在线程创建之前就帮它准备好一些专有数据,最典型的用法就是使用C++编程时的this指针。start_routine()有一个返回值,这个返回值可以通过pthread_join()接口获得
  4. 线程属性 attr: pthread_create()接口的第二个参数用于设置线程的属性。这个参数是可选的,当不需要修改线程的默认属性时,给它传递NULL就行。具体线程有那些属性,我们后面再做介绍。

问题11:pthread_create 执行崩溃问题

线程的执行函数需要有返回值

// 错误:
void*  pthreadMethod(void* args) {
    __android_log_print(ANDROID_LOG_ERROR, kTAG, "%s", "updateTicks");
	
}

// 正确:
void*  pthreadMethod(void* args) {
    __android_log_print(ANDROID_LOG_ERROR, kTAG, "%s", "updateTicks");
	
	return nullptr;
}

参考博客

问题12:native 线程使用JNI函数

    JavaVM* javaVm = pctx->javaVM;
    JNIEnv* jniEnv;
    jint res = javaVm->GetEnv((void**)&jniEnv,JNI_VERSION_1_6);
    if (res != JNI_OK) {
        res = javaVm->AttachCurrentThread(&jniEnv, nullptr);
        if (res != JNI_OK) {
            LOGE("Failed to AttachCurrentThread, ErrorCode = %d", res);
            return nullptr;
        }
    }

问题12:JNI NewStringUTF字串的释放

void sendJavaMsg(JNIEnv* env, jobject instance, jmethodID func, const char* msg) {
    jstring javaMsg = env->NewStringUTF(msg);
    
	/* delete local reference */
    env->DeleteLocalRef(javaMsg);
}

问题13: 时间结构

1s = 1000ms(毫秒) = 10001000us(微妙) = 10001000*1000ns(纳秒)

 struct timespec 
 //精确到纳秒
{
    time_t tv_sec; //秒
    long tv_nsec;//纳秒
}

struct timeval
{
     time_t tv_sec; //秒 
     long tv_usec;  //微妙
};

// 获取当前时间函数:
gettimeofday(&curTime, nullptr);

问题14:抛出java异常

jniThrowRuntimeException(env, "Unable to allocate native queue");

参考博客

问题15:#ifdef __cplusplus” 和 " extern “C” 的问题


#ifdef __cplusplus 
extern "C" { 
#endif 
 
//一段代码 
 
#ifdef __cplusplus 
} 

首先,__cplusplus是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,也就是说,上面的代码的含义是:如果这是一段cpp的代码,那么加入extern “C”{和}处理其中的代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值