Android-通过NDK编译so库

JNI编译成Android能使用的so库,与Java工程所能使用的jnilib库不同,需要借助NDK(Native Development Kit)完成。

JNI在Java工程中的步骤参考:Java-OS X系统下JNI步骤

通过NDK编译so库, 步骤如下:

安装NDK

下载NDK,解压
打开命令行,输入:
pico .bash_profile
设置环境变量,如下图

NDK-Install

按Control+X退出,并按Y保存
重启命令行,输入
cd ANDROID_NDK_ROOT
如果能跳到对应的目录,说明设置成功

编写Demo代码

注意,Android的Java文件必须有包名,例如下例:

package com.baidu.carlife;
import java.util.ArrayList;

public class KeyboardService {
    public native void initService(String dbPath);
    public native ArrayList<String> search(String key);
    public native ArrayList<String>  relateWords(String keyWord);
}

编译生成.H文件

在项目根目录下,创建jni文件夹,与src同级

NDK-Project

注意必须根目录下。

在命令行中cd到根目录,执行命令
javac -d ./jni/ src/com/baidu/carlife/KeyboardService.java -classpath /Users/YI/Documents/JavaWorkSplace/Android/platforms/android-21/android.jar

完成后,在jni目录下,会生成文件:

KeyboardService.class
在命令行中cd到jni目录,执行命令
javah -jni com.baidu.carlife.KeyboardService

完成后,在jni目录下,会生成文件:

com_baidu_carlife_KeyboardService.h

其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_baidu_carlife_KeyboardService */

#ifndef _Included_com_baidu_carlife_KeyboardService
#define _Included_com_baidu_carlife_KeyboardService
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_baidu_carlife_KeyboardService
* Method: initService
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_baidu_carlife_KeyboardService_initService
(JNIEnv *, jobject, jstring);

/*
* Class: com_baidu_carlife_KeyboardService
* Method: search
* Signature: (Ljava/lang/String;)Ljava/util/ArrayList;
*/
JNIEXPORT jobject JNICALL Java_com_baidu_carlife_KeyboardService_search
(JNIEnv *, jobject, jstring);

/*
* Class: com_baidu_carlife_KeyboardService
* Method: relateWords
* Signature: (Ljava/lang/String;)Ljava/util/ArrayList;
*/
JNIEXPORT jobject JNICALL Java_com_baidu_carlife_KeyboardService_relateWords
(JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

编写CPP实现文件

新建KeyboardService.cpp文件,包含.h头文件并实现函数
1
2
3
4
#include <iostream>
#include <stdio.h>

#include "com_baidu_carlife_KeyboardService.h"

具体实现,可以参考Java-JNI对象构造和字符串转换

这里,注意在实现.h文件定义的函数时,不要调用全局变量,否则可能识别不到。同时,函数名不要与系统函数同名。

创建Android.mk

在jni目录下,创建Android.mk文件
1
2
3
4
5
6
7
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := KeyboardService
LOCAL_SRC_FILES := KeyboardService.cpp \
sqlite3.c
include $(BUILD_SHARED_LIBRARY)

这里的:

1
2
LOCAL_MODULE:最后生成的so名称,会自动加上"lib"前缀和"so"后缀,本例是libKeyboardService.so
LOCAL_SRC_FILES:需要编译的实现文件,用“\”分割

根据需要可以加上:

1
2
LOCAL_C_INCLUDES:=头文件的搜索路径
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog 用于在C++中调用Android的Log方法,打印日志到LogCat

创建Application.mk

在jni目录下,创建Application.mk文件
1
2
3
APP_PLATFORM := android-21
APP_STL := stlport_static
APP_ABI := all

这里的:

1
2
3
APP_PLATFORM:Android版本
APP_STL:STL库的类型
APP_ABI:需要编译的CPU平台,如果不设置,默认是armeabi

如果不设置APP_STL,会编译出错,标准库的头文件将找不到。

编译生成so库

cd到项目的根目录,执行命令
NDK-BUILD

可以看到,针对多个CPU平台的so库,都编译成功。

1
2
3
4
5
6
7
8
9
10
Android NDK: WARNING: Rebuilding STLport libraries from sources!    
Android NDK: You might want to use $NDK/build/tools/build-cxx-stl.sh --stl=stlport
Android NDK: in order to build prebuilt versions to speed up your builds!
[arm64-v8a] Install : libKeyboardService.so => libs/arm64-v8a/libKeyboardService.so
[x86_64] Install : libKeyboardService.so => libs/x86_64/libKeyboardService.so
[mips64] Install : libKeyboardService.so => libs/mips64/libKeyboardService.so
[armeabi-v7a] Install : libKeyboardService.so => libs/armeabi-v7a/libKeyboardService.so
[armeabi] Install : libKeyboardService.so => libs/armeabi/libKeyboardService.so
[x86] Install : libKeyboardService.so => libs/x86/libKeyboardService.so
[mips] Install : libKeyboardService.so => libs/mips/libKeyboardService.so

根目录下,生成了libs文件夹:

NDK-Project

使用so库

加载so库
static {
    System.loadLibrary("KeyboardService");
    System.load("/data/data/com.baidu.carlife/libKeyboardService.so");
}

这里,有两种加载so文件的方式:

(1)用loadLibrary调用的时候需要去掉”lib”前缀和”so”后缀;

(2)用load调用的时候需要写全路径名且不能去掉前缀和后缀.

调用Demo
KeyboardService service = new KeyboardService();
service.initService(“”);
service.search("");
service.relateWords("");