NDK JNI開發 之 Eclipse中靜態注冊(二)

JNI技術概述

JNI是Java Native Interface的縮寫,通過使用 Java本地接口書寫程序,可以確保代碼在不同的平臺上方便移植。它允許Java代碼和其他語言寫的代碼進行交互。
其實就是java調用本地語言的技術,對于Linux和Windows而言本地語言自然是C和C++。換句話說就是java如何調用C或C++代碼,而你要把C或C++代碼給他人調用自然要編寫成動態鏈接庫了,在Windows是dll,在Linux是so。(這里我們討論Android上的so庫編寫和調用)

這個技術當初在用友工作時就接觸到了,由于當時技術的局限性,還被卡了一個星期。導致同事在外地待了一個星期。

開發環境

Eclipse+adt
android-ndk-r20(ndk的開發基礎在上一章介紹:http://www.lxweimin.com/p/c546783ad284

靜態注冊

要對java中native關鍵字定義的方法進行注冊, 注冊方式有兩種: 靜態注冊和動態注冊, 這里我們先介紹靜態注冊

Android代碼

我們在Android中先寫一個簡單的點擊事件,并在static中加載so庫,在按鈕點擊時調用so庫中的函數

package com.example.jnitest;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {
    
    static {
        //加載libhello_jni.so(默認去項目目錄下的libs下查找)
        System.loadLibrary("hello_jni");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }


    // 測試JNI
    public void testJni(View view) {
        String jniString = HelloNdk();
        Toast.makeText(MainActivity.this, jniString, Toast.LENGTH_LONG).show();
    }
    
    //定義一個native方法 native關鍵字說明方法的實現在so里面
    public native String  HelloNdk();
}

界面如下:


image.png

SO庫編寫

靜態注冊 在c代碼中我們的函數名要遵守一定的約定,不然jni無法識別函數,約定如下:

  • (1)函數名:JNIEXPORT + 返回類型 + JNICALL Java_+包名 +類型 + 函數名(java 中聲明的),以下劃線連接
  • (2)返回值類型,是 jni 中的數據類型,若沒有返回類型,則使用void
  • (3)默認傳入兩個參數 JNIEnv* env(jvm運行環境), jobject obj(調用這個函數的Java對象)

例如
java中函數聲明:public native String HelloNdk();
c中函數的實現:JNIEXPORT jstring JNICALL Java_com_example_jnitest_MainActivity_HelloNdk (JNIEnv * env, jobject obj)

這里你可以手動編寫,但是java提供了javah來自動從java代碼生成對應的頭文件。

  • 使用cmd進入項目的根目錄,在項目的根目錄創建jni目錄
javah -classpath src -d jni com.example.jnitest.MainActivity
  • classpath 表示類文件路徑
  • src表示java代碼的文件夾
  • d表示生成頭文件所在路徑
  • jni 表示 jni 文件夾下
  • com.example.jnitest.MainActivity,表示需要編譯的類文件,也就是包名+類名(也就是包含 native 方法的類文件)

生成文件內容:com_example_jnitest_MainActivity.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jnitest_MainActivity */

#ifndef _Included_com_example_jnitest_MainActivity
#define _Included_com_example_jnitest_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jnitest_MainActivity
 * Method:    HelloNdk
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_jnitest_MainActivity_HelloNdk
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

這里我們只做c的編寫所以去除c++的一些東西,在jni目錄下創建hello_jni.c,并拷貝代碼修改為以下:

#include <jni.h>
#include <string.h>

/* Header for class com_example_jnitest_MainActivity */

/*
 * Class:     com_example_jnitest_MainActivity
 * Method:    HelloNdk
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_jnitest_MainActivity_HelloNdk
  (JNIEnv * env, jobject obj){
    return (*env)->NewStringUTF(env, "Hello JNI!");
}

在jni.h中我們看到如下定義(在%java_home%\include\jni.h):

#include "jni_md.h"

typedef struct _jobject *jobject;
typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
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 jarray jobjectArray;

再進入jni_md.h中看看(在%java_home%\include\win32\jni_md.h):

#define JNIEXPORT __declspec(dllexport)
#define JNIIMPORT __declspec(dllimport)
#define JNICALL __stdcall

所以我們直接就不用解釋JNIEXPORT 和 JNICALL 了
現在來討論一下jstring 返回值。

java和c是如何互通的?

其實不能互通的原因主要是數據類型的問題,jni解決了這個問題,例如那個c文件中的jstring數據類型就是java傳入的String對象,經過jni函數的轉化就能成為c的char
對應數據類型關系如下表:

image.png

image.png

這里我們使用(
env)->NewStringUTF(env, "Hello JNI!");生成了一個jstring,并返回它。

使用ndk編譯so庫

  • 在jni目錄編寫實現函數對應的Android.mk(該文件的描述在上一章中)
LOCAL_PATH := $(call my-dir)
 
include $(CLEAR_VARS)

LOCAL_MODULE    := hello_jni
LOCAL_SRC_FILES := hello_jni.c
     
include $(BUILD_SHARED_LIBRARY)

返回到項目根目錄運行ndk

ndk-build

在項目根目錄下會生成libs文件夾里面有所有平臺的so庫(這里你也可以指定平臺)


image.png

由于我的虛擬機是X86的所有它使用的是x86文件夾下的so

運行

現在所有準備工作做完我們部署項目到手機上,點擊按鈕后如下:


image.png

尾言

這里我們只對JNI的類型對應做了一些基本的說明,由于此文的專注點是如何靜態注冊jni函數所以這里不做過多的描述,將來我將用一文詳細說明jni的一些技術細節。下一章我們將對動態注冊進行說明。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。