最近在接触 Dex 底层操作时,了解了下 NDK 的开发。
虽然 Android Studio 对于 NDK 开发还是实验性的支持。但是对于我个人入门来说。
我感觉比基于 Eclipse 的 NDK 开发方便很多。
但是当我后面用 JNI 实现的一个 `sayHi` 的方法之后。
我竟然心里突然觉得 Java 这个语言真是太方便好用了。
## 项目创建与配置
### 0x0 下载 安装 NDK SDK
[NDK Downloads](
https://developer.android.com/intl/zh-cn/ndk/downloads/index.html)
### 0x1 创建一个新的空白的 Application
然后修改 项目目录的 `local.properties` 添加 NDK-SDK 的路径配置。
根据安装的位置自行修改路径,如下:
`ndk.dir=/Users/youjianame/android-ndk-r10e`
### 0x2 更新 gradle 版本到 2.5 .
### 0x3 使用支持 NDK 的 gradle 编译插件
修改项目目录下的 `build.gradle` 编译插件路径,由
`classpath
com.android.tools.build:gradle:1.3.0`
改为
`classpath
com.android.tools.build:gradle-experimental:0.2.1`
### 0x4. 修改 `app/build.gradle`
应用的 DSL 需要的修改会比较大。
由原来生成的:
```java
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "com.banxi1988.ndk_jni_helloworld"
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), '
proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.0'
}
```
修改为如下:
```java
apply plugin: 'com.android.model.application'
model{
android {
compileSdkVersion = 23
buildToolsVersion = "23.0.1"
defaultConfig.with{
applicationId = "com.banxi1988.ndk_jni_helloworld"
minSdkVersion.apiLevel = 16
targetSdkVersion.apiLevel = 23
versionCode = 1
versionName = "1.0"
}
compileOptions.with{
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
}
compileOptions.with {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
android.buildTypes {
release {
minifyEnabled = false
proguardFiles += file('
proguard-rules.pro')
}
}
android.ndk{
moduleName = "hello-jni"
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.1.0'
}
```
## 编写 Native 代码
### 0x1 在 Java 类中编写静态方法声明,让 Studio 帮助生成 Native 代码。
在应用主包下创建一个 名为 `NativeGreetings`的类
如下:
```java
package com.banxi1988.ndk_jni_helloworld;
public class NativeGreetings {
public native String helloWorld();
}
```
此时将光标停留在 `helloWorld` 方法名上面,左右会出现一个修复错误的提示。
点击小灯泡之后就会弹出如下修复菜单:
第一个菜单项即是:
`Create function Java_com_banxi1988_ndk_1jni_1helloworld_NativeGreetings_helloWorld`
选择创建之后就会自动为我们创建相应的 C 函数文件及对应的方法。
为我们创建了如下的文件 `app/src/main/jni/nativegreetings.c`
```c
#include <jni.h>
JNIEXPORT jstring JNICALL
Java_com_banxi1988_ndk_1jni_1helloworld_NativeGreetings_helloWorld(JNIEnv *env, jobject instance) {
return (*env)->NewStringUTF(env, returnValue);
}
```
### 0x2 返回 Hello World 字符串
下面只是简单的实现如下:
```c
JNIEXPORT jstring JNICALL
Java_com_banxi1988_ndk_1jni_1helloworld_NativeGreetings_helloWorld(JNIEnv *env, jobject instance) {
return (*env)->NewStringUTF(env, "A Hello World Greeting From JNI! ");
}
```
### 0x3 在 NativeGreetings 类的静态区加载相应的静态原生库
将 NativeGreetings 类修改如下。增加一个 static 区,在其中加载静态链接库。
库名称即是我们在 `android.ndk` 中配置的 `moduleName`
```java
public class NativeGreetings {
public native String helloWorld();
static{
System.loadLibrary("hello-jni");
}
}
```
### 0x4 修改一下我们的应用以使用此原生方法:
```java
NativeGreetings greetings = new NativeGreetings();
TextView greetingsView = (TextView) findViewById(R.id.greetings);
greetingsView.setText(greetings.helloWorld());
```
运行即可看到效果了。
## 一个复杂一点 JNI 代码示例
在 Java 代码中添加一个 `sayHi`的方法声明
同时添加一模型字段。如下:
```java
public native String sayHi();
public String firstName;
public String lastName;
public NativeGreetings(String firstName,String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
```
这个 `sayHi` 想要实现的是类似下面的效果:
```java
public String sayHi(){
return "Hi, "+this.firstName+" "+this.lastName;
}
```
下面是对应的 JNI 代码:
```c
JNIEXPORT jstring JNICALL
Java_com_banxi1988_ndk_1jni_1helloworld_NativeGreetings_sayHi(JNIEnv *env, jobject instance) {
jclass class = (*env)->GetObjectClass(env,instance);
jfieldID firstNameID = (*env)->GetFieldID(env,class,"firstName","Ljava/lang/String;");
jfieldID lastNameID = (*env)->GetFieldID(env,class,"lastName","Ljava/lang/String;");
jstring firstName = (*env)->GetObjectField(env,instance,firstNameID);
jstring lastName = (*env)->GetObjectField(env,instance,lastNameID);
jbyte *firstNameBytes = (*env)->GetStringUTFChars(env,firstName,NULL);
jbyte *lastNameBytes = (*env)->GetStringUTFChars(env,lastName,NULL);
char * hi = "Hi, ";
char * greetingChars = malloc(strlen(hi) + strlen(firstNameBytes) + strlen(" ") +strlen(lastNameBytes) + 1);
strcpy(greetingChars,hi);
strcat(greetingChars,firstNameBytes);
strcat(greetingChars," ");
strcat(greetingChars,lastNameBytes);
jstring greeting = (*env)->NewStringUTF(env,greetingChars);
(*env)->ReleaseStringUTFChars(env,firstName,firstNameBytes);
(*env)->ReleaseStringUTFChars(env,lastName,lastNameBytes);
free(greetingChars);
return greeting;
}
```