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


----------



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章