Android Studio2.2下NDK开发初试

16 篇文章 0 订阅
2 篇文章 0 订阅

很久没更博了,这次来记录一下如何在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文件,如下所示

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文件,这里面一般都用来配置全局设置,如有些编译头设置,可以参考如下:

APP_ABI := all
APP_PLATFORM := android-8
APP_CFLAGS += -DSTDC_HEADERS

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

externalNativeBuild {
        ndkBuild {
            path file("src\\main\\jni\\Android.mk")
        }
    }
整个build.gradle文件如下:
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

static {
        System.loadLibrary("hello-jni");
    }
然后,声明需要调用的native方法,参数,返回值都写好后,如:

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

#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,如下:

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方法吧,挺常用的。

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,如下

(*env)->CallVoidMethod(env, instance, progressMethod, total);

还有,在JNI的c代码中需要注意,可以运行时会报如下错误
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开发的基础就讲到这里吧,还是更博太少了,以后得保证一周一更,注意知识的积累才行~




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值