Android Studio NDK jni 开发

android studio 已经成为目前目前开发Android的主流ide了,所以但是其目前对ndk开发做的不是很好,所以特地写一篇博客来记录下楼主目前用Android studio 开发ndk项目的方法

首先新建一个项目,创建一个Activity,添加如下代码:

   /**
     * cd app/src/main/java
     * javah -d ../jni com.example.ndksample.myapplication.MainActivity
     *
     * @return string
     */
    private static native String stringFromJni();

    static {
        System.loadLibrary("jni");
    }
android studio 自带window命令行终端,这点特别方便。打开studio下面的Terminal,如下图所示,终端会自动定位到当前项目:


如果没有请到如下位置自己打开:



然后进入 项目的java目录,利用javah命令生成.h文件,cmd命令如下:

cd app/src/main/java
javah -d ../jni com.example.ndksample.myapplication.MainActivity

然后就会在 ‘app/src/main/java/jni“ 目录看到生成的com_example_ndksample_myapplication_MainActivity.h文件,内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_ndksample_myapplication_MainActivity */

#ifndef _Included_com_example_ndksample_myapplication_MainActivity
#define _Included_com_example_ndksample_myapplication_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_ndksample_myapplication_MainActivity
 * Method:    stringFromJni
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_ndksample_myapplication_MainActivity_stringFromJni
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

其实不需要这个文件,只需要看看jni对本地方法的命名的参数,因为楼主所有的是利用一个 JNINativeMethod 的结构体构建本地方法表,来注册本地方法。

然后再到jni目录新建一个main.cpp(PS:其实c也行,不过楼主更习惯用C++文件),main.cpp文件就是写jni本地方法的具体实现,具体内容如下,楼主会一一解释怎么做的:

#include <jni.h>
#include <android/log.h>
#include <stdlib.h>

#ifndef NELEM
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif

#define LOG_TAG "android_native"

#ifndef LOGI
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#endif

#ifndef LOGE
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#endif


const char *className = "com/example/ndksample/myapplication/MainActivity";     // the jni class


/*
 * Class:     com_example_ndksample_myapplication_MainActivity
 * Method:    stringFromJni
 * Signature: ()Ljava/lang/String;
 */
jstring stringFromJni(JNIEnv *env, jclass clazz) {
    return env->NewStringUTF("this message is from jni");
}


/**
 * name:      stringFromJni
 * signature: ()Ljava/lang/String;
 * fnPtr:     stringFromJni
 *
 * jni.h struct JNINativeMethod
 */
static const JNINativeMethod sMethods[] = {                                  // jni methods table
        {"stringFromJni", "()Ljava/lang/String;", (void *) stringFromJni},
};


jint JNI_OnLoad(JavaVM *vm, void *reserved) {

    JNIEnv *env = NULL;
    jint result = -1;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("GetEnv failed!");
        return result;
    }
    LOGI("Retrieve the env success !");

    jclass clazz = env->FindClass(className);              // find the jni class
    if (clazz == NULL) {
        LOGE("FindClass Failed !");
        return JNI_ERR;
    }

    if (env->RegisterNatives(clazz, sMethods, NELEM(sMethods)) < 0) {      //register jni methods
        LOGE("RegisterNatives Failed !");
        return JNI_ERR;
    }

    return JNI_VERSION_1_4;                              // use jni version 1.4
}

说明:

前面是一些宏定义就不用多说了。

className 是你调用jni本地方法的那个类名,这个是给注册方法的时候查找类用。

stringFromJni 这个函数就是本地方法的实现,其实名字可以随意,只要你对应好,这就是楼主推荐的注册本地方法来实现jni的一些好处,不用谢那个长的函数名。

sMethods 这是一个结构体数组,在jni.h里面有定义JNINativeMethod的结构,我这边把那个结构体直接贴上来

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;
第一个参数是函数名,也就是你java代码中写的那个本地方法的函数名字;

第二个参数是能唯一表明函数的一些特征(参数和返回值类型),这个可以在刚刚生成的那个.h文件中查看;

第三个就是函数指针地址,指向你本地具体实现的那个C/C++函数;

JNI_OnLoad 最后一个就是jni so库首次加载的时候会去调用的一个方法,这个方法是在jni.h中定义的,我们需要在这里面注册jni methods,这样每次加载时就能根据那个结构体数组的映射关系,正确的找到函数的执行地址,返回的时候需要返回JNI_VERSION_1_4,因为默认会返回低版本的,所以需要强行改一下。

好了,完成了.cpp文件接下来就是添加Android,mk文件,内容如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog -lm
LOCAL_MODULE := jni
LOCAL_SRC_FILES := main.cpp

include $(BUILD_SHARED_LIBRARY)
这个也没什么好解释的,具体相关自行百度。

然后再添加一个Application.mk,内容如下:

APP_ABI := all
APP_STL := gnustl_static
APP_CFLAGS += -Wno-error=format-security
第一个标记是为所有平台编译so库,例如armeabi、armeabi-v7a、x86、x86_64等等,如果你不想所有都编译可以指定几个编译。

第二个和第三个是防止编译so库时出现一些错误而添加的,不加也可以。


好了,说了这么多,以上这些基本上在eclipse上就可以了,但是Android studio 是利用gradle构建的,如果设置其他的是不会编译so库的,在app目录的build.gradle进行如下修改:

import org.apache.tools.ant.taskdefs.condition.Os

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.example.ndksample.myapplication"
        minSdkVersion 10
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    sourceSets {
        main {
            jniLibs.srcDirs 'src/main/libs'
            jni.srcDirs = []                    // disable automatic ndk-build call
        }
    }

    // call regular ndk-build(.cmd) script
    task ndkBuild(type: Exec) {
        Properties properties = new Properties()
        properties.load(project.rootProject.file('local.properties').newDataInputStream())
        if (Os.isFamily(Os.FAMILY_WINDOWS)) {
            def command = properties.getProperty('ndk.dir', null) + "/ndk-build.cmd"
            commandLine command, '-C', file('src/main/jni').absolutePath
        } else {
            def command = properties.getProperty('ndk.dir', null) + "/ndk-build"
            commandLine command, '-C', file('src/main/jni').absolutePath
        }
    }

    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn ndkBuild
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.0'
    compile 'com.android.support:design:23.1.0'
}

说明:

首先,你得在sourceSets中设置下 jni.srcDirs = [] 禁用gradle自动jni build,因为目前gradle plugin还不完善,支持的还不好,所以我们还是跟eclipse那样利用Android.mk去进行ndk开发,然后指定so库的目录为 src/main/libs,这样打包时studio才会把libs下的所有so库打进apk中,因为我们的jni目录是在src/main/jni ,当编译so库时会自动把生成的so库导入src/main/libs目录,所以这个地方路径不要写错。

其次,我们新建一个ndkbuild task来执行cmd命令,windows下利用ndk-build.cmd来执行,其他系统直接用ndk-build,在这里我们把ndk的根目录保存在了local.properties文件中,所以千万别忘了在这个文件中添加ndk根目录。

android studio其实可以图形化设置ndk的目录,具体在项目-右键-Open Modules Settings中去设置,如图:



如果你是用旧版的 studio,你可以自己手动添加ndk目录到 local.properties文件,例如 :

ndk.dir=F\:\\android-ndk-r10e


到此,如果你所有的都设置好了之后,直接运行项目,studio便会自动编译so库,然后打包时加进apk中,设置好之后,之后就基本不需要改动了,这样就完美支持jni开发 。


楼主是基于目前最新的开发环境给的例子,下面说下楼主所有的环境:

Android Studio 141.2343393

Gradle 2.8

com.android.tools.build:gradle:1.3.1

android-ndk-r10e


整个项目的git地址:点击打开链接


当然,你也可以使用体验版的gradle plugin来做ndk开发:

com.android.tools.build:gradle-experimental:0.2.1


使用介绍:http://tools.android.com/tech-docs/new-build-system/gradle-experimental
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值