前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android Studio2.2下NDK开发初试

Android Studio2.2下NDK开发初试

作者头像
forrestlin
发布2018-05-24 10:48:56
9630
发布2018-05-24 10:48:56
举报
文章被收录于专栏:蜉蝣禅修之道

很久没更博了,这次来记录一下如何在Android Studio2.2中进行NDK开发吧,NDK开发嘛,就是将C/C++的代码编译成so类库,供java调用(当然c调用java也是可以的),还记得以前没有IDE的时候,需要在linux环境编译,非常麻烦,光是看完教程就不想弄了,但不得不说Android Studio是Android开发的神器,它将一切都弄的如此简单。废话不多说,马上进入主题吧。

开始之前,我们需要在SDK Manager中安装NDK开发组件,即LLDB和NDK,如下图

第二步,配置环境变量,在用户变量中添加NDK_ROOT = SDK所在目录/ndk-bundle

然后再在path变量中添加%NDK_ROOT%

第三步,选择工程文件的Project视图,在src/main下创建jni目录,在这个目录里就放mk文件和c/c++头文件、源代码文件,我们以hello-jni.c文件为例,说一下开发流程。

首先,配置Android.mk文件,如下所示

代码语言:javascript
复制
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello-jni

LOCAL_SRC_FILES := hello-jni.c

LOCAL_LDLIBS += -llog

include $(BUILD_SHARED_LIBRARY)

另外,我们还可以配置Application.mk文件,这里面一般都用来配置全局设置,如有些编译头设置,可以参考如下:

代码语言:javascript
复制
APP_ABI := all
APP_PLATFORM := android-8
APP_CFLAGS += -DSTDC_HEADERS

下一步,需要在app的build.gradle中说明ndk-build的mk文件在哪里,在android范围内添加

代码语言:javascript
复制
externalNativeBuild {
        ndkBuild {
            path file("src\\main\\jni\\Android.mk")
        }
    }

整个build.gradle文件如下:

代码语言:javascript
复制
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"
    defaultConfig {
        applicationId "com.example.dave.hellojni"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        ndkBuild {
            path file("src\\main\\jni\\Android.mk")
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:23.3.0'
    testCompile 'junit:junit:4.12'
}

接下来,Android Studio为了我们开发方便,提供了小trick,就是我们可以在需要调用hello-jni的地方,先loadLibrary

代码语言:javascript
复制
static {
        System.loadLibrary("hello-jni");
    }

然后,声明需要调用的native方法,参数,返回值都写好后,如:

代码语言:javascript
复制
public static native String getHelloString();

接着,Android Studio可以帮助我们构建hello-jni.c文件,在错误的地方,alt+enter后,就会发现多了一个hello-jni.c文件,里面需要引用的文件和声明的函数头,as都帮我们做好了,我们只需要写好函数体就好,这功能真的非常贴心,我们开发只用关心业务逻辑即可。下面是as生成的hello-jni.c,我已经写好了返回值。

代码语言:javascript
复制
#include <jni.h>

JNIEXPORT jstring JNICALL
Java_com_example_dave_hellojni_MainActivity_getHelloString(JNIEnv *env, jobject type) {

    // TODO


    return (*env)->NewStringUTF(env, "Hello, JNI");
}

好了,所有准备工作都已经完事了,需要注意的是,c代码中的函数名相信很多人都已经发现了,和我们在java代码中声明的native不同,长了一大串,它的格式其实是

JNIEXPORT 返回值类型 JNICALL

Java_java类包名_类名_函数名(JNIEnv *env, jobject type, 自定义参数...) {}

OK,既然前面提及过JNI也可以调用java方法,那么这里也一并说了吧。

首先,我们需要获取java中函数的methodID,例如我这里是获取设置进度条进度的方法,先获取方法所在jclass,需要注意的是,这里FindClass中第二个参数classname需要完整的类名,因此需要包名,而且用/连接,然后调用GetMethodID即可获取jmethodID,如下:

代码语言:javascript
复制
jmethodID getProgressMethod(JNIEnv *env, char* classname) {
    // 1.找到java的MainActivity的class
    jclass clazz = (*env)->FindClass(env, classname);
    if (clazz == 0) {
        LOGI("can't find clazz");
        return 0;
    }
    LOGI("find clazz");

    //2 找到class 里面的方法定义
    jmethodID methodid = (*env)->GetMethodID(env, clazz, "setConvertProgress", "(I)V");
    if (methodid == 0) {
        LOGI("can't find methodid");
    }
    LOGI("find methodid");
    (*env)->DeleteLocalRef(env,clazz);
    return methodid;
}

顺便,这里给出jstring转换char*的c方法吧,挺常用的。

代码语言:javascript
复制
char *Jstring2CStr(JNIEnv *env, jstring jstr) {
    char *rtn = NULL;
    jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //String
    jstring strencode = (*env)->NewStringUTF(env, "GB2312"); // 得到一个java字符串 "GB2312"
    jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
                                        "(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
    jbyteArray barr = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid,
                                                            strencode); // String .getByte("GB2312");
    jsize alen = (*env)->GetArrayLength(env, barr); // byte数组的长度
    jbyte *ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char *) malloc(alen + 1); //"\0"
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    (*env)->ReleaseByteArrayElements(env, barr, ba, 0); //
    return rtn;
}

最后,我们调用的方法是(*env)->CallXXXMethod,如下

代码语言:javascript
复制
(*env)->CallVoidMethod(env, instance, progressMethod, total);

还有,在JNI的c代码中需要注意,可以运行时会报如下错误

代码语言:javascript
复制
JNI ERROR (app bug): local reference table overflow (max=512)

这是JNI对java对象引用的限制,防止内存使用过多,这时候我们需要查看代码中是不是有些地方忘记释放java对象的引用,这时我们可以用DeleteLocalRef方法释放

参考http://blog.csdn.net/xpz445094213/article/details/46633889的解释,如下:

产生Local Reference的操作有:  1.FindClass  2.NewString/ NewStringUTF/NewObject/NewByteArray  3.GetObjectField/GetObjectClass/GetObjectArrayElement  4.GetByteArrayElements和GetStringUTFChars

解决方法:  在native method中引用完java对象后及时调用env->DeleteLocalRef方法手动释放本地引用  如果native method返回java对象就不需要手动release,因为java会自动回收

好了,NDK开发的基础就讲到这里吧,还是更博太少了,以后得保证一周一更,注意知识的积累才行~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016年12月17日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档