之前弄過一點(diǎn) jni 相關(guān)的東西,使用過程中總是折騰很久,之后用到 jni 工程配置時(shí),又忘記之前的操作了。
哎,記憶力不好,這也是作為一位偽碼農(nóng)的硬傷啊!所以為了以后重復(fù)使用,只能寫寫了,以便日后再用!好了,就開始記錄吧!
由于 Jni 相關(guān)知識(shí)操作比較多,每部分寫一塊的內(nèi)容,不至于文章過長(zhǎng)!
概要:
- NDK 開發(fā)簡(jiǎn)介
- Jni 簡(jiǎn)介
- NDK 開發(fā)環(huán)境搭建
1.NDK 簡(jiǎn)介
1.1 什么是 NDK 開發(fā)?
NDK(Native Development Kit)是 Android 所提供的一個(gè)工具集合,通過 NDK 可以在 Android 中
更加方便地通過JNI來調(diào)用本地代碼(C/C++)。NDK 提供了交叉編譯器,開發(fā)時(shí)只需要修改 .mk 文件就
能生成特定 CPU 平臺(tái)的動(dòng)態(tài)庫,并能自動(dòng)將so和java應(yīng)用一起打包成apk。
簡(jiǎn)單點(diǎn)說,就是 NDK 幫助你編譯 C/C++ 代碼,通過也提供一些 API 供你調(diào)用,使用時(shí)需要你指定 NDK 的路徑。
1.2 為什么使用 NDK 呢?
先上一張腦圖看看,羅列了一些要點(diǎn)。
NDK開發(fā)相比于 JAVA 開發(fā)有一定難度,但有時(shí)又不得不使用 NDK 開發(fā),主要也就是圖中列出的幾點(diǎn):
(1)控制硬件,便于移植:因?yàn)橐{(diào)用底層的一些功能,如列出的控制 I2C,驅(qū)動(dòng)開發(fā),藍(lán)牙、Wifi,做硬件移植,使得程序跑在不同的硬件上;
(2)安全性:java是半解釋型語言,很容易被反匯編后拿到源代碼文件,我們可以在重要的交互功能使用C語言代替
(3)高效性:C/C++開發(fā)比較高效,像做數(shù)學(xué)運(yùn)算、實(shí)時(shí)渲染游戲、音視頻處理、文件壓縮、人臉識(shí)別等
- 優(yōu)點(diǎn)
也就是上面面列出來的為什么使用 NDK 開發(fā)的幾點(diǎn)
- 缺點(diǎn)
(1)C/C++:NDK 開發(fā)底層是 C/C++ 寫的,所以就需要會(huì)這兩種語言,毫無疑問,這兩種語言是公認(rèn)的比較難的語言,學(xué)習(xí)成本高
(2)內(nèi)存泄露:雖然高效,但是內(nèi)存需要程序員進(jìn)行管理,容易發(fā)生內(nèi)存泄露等錯(cuò)誤
(3)調(diào)試?yán)щy:相比于上層 Java 調(diào)試起來還有有一定難度,要求熟練使用call chain
2.Jni 簡(jiǎn)介
2.1 什么是Jni?
JNI:全稱:Java Native Interface,是一層接口,用于 Java 和 C/C++ 溝通的橋梁。通過 Jni 可以實(shí)現(xiàn) Java 調(diào)用 C/C++ 庫中的方法,也可以實(shí)現(xiàn) C/C++ 調(diào)用 Java 中的方法。
Java 通過 JVM 實(shí)現(xiàn)在不同的系統(tǒng)上運(yùn)行,具有跨平臺(tái)的能力;若要調(diào)用一些和操作系統(tǒng)的操作(一般通過 C/C++ 實(shí)現(xiàn)),就需要通過 Jni 來實(shí)現(xiàn)。
Jni 既然是一個(gè)接口,那么也會(huì)有它自己的一定規(guī)則,像 Jni 的數(shù)據(jù)類型,后面再詳細(xì)介紹數(shù)據(jù)類型,方法的操作,這里只需要先有一個(gè)概念,Jni 在程序中的作用以及與 NDK 之間的關(guān)系。
3.NDK 開發(fā)環(huán)境搭建
3.1 安裝與部署
- 指定 NDK
之前如果你沒有配置過 NDK 的話,那么需要指定 NDK 的路徑,打開 File--->Project Structure--->SDK Location.
NDK 的路徑需要指定,分為兩種情況:
(1)已下載過NDK:通過找到本地的 NDK 的位置并指定
(2)如果本地沒有 NDK,那么就需要下載 NDK
a.打開 Tool--->Android--->SDK Manager
b.找到System Settings--->Android SDK----SDK Tool,選擇要下載的選項(xiàng),進(jìn)行下載,下載和解壓,并安裝的時(shí)間會(huì)有點(diǎn)長(zhǎng),請(qǐng)耐心等待!
下載好 NDK 后,就可以指定了,也可以通過 local.properties 文件指定 NDK 位置
3.2 開發(fā)步驟
NDK 的指定后,就可以進(jìn)行 NDK 開發(fā),下面列出 NDK 開發(fā)的主要步驟和其中的一些要點(diǎn)。
那下面我們就按步驟,結(jié)合一個(gè)例子來嘗試一下
- 創(chuàng)建工程 JniTest ,并加入 native 方法
MainActivity中代碼
package com.ralf.www.jnitest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button)findViewById(R.id.button);
final TextView textView = (TextView)findViewById(R.id.text_view);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//從JNI中獲取字符串
textView.setText(JniUtils.getString());
}
});
}
}
JniUtils類代碼,我把 native 方法單獨(dú)拿出來,放到這個(gè)類里面
package com.ralf.www.jnitest;
/**
* 作者:Ralf on 2017/11/9 17:31
* desc:
*/
public class JniUtils {
static {
System.loadLibrary("jnitest");
}
public static native String getString();
}
可以看到 native 方法書寫方式,類似于接口,但需要有關(guān)鍵字 native。
- 創(chuàng)建 jni文件夾
在 src---> main 下創(chuàng)建 jni 文件夾,并在連添加三個(gè)文件
(1)jnitest.cpp
(2)Android.mk
(3)Applicationm.mk
- 編寫 .c或.cpp文件
jnitest.cpp 代碼,其中extern “C” 聲明,是為了說明可能會(huì)用到 C 的代碼
#include <jni.h>
extern "C" {
/*
* Class: com_ralf_www_jnitest_JniUtils
* Method: getString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_ralf_www_jnitest_JniUtils_getString
(JNIEnv *env, jclass jc){
const char* ch = "String From JNI";
return env->NewStringUTF(ch);
}
}
注意:
(1)函數(shù)名:JNIEXPORT + 返回類型 + JNICALL Java_+包名 + 類型 + 函數(shù)名(java中聲明的),以下劃線連接
(2)返回值類型,是 jni 中的數(shù)據(jù)類型,若沒有返回類型,則使用 void
(3)默認(rèn)傳入兩個(gè)參數(shù) JNIEnv* env(jvm運(yùn)行環(huán)境), jobject obj(調(diào)用這個(gè)函數(shù)的Java對(duì)象)
-
配置編譯環(huán)境
(1)Android.mk文件
.cpp 文件為 jnitest.cpp,對(duì)應(yīng)的庫的名字為 jnitest,即生成的 so 為 jnitest.so
LOCAL_PATH := $(call my-dir) 指定cpp文件位置
include $(CLEAR_VARS) #編譯時(shí)清除舊庫
LOCAL_MODULE := libjnitest #生成so的名字,前面加lib
LOCAL_SRC_FILES := jnitest.cpp #需要編譯的cpp文件
include $(BUILD_SHARED_LIBRARY) #注明生成動(dòng)態(tài)庫
(2)Application.mk文件
這個(gè)文件中一般進(jìn)行ABI管理,告訴ndk-build生成適用于那些CPU指令集的庫文件,=all就是編譯生成所有CPU指令集的庫文件
APP_ABI :=all
(3)build.gradle 配置
Android{
...
externalNativeBuild{
//指定Android.mk文件
ndkBuild{
path 'src/main/jni/Android.mk'
}
}
//生成so到指定路徑下
sourceSets{
main{
jni.srcDirs = []
jniLibs.srcDirs = ['libs']
}
}
}
(4)gradle.properties設(shè)置
該文件中要加上這一句話
android.useDeprecatedNdk=true
- 加載動(dòng)態(tài)庫
加載比較簡(jiǎn)單了,前面得代碼中已經(jīng)寫過了
(1)需要在static代碼塊中加載
(2)System.loadLibrary
(3) 庫文件去掉.so, 去掉前面的lib
static {
//加載動(dòng)態(tài)庫 libjnitest.so
System.loadLibrary("jnitest");
}
4.常見錯(cuò)誤
(1)函數(shù)名編寫中容易出錯(cuò),注意格式
(2)配置文件中Android.mk 中的module name 注意不要寫錯(cuò)
(3)注意gradle文件的配置