Android JNI与Android NDK扫盲

引言

什么是JNI和�Android NDK

  • JNIJava Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++).
    就是说我们使用在Java代码里面去使用其他语言现有的API.JNI不局限与Android平台.
  • Android NDK是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google称为NDK
    它是Google公司给我们提供的一套开发工具,就是为了使Android程序能够执行C&C++等部分原生代码.

使用JNI有什么用

  1. 代码的保护,由于APK的Java层代码很容易被反编译,而C/C++库反汇难度较大。
  2. 在NDK中调用第三方C/C++库,因为大部分的开源库都是用C/C++代码编写的。
  3. 便于移植,用C/C++写的库可以方便在其他的嵌入式平台上再次使用。
  4. 因为Android应用程序是跑在虚拟机上面,所以有些底层的硬件调用需要使用更底层的语言去调用.

环境

本机的环境

  • 操作系统: OSX 10.11.5
  • Java版本: build 1.8.0_51-b16
  • NDK版本: r11c
  • IDE: Eclipse Mars.1 Release (4.5.1)
  • Android工程:
  • minSDKVersion : 19
  •  targetSDKVersion    : `19`
    
  •  buildToolsVersion : `19`
    

具体的自己去下载,这里给一下NDK的下载地址
PS:自备梯子

配置NDK的执行路径

解压NDK,路径信息不要出现中文

配置环境变量(OSX为例)

我的NDK目录路径为/Users/August/android-ndk-r11c,下面自行修改NDK目录

  1. 编辑profile文件: 使用自己熟悉的编辑器打开~/.profile文件
  2. ~/.profile添加: export PATH="/Users/August/android-ndk-r11c:$PATH"
  3. 保存~/.profile文件.
  4. 使配置文件生效: source ~/.profile

如果是Windows系统的话,跟上面雷同,不过是直接修改环境变量,而不是去修改profile文件.
Windows用户可以这里

配置Eclipse环境

这里假设你已经开始在Eclipse上面做过�Android开发了

  1. 选择Eclipse的设置,设置里搜索NDK,选择下面的NDK然后在右边选项卡设置NDK的路径(/Users/August/android-ndk-r11c)
  2. 然后我们右键项目,选择Android Tools->Add native support->输入C/C++的源文件名(这里是用test)
  3. 然后我们发现工程里面多了一个jni的文件夹,我们打开后发现新建了test.cpp,和Android.mk.我们右键test.cpp->rename->改成test.c,并且把Android.mk里面的test.cpp改成test.c.
  4. 你有没有发现,你的jni文件中#include<jni.h>Unresolved inclusion: <jni.h>的错误了?没事,右键项目->Properties->C/C++ General->Paths and Symbols->Add->File System->选择/Users/August/android-ndk-r11c/platforms/android-19/arch-arm/usr/include.因为这里使用的Android工程师API19,所以这里我们对应选择android-19的arm平台

Android.mk

LOCAL_PATH := $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_MODULE    := test
    LOCAL_SRC_FILES :=  test.c

    include $(BUILD_SHARED_LIBRARY)

其中test就是模块名,test.c就是需要编译的源文件

Demo1:Hello world

native方法声明

我们在MainActivity中声明方法public native String getHelloFromC();,这里getHelloFromC()主要实现我们会在C语言里面实现.

方法调用

很简单,getHelloFromC()就能够得到函数返回的值.你可以打个Log或者弹个吐司.

使用javah

我们可以使用javah 包名.类名去生成native函数的C语言声明

编写函数

  1. 命令后环境切换到项目的src目录下面
  2. 输入javah com.example.jniproject.MainActivity
  3. 回到Eclipse中,右键src文件夹,选择refresh操作
  4. 我们看到多了一个文件,找到对应的方法声明JNIEXPORT jstring JNICALL Java_com_example_jniproject_MainActivity_getHelloFromC (JNIEnv *, jobject);后,我们拷贝声明代码到test.c
  5. 最后把test.c的代码修改成
#include <jni.h>
jstring Java_com_example_jniproject_MainActivity_getHelloFromC(JNIEnv *env,
       jobject obj) {
   char* cstr = "Hello World!";
   jstring jstr = (*env)->NewStringUTF(env, cstr);
   return jstr;
}

修改的4->5修改的内容:

  • 去掉JNIEXPORT,JNICALL.
  • 增加JNIEnv *参数和jobjectenvobj参数名
    env变量: Java虚拟机环境
    obj: 调用该代码的主体

变量类型

上面我们可以看到jstring这种类型.是什么鬼?
通过源文件查看(Windows下按住ctrl键鼠标左键点击jstring)我们可以看到.无论是JNIEnv还是jstring都是在jni.h里面有两份定义.第一份是C语言,第二份是C++

  • 公用类型

#ifdef HAVE_INTTYPES_H
# include <inttypes.h>      /* C99 */
typedef uint8_t         jboolean;       /* unsigned 8 bits */
typedef int8_t          jbyte;          /* signed 8 bits */
typedef uint16_t        jchar;          /* unsigned 16 bits */
typedef int16_t         jshort;         /* signed 16 bits */
typedef int32_t         jint;           /* signed 32 bits */
typedef int64_t         jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */
#else
typedef unsigned char   jboolean;       /* unsigned 8 bits */
typedef signed char     jbyte;          /* signed 8 bits */
typedef unsigned short  jchar;          /* unsigned 16 bits */
typedef short           jshort;         /* signed 16 bits */
typedef int             jint;           /* signed 32 bits */
typedef long long       jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */
#endif

  • C的类型
typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
typedef jarray          jobjectArray;
typedef jarray          jbooleanArray;
typedef jarray          jbyteArray;
typedef jarray          jcharArray;
typedef jarray          jshortArray;
typedef jarray          jintArray;
typedef jarray          jlongArray;
typedef jarray          jfloatArray;
typedef jarray          jdoubleArray;
typedef jobject         jthrowable;
typedef jobject         jweak;
  • C++的类型
typedef _jobject*       jobject;
typedef _jclass*        jclass;
typedef _jstring*       jstring;
typedef _jarray*        jarray;
typedef _jobjectArray*  jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray*    jbyteArray;
typedef _jcharArray*    jcharArray;
typedef _jshortArray*   jshortArray;
typedef _jintArray*     jintArray;
typedef _jlongArray*    jlongArray;
typedef _jfloatArray*   jfloatArray;
typedef _jdoubleArray*  jdoubleArray;
typedef _jthrowable*    jthrowable;
typedef _jobject*       jweak;

通过变量类型名称的字面意思,我们也可以跟Java里面的类型对应上.
需要注意的是,例如Java中传递String类型参数,在C语言里面需要把String转换成char[]等类型才能操作.
同时我们也看下NewStringUTF这个函数

  • C++版本
jstring NewStringUTF(const char* bytes) {
    return functions->NewStringUTF(this, bytes);
}
  • C版本
jstring (*NewStringUTF)(JNIEnv*, const char*);

this实参我们不难看出,C++使用了面向对象的方法.而C语言的仅仅是结构体包装.所以当我们使用的时候对env这个变量使用也不一样.在C版本中是(*env)->xxx,在C++版本中是env->xxx.

更多JNI函数用法参考<<The Java(TM) Native Interface–Programmer’s Guide and Specification>>中的JNI FUnctions章节.网上有电子版

加载模块

虽然现在模块是还没编译出来,但是我们运行程序的时候.NDK会自动帮我们编译.我们先写入加载的代码:

public class MainActivity extends Activity {
    static {
        System.loadLibrary("test");
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toast.makeText(this, getHelloFromC(), Toast.LENGTH_SHORT).show();
    }
    
    public native String getHelloFromC();
    
}

System.loadLibrary的参数是模块的名字,关于名字有两个常用的方法识别:

  • 自己的模块:直接查看Android.mkLOCAL_MODULE参数
  • 别人的模块:例如libtest.so,把lib.so去掉.剩下的test就是模块名称了

走起

直接运行程序,自动编译模块

Demo2:字符串传参

我们来实现一个移位的加解密
具体步骤上面都有,所以就简要说一下步骤.

定义函数

```java
public native String encode(String str, int length);

public native String decode(String str, int length);
```

生成native函数版本的函数声明

```
javah com.example.jniproject.MainActivity
```

修改函数声明并添加到test.c

```c
jstring Java_com_example_jniproject_MainActivity_encode(JNIEnv *env,
        jobject obj, jstring orgin, jint length) {
    jstring encrypt;

    return encrypt;
}

jstring Java_com_example_jniproject_MainActivity_decode(JNIEnv * env,
        jobject obj, jstring encrypt, jint length) {
    jstring orgin;
    return orgin;
}
```

编写test.c具体代码

jstring Java_com_example_jniproject_MainActivity_encode(JNIEnv *env,
        jobject obj, jstring orgin, jint length) {

    jstring encrypt;

    char *cstr = (*env)->GetStringUTFChars(env, orgin, 0);

    int i;

    for (i = 0; i < length; i++) {
        cstr[i] += 1;
    }

    encrypt = (*env)->NewStringUTF(env, cstr);

    return encrypt;
}

jstring Java_com_example_jniproject_MainActivity_decode(JNIEnv * env,
        jobject obj, jstring encrypt, jint length) {
    jstring orgin;
    char *cstr = (*env)->GetStringUTFChars(env, encrypt, 0);

    int i;

    for (i = 0; i < length; i++) {
        cstr[i] -= 1;
    }

    orgin = (*env)->NewStringUTF(env, cstr);
    return orgin;
}

从上面例子(模板)可以看到,需要操作类型的时候,传参返回值都要转换成对应的语言的类型.
这篇博客大概了解一下什么是JNI,其实我也是刚在学习.就mark一下吧.希望大家多多交流.

配置Eclipse的javah

其实我们也可以去配置Eclipse的启动配置,不用手动切换目录去生成C/C++的文件声明

第一步

导航菜单->Run->External Tools->External Tools Configurations->Program->选项卡左上角加号->出现新配置项

第二步

说一下几个需要填写的东西

  • Name: 启动配置名称
  • Location : 启动项的外部文件路径
  • Working Directory : 外部文件的工作目录
  • Arguments : 外部文件运行的参数

下面是我的参考配置,除了Location外,其他可以直接拷贝

  • Name: javah
  • Location : /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/javah
  • Working Directory : ${project_loc}/src
  • Arguments : -d ${project_loc}/jni ${java_type_name}

apply上面配置后切换到Refresh选项卡,然后选中The project containing the selected resourceapply一次就ok了

第三步

假设现在我们要为MainActity生成对应的C/C++声明文件.

  1. 选中或者打开MainActity.java,一定要保证当前鼠标焦点在需要生成的Java文件中
  2. Run->External Tools->javah,直接就看到jni目录下面生成了头文件
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容