Android Studio下 NDK开发流程

    xiaoxiao2022-06-29  33

    Android Studio下 NDK开发流程


    Android Studio下 NDK开发流程 NDK环境搭建native方法的使用使用本地方法实现计时 一java层的实现二native层的实现


    Android Studio NDK开发规则介绍

    NDK环境搭建


    1.在android studio中新建一个测试项目,并进行配置 如果已经安装了ndk可以在项目的根目录右键open Module Settings中看到你配置的ndk路径 如果没有安装过ndk在这个地方会出现安装NDK的提示。 2.配置根目录的build.gradle 根目录build.gradle的配置比较简单,将原有插件即

    classpath'com.android.tools.build:gradle:2.2.0-rc2'

    换成

    classpath'com.android.tools.build:gradle-experimental:0.7.0'

    3.配置module(模块)的build.gradle 1)与正常的配置区别1 正常的配置使用的是以下这个插件

    apply plugin: 'com.android.application'

    而NDK则需要使用以下插件

    apply plugin: 'com.android.model.application'

    2)与正常配置区别2 需要在最外层添加一个model标签来包裹android标签 而依赖的标签需要在model标签外层 效果如下: 3)与正常配置区别3 观察android标签会发现里面的变量类型和变量名不再像正常以”空格”作为分隔符,而是以”=”作为分割符因此需要将android标签下的

    compileSdkVersion 24 buildToolsVersion "24.0.2"

    改成

    compileSdkVersion=24 buildToolsVersion="24.0.2"

    同理defaultConfig标签下的分割符也需要修改成 “=”同时还要注意到 minSdkVersion变为了minSdkVersion.apiLevel targetSdkVersion变为了targetSdkVersion.apiLevel 即变成了

    defaultConfig { applicationId="com.wbl.ndktest" minSdkVersion.apiLevel=15 targetSdkVersion.apiLevel=24 versionCode=1 versionName="1.0" testInstrumentationRunner= "android.support.test.runner.AndroidJUnitRunner" }

    buildType标签由原来的

    buildTypes { release { minifyEnabled false proguardFiles.getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }

    变为

    buildTypes { release { minifyEnabled false proguardFiles.add(file('proguard-rules.txt')); } debug { ndk.debuggable = true //有这个才会支持调试native 代码,这个放到release里一样能用 } }

    4)与正常配置的区别4 在android标签下添加了一个ndk的标签所有关于ndk的配置都可以在此完成,我这里只配置了三个属性:

    ndk{ moduleName='wbl-jni' //动态库的名称 toolchain= 'clang' //编译器,据说这个比gcc要快,没有这个写native代码时没有自动补全的功能 CFlags.addAll(['-Wall']) //对应gcc中的编译选项 CFLAGS,方括号内是一个数组,可以有多个值 }

    5)与正常配置的区别5 需要在productFlavors标签中添加对各个不同cpu的支持

    productFlavors { create("arm") { ndk.abiFilters.add("armeabi") } create("arm7") { ndk.abiFilters.add("armeabi-v7a") } create("arm8") { ndk.abiFilters.add("arm64-v8a") } create("x86") { ndk.abiFilters.add("x86") } create("x86-64") { ndk.abiFilters.add("x86_64") } create("mips") { ndk.abiFilters.add("mips") } create("mips-64") { ndk.abiFilters.add("mips64") } create("all") }

    然后同步一下gradle编辑完成即可

    native方法的使用


    1.在项目的src/mian/下新建文件夹jni并在该文件夹下新建一个.c文件

    //添加头文件 #include <string.h> #include <jni.h> jstring //本地函数定义需要遵循一定规则可以到 //该链接去查看相应规则,这里不作介绍了 Java_com_wbl_ndktest_MainActivity_stringFromJNI( JNIEnv* env, jobject thiz ) { //预编译处理 #if defined(__arm__) #if defined(__ARM_ARCH_7A__) #if defined(__ARM_NEON__) #if defined(__ARM_PCS_VFP) #define ABI "armeabi-v7a/NEON (hard-float)" #else #define ABI "armeabi-v7a/NEON" #endif #else #if defined(__ARM_PCS_VFP) #define ABI "armeabi-v7a (hard-float)" #else #define ABI "armeabi-v7a" #endif #endif #else #define ABI "armeabi" #endif #elif defined(__i386__) #define ABI "x86" #elif defined(__x86_64__) #define ABI "x86_64" #elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ #define ABI "mips64" #elif defined(__mips__) #define ABI "mips" #elif defined(__aarch64__) #define ABI "arm64-v8a" #else #define ABI "unknown" #endif return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI " ABI "."); }

    之后在java层创建一个TextView在setText中调用这个函数stringFromJNI,该函数会告诉你当前使用的是什么平台。 java层的代码如下:

    使用本地方法实现计时

    简介:之前使用一个简单的例子来描述NDK的使用,接下来通过计时的例子来加深对ndk的使用,这里可能会涉及到java层在native层的回调和native层在java层的回调。

    一、java层的实现

    java层实现较为简单

    1.首先定义三个整形变量 hour,minute,second并进行赋值 int hour = 0; int minute = 0; int second = 0; TextView tickView; 2.在onCreate()函数中拿到tickView的引用 @Override public void onCreate(Bundle savedInstance){ super.onCreate(savedInstance); tickView=(TextView)findViewById(R.id.tv); } 3.在onResume()函数中调用本地函数startTicks()开始计时 @Override public void onResume() { super.onResume(); hour = minute = second = 0; startTicks(); } 4.在onPause()函数中调用本地函数StopTocks()停止计时 @Override public void onPause () { super.onPause(); StopTicks(); } 5.在updateTimer()函数用于处理每秒中的UI更新。 @Keep private void updateTimer() { ++second; if(second >= 60) { ++minute; second -= 60; if(minute >= 60) { ++hour; minute -= 60; } } runOnUiThread(new Runnable() { @Override public void run() { String ticks = "" + MainActivity.this.hour + ":" + MainActivity.this.minute + ":" + MainActivity.this.second; MainActivity.this.tickView.setText(ticks); } }); } 6.加载本地库 static { System.loadLibrary("wbl-jni"); } 7.声明本地函数 public native void startTicks(); public native void StopTicks();

    在java层使用这些函数时你会发现有一个函数似乎被架空了,它没有被任何人调用,但却一直被执行。那就是 updateTimer()这个函数 它为什么会被执行呢?,可以从下面的native层找到答案

    二、native层的实现

    当进程初始化时,会产生一个JavaVM的结构体,这个结构体在一个进程中只存在一个 当java层通过System.loadLibrary加载完JNI动态库后,接着会调用一个JNI_OnLoad函数,在这里可以完成初始化的工作

    头文件 #include <string.h> #include <jni.h> #include <pthread.h> #include <assert.h> 1.调用UpdateTicks实现每秒的计时 /* *在java层的UI线程中被调用 * java层通过MainActivity::updateTimer() 在UI线程中展示计时 * java层通过JniHandler::updateStatus(String msg)获取更新的信息 */ void* UpdateTicks(void* context) { //TickContext *pctx = (TickContext*) context; //得到tick_context结构体 struct tick_context *pctx=(struct tick_context*)context; JavaVM *javaVM = pctx->javaVM; JNIEnv *env; jint res = (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_6); if (res != JNI_OK) { res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL); if (JNI_OK != res) { return NULL; } } // 得到 mainActivity updateTimer 函数 jmethodID timerId = (*env)->GetMethodID(env, pctx->mainActivityClz, "updateTimer", "()V"); //timeval结构体定义了两个变量,a用来表示秒数,b用来表示微秒 即秒的零头 struct timeval beginTime, curTime, usedTime, leftTime; const struct timeval kOneSecond = { (__kernel_time_t)1, //秒数 (__kernel_suseconds_t) 0 //微秒数 }; while(1) { //获得当前精确时间 //其参数1是保存获取时间结果的结构体,参数2用于保存时区结果 gettimeofday(&beginTime, NULL); //加上线程同步锁 pthread_mutex_lock(&pctx->lock); //用于判断是否停止计时 int done = pctx->done; if (pctx->done) { pctx->done = 0; } pthread_mutex_unlock(&pctx->lock); if (done) { break; } //timerId是java层的方法updateTimer (*env)->CallVoidMethod(env, pctx->mainActivityObj, timerId); gettimeofday(&curTime, NULL); timersub(&curTime, &beginTime, &usedTime); timersub(&kOneSecond, &usedTime, &leftTime); //第一个参数与timeval一样,第二个参数则是精确到纳秒的时间 struct timespec sleepTime; sleepTime.tv_sec = leftTime.tv_sec; sleepTime.tv_nsec = leftTime.tv_usec * 1000; if (sleepTime.tv_sec <= 1) { nanosleep(&sleepTime, NULL); } } } (*javaVM)->DetachCurrentThread(javaVM); return context; } 2.startTicks()函数的实现 JNIEXPORT void JNICALL Java_com_example_hello_1jnicallback_MainActivity_startTicks(JNIEnv *env, jobject instance) { //线程ID pthread_t threadInfo_; //线程属性 /* typedef struct { int detachstate; 线程的分离状态 int schedpolicy; 线程调度策略 struct sched_param schedparam; 线程的调度参数 int inheritsched; 线程的继承性 int scope; 线程的作用域 size_t guardsize; 线程栈末尾的警戒缓冲区大小 int stackaddr_set; void * stackaddr; 线程栈的位置 size_t stacksize; 线程栈的大小 }pthread_attr_t; */ pthread_attr_t threadAttr_; //初始化线程属性 pthread_attr_init(&threadAttr_); //分离状态启动,可以不用管理线程的结束与资源释放 pthread_attr_setdetachstate(&threadAttr_, PTHREAD_CREATE_DETACHED); //初始化线程的互斥锁 pthread_mutex_init(&g_ctx.lock, NULL); //instance就是java层对应class的实例,这里获取到java层的class类 jclass clz = (*env)->GetObjectClass(env, instance); //引用这个class类 g_ctx.mainActivityClz = (*env)->NewGlobalRef(env, clz); //引用这个实例 g_ctx.mainActivityObj = (*env)->NewGlobalRef(env, instance); //创建线程,用于计时,参数分别表示线程ID,线程属性,线程起始地址,传递给起始地址的参数 int result = pthread_create( &threadInfo_, &threadAttr_, UpdateTicks, &g_ctx); assert(result == 0); (void)result; } 3.StopTicks()函数的实现 JNIEXPORT void JNICALL Java_com_example_hello_1jnicallback_MainActivity_StopTicks(JNIEnv *env, jobject instance) { //加互斥锁,只允许在同一时间存在一个线程执行,其他线程等待 pthread_mutex_lock(&g_ctx.lock); g_ctx.done = 1; //解锁,释放互斥锁,其他线程可以调用被锁资源 pthread_mutex_unlock(&g_ctx.lock); // 等待计时线程将计时标志位记为1 struct timespec sleepTime; memset(&sleepTime, 0, sizeof(sleepTime)); sleepTime.tv_nsec = 100000000; while (g_ctx.done) { nanosleep(&sleepTime, NULL); } // 释放引用的资源 (*env)->DeleteGlobalRef(env, g_ctx.mainActivityClz); (*env)->DeleteGlobalRef(env, g_ctx.mainActivityObj); g_ctx.mainActivityObj = NULL; g_ctx.mainActivityClz = NULL; //销毁互斥变量 pthread_mutex_destroy(&g_ctx.lock); } 4.首先创建结构体tick_context struct tick_context{ //另外一种建立结构体的方式 JavaVM *javaVM; //可以从中获取线程的 JNIEnv* 结构体 jclass jniHelperClz; //java层class类型的变量 jobject jniHelperObj; //java层自定义变量 jclass mainActivityClz; jobject mainActivityObj; pthread_mutex_t lock; //线程同步锁 int done; } g_ctx; // 5.在JNI_OnLoad()函数中初始化结构体,该结构体用于调用来自java层的函数 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; //将结构体所在内存的每个字节的内容设置为0; memset(&g_ctx, 0, sizeof(g_ctx)); //为结构体的javaVm赋值 g_ctx.javaVM = vm; //拿到该线程的JNIEnv*结构体存入env中 if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; // JNI version not supported. } //初始化计时标志 g_ctx.done = 0; g_ctx.mainActivityObj = NULL; return JNI_VERSION_1_6; } ----------
    转载请注明原文地址: https://ju.6miu.com/read-1124917.html

    最新回复(0)