博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android鬼点子-通过Google官方示例学NDK(1)
阅读量:6904 次
发布时间:2019-06-27

本文共 11761 字,大约阅读时间需要 39 分钟。

如果你看遍了网上那些只是在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的标志。我的理解是要等待计数线程退出之后,这里的方法才可以继续往下走,收回资源。

代码中我觉得有必要的地方我都加了中文注解,如果有理解错误的地方欢迎交流。

转载地址:http://wwmdl.baihongyu.com/

你可能感兴趣的文章
吉特仓库管理系统-.NET打印问题总结
查看>>
POJ 3026 Borg Maze(bfs+最小生成树)
查看>>
sqlplus 返回2 由于命令后没有加分号
查看>>
android Intent类
查看>>
IOS设计模式学习(19)策略
查看>>
poj 2155 Matrix
查看>>
shell中(),[]和[[]]的区别
查看>>
C# 利用反射动态创建对象[摘录]
查看>>
NLP系列学习:潜在语义牵引
查看>>
Swift 4官方文档中文版 Collection Types(上)
查看>>
Linux学习之初识
查看>>
NLP系列学习:DBOW句向量
查看>>
Centos7.x下Nginx安装及SSL配置与常用命令
查看>>
币安布局去中心化交易所,原来是因为这三个原因!
查看>>
Python爬虫笔记3-解析库Xpath的使用
查看>>
刷前端面经笔记(九)
查看>>
98. Validate Binary Search Tree
查看>>
《Java编程思想》读书笔记-对象导论
查看>>
mysql 的delete from where 子查询的一些限制
查看>>
【Android】Retrofit 2.0 的使用
查看>>