如果你看遍了网上那些只是在C++里面输出一个 ‘ helloWorld ’ 的NDK教程的话,可以看看本系列的文章,本系列是通过NDK的运用的例子来学习NDK。
——主要是说的不使用java代码,用c++写一个activity。
——这是一个opengl的例子。
——主要是说的视频解码相关的内容。
,建议先拉下来看看。
代码的功能就是一个简单的计时器。界面上的时间每秒增加1。
通过这个里可以了解到如何C++调用java代码。 如何在C++起一个线程。
/* * 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方法。
首先看stringFromJNI()方法,这只是一个简单的C++调用java获取数据。
JNIEXPORT jstring JNICALLJava_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)。
第一个方法就这样,说另外两个方法之前,先看一下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, "", "()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 JNICALLJava_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 JNICALLJava_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的标志。我的理解是要等待计数线程退出之后,这里的方法才可以继续往下走,收回资源。