若是你看遍了網上那些只是在C++裏面輸出一個 ‘ helloWorld ’ 的NDK教程的話,能夠看看本系列的文章,本系列是經過NDK的運用的例子來學習NDK。java
Android鬼點子-經過Google官方示例學NDK(2)——主要是說的不使用java代碼,用c++寫一個activity。android
Android鬼點子-經過Google官方示例學NDK(3)——這是一個opengl的例子。c++
Android鬼點子-經過Google官方示例學NDK(4)——主要是說的視頻解碼相關的內容。git
代碼地址,建議先拉下來看看。github
代碼的功能就是一個簡單的計時器。界面上的時間每秒增長1。 express
經過這個裏能夠了解到如何C++調用java代碼。 如何在C++起一個線程。apache
/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
package com.example.hellojnicallback;
import android.support.annotation.Keep;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
int hour = 0;
int minute = 0;
int second = 0;
TextView tickView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tickView = (TextView) findViewById(R.id.tickView);
}
@Override
public void onResume() {
super.onResume();
hour = minute = second = 0;
((TextView)findViewById(R.id.hellojniMsg)).setText(stringFromJNI());
startTicks();
}
@Override
public void onPause () {
super.onPause();
StopTicks();
}
/* * A function calling from JNI to update current timer */
@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);
}
});
}
static {
System.loadLibrary("hello-jnicallback");
}
public native String stringFromJNI();
public native void startTicks();
public native void StopTicks();
}
複製代碼
Activity中有3個jni方法。一個更新界面方法。這裏主要了解這3個jni方法。bash
首先看stringFromJNI()方法,這只是一個簡單的C++調用java獲取數據。app
JNIEXPORT jstring JNICALL Java_com_example_hellojnicallback_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 ".");
}
複製代碼
這個方法有兩個參數,JNIEnv* env 是jni的運行環境,不一樣線程的JNIEnv 彼此獨立; jobject thiz是該方法的調用者(static方法的話就是clazz)。less
第一個方法就這樣,說另外兩個方法以前,先看一下JniHandler.java:
/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */
package com.example.hellojnicallback;
import android.os.Build;
import android.support.annotation.Keep;
import android.util.Log;
/* * A helper class to demo that JNI could call into: * private non-static function * public non-static function * static public function * The calling code is inside hello-jnicallback.c */
public class JniHandler {
/* * Print out status to logcat */
@Keep
private void updateStatus(String msg) {
if (msg.toLowerCase().contains("error")) {
Log.e("JniHandler", "Native Err: " + msg);
} else {
Log.i("JniHandler", "Native Msg: " + msg);
}
}
/* * Return OS build version: a static function */
@Keep
static public String getBuildVersion() {
return Build.VERSION.RELEASE;
}
/* * Return Java memory info */
@Keep
public long getRuntimeMemorySize() {
return Runtime.getRuntime().freeMemory();
}
}
複製代碼
JniHandler中的3個方法會在jni中被調用。
再看一下hello-jnicallback.c中的JNI_OnLoad方法 。這個方法,會在System.loadLibrary("hello-jnicallback");的時候被調用。
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
memset(&g_ctx, 0, sizeof(g_ctx)); //所有初始化未0
g_ctx.javaVM = vm; //javaVM
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) { //env
return JNI_ERR; // JNI version not supported.
}
jclass clz = (*env)->FindClass(env,
"com/example/hellojnicallback/JniHandler");//找到clz
g_ctx.jniHelperClz = (*env)->NewGlobalRef(env, clz);//生成引用
jmethodID jniHelperCtor = (*env)->GetMethodID(env, g_ctx.jniHelperClz,
"<init>", "()V"); //獲取clz的構造函數並生成一個對象
jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz,
jniHelperCtor);
g_ctx.jniHelperObj = (*env)->NewGlobalRef(env, handler);//生成並保持引用
queryRuntimeInfo(env, g_ctx.jniHelperObj);
g_ctx.done = 0;
g_ctx.mainActivityObj = NULL;
return JNI_VERSION_1_6;
}
複製代碼
這個方法主要是初始化了下面的結構體
typedef struct tick_context {
JavaVM *javaVM;
jclass jniHelperClz;
jobject jniHelperObj;
jclass mainActivityClz;
jobject mainActivityObj;
pthread_mutex_t lock;
int done;
} TickContext;
TickContext g_ctx;
複製代碼
這個結構裏面保存了JniHandler對象和activity對象,還有一個線程鎖。
queryRuntimeInfo(env, g_ctx.jniHelperObj)方法是在拿到了JniHandler對象後,調用JniHandler對象的方法。這裏就是C++調用Java的例子。由於以前已經保存了JniHandler對象和JniHandler的clazz,因此queryRuntimeInfo方法中只是找方法,而後調用方法。
jmethodID versionFunc = (*env)->GetStaticMethodID(
env, g_ctx.jniHelperClz,
"getBuildVersion", "()Ljava/lang/String;");
jstring buildVersion = (*env)->CallStaticObjectMethod(env,g_ctx.jniHelperClz, versionFunc);
複製代碼
若是你看不懂「()Ljava/lang/String;」,這裏就是表示方法的返回值類型和參數類型。具體的能夠參考。
上面是靜態方法的調用,若是是普通方法,就須要對象做爲參數。
jmethodID memFunc = (*env)->GetMethodID(env, g_ctx.jniHelperClz,
"getRuntimeMemorySize", "()J");
jlong result = (*env)->CallLongMethod(env, instance, memFunc);
複製代碼
instance就是g_ctx.jniHelperObj。
那麼,接下來看開始計時的實現:
JNIEXPORT void JNICALL
Java_com_example_hellojnicallback_MainActivity_startTicks(JNIEnv *env, jobject instance) {
pthread_t threadInfo_; //用於聲明線程ID
pthread_attr_t threadAttr_; //用於保存線程屬性
pthread_attr_init(&threadAttr_);
//表示新線程是否與進程中其餘線程脫離同步,新線程不能用pthread_join()來同步,且在退出時自行釋放所佔用的資源
pthread_attr_setdetachstate(&threadAttr_, PTHREAD_CREATE_DETACHED);
//使用互斥鎖,NULL使用默認的互斥鎖屬性,默認屬性爲快速互斥鎖
pthread_mutex_init(&g_ctx.lock, NULL);
jclass clz = (*env)->GetObjectClass(env, instance);//這個方法依賴的類對象的class對象
g_ctx.mainActivityClz = (*env)->NewGlobalRef(env, clz);
g_ctx.mainActivityObj = (*env)->NewGlobalRef(env, instance);////這個方法依賴的類對象
//線程建立,第三個參數是方法,該方法的參數是經過第四個參數傳入的(void*類型)
int result = pthread_create( &threadInfo_, &threadAttr_, UpdateTicks, &g_ctx);//把第二個參數設置爲NULL的話,將採用默認的屬性配置
assert(result == 0);
pthread_attr_destroy(&threadAttr_);
(void)result;
}
複製代碼
主要是起了一個線程,而後讓UpdateTicks方法運行在上面。接下來看看UpdateTicks方法。
void* UpdateTicks(void* context) {
TickContext *pctx = (TickContext*) context;
JavaVM *javaVM = pctx->javaVM;
JNIEnv *env;//不一樣線程的JNIEnv相互獨立
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) {
LOGE("Failed to AttachCurrentThread, ErrorCode = %d", res);
return NULL;
}
}
jmethodID statusId = (*env)->GetMethodID(env, pctx->jniHelperClz,
"updateStatus",
"(Ljava/lang/String;)V");//找到方法JniHandler.updateStatus
sendJavaMsg(env, pctx->jniHelperObj, statusId,
"TickerThread status: initializing...");
// get mainActivity updateTimer function
jmethodID timerId = (*env)->GetMethodID(env, pctx->mainActivityClz,
"updateTimer", "()V");
struct timeval beginTime, curTime, usedTime, leftTime;
const struct timeval kOneSecond = {
(__kernel_time_t)1,//秒
(__kernel_suseconds_t) 0//零頭:微秒
};
sendJavaMsg(env, pctx->jniHelperObj, statusId,
"TickerThread status: start ticking ...");
while(1) {
gettimeofday(&beginTime, NULL);//得到當前精確時間(1970年1月1日到如今的時間),時區是null
pthread_mutex_lock(&pctx->lock);//加鎖
int done = pctx->done;
if (pctx->done) {
pctx->done = 0;
}
pthread_mutex_unlock(&pctx->lock);//解鎖
if (done) {
break;
}
(*env)->CallVoidMethod(env, pctx->mainActivityObj, timerId);
gettimeofday(&curTime, NULL);//當前時間
timersub(&curTime, &beginTime, &usedTime); //usedTime = curTime - beginTime,使用了的時間
timersub(&kOneSecond, &usedTime, &leftTime); //leftTime = kOneSecond(1s) - usedTime,剩餘的時間
struct timespec sleepTime; //計算須要睡眠的時間
sleepTime.tv_sec = leftTime.tv_sec;
sleepTime.tv_nsec = leftTime.tv_usec * 1000; //微妙轉毫秒
if (sleepTime.tv_sec <= 1) {
nanosleep(&sleepTime, NULL);//線程暫停
} else {
sendJavaMsg(env, pctx->jniHelperObj, statusId,
"TickerThread error: processing too long!");
}
}
sendJavaMsg(env, pctx->jniHelperObj, statusId,
"TickerThread status: ticking stopped");
(*javaVM)->DetachCurrentThread(javaVM);//white結束後,收回線程
return context;
}
複製代碼
這個有個while循環,而且經過pctx->done來做爲終止條件。而後看1秒鐘還剩多少時間,而後剩餘的時間進行睡眠。
這裏有個if (pctx->done) { pctx->done = 0;}
的操做,我看到這裏也比較迷惑,可是咱們接下來日後看。
JNIEXPORT void JNICALL Java_com_example_hellojnicallback_MainActivity_StopTicks(JNIEnv *env, jobject instance) {
pthread_mutex_lock(&g_ctx.lock);
g_ctx.done = 1;
pthread_mutex_unlock(&g_ctx.lock);
// waiting for ticking thread to flip the done flag
// 等待UpdateTicks中執行break後,才能夠繼續收回資源
struct timespec sleepTime;
memset(&sleepTime, 0, sizeof(sleepTime));
sleepTime.tv_nsec = 100000000;//100s
while (g_ctx.done) {
nanosleep(&sleepTime, NULL);
}
// release object we allocated from StartTicks() function
(*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);
}
複製代碼
原註釋waiting for ticking thread to flip the done flag的意思是等待計數線程翻轉done的標誌。個人理解是要等待計數線程退出以後,這裏的方法才能夠繼續往下走,收回資源。