andorid jni入門教程一之helloworld

andorid jni入門教程一之helloworld

開發環境:windows2007, eclipse

做anroid越深發現用到底層開發的時候越多,但是我以前也沒有搞過,因此現在打算好好學習學習。先從最簡單的做起。正所謂萬事開頭難啊。

搞了近一天終於把在windows下,用eclipse開發Android JNI給倒騰通了。下面將詳細講解其操作步驟和我在其中遇到的問題

參考:http://www.cnblogs.com/bastard/archive/2012/05/19/2508913.html

         http://blog.csdn.net/cghs123/article/details/7044826

 

1.新建Android工程 JNITest

在HelloWorld的Activity中添加加載c庫的代碼,系統運行後自動執行該段

    static
    {
        //加載庫文件
        System.loadLibrary("HelloWorldJni");
    }

聲明c庫中需要自定義的原生函數

    private native String printJNI(String inputStr);

HelloWorld整段代碼如下:

複製代碼
 1 package com.sirc.jni;
 2 
 3 import android.os.Bundle;
 4 import android.app.Activity;
 5 import android.util.Log;
 6 import android.view.Menu;
 7 
 8 public class HelloWorld extends Activity {
 9 
10     @Override
11     protected void onCreate(Bundle savedInstanceState) {
12         super.onCreate(savedInstanceState);
13         setContentView(R.layout.activity_main);
14 
15         String jniStr=printJNI("I am HelloWorld Activity");
16         Log.v("android", jniStr);
17         
18     }
19     static
20     {
21         //加載庫文件
22         System.loadLibrary("HelloWorldJni");
23     }
24     //聲明原生函數 參數爲String類型 返回類型爲String
25     private native String printJNI(String inputStr);
26 
27     
28     @Override
29     public boolean onCreateOptionsMenu(Menu menu) {
30         // Inflate the menu; this adds items to the action bar if it is present.
31         getMenuInflater().inflate(R.menu.main, menu);
32         return true;
33     }
34 
35 }
複製代碼

2.生成共享庫的頭文件

詳細步驟見上一文章《Android JNI開發生成.h頭文件問題》http://www.cnblogs.com/gisdream/p/3521090.html

我們都知道在Eclipse中新建類後,eclipse會幫助我們在項目下自動建立去對應的class文件,位於項目目錄下的\bin\classes中。共享庫的頭文件可以根據class文件通過javah編譯生成。

通過命令進入項目根目錄

再輸入命令:javah -classpath bin\classes -d jni com.sirc.jni.HelloWorld 

可以得到頭文件com_sirc_jni_HelloWorld.h,位於跟目錄/jni/下面

 

複製代碼
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_sirc_jni_HelloWorld */

#ifndef _Included_com_sirc_jni_HelloWorld
#define _Included_com_sirc_jni_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_sirc_jni_HelloWorld
 * Method:    printJNI
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_sirc_jni_HelloWorld_printJNI
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif
複製代碼

頭文件中定義了與上層的訪問接口Java_com_sirc_jni_HelloWorld_printJNI,方法名是按照”Java_包名_類名_方法名“來命名的。

3.實現JNI原生函數源文件

在jni文件夾下面新建com_sirc_jni_HelloWorld.c文件,文件代碼如下:

複製代碼
#include <jni.h>
#define LOG_TAG "HelloWorld"
#include <android/log.h>

#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)
/* Native interface, it will be call in java code */
JNIEXPORT jstring JNICALL Java_com_sirc_jni_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;
}
複製代碼

其中#include <android/log.h>應用了打印日誌頭文件,並聲明瞭集中打印方法,包括info,debug,error。我想這應該是調用的android的內部系統文件,在這裏對其進行了一個重聲明

#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)

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

Java_com_sirc_jni_HelloWorld_printJNI是頭文件中方法的實現,可以通過這裏獲取上層傳過來的參數,並在這裏返回數據給上層框架使用。

注:這段代碼是從其他網站上copy過來的,運行時如果出現錯誤stray '/241' in program 該錯誤是指源程序中有非法字符,需要去掉非法字符。

解決方法:來源於http://blog.sina.com.cn/s/blog_6bc5571a0100zmft.html

(1)把所粘的文字放到記事本里就行了 

(2)把出錯行的空格刪掉重新打一下試試。

4.編譯生成so

4.1在jni文件夾下面新建Android.mk文件,文件內容如下:

複製代碼
LOCAL_PATH:= $(call my-dir)
# 一個完整模塊編譯
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=com_sirc_jni_HelloWorld.c
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
LOCAL_MODULE := libHelloWorldJni
LOCAL_SHARED_LIBRARIES := libutils
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_TAGS :=optional
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
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

4.2編譯

我這裏使用NDK來進行編譯,首先要下載安裝NDK。

Android NDK安裝很簡單,直接到http://developer.android.com/tools/sdk/ndk/index.html 下載android-ndk-r8e-windows-x86.zip 解壓即可,接着設置環境變量。右擊我的電腦屬性,切換到高級選項卡,單擊環境變量,在系統變量下單擊編輯在Path變量名下直接變量值;D:\Android\android-ndk-r8e-windows-x86\android-ndk-r8e\ ,也就是你的解壓路徑,其中有個封號與前面的變量值分割。單擊確定NDK就安裝好了。

在命令行中使用ndk-build編譯,過程如下

 

編譯成功後會自動生成libs/armeabi/libHelloWorldJni.so文件。

5.運行測試

運行JNITest項目,在logcat中查看運行結果。可以看到

上層打印情況:

底層打印情況:

 

以上便是一個完整的開發JNI的實例,下一篇將詳細研究參數傳值,java中基本類型和c++基本類型的關聯。

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