JNI & NDK

       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就是簡化了這個過程。


關於交叉編譯

編譯器將中間代碼連接成當前計算機可執行的二進制程序時,連接程序會根據當前計算機的CPU、操作系統的類型來轉換。
根據運行的設備的不同,可以將cpu分爲:
|-  arm結構:主要在移動手持、嵌入式設備上。
|-  x86結構:主要在臺式機、筆記本上使用。如Intel和AMD的CPU 。
若想在使用了基於x86結構CPU的操作系統中編譯出可以在基於arm結構CPU的操作系統上運行的代碼,就必須使用交叉編譯。
交叉編譯:在一個平臺下編譯出在另一個平臺中可以執行的二進制代碼。Google提供的NDK就可以完成交叉編譯的工作。

安卓體系中,很多涉及到底層硬件的方法,都是native的,只有聲明,沒有方法體,虛擬機加載到native時,會用JNI的機制來調用這個方法,比如音視頻播放,看源碼
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++
故而JNI機制在安卓裏還是很普遍的

另外,在實際應用中,可以把一些核心代碼編成C\C++,弄成本地的機器碼,這樣就能達到保密的目的,提高安全性,應對反編譯

C\C++的程序
在桌面端,CPU基本都是x86構架,生成的庫文件 一般是 .libs .dll格式,而在移動端,現在主要是ARM,生成的是 .so文件

與SDK相似,NDK要導入eclipse關聯之
解壓後,配置環境變量即可,當然,如果你精通C\C++,那麼可以再安裝一個CDT,自己寫C庫自己調用

NDK的目錄



•build/tools:linux的批處理文件
•docs:幫助文檔
•platforms:編譯c代碼需要使用的頭文件和類庫
•prebuilt:預編譯使用的二進制可執行文件
•sample:jni的使用例子
•source:ndk的源碼
•toolchains:工具鏈
•ndk-build.cmd:編譯打包c代碼的一個指令


大概流程

1.創建一個android工程
2.JAVA代碼中寫聲明native 方法 public native String helloFromJNI();
3. 創建jni目錄,編寫c代碼,方法名字要對應
4.編寫Android.mk文件
5.Ndk編譯生成動態庫
6.Java代碼load 動態庫.調用native代碼


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了,寫完函數之後就可以直接跑了,無需手動編譯


  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章