什么是JNI
JNI是Java Native Interface(Java 原生接口)的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。JNI支持一个“调用接口”(invocation interface),它允许你把一个JVM嵌入到本地程序中。本地程序可以链接一个实现了JVM的本地库,然后使用“调用接口”执行JAVA语言编写的软件模块。例如,一个用C语言写的浏览器可以在一个嵌入式JVM上面执行从网上下载下来的applets。
如何在Android Studio中集成JNI开发环境
Android Studio 2.2之后,支持CMake构建工程的JNI开发环境,在Android Studio中创建JNI工程更加方便了。下面我们先来看看配置JNI开发环境大致的步骤:
- 下载NDK,CMake插件,LLDB(Native层)代码调试插件
- 创建CMakeLists.txt文件,并指明要生成的本地库信息
- 配置build.gradle文件,将CMakeLists.txt文件中原生库的配置信息利用Gradle构建起来
1.下载NDK,CMake插件,LLDB(native层)代码调试插件
打开Android Studio依次按File->Setting 打开Android SDK工具管理面板,将CMake,LLDB和NDK三个选项勾选,点击Apply,Android Studio就会帮我们下载CMake插件,LLDB插件和NDK开发工具包。下载之后可以SDK根目录下看到ndk-bundle,如下所示:
下载好后,在SDK根目录下可以看到ndk-build文件夹。其实也可以在Android官网下载NDK,再将其放到SDK的目录下NDK官网下载地址,然后打开Project Structure指明NDK路径即可。
最后将NDK的安装路径配置到系统环境变量中,便于可以在命令行中编译生成c头文件等。
创建CMakeLists.txt文件,并指明要生成的本地库信息
安装好NDK,CMake和LLDB插件后,我们便可以创建JNI工程了,我们先用Android Studio创建一个JNIDemo工程,然后再分析其项目结构。
新建Android Studio工程JNIDemo,勾选Include C++ support选项即可创建JNI工程,如下图:
配置C++支持功能(Customize C++ Support)
在Customize C++ Support 界面默认即可。
指定编译库的环境,其中Toolchain Default使用的是默认的CMake环境;C++ 11也就是C++环境。两种环境都可以编库。
* Exceptions Support
如果选中复选框,则表示当前项目支持C++异常处理,如果支持,在项目Module级别的build.gradle文件中会增加一个标识 -fexceptions到cppFlags属性中,并且在so库构建时,gradle会把该属性值传递给CMake进行构建。
* Runtime Type Information Support
同理,选中复选框,项目支持RTTI,属性cppFlags增加标识-frtti
建好的工程如下所示:
建好的Test工程既已经集成好JNI开发环境了,运行效果如下:
对比下该工程和普通AS工程的区别,发现多了CMakeLists.txt文件,CMakeList.txt文件用于配置JNI项目属性,主要用于声明CMake使用版本,so库名称,c和c++文件路径等信息。打开CMakeLists.txt文件,内容如下:
其中cmake_minimum_required(VERSION 3.4.1)
指明CMake最小使用的版本是3.4.1
- add_library()
表示配置so库信息(为当前脚本添加库)
native-lib
这个是声明引用so库的名称,在项目中,如果需要使用这个so文件,引用的名称就是这个。值得注意的是,实际上生成的so文件名称是libnative-lib。当Run项目或者build项目是,在Module级别的build文件下的intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main下会生成相应的so库文件。SHARED
这个参数表示共享so库文件,也就是在Run项目或者build项目时会在目录intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main下生成so库文。此外,so库文件都会在打包到.apk里面,可以通过选择菜单栏的Build->Analyze Apk…*查看apk中是否存在so库文件,一般它会存放在lib目录下。src/main/cpp/native-lib.cpp
构建so库的源文件。- STATIC
静态库,是目标文件的归档文件,在链接其它目标的时候使用。 - SHARED
动态库,会被动态链接,在运行时被加载。 - MODULE
模块库,是不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数动态链接。 - 头文件
也可以配置头文件路径,方法是(注意这里指定的是目录而非文件):include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 …]) - find_library()
这个方法与我们要创建的so库无关而是使用NDK的Apis或者库,默认情况下Android平台集成了很多NDK库文件,所以这些文件是没有必要打包到apk里面去的。直接声明想要使用的库名称即可(猜测:貌似是在Sytem/libs目录下)。在这里不需要指定库的路径,因为这个路径已经是CMake路径搜索的一部分。如示例中使用的是log相关的so库。 - log-lib
这个指定的是在NDK库中每个类型的库会存放一个特定的位置,而log库存放在log-lib中 - log
指定使用log库 - target_link_libraries()
如果你本地的库(native-lib)想要调用log库的方法,那么就需要配置这个属性,意思是把NDK库关联到本地库。 - native-lib
要被关联的库名称 - ${log-lib}
要关联的库名称,要用大括号包裹,前面还要有$符号去引用。
实际上NDK还预置一个源代码(C/C++)库,如果本地库想要关联这些代码,可以做如下配置
add_library( app-glue
STATIC
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )
# You need to link static libraries against your shared native library.
target_link_libraries( native-lib app-glue ${log-lib} )
app-glue 仍然是自定义库的名称
ANDROID_NDK这个是Android Studio已经定义好的变量,可以直接使用
它指定的是NDK源代码的根目录
使用第三方库在脚本导入第三方库,可在脚本加入如下配置
add_library( imported-lib
SHARED
IMPORTED )
其中IMPORTED表示只需要导入,不需要构建so库
接着设置so库路径
set_target_properties(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
set_target_properties( imported-lib // so库的名称
PROPERTIES IMPORTED_LOCATION // import so库
libs/libimported-lib.so // so库路径
)
3.配置build.gradle文件,将CMakeLists.txt文件中原生库的配置信息利用Gradle构建起来
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.example.msi.jnidemo"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.0.0-beta1'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:0.5'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
}
其中还可以在defaultConfig中用abiFilters指明编译库的ABI配置类型,如:
defaultConfig {
applicationId "com.example.javacallc"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
}
ndk {
// Specifies the ABI configurations of your native
// libraries Gradle should build and package with your APK.
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
'arm64-v8a'
}
}
}
当使用已经存在so库时,不应该配置target_link_libraries()方法,因为只有在build 库文件时才能进行link操作。
在MainActivity中加载动态链接库,并声明native方法,即可调用链接库里面的C/C++函数
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
native-lib.cpp文件代码如下
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring
JNICALL
Java_com_example_administrator_JNIDemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
运行即可获取cpp文件返回的字符串,将其显示在TextView中,如下所示:
限于文章篇幅,有关Java和C/C++的相互调用机制,以及打包so作为系统库的方法将在下篇博文中结合实际例子分析,尽情关注。