在前面的學習中,我們已經講解了關於NDK編程的環境搭建流程,簡單的使用我們也通過官網本身自帶的例子進行說明了。可是相信大家一定還存在這麼的一個疑惑:“如果我要自己利用NDK編寫一個Android應用,具體應該怎麼做?有什麼要求”。OK,別擔心,下面就讓我們一起來利用NDK來編寫一個簡單的Android應用。
1 創建一個新的Project
1) 通過以下命令創建一個新的Android Project (詳細的使用方法,大家可以回去重新參考博文《Android學習第二天-android常用命令》)
android create project -n myfirstndk -t 1 -p ./myfirstndk -k cn.uc.myjni -a MainActivity
當Project創建成功後,我們可以通過查看文件夾發現它的大體架構如下圖:
2 開始Coding
1) 進入該項目的MainActivity.java所在的目錄下,新建一個定義本地方法的類 NumberSum.java ,輸入以下代碼:
1 package cn.uc.myjni;
2
3 public class NumberSum {
4
5 // 聲明一個本地方法
6 public native int add (int a, int b);
7
8 // 加載名為 libnumber_sum.so的庫
9 // 根據Unix的規則,系統為自動為number_sum
10 // 添加上lib前綴和.so後綴
11 static {
12 System.loadLibrary("number_sum");
13 }
14 }
2) 調用以下命令,編譯NumberSum.java
javac -encoding UTF-8 NumberSum.java
編譯成功後,控制台並沒有特別的輸出,同時,我們可以在目錄下發現Number.class文件
這裡需要注意的是,我的命令中之所以指定源文件使用的編碼是因為我使用的是UTF-8編碼,在windows中直接通過javac NumberSum.java 進行編譯的話,會出現以下錯誤:
3) 返回項目源代碼文件夾下,比如我的就是返回到src目錄下,通過javah命令,生成頭文件:
javah cn.uc.myjni.NumberSum
執行成功後,控制台沒有輸出什麼特別的內容,同時,我們可以發現在src目錄下多了一個頭文件:
javah生成的頭文件知識講解:
① 從頭文件的名字我們可以看出,它是由編譯的調用本地方法的類名及其所在的包名通過下劃線"_"分割組成。如我們的程序中是編譯cn.uc.myjni.NumberSum生成的,所以就算cn_uc_myjni_NumberSum
② 頭文件中的內容可能很多,但是我們只關注這個方法的聲明:
JNIEXPORT jint JNICALL Java_cn_uc_myjni_NumberSum_add (JNIEnv *, jobject, jint, jint);
在這個聲明中,我們可以清晰的看到方法名的最後面add就是在NumberSum類中聲明的本地方法add,而前面的是 cn_uc_myjni_NumberSum就算完整的包名和類名,由此我們可以知道,一個完整的JNI函數名有3部分組成:Java、定義native方法的類的全名(包名+類名)、實際的函數名。這三部分用"_"進行連接。
4) 在Project的根目錄下,新建一個文件夾jni (必須叫做jni),將我們之前生成的頭文件移動到jni文件夾下,如圖:
5) 新建一個c文件,名字為 number_sum.c,輸入以下代碼:
1 #include <jni.h>
2
3 // 此處的方法簽名與頭文件中聲明的方法簽名是相同的
4 // 建議直接從頭文件中復制過來
5 JNIEXPORT jint JNICALL Java_cn_uc_myjni_NumberSum_add(JNIEnv *env,jobject obj,jint a,jint b)
6 {
7 return (a+b);
8 }
下面講解下上面的一些相關內容:
① JNIEXPORT jint JNICALL JNIEnv 等都是在jni.h中定義的;
② 其中上面參數中的env表示JNI的調用環境,obj表示定義native方法的Java類的對象本身。
6) 新建一個Android.mk 文件,建議直接從官方文檔給的例子 hello-jni 中復制出來進行修改LOCAL_MODUE 和 LOCAL_SRC_FILES,代碼如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := number_sum
LOCAL_SRC_FILES := number_sum.c
include $(BUILD_SHARED_LIBRARY)
下面我們簡單講解一下上面幾個參數的含義吧:
① LOCAL_PATH:Android.mk 的第一行必須是LOCAL。用於指定參與編譯的C/C++源文件的位置。在上面例子中,宏函數mydir 是由系統提供的,用來返回當前目錄的路徑。也就是包含Android.mk文件的目錄的路徑。
② include ${CLEAR_VARS}:CLEAR_VARS變量是在系統中定義的,用來指定一個特殊的GNU Make文件。該文件用來清空很多以LOCAL_開頭的變量,例如LOCAL_MODULE、LOCAL_SRC_FILES、LOCAL_STATIC_LIBRARIES 等。但這些變量不包括LOCAL_PATH。之所以要清空這些變量,是因為這些都是全局變啦ing。同時這些變量又要在不同的GNU Make文件中使用,為了多個GNU Make文件不相互影響,就需要在執行每一個GNU make文件(Android.mk文件)之前先清空這些變量。
③ LOCAL_MODULE := number_sum :在每一個模塊中必須定義 LOCAL_MODULE變量,用來指定生成的模塊名。該變量的值必須是唯一的,而且不能包含任何空白分隔符。實際上,LOCAL_MODULE 變量的值就是生產共享庫的文件名(不包括lib和.so),在編譯時,系統會自動在文件名的前後添加上lib 和.so 。如果該模塊名前綴加了lib,在生產共享庫的時候不會進行添加。
④ LOCAL_SRC_FILES := number_sum.c:用來指定一個C/C++源文件列表,這裡不需要指定頭文件,系統會自動計算當前C/C++源文件 include的頭文件。系統就直接將LOCAL_SRC_FILES變量指定的源文件傳給編譯器。C++源文件的默認擴展名是.cpp,但可以通過LOCAL_DEFAULT_CPP_EXTENSION 變量改變 C++文件默認拓展名,例如將該變量的值設成".cxx",注意不要忘記了" . "
⑤ include ${BUILD_SHARED_LIBRARY} : BUILD_SHARED_LIBRARY是在系統中定義的,用來指定一個GNU Make腳本文件。該腳本文件會根據以LOCAL_開頭的變量來生成共享庫文件。如果想生成靜態庫文件,可以使用BUILD_STATIC_LIBRARY變量。
7) 為了能夠把我們調用共享代碼庫執行的程序結果顯示出來,我們對生成的代碼界面進行一定的修改:
I. 打開 res/layout/main.xml 文件,給TextView添加上一個Id,方便我們在後台通過id獲取組件進行顯示內容的修改:
1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:orientation="vertical"
4 android:layout_width="fill_parent"
5 android:layout_height="fill_parent"
6 >
7 <!-- 給TextView 添加了ID -->
8 <TextView
9 android:id="@+id/info_text"
10 android:layout_width="fill_parent"
11 android:layout_height="wrap_content"
12 android:text="Hello World, MainActivity"
13 />
14 </LinearLayout>
II. 給我們的主界面MainActivity.java的onCreate() 方法添加上將結果顯示到界面上的邏輯代碼:
1 package cn.uc.myjni;
2
3 import android.app.Activity;
4 import android.widget.TextView;
5 import android.os.Bundle;
6
7 public class MainActivity extends Activity
8 {
9 /** Called when the activity is first created. */
10 @Override
11 public void onCreate(Bundle savedInstanceState)
12 {
13 super.onCreate(savedInstanceState);
14 setContentView(R.layout.main);
15
16 // 通過Id獲取TextView組件
17 TextView textView=(TextView) findViewById(R.id.info_text);
18 NumberSum sum = new NumberSum();
19 // 調用本地方法
20 int result=sum.add(10, 20);
21 textView.append("\n 10+20="+result);
22 }
23 }