說在最前面斋攀,當(dāng)前使用的系統(tǒng)是 Windows 10,筆者使用過Mac,Linux 和 Windows感覺不同的系統(tǒng)的差異不過是在環(huán)境配置和指令參數(shù)方面的問題,使用其它系統(tǒng)的小伙伴可以靈活變化硕旗。
1 JNI 及 NDK工具的 Simply 介紹
不記錄啥大道理,筆者只是簡單粗暴地做一下說明
- JNI(Java Native Interface) Java 本地化(C/C++)接口的集合女责,目的是在Java中使用C/C++的庫
- NDK(Native Develop Kit) 本地(C/C++)開發(fā)工具集漆枚,包括了C/C++編譯需要的工具集合
2 使用NDK生成SO庫
下載NDK
有梯子的小伙伴可以去Google官方下載
https://developer.android.com/ndk/downloads/index.html
或者國內(nèi)Google Android
https://developer.android.google.cn/ndk/downloads/revision_history
或者國內(nèi)小伙伴常去的
http://www.androiddevtools.cn/
或者Android Studio中【使用國內(nèi)鏡像】直接下載
總而言之 最終的目的就是下載NDK,條條大道通羅馬抵知!筆者有梯子墙基,所以到官方下了的,為了說明特意下載了一個(gè)r12
和r16
兩個(gè)版本-
環(huán)境變量配置
首先說明刷喜,配置環(huán)境變量只是方便在命令行在所有目錄都可以使用ndk指令残制,否則需要使用NDK指令時(shí),必須cd
到NDK的安裝目錄下執(zhí)行指令(很煩有沒有掖疮?)
使用Windows打開環(huán)境變量配置的方法:"Win鍵 + Pause|Break鍵" 打開系統(tǒng)面板 -> 高級系統(tǒng)設(shè)置 -> 環(huán)境變量 -> 在系統(tǒng)變量那一塊中找到并選擇Path
變量 -> 編輯 -> 點(diǎn)擊"添加" -> <輸入你的NDK build文件夾完整目錄> -> 一路點(diǎn)擊"確定"返回!!!
驗(yàn)證環(huán)境:命令行任意目錄執(zhí)行ndk-build --version
,輸出如圖
-
編寫C/C++代碼(筆者寫C)
在桌面新建一個(gè)"NDK-Test"文件夾(其它目錄和名字也隨意了初茶,這個(gè)小伙伴們都懂),在“NDK-Test”中新建文件age.h
age.c
student.c
浊闪,同一個(gè)目錄新建文件夾include
如圖:
接著將 .h
全部文件移動到 include
文件夾中恼布。
編輯 age.h
int get_five_years_later_age(int age);
編輯 age.c
#include<stdio.h>
#include "include/age.h"
int get_five_years_later_age(int age) {
printf("In get_five_years_later_age: input age is -> %d", age);
age+=5;
return age;
}
編輯 student.c
#include <stdio.h>
#include "include/age.h"
int get_ten_years_later_age(int age) {
int get_age = get_five_years_later_age(age);
printf("In get_ten_years_later_age: get age is -> %d", get_age);
get_age += 5;
return get_age;
}
int main() {
int final_age = get_ten_years_later_age(8);
printf("In main: final age is -> %d", final_age);
getchar();
return 0;
}
- 編寫 Android.mk 文件
#build-so.mk
# 初始化編譯目錄由編譯系統(tǒng)完成: 取得調(diào)用宏函數(shù)my-dir的返回值(當(dāng)前目錄)存放到LOCAL_PATH變量
LOCAL_PATH := $(call my-dir)
# 隔離系統(tǒng)變量(除了我們設(shè)置的LOCAL_PATH變量)
include $(CLEAR_VARS)
# 模塊名字(輸出庫的名字)
LOCAL_MODULE := student
# 編譯中依賴的源文件(非頭文件)
LOCAL_SRC_FILES := student.c age.c
# 編譯中依賴的頭文件(系統(tǒng)頭文件自動依賴添加)
LOCAL_C_INCLUDES := include/age.h
# 輸出庫的類型 BUILD_SHARED_LIBRARY 動態(tài)庫 .so,BUILD_STATIC_LIBRARY 靜態(tài)庫 .a搁宾,可執(zhí)行文件 BUILD_EXECUTABLE
include $(BUILD_SHARED_LIBRARY)
- 編譯.so動態(tài)庫
打開命令行并cd
到NDK-Test
目錄折汞,然后執(zhí)行:
ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk
結(jié)果:
說明,NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk這兩個(gè)必須指定猛铅,NDK_APPLICATION_MK=./Application.mk 可選指定字支,不指定的時(shí)候?qū)⑹褂媚J(rèn)的Application.mk,也就是指定Android版本為android-14,輸出所有架構(gòu)的.so庫堕伪。假使以可執(zhí)行的方式進(jìn)行編譯揖庄,則需要在Android pak運(yùn)行時(shí),使得linux fork新的進(jìn)程執(zhí)
行
- 關(guān)于Application.mk
未完待續(xù) ~~
3 在 Eclipse 中編譯 .so庫
這里說明一下欠雌,前面是在命令行執(zhí)行代碼蹄梢,其實(shí)在Eclipse配置編譯的program就是指定路徑過程
- 新建NDKDemo項(xiàng)目
在配置好Android SDK和NDK環(huán)境后可以直接新建Android項(xiàng)目,這里順帶提一下幾個(gè)問題:
- min SDK 最低SDK版本(4.2+)富俄,選擇過低的版本比如 Android 2.2等禁炒,會報(bào)style的theme找不到的錯(cuò)。
- target version 編譯版本霍比,一般推薦的是 6.0了幕袱,6.0是個(gè)分水嶺,想知道的留言悠瞬,筆者給你Google一下
- compile version 使用已經(jīng)下載的最高版本吧
拷貝源文件到 NDKDemo 項(xiàng)目
在新建的項(xiàng)目中们豌,右鍵項(xiàng)目新建jni
文件夾,將我們前面的NDK-Test
中的文件夾和源文件拷貝到j(luò)ni文件夾下浅妆,配置項(xiàng)目 NDK builde program
右鍵項(xiàng)目 -> 屬性 -> Builders -> New -> Program ->
Then -> project -> clean
We make it!!!
4 在 Eclipse 中編譯使用現(xiàn)有的 .so 庫(第三方庫)并運(yùn)行
還是基于上面的項(xiàng)目進(jìn)行
獲取一個(gè)
armeabi-v7a
架構(gòu)的 .so 庫作為我們的引用庫
這里小伙伴們可以刪除libs
目錄下的所有文件望迎,和JNI中的源文件(.c),筆者這里為了保留上次編譯痕跡凌外,就不刪除了辩尊。我們把armeabi-v7a
中的libstudent.so
庫到 jni目錄下編寫調(diào)用符合JNI格式文件
在jni目錄下新建并編寫我們調(diào)用庫的teacher.c
文件,這里注意teacher.c
中供Java
使用的方法必須按照JNI
格式寫康辑!(com_example_ndkdemo_MainActivity 這是java調(diào)用類的權(quán)限類名)
#include <jni.h>
#include "include/age.h"
int add_one(int age) {
age += 1;
return age;
}
JNIEXPORT jint JNICALL Java_com_example_ndkdemo_MainActivity_get(JNIEnv *env,
jobject obj);
JNIEXPORT jint JNICALL Java_com_example_ndkdemo_MainActivity_get(JNIEnv *env,
jobject obj) {
int result = get_five_years_later_age(12);
result = add_one(result);
return result;
}
- 編寫我們的Android.mk
LOCAL_PATH := $(call my-dir)
# 重新打包已存在的 .so 庫
include $(CLEAR_VARS)
LOCAL_MODULE := student
LOCAL_SRC_FILES := libstudent.so
# .so 頭文件
LOCAL_EXPORT_C_INCLUDES := include
include $(PREBUILT_SHARED_LIBRARY)
#
include $(CLEAR_VARS)
LOCAL_MODULE := teacher
LOCAL_SRC_FILES := teacher.c
LOCAL_LDLIBS += -L$(SYSROOT) -llog
LOCAL_CFLAGS := -g
# 引用重新打包后的 .so 庫
LOCAL_SHARED_LIBRARIES := student
include $(BUILD_SHARED_LIBRARY)
- !!! 編寫我們的 Application.mk
這個(gè)在現(xiàn)在才提出來摄欲,其實(shí)是有原因的,跟著一起編下來的小伙伴會發(fā)現(xiàn)晾捏,如果沒有 Application.mk 中指定的參數(shù)蒿涎,會導(dǎo)致各種的方法索引不到,庫的格式錯(cuò)誤的問題惦辛。筆者大概是在編armeabi-v8a過程中劳秋,引發(fā)這個(gè)問題,這才在這里提出Application.mk胖齐,讓小伙伴們可以更加貼切地感受Application.mk的強(qiáng)大之處玻淑!
Application.mk
APP_STL:=gnustl_static
APP_CPPFLAGS:=-frtti -fexceptions
APP_ABI := armeabi-v7a armeabi
這里筆者只說明Application.mk用到的變量:
APP_STL:=gnustl_static
# APP_STL := stlport_static --> static STLport library
# APP_STL := stlport_shared --> shared STLport library
# APP_STL := system --> default C++ runtime library
APP_CPPFLAGS:=-frtti -fexceptions
# C++代碼的編譯, < r15 此變量只適用于C++呀伙,> 15 適用于C和C++
APP_ABI := armeabi armeabi-v7a x86 # 指定輸出架構(gòu)
-
編譯我們的 .so 庫
project -> clean
編寫我們的Java調(diào)用類
上面我們有提到 com_example_ndkdemo_MainActivity补履, 也就是我們JNI格式方法中包含的字段,而且剿另,本地方法需要在這個(gè)類中加載庫和本地方法箫锤,而其它類可以調(diào)用該類中的方法贬蛙,但不能在其它類中再次加載動態(tài)庫的本地方法
package com.example.ndkdemo;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
static {
System.loadLibrary("student");
System.loadLibrary("teacher");
}
public static native int get();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i(TAG, "get() = " + get());
}
}
-
Run!!!
寫在最后
Android NDK Eclipse 入門篇目到這里就結(jié)束了,之前開發(fā)過程中都是簡簡單單地了解谚攒,現(xiàn)在特意花兩天時(shí)間整理了基礎(chǔ)的流程阳准,感覺吧,寫文章還是比較費(fèi)時(shí)費(fèi)勁的馏臭,但是可以分享給一起工作的小伙伴們野蝇,我也是拼了老命堅(jiān)持寫下來了,筆者也會多寫寫總結(jié)括儒,多輸出輸出绕沈。而NDK的編譯遠(yuǎn)不止于此,還有更多的功能帮寻,我也希望能夠抽空整理一番分享給小伙伴們乍狐。