JNI是java語言提供的Java和C/C++相互溝通的機制,Java可以通過JNI調用本地的C/C++代碼,本地的C/C++的代碼也可以調用java代碼。JNI
是本地編程接口,Java和C/C++互相通過的接口。Java通過C/C++使用本地的代碼的一個關鍵性原因在於C/C++代碼的高效性。
NDK是一系列工具的集合,幫助開發者快速開發C(或C++)的動態庫,並能自動將so和java應用一起打包成apk。這些工具對開發者的幫助是巨大的。它集成了交叉編譯器,並提供了相應的mk文件隔離CPU、平臺、ABI等差異,開發人員只需要簡單修改mk文件(指出“哪些文件需要編譯”、“編譯特性要求”等),就可以創建出so。它可以自動地將so和Java應用一起打包,極大地減輕了開發人員的打包工作。
JNI(Java Native Interface)是Java調用Native機制,是Java語言自己的特性。類似的還有微軟.Net Framework上的p/invoke,可以讓C#或Visual Basic.Net可以調用C/C++的API,所以說JNI和Android沒有關係,在PC上開發Java的應用,如果運行在Windows平臺使用JNI是是經常的,比如說讀寫Windows的註冊表。
而NDK(Native SDK)是Google公司推出的幫助Android開發者通過C/C++本地語言編寫應用的開發包,包含了C/C++的頭文件、庫文件、說明文檔和示例代碼,我們可以理解爲Windows Platform SDK一樣,是純C/C++編寫的,但是Android並不支持純C/C++編寫的應用,同時NDK提供的庫和函數功能很有限,僅僅處理些算法效率敏感的問題
簡單點說,用C語言生成一個庫文件,在java中通過JNI機制調用這個庫文件的函數。而JNI的過程比較複雜,生成.so需要大量操作,而NDK就是簡化了這個過程。
關於交叉編譯
public void start() throws IllegalStateException {
stayAwake(true);
_start();
}
private native void _start() throws IllegalStateException;
/**
* Stops playback after playback has been stopped or paused.
*
* @throws IllegalStateException if the internal player engine has not been
* initialized.
*/
public void stop() throws IllegalStateException {
stayAwake(false);
_stop();
}
private native void _stop() throws IllegalStateException;
安卓體系的四層,上面的應用層和框架層都是用Java寫的,第三層的核心庫是Java寫的,別的很多庫是用C\C++寫的,底層的驅動什麼也是C\C++
DEMO
1.先要建一個工程,在工程裏建一個folder,裏面加一個 .c的file
2.在佈局里加一個button
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="點擊調用本地C方法"
android:onClick="click"/>
3.mainactivity裏響應,同時我們在這裏要調用native的C函數,這個函數返回一個字符串,用toast彈出這個字符串
注意,Java裏只是調用這個函數,函數的具體實現是在 .c裏
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//toast返回的字符串
public void click(View v){
Toast.makeText(this, getStringFromC(), Toast.LENGTH_LONG).show();
}
//使用本地C語言寫一個函數,負責返回一個字符串
private native String getStringFromC();
}
4.完成這個函數
4.1關於函數名,應該是 Java+包名+類名+函數名; 點都要換成下劃線
4.2 關於返回值,這裏要去頭文件裏看C和JAVA的對應列表
/*
* Reference types, in C.
*/
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;
#include<jni.h>
jstring Java_com_example_myjnidemo_MainActivity_getStringFromC(JNIEnv* env,jobject thiz){
char* cstr = "helloword";
return cstr;
}
注意參數是必須的,JNI規範裏的規定,都是在jni.h裏定義
後者是一個指向任一類型的指針,就是指的MainActivity的引用
前者是一個結構體的指針,那麼參數裏傳的是一個指針的指針,是一個二級指針
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
4.3一個*找到指針,二級*找到結構體,找到結構體後,直接點,就能調用其中的函數,不過需要上面的兩個參數
這裏要返回string值,調用JNI裏的方法,NewStringUTF,第二個參數應該是指向需要被轉換成string的字符串數組的指針,並且可以加一個const,常量化,防止指針被修改
#include<jni.h>
jstring Java_com_example_myjnidemo_MainActivity_getStringFromC(JNIEnv* env,jobject thiz){
char* cstr = "helloword";
//調用JNI裏的方法,將字符數組轉換成string
jstring jstr = (*(*env)).NewStringUTF(env,cstr);
return jstr;
}
在Java中,如果是無參,這裏只需要傳那兩個參數即可;如果是有參,那麼這裏還要在後面再傳參數
4.4 Android.mk 文件,放到c原文件同文件夾下,裏面要表明源文件和目標文件,用來編譯
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#該模塊編譯後需要生成的lib庫的名稱
LOCAL_MODULE :=helloworld
#編譯該模塊需要用到的src源文件
LOCAL_SRC_FILES :=helloworld.c
include $(BUILD_SHARED_LIBRARY)
4.5 編譯
交叉編譯,變異成 .so庫,需要使用 ndk-build.cmd來編譯
右擊C程序源文件,properties,找到文件的文件夾路徑
命令行
進入D盤
進入文件夾
cmd指令
lib庫裏生成 .so
4.6 java里加載類庫,否則提示找不到本地方法
public class MainActivity extends Activity {
//傳進來的要和mk裏的module名相同
static{
System.loadLibrary("helloworld");
}
@Override
放在static裏,隨着類的加載自動調用
傳進來的要和module裏的定義相同
OK,可以跑起來了
另外,如果此時在C文件裏做了修改,要重新利用命令行編譯一下~
如果實在Linux下,直接生成了 .so ,拿來就可以用,無需自己編譯
還有,可以讓eclipse編譯,而無需自己用命令行手動編譯
這裏需要在preference下的安卓選項臺裏去設置路徑
有可能出現這裏沒有NDK選項的情況,需要更新一下SDK,版本問題,詳情請看這篇文章 解決缺少NDK選項問題,點擊以打開
右擊工程名,點擊add native support
他會自動生成jni文件夾和裏面的 cpp文件和mk文件,如果是想用C的話,改下文件名和mk裏的源文件名
這時候還要添加c\c++的path and symbols
右擊工程名,preference,c\c++ general,path and symbols,add,file system -NDK裏的platforms下的對應includes
OK,可以寫C了,寫完函數之後就可以直接跑了,無需手動編譯