Android环境下的JNI调用

此文章写于2015.02.03,发布于网易博客,于2020.03.25迁移至此。

前言:经多多次尝试与查阅资料,得出结论:Android环境下的JNI调用不能像Java环境下一样加载Windows下的dll文件,需要加载的是Linux下的so文件。

参考文章:http://www.cnblogs.com/bastard/archive/2012/05/19/2508913.html
以下过程说明了Android是如何通过Jni来实现Java对C/C++函数调用的:
首先,编写Android应用程序:
package com.example.helloworld;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		System.out.println("------>>" + printJNI("I am HelloWorld Activity"));
	}

	static
	{
		//加载库文件
		System.loadLibrary("HelloWorldJni");
	}
	//声明原生函数 参数为String类型 返回类型为String
	private native String printJNI(String inputStr);
	
	@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;
	}

}

 eclipse会自动为我们编译此Java文件,后面要用到。

然后,生成共享库的头文件:

进入到Android 项目目录中 :D:\workspace\HelloWorld\bin\classes\com\example\helloworld,可以看到里面有很多后缀为.class的文件,就是eclipse为我们自动编译好了的java文件,找到MainActivity.class文件。退回到classes级目录:D:\workspace\HelloWorld\bin\classes\

执行如下命令:javah com.example.helloworld.MainActivity,生成文件:com_example_helloworld_MainActivity.h

打开如下:

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

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

可以看到自动生成对应的函数:Java_com_example_helloworld_MainActivity_printJNI。

java虚拟机就可以在调用printJNI接口的时候自动找到这个C实现的Native函数。(函数名太长可以在.c文件中通过函数名映射表来实现简化)

下一步,实现JNI原生函数源文件:

新建com_example_helloworld_MainActivity.c文件:

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

#define LOG    "ffmpegDemo-jni" // 这个是自定义的LOG的标识  
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG,__VA_ARGS__) // 定义LOGD类型  
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG,__VA_ARGS__) // 定义LOGI类型  
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG,__VA_ARGS__) // 定义LOGW类型  
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG,__VA_ARGS__) // 定义LOGE类型  
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG,__VA_ARGS__) // 定义LOGF类型  

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

/* 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;
	LOGD("LXK----->JNI_OnLoad!");
	if ((*vm)->GetEnv(vm, (void**)&venv, JNI_VERSION_1_4) != JNI_OK) {
		LOGE("LXK--->ERROR: GetEnv failed");
		return -1;
	}
	return JNI_VERSION_1_4;
}

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

下一步,编译生成so库(假设你已经搭建好了Android NDK开发环境):

在当前项目目录下建立jni文件夹:HelloWorld/jni/,并将com_example_helloworld_MainActivity.c com_example_helloworld_MainActivity.h 文件都拷贝进去,然后在此目录建立Android.mk 文件编写用于编译so库的Android.mk文件:

LOCAL_PATH:= $(call my-dir)
# 一个完整模块编译
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=com_example_helloworld_MainActivity.c
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
LOCAL_MODULE := libHelloWorldJni
LOCAL_SHARED_LIBRARIES := libutils
LOCAL_LDLIBS := -llog
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_TAGS :=optional
include $(BUILD_SHARED_LIBRARY)

系统变量解析:

LOCAL_PATH - 编译时的目录
$(call 目录,目录….) 目录引入操作符,如该目录下有个文件夹名称 src,则可以这样写 $(call src),那么就会得到 src 目录的完整路径

 

include $(CLEAR_VARS) -清除之前的一些系统变量
LOCAL_MODULE - 编译生成的目标对象
LOCAL_SRC_FILES - 编译的源文件
LOCAL_C_INCLUDES - 需要包含的头文件目录
LOCAL_SHARED_LIBRARIES - 链接时需要的外部库
LOCAL_PRELINK_MODULE - 是否需要prelink处理 
include$(BUILD_SHARED_LIBRARY) - 指明要编译成动态库

打开cygwin命令行,进入到刚才建立的jni目录里,输入命令:$NDK/ndk-build 即可,此时在你项目的HelloWorld/libs/armeabi目录下即可生成需要的.so文件。

 
最后,验证执行:

将编译好的apk安装到手机上

  使用adb push到手机上的需要自己去导入库文件libHelloWorldJni.so到data/data/com.example.helloworld/lib/目录下

  使用adb install方式安装到手机上的则会自动导入。

 

另外需要注意的一点是,在.c文件中用printf进行的打印是无法在logcat中看到的,因为NDK根本就不支持。要想在.c文件中加打印进行调试,可以调用NDK 下的log.h 来打印Log 日志,具体方法如下:

1. 在你的Android.mk文件中加入:LOCAL_LDLIBS := -llog

2. 导入log头文件:在你使用的 .c/ .cpp 文件中,导入 log.h 头文件: #include<android/log.h>

3. 定义LOG 函数:先定义一个全局变量,再定义一些输出的LOG函数:

  1. #define LOG    "HelloWorldDemo-jni" // 这个是自定义的LOG的标识  
  2. #define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG,__VA_ARGS__) // 定义LOGD类型  
  3. #define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG,__VA_ARGS__) // 定义LOGI类型  
  4. #define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG,__VA_ARGS__) // 定义LOGW类型  
  5. #define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG,__VA_ARGS__) // 定义LOGE类型  
  6. #define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG,__VA_ARGS__) // 定义LOGF类型  

上述代码中定义的函数,分别对应于Android 的Java代码中的 Log.d(), Log.i(), Log.w(),Log.e(), Log.f()等方法;其中:

ANDROID_LOG_DEBUG:是打印日志的级别;
LOG:是要过滤的标签,可以在LogCat视图中进行过滤。
__VA_ARGS__:是实际的打印内容。

4. 使用上述方法:在.c 代码中直接按照以下方式书写:

LOGD("这是Debug的信息");       LOGE("程序错误!!!");  

除此之外,更可以给在打印时带上一些变量:

int width=10;     int height=20;     LOGI("长和宽分别为 %d ,%d“,width,height);  

这种方式非常灵活,类似于 C语言中的 printf()函数。

注:在你编译.so文件的过程中,如果你的.c或.c++文件出现类似下面的错误,则原因是由于你从网页上复制下来粘贴而引起的空格与linux下的space键不同的问题,所以最好自己手动输入,这样可以避免格式上的错误。

发布了4 篇原创文章 · 获赞 0 · 访问量 102

猜你喜欢

转载自blog.csdn.net/lxkscm/article/details/105092467
今日推荐