`

android jni

 
阅读更多
1.JNI在Android系统中有着广泛的应用。Android系统底层都是C/C++实现的,上层提供的API都是Java的,Java通过JNI调用底层的实现。比如:Android API多媒体接口MediaPlayer类,其实底层通过JNI调用libmedia库。由于JNI的存在可以让我们重用很多已经存在C/C++的库,省去了重复开发的麻烦,并且可以利用很多开源的库(Android库中就有很多开源库,比如libjpeg,libpng等等),并且让我们开发的程序更有效率(C/C++代码发挥硬件最佳性能)。如果你对标准JNI不熟悉的话,可以先参考我的博文《Linux下JNI实现》,文中介绍了怎样用标准JNI在Linux系统中实现一个Helloworld程序,可以让你对JNI有一个初步的认识。本文简单介绍一下怎样在Android下面怎样用JNI开发程序,并开发一个经典Helloworld应用程序。

交叉编译环境

    首先是要搭建交叉编译环境,因为Java层的应用程序是和硬件无关的,JDK编译即可;但是Native C/C++代码是和硬件相关的,必须要用交叉编译器编译成特定硬件可执行代码。请根据你的硬件平台搭建你的交叉编译环境,我的MIPS平台,当然选择的是MIPS的交叉编译器,如果你是Arm的请配置自己的交叉编译器。

    我们首先编译一个Native C的helloworld程序,一个学习怎样在Android中增加一个程序,另外也可以验证我们的交叉编译环境是否正确,可以参考《Android编译环境(1) – 编译Native C的helloworld模块》。如果这个步没有问题说明你的交叉编译环境是没有问题的,可以继续往下面进行。

Java编写的Android应用程序

    我们首先用java编写helloworld应用程序(APK),这个代码很简单创建一个HelloWorld activity。代码如下:
package com.simon;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class HelloWorld extends Activity {
    private static final String TAG = "HelloWorld";
static {
System.loadLibrary("helloworld");
}

private native String printJNI();

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Log.d(TAG, "Activity call JNI: " + printJNI());
    }
}
     这个HelloWorld Activity非常简单,只是调用JNI接口printJNI()打印一些信息到Android logger上面。我们需要关注一下的是printJNI()的声明,有一个native的关键字,说明他是一个用native代码实现的函数,需要用JNI调用Native代码。另外注意static代码段,这段代码意思是当类HelloWorld第一次被加载的时候,加载libhelloworld.so(请注意这里写的是库名称,在Linux中共享库名为xxx共享库,文件存在形式为libxxx.so。所以loadLibrary的参数不是libhelloworld.so,而是helloworld。如果写错误了将会加载库失败,将会收到异常)。

C语言实现helloworld共享库

     接下来我们需要来完成Native 代码部分了,这里需要强调一下,Android JNI实现中为C/C++提供了两套不同的API,调用的时候需要注意,否则非常有可能你会受到一些libc库的崩溃信息,没准儿会把你整“崩溃”,呵呵!下面先实现Native C来实现helloworld库。

     如果你对Java标准JNI熟悉的话,肯定知道javah工具,可以根据java源程序,生成Native代码的头文件(可以参考我的博文《Linux下JNI实现》)。如果你是在Eclipse中开发apk的话,可以在打开终端进入bin目录,然后执行:
javah com.simon.HelloWorld
你将会得到,一个头文件com_simon_Helloworld.h,这里包含有printJNI接口的C/C++声明。这个声明肯定正确,如果你把printJNI接口声明写错了,HelloWorld将找不到printJNI接口,然后产生崩溃。

     我们创建com_simon_Helloworld.c文件,并在该文件中输入:
#include <jni.h>
#define LOG_TAG "HelloWorld"
#include <utils/Log.h>

/* Native interface, it will be call in java code */
JNIEXPORT jstring JNICALL Java_com_simon_HelloWorld_printJNI(JNIEnv *env, jobject obj)
{
    LOGI("Hello World From libhelloworld.so!");

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

/* 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)
{
    void *venv;
    LOGI("JNI_OnLoad!");

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

     return JNI_VERSION_1_4;
}
请注意Java_com_simon_HelloWorld_printJNI函数的名字,这个名字是符合JNI规范的,Java_开头,后面紧跟着调用他类名(包含包名和类名,com_simon_HelloWorld),然后才是接口的名字printJNI。这样java虚拟机就可以在com.simon.HelloWorld类调用printJNI接口的时候自动找到这个C实现的Native函数调用。你可能注意到这个名字非常的长,作为一个函数名它非常有可能不是一个好的选择。JNI API允许你提供一个函数映射表,注册给Jave虚拟机,这样Java虚拟机就可以用函数映射表来调用相应的函数,就可以不必通过函数名来查找需要调用的函数了。这样你的函数名也可以随便定义了(可以定义最能表现函数功能的函数名),这个将会在helloworld共享库的C++实现中演示。但是Android系统中,还是推荐用JNI标准的函数名定义的。

    JNI_OnLoad函数JNI规范定义的,当共享库第一次被加载的时候会被回调,这个函数里面可以进行一些初始化工作,比如注册函数映射表,缓存一些变量等,最后返回当前环境所支持的JNI环境。本例只是简单的返回当前JNI环境。


注意网上很多例子都说可以不实现JNI_OnLoad,我发现如果不是实现的话,printJNI只可以返回整型类型的值,如果返回其他类型的值都会崩溃。并且GetEnv的调用也是必须的,否则也是崩溃,但是GetEnv的返回值我并没有用到,这些地方使我非常迷惑,期待达人解惑。

    接下来编写Android.mk文件,创建之,并输入:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:=com_simon_Helloworld.c

LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)

LOCAL_MODULE := libhelloworld

LOCAL_SHARED_LIBRARIES := libutils

LOCAL_PRELINK_MODULE := false

include $(BUILD_SHARED_LIBRARY)
     这里面有几个标签需要说明:


1、LOCAL_C_INCLUDES说明包含的头文件,这里需要包含JNI的头文件。

2、LOCAL_MODULE当前模块的名称

3、LOCAL_SHARED_LIBRARIES当前模块需要依赖的共享库,因为在hellowold中我们调用Android打印系统输出到logger,所以我们必须要依赖libutils库。

4、LOCAL_PRELINK_MODULE指明该模块是否被启动就加载,可以参考《动态库优化——Prelink(预连接)技术》。我们的helloworld库不需要prelink,所以置为false。



    编译这个模块,根据你的环境不同,选择不同的编译途径。然后安装apk,然后运行他们,通过logcat工具将会看到相应的输出。
2.C++实现HelloWorld共享库

    在本例中Android应用程序不需要有任何变化,我们需要重新用C++实现HelloWorld共享库。创建com_simon_Helloworld.cpp文件,并在文件中输入如下内容:
#include <jni.h>
#define LOG_TAG "HelloWorld"
#include <utils/Log.h>

/*
  * Class:     com_simon_Helloworld
  * Method:    print
  * Signature: ()V
  */
/*JNIEXPORT void JNICALL Java_com_simon_Helloworld_print(JNIEnv *, jobject)*/
JNIEXPORT jstring JNICALL Java_com_simon_HelloWorld_printJNI(JNIEnv *env, jobject obj)
{
    LOGI("Hello World From libhelloworld.so!");
return env->NewStringUTF("Hello World!");
}

static const char *classPathName = "com/simon/HelloWorld";

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

/*
* Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv* env, const char* className,
    JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    clazz = env->FindClass(className);
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s'", className);
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s'", className);
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

/*
* Register native methods for all classes we know about.
*
* returns JNI_TRUE on success.
*/
static int registerNatives(JNIEnv* env)
{
  if (!registerNativeMethods(env, classPathName,
                 methods, sizeof(methods) / sizeof(methods[0]))) {
    return JNI_FALSE;
  }

  return JNI_TRUE;
}

typedef union {
    JNIEnv* env;
    void* venv;
} UnionJNIEnvToVoid;

/* This function will be call when the library first be loaded */
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    UnionJNIEnvToVoid uenv;
    JNIEnv* env = NULL;
    LOGI("JNI_OnLoad!");

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

    env = uenv.env;;

    if (registerNatives(env) != JNI_TRUE) {
        LOGE("ERROR: registerNatives failed");
        return -1;
    }

     return JNI_VERSION_1_4;
}
本例与上文《Android JNI开发入门之一》对比有如下几点不同需要注意:


1、C和C++实现共享库调用不同JNI API。前面已经提到Android系统JNI为C和C++提供了两套不同的API。请仔细对比NewStringUTF,GetEnv函数,就会发现JNI API不同。

2、C++版的helloworld共享库提供了函数映射表。前文《

Android JNI开发入门之一》也已经提到,JNI API为了避免丑陋的函数名,提供了方法向Java虚拟机注册函数映射表。这样当Java调用Native接口的时候,Java虚拟机就可以不用根据函数名来决定调用哪个函数了,直接通过查询表格就可以找到需要调用的函数了。

3、我们注意到RegisterNatives第一个参数(C语言接口中是第二个参数)为调用该函数的Java类。这也和标准JNI函数名包含类名(包名和类名)的作用一样——声明那个Java类可以调用这个方法。

4、函数映射表的定义非常的怪异。你可以参考

Android JNI 使用的数据结构JNINativeMethod详解和JNI标准手册相关类型的部分。


    通过对比你会发现C++的实现同样功能的共享库比C加入更多的代码,另外你可能会有疑问既然Java虚拟机能用通过函数名访问到相应的Native code函数,为什么还要提供注册映射函数表呢?没错!作为一个HelloWorld程序,确实简单为第一要务!如果Java虚拟机能用函数名能访问到相应的函数的话,我是不会多此一举来注册映射函数表。在实践中我发现:

   标准JNI不能通过标准函数名找到C++实现的Helloworld共享库中的函数,但是C实现的helloworld共享没有这个问题。我不知道为什么会这样,请达人指教。没有办法才提供注册映射函数表。

    下面提供一个helloworld共享库的Makefile——Android.mk:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:=com_simon_Helloworld.cpp

LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)

LOCAL_MODULE := libhelloworld

LOCAL_SHARED_LIBRARIES := libutils

LOCAL_PRELINK_MODULE := false

include $(BUILD_SHARED_LIBRARY)
    和前文《Android JNI开发入门之一》的Android.mk文件相比也就是修改了一下源文件,没有什么可说。编译生成libhelloworld.so文件,允许前文HelloWorld Android应用程序,你将会得到和前文相同的结果。
分享到:
评论

相关推荐

    Android JNI

    android端使用jni+ndk实现调用C++代码,实现调用硬件的时候会提升速度

    android JNI简单例子

    该app为android系统下简单的jni示例,是学习JNI的好例子,为入门者提供简单的JNI环境配置示例

    Android Jni的简单使用详解

    主要介绍了Android Jni的简单使用详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    android jni调用demo

    这是Android的NDK开发之Android JNI调用流程程一个demo。下载下来可以直接在android studio上运行。

    Android JNI demo

    参考:http://blog.csdn.net/linmiansheng/article/details/25113461

    AndroidJni

    最简单的Android Jni代码,在Java层调用JNI层字符串,数据。博客地址:http://blog.csdn.net/tangguna123?skin=ink

    Android JNI 实例

    Android JNI 实例,包括动态注册JNI ,静态注册JNI

    Android jni

    Android jni

Global site tag (gtag.js) - Google Analytics