Android與JNI(一)


Android與JNI(一)

 

軟件版本:
  ubuntu10.04
  java version "1.6.0_30-ea"
  eclipse
  android-ndk-r5b

目錄:

  1. JNI 開發的基本步驟
  2. 創建一個 android 工程
  3. 生成 jni 的頭文件
  4. 編寫 c/c++ 代碼
  5. 編譯生成動態庫
  6. 測試
  7. 參考資料

1. JNI 開發的基本步驟

  在 JAVA 程序中調用 c/c++ 函數的方法一般是:

  1、創建一個類(HelloWorld.java)或者在原來的類中聲明本地方法。
  2、使用 javac 編譯源文件 HollowWorld.java,生成 HelloWorld.class。
  3、使用 javah –jni 來生成頭文件(HelloWorld.h),這個頭文件里面包含了本地方法的函數原型。
  4、編寫 c/c++ 代碼(HelloWorld.c)實現頭文件中的函數原型。
  5、將 HelloWorld.c 編譯成一個動態庫,生成 Hello-World.dll 或者 libHello-World.so。
  6、使用 java 命令運行 HelloWorld 程序,類文件 HelloWorld.class 和本地庫(HelloWorld.dll 或者 libHelloWorld.so)在運行時被加載。

  如圖所示:

2. 創建一個 android 工程

  創建一個新的 android 工程 HelloJNI 便於演示:

 1 package com.example.hellojni;
 2 
 3 import android.os.Bundle;
 4 import android.app.Activity;
 5 import android.view.Menu;
 6 import android.view.MenuItem;
 7 import android.widget.TextView;
 8 import android.support.v4.app.NavUtils;
 9 
10 public class HelloJNI extends Activity {
11 
12     @Override
13     public void onCreate(Bundle savedInstanceState) {
14         super.onCreate(savedInstanceState);
15         setContentView(R.layout.hello_jni);
16         getActionBar().setDisplayHomeAsUpEnabled(true);
17         
18         /* Create a TextView and set its content.
19          * the text is retrieved by calling a native
20          * function.
21          */
22         TextView  tv = new TextView(this);
23         tv.setText( stringFromJNI() );
24         setContentView(tv);
25     }
26 
27     @Override
28     public boolean onCreateOptionsMenu(Menu menu) {
29         getMenuInflater().inflate(R.menu.hello_jni, menu);
30         return true;
31     }
32 
33     
34     @Override
35     public boolean onOptionsItemSelected(MenuItem item) {
36         switch (item.getItemId()) {
37             case android.R.id.home:
38                 NavUtils.navigateUpFromSameTask(this);
39                 return true;
40         }
41         return super.onOptionsItemSelected(item);
42     }
43 
44     /* A native method that is implemented by the
45      * 'HelloJNI' native library, which is packaged
46      * with this application.
47      */
48     public native String  stringFromJNI();
49 
50     /* This is another native method declaration that is *not*
51      * implemented by 'HelloJNI'. This is simply to show that
52      * you can declare as many native methods in your Java code
53      * as you want, their implementation is searched in the
54      * currently loaded native libraries only the first time
55      * you call them.
56      *
57      * Trying to call this function will result in a
58      * java.lang.UnsatisfiedLinkError exception !
59      */
60     public native String  unimplementedStringFromJNI();
61 
62     /* this is used to load the 'HelloJNI' library on application
63      * startup. The library has already been unpacked into
64      * /data/data/com.example.HelloJni/lib/libHelloJNI.so at
65      * installation time by the package manager.
66      */
67     static {
68         System.loadLibrary("HelloJNI");
69     }
70 }

  代碼很簡單,主要是調用本地方法返回一個字符串,顯示在屏幕上。有兩點需要針對說明一下:

1 static {
2         System.loadLibrary("HelloJNI");
3 }

  上面這幾行代碼是用來加載動態庫 libHelloJNI.so 。那么是在什么時候加載呢?當第一次使用到這個類的時候就會加載。

1 public native String stringFromJNI();
2 public native String unimplementedStringFromJNI(); 

  使用關鍵字 native 聲明本地方法,表明這兩個函數需要通過本地代碼 c/c++ 實現。

  通過 eclipse 編譯代碼,生成 .class 文件。為生成 jni 的頭文件做好准備。

3. 生成 jni 的頭文件 

  現在的問題是要怎么樣實現 stringFromJNI 和 unimplementedStringFromJNI 這兩個函數呢?這兩個函數要怎么命名?直接用這兩個名字行不行?要解決這些疑問,就要用到 javah 這個命令。

  在你新建成的工程根目錄下,鍵入以下命令:

$javah -classpath bin/classes -d jni com.example.hellojni.HelloJNI

  先簡單說一下這個命令有什么用。這個命令是用來生成與指定 class 想對應的本地方法的頭文件。

  -classpath:指定類的路徑。
  -d:輸出目錄名。
  com.example.hellojni.HelloJNI:完整的類名。

  命令的結果是在本地生成一個名為 jni 的目錄,里面有一個名為 com_example_hellojni_HelloJNI.h 的頭文件。這個文件就是我們所需要的頭文件,他聲明了兩個函數

$ls
AndroidManifest.xml  assets  bin  gen  ic_launcher-web.png  jni  libs  obj  proguard-project.txt  project.properties  res  src
 1 /* DO NOT EDIT THIS FILE - it is machine generated */
 2 #include <jni.h>
 3 /* Header for class com_example_hellojni_HelloJNI */
 4 
 5 #ifndef _Included_com_example_hellojni_HelloJNI
 6 #define _Included_com_example_hellojni_HelloJNI
 7 #ifdef __cplusplus
 8 extern "C" {
 9 #endif
10 /*
11  * Class:     com_example_hellojni_HelloJNI
12  * Method:    stringFromJNI
13  * Signature: ()Ljava/lang/String;
14  */
15 JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJNI_stringFromJNI
16   (JNIEnv *, jobject);
17 
18 /*
19  * Class:     com_example_hellojni_HelloJNI
20  * Method:    unimplementedStringFromJNI
21  * Signature: ()Ljava/lang/String;
22  */
23 JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJNI_unimplementedStringFromJNI
24   (JNIEnv *, jobject);
25 
26 #ifdef __cplusplus
27 }
28 #endif
29 #endif

  上面代碼中的 JNIEXPORT 和 JNICALL 是 jni 的宏,在 android 的 jni 中不需要,當然寫上去也不會有錯。

  函數名比較長,不過是有規律的,按照:java_pacakege_class_method 形式來命名。調用 stringFromJNI() 就會執行 JNICALL Java_com_example_hellojni_HelloJNI_stringFromJNI() 。

  還有一個地方需要注意一下,那就是第13行

  Signature: ()Ljava/lang/String;
  
()表示函數的參數為空(這里為空是指除了JNIEnv *, jobject 這兩個參數之外沒有其他參數,JNIEnv* 和 jobject 是所有 jni 函數必有的兩個參數,分別表示 jni 環境和對應的 java 類(或對象)本身);
  Ljava/lang/String; 表示函數的返回值是 java 的 String 對象。

4. 編寫 c/c++ 代碼

  按照 com_example_hellojni_HelloJNI.h 中聲明的函數名,在 jni 目錄下建立一個 HelloJNI.c 文件實現其函數體。

 1 /*
 2  * Copyright (C) 2009 The Android Open Source Project
 3  *
 4  * Licensed under the Apache License, Version 2.0 (the "License");
 5  * you may not use this file except in compliance with the License.
 6  * You may obtain a copy of the License at
 7  *
 8  *      http://www.apache.org/licenses/LICENSE-2.0
 9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 #include <string.h>
18 #include <jni.h>
19 #include "com_example_hellojni_HelloJNI.h"
20 
21 /* This is a trivial JNI example where we use a native method
22  * to return a new VM String. See the corresponding Java source
23  * file located at:
24  *
25  *   apps/samples/hello-jni/project/src/com/example/HelloJni/HelloJni.java
26  */
27 jstring Java_com_example_hellojni_HelloJNI_stringFromJNI(JNIEnv *env, jobject this)
28 {
29     return (*env)->NewStringUTF(env, "Hello from JNI !");
30 }

  這里只實現了 Java_com_example_hellojni_HelloJNI_stringFromJNI() ,函數很簡單,返回一個字符串。但是由於函數定義中的返回值是 java 的 String 類,所以不能簡單的返回一個字符串,需要通過 JNI 函數 NewStringUTF 在本地創建一個新的 java.lang.String 對象。這個新創建的字符串對象擁有一個與給定的 UTF-8 編碼的 C 類型字符串內容相同的 Unicode 編碼字符串。這里拋出了一個問題,就是說我們在寫 JNI 的時候,有些數據類型是需要做轉換的。

5. 編譯生成動態庫

  如果你還沒有下載 ndk 的話,請到這里來下載。ndk 有什么用?就是用來編譯 jni 代碼的。安裝方法很簡單,只要把 ndk-build 命令加入到環境變量 PATH 中就可以使用了。驗證方法就是鍵入 ndk-build 命令后,不會出現 command not found 的字樣。

  在 jni 目錄下創建一個名為 Android.mk 的文件,並輸入以下內容:

 1 # Copyright (C) 2009 The Android Open Source Project
 2 #
 3 # Licensed under the Apache License, Version 2.0 (the "License");
 4 # you may not use this file except in compliance with the License.
 5 # You may obtain a copy of the License at
 6 #
 7 #      http://www.apache.org/licenses/LICENSE-2.0
 8 #
 9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 #
15 LOCAL_PATH := $(call my-dir)
16 
17 include $(CLEAR_VARS)
18 
19 LOCAL_MODULE    := HelloJNI
20 LOCAL_SRC_FILES := HelloJNI.c
21 
22 include $(BUILD_SHARED_LIBRARY)

  然后在 jni 目錄下輸入 ndk-build 命令就可以編譯了。

$ndk-build 
Compile thumb  : HelloJNI <= HelloJNI.c
SharedLibrary  : libHelloJNI.so
Install        : libHelloJNI.so => libs/armeabi/libHelloJNI.so

  將會在 HelloJNI/libs/armeabi 目錄下生成一個名為 libHelloJNI.so 的動態庫。編譯生成 apk 時會把這個庫一起打包。

6. 測試

  7. 參考資料

  [1]. Android: NDK編程入門筆記
  [2]. JNI及Android JNI 開發基本知識和具體操作步驟
  [3]. 如何在Android下使用JNI


免責聲明!

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



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