Android Studio下 NDK开发流程
来源:互联网 发布:淘宝野马软件视频 编辑:程序博客网 时间:2024/06/07 00:34
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 24buildToolsVersion "24.0.2"
改成
compileSdkVersion=24buildToolsVersion="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更新。@Keepprivate 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 JNICALLJava_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 JNICALLJava_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_contextstruct 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;}----------
- android studio下的ndk开发流程
- Android Studio下 NDK开发流程
- Android Studio下NDK开发流程
- Android Studio 2.3下NDK开发流程
- Android studio下NDK开发环境配置及开发流程
- Android Studio NDK 开发流程
- Android Studio下 NDK配置与Jni开发流程
- android studio下ndk开发
- Android Studio下NDK开发
- android studio下ndk开发
- Android Studio下使用NDK的流程
- Ndk android studio 开发详细流程
- Android Studio开发NDK流程--经验
- android studio ndk开发 操作流程
- Android Studio NDk-JNi开发(二)AS下开发流程
- android studio下的NDK开发详解
- android studio下ndk C++开发
- Android Studio下的ndk开发
- 阶乘1!+2!+3!+.......+10!
- webApi——启用 session
- LeapMotion开发(二)--Opencv绘制手掌位置
- java socket client简单的检测断开并重连发送队列消息实现
- android中的四种启动模式-图文并茂更好理解
- Android Studio下 NDK开发流程
- Android 增量升级学习使用记录
- 中小型商城系统中的分类/产品属性/扩展属性的数据库设计
- Java中常见的名词解释集合
- 虚函数的识别2
- Stanford CoreNLP 3.6.0 使用入门
- com.sun.xml.internal.ws.model.RuntimeModelerException: class:com.mark.cxf.service could not be found
- Tomcat源码解析(十):启动和关闭
- 项目应用:建立解释器完成对用户的获取