Android jni使用入门

date: 2016.07.05; modification:2016.07.05

目录:

1 创建与使用jni的整体步骤:

  1. 在java层声明需要的java层接口, 但是只是声明一下, 并没有实际的实现部分, 实现部分在native层(C/C++).
  2. 将java层的接口, 翻译为C/C++的接口原型.
  3. 实现C/C++的接口.
  4. 将C/C++中实现的接口, 注册jni, 这样java可以调用到了.
  5. 将C/C++中的实现部分编译成so库.

2 eclipse创建android工程

eclipse创建工程testjni, 代码如下:


package com.example.testjni;

import android.support.v7.app.ActionBarActivity;
//import android.app.Activity;
import android.util.Log;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;


public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyJni myJni = new MyJni();
        Log.v("dufresne", myJni.printJNI("\033[01;32mI am HelloWorld Activity\033[0m"));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

}

这里和eclipse创建的默认最简工程相比, 只增加了两行:


    MyJni myJni = new MyJni();
    Log.v("dufresne", myJni.printJNI("\033[01;32mI am HelloWorld Activity\033[0m"));

其中的printJNI, 就是我们要用jni来实现的方法.

3 在java层声明需要的java层接口

接下来我们创建MyJni这个类:

在eclipse的: src/com/example/testjni/目录右击, new->class, 创建上面用到的类: MyJni.

其内容如下:


package com.example.testjni;

public class MyJni {
    static
    {
        //加载库文件
        System.loadLibrary("HelloWorldJni");
    }

    //声明原生函数 参数为String类型 返回类型为String
    public native String printJNI(String inputStr);
}

可以看到这里做了两件与jni相关的事.

4 将java层的接口,翻译为C/C++的接口原型

要想制作java可以调用的native库, 就先要根据java层调用的原型, 转换成C++的接口原型. 但是这个工作不用手动来做, 可以用javah这个命令来做.

在项目的src目录下, 执行javah, 对于本例, 命令为:

javah com.example.testjni.MyJni

网上有人说是在项目的bin/classes下面执行该命令, 但是我在那目录下会报 "无法访问android.support.v7.app.ActionBarActivity" 的错误, 但在src下就OK. 而且很神奇的, 在bin/classes下也同样生成了该文件.

这样会生成: com_example_testjni_MyJni.h

生成的文件内容类似如下:


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

#ifndef _Included_com_example_testjni_MyJni
#define _Included_com_example_testjni_MyJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_testjni_MyJni
 * Method:    printJNI
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_testjni_MyJni_printJNI
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif
jni/com_example_testjni_MyJni.h

其中的Java_com_example_testjni_MyJni_printJNI便是我们要实现的C++的接口.

5 实现C/C++的接口,并注册jni

在工程总目录下, 创建jni目录, 并在其中创建jni/com_example_testjni_MyJni.cpp文件. 内容如下:


#include "jni.h"
#define LOG_TAG "HelloWorld"
#include "android/log.h"
#pragma once
#define LOGI(fmt, args...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)

/* Native interface, it will be call in java code */
JNIEXPORT jstring JNICALL Java_com_example_testjni_MyJni_printJNI(JNIEnv *env, jobject obj, jstring inputStr)
{
    LOGI("dufresne Hello World From libhelloworld.so!");
    const char *str = (const char *)(env)->GetStringUTFChars(inputStr, JNI_FALSE ); // 从 instring 字符串取得指向字符串 UTF 编码的指针
    LOGI("dufresne--->%s",(const char *)str);
    env->ReleaseStringUTFChars(inputStr, str); // 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。
    return env->NewStringUTF("Hello World! I am Native interface");
}

static JNINativeMethod gMethods[] = {
    {"printJNI",       "(Ljava/lang/String;)Ljava/lang/String;",            (void *)Java_com_example_testjni_MyJni_printJNI},
};


/* This function will be call when the library first be load.
* You can do some init in the libray. return which version jni it support.
*/
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env;
    jint result = -1;
    jclass clazz;

    LOGI("dufresne----->JNI_OnLoad!");

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("ERROR: GetEnv failed\n");
        return -1;
    }

    /*look up the class */
    clazz = env->FindClass("com/example/testjni/MyJni");
    if (clazz == 0) {
        LOGE("FindClass error\n");
        return-1;
    }

    if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK) {
        LOGE("ERROR: RegisterNatives failed\n");
        return -1;
    }
    LOGD("RegisterNatives OK\n");

    return JNI_VERSION_1_4;
}

可以看到这里的原型就是刚刚用javah产生的.h中的声明. 刚刚的.h本身并不一定需要, 只是要它的个原型而已.

这里的JNI_OnLoad, 就是java的jni机制提供给我们的固定接口, 程序加载的时候, 会固定的调用该接口, 本例中, 就是在该函数中对我们的native方法的实现, 进行了注册.

至此, 这个小例程的代码就全了. 接下来就是编译运行了.

6 编译运行(将C/C++中的实现部分编译成so库)

6.1 添加jni支持

在eclipse中右击工程总目录, 然后选择Android Tools -- Add Native Support. 然后在 输入名称HelloWorldJni, 该名称与之前java代码中System.loadLibrary的名称一致.

此时会在jni下生成一个Android.mk, 以及一个cpp. 其实本来上述的C++代码应该是加在这个cpp的, 但是我们前面已经自己手动创建了cpp了, 所以就将其删除即可, 后面我们将要在Android.mk的中将其指向我们自己创建的文件.

6.2 修改makefile

jni/Android.mk就相当于我们的jni实现部分的makefile了. 该文件已经自动生成, 但是 其内容还不足以使我们编译通过, 我们将其删除, 并且换成自己的内容:


LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=com_example_testjni_MyJni.cpp
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
LOCAL_MODULE := libHelloWorldJni
LOCAL_SHARED_LIBRARIES := libutils
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_TAGS :=optional
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)

6.3 编译运行

此时在eclipse中点运行, 程序应该就能跑起来了. 从log中应该可以看到如下两行:

dufresne--->I am HelloWorld Activity
Hello World! I am Native interface