Android JNI使用例子

Android中JNI的使用方法

首先看一下Android平臺的框架圖:(網上盜用)

    


  可以看到Android上層的ApplicationApplicationFramework都是使用Java編寫,

底層包括系統和使用衆多的LIiraries都是C/C++編寫的。

  所以上層Java要調用底層的C/C++函數庫必須通過JavaJNI來實現。

下面將學習Android是如何通過Jni來實現JavaC/C++函數的調用。以HelloWorld程序爲例:

第一步:

使用Java編寫HelloWorld Android應用程序:

複製代碼
package com.lucyfyr;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class HelloWorld extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  Log.v("dufresne", printJNI("I am HelloWorld Activity"));
}
  static
  {
    //加載庫文件
    System.loadLibrary("HelloWorldJni");
  }
   //聲明原生函數 參數爲String類型 返回類型爲String
  private native String printJNI(String inputStr);
}
複製代碼

 

  這一步我們可以使用eclipse來生成一個App

因爲eclipse會自動爲我們編譯此Java文件,後面要是用到。


第二步:

  生成共享庫的頭文件:

  進入到eclipse生成的Android Project中 :/HelloWorld/bin/classes/com/lucyfyr/ 下:

  可以看到裏面後很多後綴爲.class的文件,就是eclipse爲我們自動編譯好了的java文件,其中就有:

    HelloWorld.class文件。

  退回到classes一級目錄:/HelloWorld/bin/classes/

執行如下命令:

  javah com.lucyfyr.HelloWorld

  生成文件:com_lucyfyr_HelloWorld.h

 

複製代碼
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_lucyfyr_HelloWorld */
#ifndef _Included_com_lucyfyr_HelloWorld
#define _Included_com_lucyfyr_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_lucyfyr_HelloWorld
* Method: printJNI
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_lucyfyr_HelloWorld_printJNI
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
複製代碼

 

  可以看到自動生成對應的函數:Java_com_lucyfyr_HelloWorld_printJNI

    Java_ + 包名(com.lucyfyr) 類名(HelloWorld) + 接口名(printJNI):必須要按此JNI規範來操作;

  java虛擬機就可以在com.simon.HelloWorld類調用printJNI接口的時候自動找到這個C實現的Native函數調用。

  當然函數名太長,可以在.c文件中通過函數名映射表來實現簡化。


第三步:

實現JNI原生函數源文件:

新建com_lucyfyr_HelloWorld.c文件:

 

複製代碼
#include <jni.h>
#define LOG_TAG "HelloWorld"
#include <utils/Log.h>
/* Native interface, it will be call in java code */
JNIEXPORT jstring JNICALL Java_com_lucyfyr_HelloWorld_printJNI(JNIEnv *env, jobject obj,jstring inputStr)
{
  LOGI("dufresne Hello World From libhelloworld.so!");
  // 從 instring 字符串取得指向字符串 UTF 編碼的指針
  const char *str =
  (const char *)(*env)->GetStringUTFChars( env,inputStr, JNI_FALSE );
  LOGI("dufresne--->%s",(const char *)str);
  // 通知虛擬機本地代碼不再需要通過 str 訪問 Java 字符串。
  (*env)->ReleaseStringUTFChars(env, inputStr, (const char *)str );
  return (*env)->NewStringUTF(env, "Hello World! I am Native interface");
}
/* This function will be call when the library first be load. * You can do some init in the libray. return which version jni it support. */ jint JNI_OnLoad(JavaVM* vm, void* reserved) {   void *venv;   LOGI("dufresne----->JNI_OnLoad!");   if ((*vm)->GetEnv(vm, (void**)&venv, JNI_VERSION_1_4) != JNI_OK) {     LOGE("dufresne--->ERROR: GetEnv failed");     return -1;   }
  return JNI_VERSION_1_4; }
複製代碼

 

  OnLoadJava_com_lucyfyr_HelloWorld_printJNI

函數裏面做一些log輸出 注意JNI中的log輸出的不同。

  JNI_OnLoad函數JNI規範定義的,當共享庫第一次被加載的時候會被回調,

這個函數裏面可以進行一些初始化工作,比如註冊函數映射表,緩存一些變量等,

最後返回當前環境所支持的JNI環境。本例只是簡單的返回當前JNI環境。

 

第四步:

編譯生成so

  編譯com_lucyfyr_HelloWorld.cso庫可以和app一起編譯,也可以都單獨編譯。

在當前目錄下建立jni文件夾:HelloWorld/jni/

  下建立Android.mk ,並將com_lucyfyr_HelloWorld.c com_lucyfyr_HelloWorld.h 拷貝到進去

編寫編譯生成so庫的Android.mk文件:

 

複製代碼
LOCAL_PATH:= $(call my-dir)
# 一個完整模塊編譯
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=com_lucyfyr_HelloWorld.c
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
LOCAL_MODULE := libHelloWorldJni
LOCAL_SHARED_LIBRARIES := libutils
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_TAGS :=optional
include $(BUILD_SHARED_LIBRARY)
複製代碼

 

系統變量解析:

  LOCAL_PATH - 編譯時的目錄
  
$(call 
目錄,目錄….) 目錄引入操作符
    
如該目錄下有個文件夾名稱 src,則可以這樣寫 $(call src),那麼就會得到 src 目錄的完整路徑

  include $(CLEAR_VARS) -清除之前的一些系統變量
  LOCAL_MODULE 
- 編譯生成的目標對象
  LOCAL_SRC_FILES 
- 編譯的源文件
  LOCAL_C_INCLUDES 
- 需要包含的頭文件目錄
  LOCAL_SHARED_LIBRARIES 
- 鏈接時需要的外部庫
  LOCAL_PRELINK_MODULE 
- 是否需要prelink處理 
  include$
BUILD_SHARED_LIBRARY) - 指明要編譯成動態庫

  android.mk編譯模塊添加具體方法參考:http://blog.csdn.net/yili_xie/article/details/4906865


編譯此模塊:輸入編譯命令

  ./makeMtk mm packages/apps/HelloWorld/jni/

  上面是我的工程根目錄編譯命令。具體編譯方式根據自己系統要求執行。

編譯輸出: libHelloWorldJni.so (system/lib中視具體而定)

  此時庫文件編譯好了可以使用,如果在eclipse中模擬器上使用,需要將 libHelloWorldJni.so導入到system/lib 下,

  或者對應appdata/data/com.lucyfyr/lib/下;

但是在導入system/lib 下的時候,卻始終提示out of memory。於是網上說使用emulator去搞,

  但是此命令使用起來卻有問題,始終沒有解決。但是可以導入到data/data/com.lucyfyr/lib/下使用。但是我這裏直接在真機上弄的。

看一下HelloWorldAndroid.mk文件的配置

  其中存在:

    include $(LOCAL_PATH)/jni/Android.mk 表示編譯庫文件

    LOCAL_JNI_SHARED_LIBRARIES := libHelloWorldJni 表示app依賴庫,打包的時候會一起打包。


第五步:

驗證執行

  將編譯好的apk安裝到手機上

  使用adb push到手機上去需要自己去導入庫文件libHelloWorldJni.sodata/data/com.lucyfyr/lib/

  使用adb install方式安裝則會自動導入。

啓動HelloWorld :輸入命令 adb logcat |grep dufresne

輸出log如下:

  I/HelloWorld(28500): dufresne Hello World From libhelloworld.so!

  I/HelloWorld(28500): dufresne--->I am HelloWorld Activity

  V/dufresne(28500): Hello World! I am Native interface

符合調用打印順序正確。

 

以上通過一個簡單的例子學習了Android如何編寫編譯C庫文件,以及如何使用庫文件。

對於JNI的使用其中還涉及到其他方面的一些知識:C++接口如何調用,函數名註冊表怎麼回事,

參數類型如何匹配,JNI如何調用Java中的方法java<--->JNI等。

發佈了1 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章