Android Studio下 NDK开发流程

Android Studio下 NDK开发流程



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;
}


----------



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

有你就有时间

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

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

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

打赏作者

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

抵扣说明:

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

余额充值