引言
尽管Android Studio已经越来越流行了,但很多人还是习惯于Eclipse或源码环境下开发JNI应用。笔者是从以前在学校参加谷歌大学学术合作项目的时候接触JNI的,当时是为了模仿底层的方法实现一个功能,使用的是Eclipse,直到最近项目中需要用到SmartLink、AirKiss才又再次接触到JNI,第一次使用Android Studio开发NDK,遇到不少弯路再次总结下,以期能帮助新手快速入门不再迷茫。
一、NDK相关角色概述
1、NDK和SO
NDK 全称 Native Development Kit,是Google在Android开发中提供的一套用于快速创建native工程的一个工具。从上图这个Android系统框架来看,我们上层是通过JNI方式来调用NDK层的,使用这个工具可以很方便的编写和调试JNI的代码。因为C语言不跨平台,在Windows系统下使用NDK编译在Linux下能执行的函数库——SO文件,全称Shared Objects,其实质就是一堆c、c++的头文件和实现文件打包成一个库。目前Android系统目前支持以下七种不同的CPU架构,每一种对应着各自的应用程序二进制接口ABI:(Application Binary Interface)定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。
- ARMv5——armeabi
- ARMv7 ——armeabi-v7a
- ARMv8——arm64- v8a
- x86——x86
- MIPS ——mips
- MIPS64——mips64
- x86_64——x86_64
以本人ODM经验来说,你应该尽可能的提供专为每个ABI优化过的.so文件,但不应该混合着使用。你应该为每个ABI目录提供对应的.so文件。当一个应用安装在设备上,只有该设备支持的CPU架构对应的.so文件会被安装。在x86设备上,libs/x86目录中如果存在.so文件的 话,会被安装,如果不存在,则会选择armeabi-v7a中的.so文件,如果也不存在,则选择armeabi目录中的.so文件(因为x86设备也支 持armeabi-v7a和armeabi)。ps: Native Libs Monitor 这个应用可以帮助我们理解手机上安装的APK用到了哪些.so文件,以及.so文件来源于哪些函数库或者框架。
2、JNI
JNI 全称Java Native Inteface,即Java本地接口,是Java中定义的一种用于连接Java和C/C++接口的一种实现方式。Java语言装载到虚拟机中,不能和硬件交互,不能驱动开发。JNI扩展了Java虚拟机的能力,驱动开发、无线热点共享,底层语言(C、C++)效率高,数学运算、实时渲染的游戏,音视频处理等等,简而言之,就是Java代码调用c、c++代码,JNI模式一共涉及到三个角色:C/C++ 代码、本地方法接口类、Java层中具体业务类
1、JNI简要流程
2、JNI的变量类型与Java类型对比表
Java中变量的类型 | JNI对应的类型名 | C/C++变量类型 |
---|---|---|
boolean | jboolean | unsigned char |
float | jfloat | float |
double | jdouble | double |
byte | jbyte | signed char |
char | jchar | unsigned short |
short | jshort | short |
int | jint/jsize | int |
long | jlong | long long |
Object | jobject | void * |
String | jstring | void * |
3、JNI的基本语法
命名规则
JNIExport jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,jobject thiz )
Java端加载so
System.loadLibrary("helloJni");//加载so文件,不要带上前缀lib和后缀.so
- Java端的调用jni
((TextView)findViewById(R.id.jni_text)).setText(helloJni());
3、Gradle
Gradle 是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置。以往Android NDK开发需要在Eclipse或源码环境下,建立并配置Android.mk和Application.mk,且还要通过java命令生成.h头文件,才能编译生成so库。但在Android Studio中这些步骤都不需要,因为Gradle足够强大,只需配置Gradle即可编译生成so库。
三、开发JNI的步骤
JNI代码主要又分为Native代码和Java代码,所以我们得实现Native端和Java端
1、安装NDK配置环境变量和相关插件(NDK、CMake、LLDB)
把NDK里build目录添加到path变量 ,比如我的目录是D:\AndroidDevlopment\SDK\ndk-bundle
2、新建一个Android标准工程,并在工程设置中配置NDK路径。
3、打开app下的 build.gradle,配置NDK和在gradle.properties里配置android.useDeprecatedNdk=true
ndk{
moduleName "helloJni"//*生成的so文件名,必填
abiFilters "armeabi", "armeabi-v7a", "x86" //配置输出的abi体系结构下的so库,
}
配置支持NDK
android.useDeprecatedNdk=true//是灰色的不影响
若不配置android.useDeprecatedNdk=true点击脚本同步,之后会报这个错误
4、在Actiivty类文件里定义jni本地接口方法
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("helloJni");//加载so文件,不要带上前缀lib和后缀.so
}
public native String helloJni();//定义本地方法接口,这个方法类似虚方法,实现是用c或者c++实现的
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
5、生成对应的C文件默认实现
在我们定义了本地接口方法之后,我们在方法上按alt+Enter,然后生成对应的方法,可是不出意外的话生成的c文件只是有一个头文件的,并没有为我们生成对应的方法框架,
#include <jni.h>
如果你熟悉Jni的语法可以自己去实现,但是太麻烦了,幸好谷歌为我们提供了一个插件gradle-experimental,我们只需要在app下的gradle.build脚本里配置(仅仅在我们生成jni方法框架时添加,当我们全部添加完JNI方法框架之后,必须注释或者删除掉,否则run的时候就绝对报错)
- gradle-experimental插件
在2015年5月的Google I/O大会上, Google宣布Android Studio开始支持NDK开发,通过和JetBrains的合作,将Clion整合进了Android Studio 1.3,并免费支持NDC++开发。同年7月,在Android Studio 1.3版本上添加了 gradle-experimental插件,该插件支持NDK开发和调试,且带有代码不全和重构等高级功能。
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.tools.build:gradle-experimental:0.7.0'//仅仅在我们生成jni方法框架时添加,当我们全部添加完JNI方法框架之后,必须注释或者删除掉,否则run的时候就绝对报错
}
配置成功了之后,再执行下脚本,把原来生成的jni文件夹删除掉,再按万能键,这时候就妥妥滴生成了c文件
#include <jni.h>
JNIEXPORT jstring JNICALL Java_crazymo_train_jnitraining_MainActivity_helloJni(JNIEnv *env,jobject instance){
// TODO
return (*env)->NewStringUTF(env, "Hello Jni");
}
6、完成上面5个步骤之后,就可以make或者run了,然后就会生成对应的so文件(..\build\intermediates\ndk\debug\obj\local)
完整的Activity实现如下:
package crazymo.train.jnitraining;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("helloJni");//加载so文件,不要带上前缀lib和后缀.so
}
public native String helloJni();//定义本地方法接口,这个方法类似虚方法,实现是用c或者c++实现的
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((TextView)findViewById(R.id.jni_text)).setText(helloJni());//调用JNI
}
}
二、HelloJNI源码
这么简单的也上传源码Hello JNI源码