JNI 詳細使用 基礎【步驟】


  • 1、定義本地【native】方法。通常情況下,應單獨定義一個類來封裝所有native方法。native方法相當於一個【接口】中的方法,只有方法聲明,沒有方法體。
  • 2、在項目根目錄下創建【jni文件夾】,將利用【javah】命令生成的【.h】頭文件拷到jni目錄中。然而在實際測試中發現,這個文件沒有任何卵用,不拷貝也沒問題。
  • 3、在jni目錄中編寫所需的【.c】文件,這一步才是核心,在c代碼中,實現上面定義的本地方法。其中ndk工具中提供的<jni.h>頭文件中詳細定義了JNI【基本數據類型】和【本地等效類型】之間的轉換。
  • 4、在jni目錄中創建【Android.mk】文件,主要是聲明所引用的.c文件和生成的.so庫的文件名
  • 5、創建【Application.mk】文件,主要是聲明所有支持的平台。每增加一個支持的架構,編譯后就會在lib目錄下生成一個相應架構平台的目錄。
  • 6、使用【ndk】工具編譯生成【.so動態鏈接庫文件】。當完成這一步后,如果我們以后不需要再重新編譯,我們就可以直接刪除【jni】目錄。另外發布的時候這些文件都是應該刪掉的。
  • 7、使用時先 System.loadLibrary("hello"),然后直接調用本地native方法即可。注意,加載庫文件時的文件名要去掉前面的lib和后面的.so

1、定義本地【native】方法

      
      
      
              
定義本地方法,通常情況下,應單獨定義一個類來封裝所有native方法

/** 存放native方法的類 */

public class MyNativeMethods {
    private static MyNativeMethods mEmployee;
    private MyNativeMethods() {
    }
    public static MyNativeMethods getInstance() {
        if (mEmployee == null) {
            mEmployee = new MyNativeMethods();
        }
        return mEmployee;
    }
    //相當於在java代碼中定義了一個接口,然后用C語言實現了此接口
    public native String helloFromC();
    public native int passwordFromC(int x, int y);
}

2、利用javah命令生成的.h頭文件

      
      
      
              
1、定位到工程的【 src目錄】下
cd/d E:\HelloFromC\src

2、對native方法所在類執行【javah】命令,其中javah后面的類文件的格式是【包名.類名】
javah com.bqt.hellofromc.MyNativeMethods
執行過程如下:

報錯的原因是因為MyNativeMethods中有中文字符,即使是注釋也會報錯,且發現一處中文就報錯一次
但是這並不影響處理結果,所以大可不用理會

3、在項目根目錄下創建【jni文件夾】,將生成的【.h文件】拷貝到jni目錄下
在實際測試中發現,這個文件沒有任何卵用,不拷貝也沒問題。可能是因為我測試的代碼比較簡單吧。


文件的內容為
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_bqt_hellofromc_MyNativeMethods */
#ifndef _Included_com_bqt_hellofromc_MyNativeMethods
#define _Included_com_bqt_hellofromc_MyNativeMethods
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_bqt_hellofromc_MyNativeMethods
 * Method:    helloFromC
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_bqt_hellofromc_MyNativeMethods_helloFromC
  (JNIEnv *, jobject);
/*
 * Class:     com_bqt_hellofromc_MyNativeMethods
 * Method:    passwordFromC
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_bqt_hellofromc_MyNativeMethods_passwordFromC
  (JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif  

3、在jni目錄中編寫所需的.c文件【這一步才是核心】

      
      
      
              
在項目根目錄下創建【jni文件夾】,在jni文件夾中創建一個【.c文件】,在c代碼中,實現上面定義的本地方法
在.c文件中引入上面生成的.h文件(非必須,個人覺得有用的東西就是,它幫我們自動生成了符合JNI規范的方法聲明,我們只需把方法聲明拷走就行了)
#include "com_bqt_hellofromc_MyNativeMethods.h"  

以下為.c中的代碼

#include <stdio.h>

#include <stdlib.h>
#include <jni.h>//必須添加的頭文件

jstring Java_com_bqt_hellofromc_MyNativeMethods_helloFromC(JNIEnv* env, jobject obj) { //【返回值】【方法名】【參數列表】返回值類型jstring就是java中的string
    char* cstr = "hello from c"//  char*  在c中可用來表示一個字符串。注意,這里絕對不能有中文
    jstring jstr = (*env)->NewStringUTF(env, cstr);
    return jstr;
}

JNIEXPORT jint JNICALL Java_com_bqt_hellofromc_MyNativeMethods_passwordFromC(JNIEnv *env, jobject obj, jint a, jint b) {
//JNIEXPORT和JNICALL都是JNI的關鍵字,表明函數是被JNI調用的;JNIEXPORT 表示輸出類型;JNICALL表示參數的壓棧順序;貌似都可以省略
    return a + b + 10000; //c中的int占用字節數在不同環境下可能不同,可能是0-65535,所以,稍微大一點的數(十萬級別)都得用double

}


4、在jni目錄中創建Android.mk文件,主要是聲明所引用的.c文件和生成的.so庫的文件名

       
       
       
               
         
         
         
                 
在工程的jni目錄下創建一個【Android.mk文件】,在里面定義打包成函數【庫的名字】及對應的【c代碼的文件名】

LOCAL_PATH :$(call my-dir)

# C/C++代碼所在目錄,也就是我們的jni目錄,不必修改
include $(CLEAR_VARS)
LOCAL_MODULE    :hello
# 對應打包成函數庫的名字,編譯器會自動在前面加上lib,在后面加上.so,最終結果就是libhello.so
LOCAL_SRC_FILES :hello.c
# 對應的c代碼的文件名,即hello.c  

include $(BUILD_SHARED_LIBRARY)  


5、創建Application.mk文件,主要是聲明所有支持的平台

       
       
       
               
         
         
         
                 
默認只會生成支持arm平台的動態鏈接庫文件,若項目支持arm外的平台,需在jni目錄中添加【Application.mk】文件,並加上以下內容

APP_ABI := armeabi armeabi-v7a x86

#Application.mk文件的目的是,描述在你的應用程序中所有需要的模塊(即靜態庫或動態庫)
#APP_ABI 的值以空格區分,代表要支持的架構,默認值為【armeabi】。其他架構,ARMv7 【armeabi-v7a】;IA-32【 x86】

#每增加一個架構,編譯后都會在lib目錄下生成一個相應的文件夾,文件夾下的文件都是同名的.so文件(當然文件內容不一樣)


編譯成功后,會在lib目錄下生成對應的多個文件夾及.so文件

6、使用ndk工具編譯生成.so成動態鏈接庫文件

       
       
       
               
         
         
         
                 
可以使用 cygwin工具編譯,其中下面第三步中為Android工程的根目錄

cd ../..

cd cygdrive/

cd D/Users/Android_workspace/HelloFromC

ndk-build


或直接用cmd編譯,注意要先在"系統變量path"中增加NDK工具所在路徑,如【D:\Android\android-ndk-r10d】

cd/d D:\Users\Android_workspace\HelloFromC

ndk-build

過程:


編譯成功后會在工程libs/armeabi目錄下生成一個【libhello.so】文件
其中,eabi的含義為:Embedded Application Binary Interface(嵌入式應用二進制接口)

細心觀察會發現,除了自動生成了libs目錄下的.so文件外,還產生了一個比libs目錄大很多倍的obj目錄,並且其結構及內容也很像

網上找到的資料解釋為:
As part of the build process過程, the files in the libs folder have been stripped剝皮 of symbols符號 and debugging information. So you'll want to keep two copies of each of your .so files: One from the libs folder to install on the Android device, and one from the obj folder to install for GDB一個調試工具 to get symbols from.

簡單來說就是,obj下的是帶符號和調試信息的,lib下的是去掉這些龐大信息后的動態鏈接庫文件,在安裝到Android設備上時只需libs目錄下的.so文件即可

當完成這一步后,如果我們以后不需要再重新編譯,我們就可以直接刪除【jni】目錄和【obj】目錄了

以下是是發布前的工程結構


7、使用時,直接調用本地native方法即可

      
      
      
              
在java代碼中加載.so類庫,之后就可以調用本地方法了

public class MainActivity extends ListActivity {
    static {
        System.loadLibrary("hello");// 在java代碼中引入libs目錄下的庫函數,文件名為【libhello.so】。注意,引入時的文件名要去掉前面的lib和后面的.so
    }

    private TextView tv_info;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] array = { "調用C中的無參方法,返回一個字符串""調用C中的有參方法,返回處理1+2后的值", };
        tv_info = new TextView(this);
        tv_info.setTextColor(Color.BLUE);
        tv_info.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
        tv_info.setPadding(20, 10, 20, 10);
        getListView().addFooterView(tv_info);
        setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1new ArrayList<String>(Arrays.asList(array))));
    }
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        switch (position) {
        case 0:
            String stringFromC = MyNativeMethods.getInstance().helloFromC();
            tv_info.setText(stringFromC);
            break;
        case 1:
            int intFromC = MyNativeMethods.getInstance().passwordFromC(1, 2);
            tv_info.setText(intFromC+"");
            break;
        }
    }
} 






附件列表

     


    免責聲明!

    本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



     
    粵ICP備18138465號   © 2018-2025 CODEPRJ.COM