文章目录
1 简介
android平台需要使用C/C++语言编写的库需要经过JNI(Java Native Interface)。JNI是一种编程框架,JNI允许运行于JVM的Java程序调用本地的C/C++以及其他高性能语言编写的代码。JNI有两种调用方式:
- java代码调用native的代码。这种方式比较常用,本文只涉及这种方式。
- native代码调用java代码。
android项目调用本地代码的基本步骤如下:
- 创建一个Java类,在该类中创建一个包含
native
修饰符修饰的方法; - 使用java编译器
javac
编译1步骤创建的java类生成对应的.class
文件; - 利用生成的
.class
文件作为输入使用javah
命令生成对应的JNI头文件; - 为生成的JNI的头文件创建对应的
cpp
实现文件,并在该文件中实现对应的cpp
函数; - 编写自动化编译配置文件
Android.mk
,将其和4步骤的实现文件和3步骤的头文件存放在同一文件夹下利用ndk-build
生成对应的动态链接库。 - 在Java类中加载动态链接库并调用该Native方法。
能够看到的是1-5是生成动态库的过程,而6-7是调用动态库的过程。以上过程都可以手动编译进行,但是这里不打算采用这种方式,下面主要讲如何配置eclipse自动使用上面的命令进行ndk
开发。
2 预置环境
基本的开发环境为eclipse的安卓开发环境配套的SDK和生成动态库需要的编译工具链:jdk
+eclipse
+android sdk
+adt
+android ndk
。其中前三者为基本的android开发环境需要的套件,ndk
为生成动态库需要的交叉编译工具。
在使用前需要提前配置好相关的环境变量:如ANDROID_SDK_HOME
。除了JDK
不用配置也可以在eclipse中配置好也能使用。
下面假定已经安装好android_sdk,android_ndk,adt插件
相关套件,并能够在eclipse中创建一个简单的helloworld
示例项目运行起来。下面简单说明配置ndk
开发环境。
指定ndk: 首先在eclipse中指定ndk
路径,依次点击菜单栏Windows->Preferences->Android->NDK
,点击Browse
选择本地的ndk
路径:
配置自动生成头文件: 这里使用eclipse的external tools
自定义功能,依次点击Run->External Tools->External Tools Configurations
,然后点击左边栏中的Program
,选中后点击左上角的白色文件图标New launch configuration
,创建的新的工具,然后转入Main
选项卡填写相关的配置信息,我使用的基本配置如下:
NAME: jni_h_header_generate
Location: ${system_path:javah}
Working Directory: ${workspace_loc:/mediatask}/jni
Arguments: -classpath "${project_classpath};${env_var:ANDROID_SDK_HOME}/platforms/android-23/android.jar" ${java_type_name}
以上配置皆可以按照需求修改,NAME
即该工具的名称,随意修改;Location
表示需要使用的命令的路径,这里使用的命令是javah
,因为我配置了环境变量Path
因此直接填写的${system_path:javah}
,也可以点击Browse
两个选项卡选择,javah.exe
的路径一般在%JAVA_HOME%/bin
;Working Directory
即生成的JNI的头文件的路径,我这里填写的值表示在eclipse工作路径下的mediatask/jni
路径下,当然也可以点击Browse指定具体的路径;Arguments
即命令的参数,其中ANDROID_SDK_HOME
也可以换成绝对路径,android-23
可以换成自己的android版本。
然后转向Refresh
标签选中The project containing the seleced resource
:
然后转向Common
标签选中Display in favorites menu
中的External Tools
,表示在顶栏的工具栏中显示该工具:
3 生成JNI头文件
首先保证有一个能够顺利运行的android项目。然后分别进行如下步骤:
- 选中项目右键
Android Tools->Add Native Support
,填入生成的native库的名称,点击Finish
;
- 创建一个Java类,在该类中声明一个用
native
修饰的函数,该函数就是需要使用C/C++语言实现的方法,如public native void sayhello();
也可以在MainActivity
中添加该声明,如果只是测试JNI环境可以,正常开发环境下一般不建议; - 在创建native方法的类中添加
static { System.loadLibrary("hello");}
,需要添加到类中,其中的字符串为1步骤中的库名; - 选中包含native方法的类,使用该才创建的
external tools
,我这里创建的是jni_h_header_generate
生成对应的头文件;
- 在之前生成的
hello.cpp
中添加对应的实现,我这里使用的实现为如下,功能为在logcat中输出grayondream, hello
,这里使用了android的log库,因此需要在Android.mk
中添加LOCAL_LDLIBS += -llog
。
#include <jni.h>
#include <stdio.h>
#include <android/log.h>
#include "com_example_mediatask_MediaEncoder.h"
#define TAG "grayondream"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
JNIEXPORT void JNICALL Java_com_example_mediatask_MediaEncoder_sayhello
(JNIEnv *, jobject)
{
LOGV("grayondream, hello");
}
- 如果需要其他的库则在
Android.mk
中进行配置; - 实现完成之后不要忘记在具体的代码中调用。
4 可能遇到的问题
4.1 如果希望删除当前项目中的Native Support
如果希望删除当前项目中的Native Support,则需要删除.cproject
文件和修改.project
文件,.project
中需要删除如下内容。
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.eclipse.cdt.core.ccnature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
如果删除之后出现JNI NDK错误 c/c++ indexer has encountered a problem, An internal error occurred during xx
错误,则关闭eclipse,删除工作目录下.metadata\.plugins\org.eclipse.cdt.core
中所有以.pdom
为后缀的文件。
4.2 生成一次so之后频繁报File Exists的错误
只需要在编译前将工程清空即可,按顺序点击Project->Clean
。
5 所有文件的内容
源文件结构:
➜ mediatask ls .\src\com\example\mediatask\
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2021/3/20 22:47 445 MainActivity.java
-a--- 2021/3/20 22:38 147 MediaEncoder.java
➜ mediatask ls .\jni\
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2021/3/20 22:45 169 Android.mk
-a--- 2021/3/20 22:41 524 com_example_mediatask_MediaEncoder.h
-a--- 2021/3/20 22:44 357 hello.cpp
//MainActivity.java
package com.example.mediatask;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import com.example.mediatask.*;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MediaEncoder en = new MediaEncoder();
en.sayhello();
}
}
package com.example.mediatask;
public class MediaEncoder {
static {
System.loadLibrary("hello");
}
public native void sayhello();
}
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_mediatask_MediaEncoder */
#ifndef _Included_com_example_mediatask_MediaEncoder
#define _Included_com_example_mediatask_MediaEncoder
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_mediatask_MediaEncoder
* Method: sayhello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_mediatask_MediaEncoder_sayhello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
#include <jni.h>
#include <stdio.h>
#include <android/log.h>
#include "com_example_mediatask_MediaEncoder.h"
#define TAG "grayondream"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
JNIEXPORT void JNICALL Java_com_example_mediatask_MediaEncoder_sayhello
(JNIEnv *, jobject)
{
LOGV("grayondream, hello");
}
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello
LOCAL_SRC_FILES := hello.cpp
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
6 参考
基于eclipse进行ndk开发的环境配置
Android之JNI NDK错误 c/c++ indexer has encountered a problem, An internal error occurred during xx
NDK项目添加和删除Native Support