Android Studio 基于 NDK 的开发初体验

2015-10-22 21:53:19 +08:00
 banxi1988
最近在接触 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;
}
```
9806 次点击
所在节点    Android
4 条回复
ylqhust
2015-10-22 22:49:29 +08:00
楼主有没有源码分享一下
jukka
2015-10-22 23:19:03 +08:00
不用去纠结 android studio, ndk-build , android.mk 走起。
ybjaychou
2015-10-23 00:47:33 +08:00
我也是这几天在搞一个串口通讯的,结果老是 tcsetattr 函数报错,后来搞了好久原来是编译的时候需要把版本设置成跟目标机器一样的 SDK 版本才行……之前是直接在 mk 里面写的,现在好像是根据 build.gradle 里面的编译版本来的…
r00tt
2015-10-23 09:08:55 +08:00
还是喜欢直接写好代码,写好 android.mk 然后 ndk-build

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/230309

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX