Androd 中 NDK 編程詳解(一)

上節講解了NDK 開發環境搭建的方法,這節講解下NDK編程的相關知識。

如何將.so文件打包到.APK

1、在你的項目根目錄下建立libs/armeabi目錄;

2、將libxxx.so文件copy到 libs/armeabi/下;

3、此時ADT插件自動編譯輸出的.apk文件中已經包括.so文件了;

4、安裝APK文件,即可直接使用JNI中的方法;

我想還需要簡單說明一下libxxx.so的命名規則,沿襲Linux傳統,lib<something>.so是類庫文件名稱的格式,但在Java的System.loadLibrary(" something ")方法中指定庫名稱時,不能包括 前綴—— lib,以及後綴——.so。

當然,如果直接在模擬器上開發的話,將生成的.so 文件直接 adb remount, adb push *.so  /system/lib 即可.但是這個需要ROOT權限,感覺不實用,也不方便。

編寫自己的NDK 應用

1、首先創建含有native方法的Java類:

public class MainActivity extends Activity {

	static {
		System.loadLibrary("myjni");
	}

	public native String stringFromJni();

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		TextView textView = new TextView(this);
		String tranString = stringFromJni();
		textView.setText(tranString);
		setContentView(textView);
	}
}


2、在C文件中直接實現Native 方法

當然,也可以採用javah 生成頭文件,然後再根據相關函數名寫C源代碼文件,但是JNI的函數命名規則我們已經清楚了,直接自己寫就OK!

具體做法:

在java 工程下建立 jni 文件夾, 下面加建立 test.c 和 Android.mk 文件即可。

test.c:

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

jstring Java_com_sj_ndktest_MainActivity_stringFromJni(JNIEnv * env,
		jobject this) {
	return (*env)->NewStringUTF(env, "Hello from My-JNI !");
}

Android.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := myjni

LOCAL_SRC_FILES := myjni.c


include $(BUILD_SHARED_LIBRARY)


編譯——兩種不同的編譯環境

1、Android NDK :全稱是Native Developer Kit,是用於編譯本地JNI源碼的工具,爲開發人員將本地方法整合到Android應用中提供了方便。事實上NDK和完整源碼編譯環境一樣,都使用Android的編譯系統——即通過Android.mk文件控制編譯。NDK可以運行在Linux、Mac、Window(+cygwin)三個平臺上。

2、完整源碼編譯環境 :Android平臺提供有基於make的編譯系統,爲App編寫正確的Android.mk文件就可使用該編譯系統。該環境需要通過git從官方網站獲取完整源碼副本併成功編譯,更多細節請參考:http://source.android.com/index.html

不管你選擇以上兩種方法的哪一個,都必須編寫自己的Android.mk文件,有關該文件的編寫請參考相關文檔。

這裏我們採用 NDK 環境編譯,Cygwin 下 $NDK/ndk-build 即可。完成後會發現 文件夾下多了 libs 和 obj 文件夾,這樣就對了。Eclipse 下直接Build  RUN工程,構建APK,其會將so 文件自動打包,安裝,完成。

NDK命令介紹:

  ndk-build
  ndk-build clean --> 清空所編譯出的二進制文件們。
  ndk-build -B V=1 --> 強制完全重新編譯,並顯示命令

JNI組件的常見函數——JNI_OnLoad()、JNI_OnUnload()

JNI組件被成功加載和卸載時,會進行函數回調,當VM執行到System.loadLibrary(xxx)函數時,首先會去執行JNI組件中的JNI_OnLoad()函數,而當VM釋放該組件時會呼叫JNI_OnUnload()函數。

 JNI_OnLoad()有兩個重要的作用:

(1)指定JNI版本:告訴VM該組件使用那一個JNI版本(若未提供JNI_OnLoad()函數,VM會默認該使用最老的JNI 1.1版),如果要使用新版本的JNI,例如JNI 1.4版,則必須由JNI_OnLoad()函數返回常量JNI_VERSION_1_4(該常量定義在jni.h中) 來告知VM。

(2)初始化設定,當VM執行到System.loadLibrary()函數時,會立即先調用JNI_OnLoad()方法,因此在該方法中進行各種資源的初始化操作最爲恰當。

 JNI_OnUnload()作用:

JNI_OnUnload()的作用與JNI_OnLoad()對應,當VM釋放JNI組件時會調用它,因此在該方法中進行善後清理,資源釋放的動作最爲合適。

本地方法註冊——RegisterNatives方法

本地方法註冊的作用:本地方法名不必固定按照 Java_包名_類名_方法名 的格式,我們完全可以自定義方法名,然後註冊到虛擬機就可以。

當Java類別透過VM呼叫到本地函數時,通常是依靠VM去動態尋找.so中的本地函數(因此它們才需要特定規則的命名格式),如果某方法需要連續呼叫很多次,則每次都要尋找一遍,所以使用RegisterNatives將本地函數向VM進行登記,可以讓其更有效率的找到函數。RegisterNatives方法的另一個重要用途是,運行時動態調整本地函數與Java函數值之間的映射關係,只需要多次調用RegisterNatives()方法,並傳入不同的映射表參數即可。

本地方法註冊實質是要建立本地方法和JAVA方法之間的映射關係,而建立c/c++方法和Java方法之間映射關係的關鍵是 JNINativeMethod 結構,該結構定義在jni.h中,具體定義如下:

/*
 * used in RegisterNatives to describe native method name, signature,
 * and function pointer.
 */

typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;


定義映射關係數組:

static JNINativeMethod methods[] = { { "produceString",
		"(Ljava/lang/String;)Ljava/lang/String;", (void*) C_produceString } };

其中第一項即 name 項爲java 方法名(不需要包含包名,直接寫方法名即可);第三項爲本地方法名;第二項看着比較複雜,其代表的是函數簽名信息,括號內部是參數類型,後面是返回值類型,具體規則如下:

1)基本類型對應關係:
標識符  Jni 類型       C 類型
  V    void           void
  Z    jboolean       boolean
  I    jint           int
  J    jlong          long
  D    jdouble        double
  F    jfloat         float
  B    jbyte          byte
  C    jchar          char
  S    jshort         short
2)基本類型數組:(則以 [ 開始,用兩個字符表示)
標識串  Jni 類型        C 類型
  [Z   jbooleanArray  boolean[]
  [I   jintArray      int[]
  [J   jlongArray     long[]
  [D   jdoubleArray   double[]
  [F   jfloatArray    float[]
  [B   jbyteArray     byte[]
  [C   jcharArray     char[]
  [S   jshortArray    short[]

3)類(class):(則以 L 開頭,以 ; 結尾,中間是用 / 隔開的 包 及 類名)
標識串        Java 類型  Jni 類型
L包1/包n/類名;     類名     jobject
例子:
Ljava/net/Socket; Socket      jobject

4)例外(String 類):
標識串               Java 類型  Jni 類型
Ljava/lang/String;  String    jstring

5)嵌套類(類位於另一個類之中,則用$作爲類名間的分隔符)
標識串                         Java 類型  Jni 類型
L包1/包n/類名$嵌套類名;              類名      jobject
例子:
Landroid/os/FileUtils$FileStatus;  FileStatus  jobject

具體我覺得沒比較死記硬背,碰到了去查就可以,看多了就記住了。也可以用 java提供的一個javap的工具幫助生成函數或變量的簽名信息,用法如下:
javap -s -p xxx
xxx 爲class文件,有了它,就不用記上面的類型表示了。

生成文件如下 eg:

  public com.sj.ndktest.MainActivity();
    Signature: ()V

  public native java.lang.String stringFromJni();
    Signature: ()Ljava/lang/String;

  public native java.lang.String produceString(java.lang.String);
    Signature: (Ljava/lang/String;)Ljava/lang/String;

  public void onCreate(android.os.Bundle);
    Signature: (Landroid/os/Bundle;)V

RegisterNatives具體用法代碼示例:

//定義目標類名稱
static const char *className = "com/sj/ndktest/MainActivity";
//定義方法隱射關係
static JNINativeMethod methods[] = { { "produceString",
		"(Ljava/lang/String;)Ljava/lang/String;", (void*) C_produceString } };

//onLoad方法,在System.loadLibrary()執行時被調用
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
	LOGI("JNI_OnLoad startup~~!");

	//聲明變量
	jint result = JNI_ERR;
	JNIEnv* env = NULL;
	jclass clazz;
	int methodsLenght;

	//獲取JNI環境對象
	if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
		LOGE("ERROR: GetEnv failed\n");
		return JNI_ERR;
	}
	assert(env != NULL);

	//註冊本地方法.Load 目標類
	clazz = (*env)->FindClass(env, className);
	if (clazz == NULL) {
		LOGE("Native registration unable to find class '%s'", className);
		return JNI_ERR;
	}
	//建立方法隱射關係
	//取得方法長度
	methodsLenght = sizeof(methods) / sizeof(methods[0]);
	if ((*env)->RegisterNatives(env, clazz, methods, methodsLenght) < 0) {
		LOGE("RegisterNatives failed for '%s'", className);
		return JNI_ERR;
	}
	result = JNI_VERSION_1_4;
	return result;
}


NDK 日誌和調試

上面的代碼中看到LOGE 符號,這個是NDK 的日誌輸出,類似於 android 中的 log.e(...) ,其實質也是調用了 android 的日誌輸出。

NDK 使用日誌輸出必須引入頭文件 #include <android/log.h>,如果是在完整源碼環境下編譯,引入 include <utils/Log.h> 即可。

具體使用方法:

#ifndef __JNILOGGER_H_
#define __JNILOGGER_H_
#include <android/log.h>
#ifdef _cplusplus
extern "C" {
#endif
#ifndef LOG_TAG
#define LOG_TAG   "MY_LOG_TAG"
#endif
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)
#ifdef __cplusplus
}
#endif
#endif
/* __JNILOGGER_H_ */

調試的話,方法有兩種:一種就是上面的LOG日誌輸出,另一種利用 ndk-gdb 工具調試,使用方法與 gdb 工具一致。

Android MK 文件寫法

示例文件:

# Copyright (C) 2009 The Android Open Source Project

#

# Licensed under the Apache License, Version 2.0 (the "License");

# you may not use this file except in compliance with the License.

# You may obtain a copy of the License at

#

#      http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS,

# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

# See the License for the specific language governing permissions and

# limitations under the License.

#

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := myjni

LOCAL_SRC_FILES := myjni.c

LOCAL_LDLIBS    := -llog


include $(BUILD_SHARED_LIBRARY)

LOCAL_LDLIBS 指的是需要鏈接的庫文件

 #宏函數'my-dir',編譯系統提供,用於返回當前路徑(即包含Android.mk文件的路徑)                                                                 LOCAL_PATH -編譯時的目錄
$(call
目錄,目錄….)目錄引入操作符
  
如該目錄下有個文件夾名稱src,則可以這樣寫$(call src),那麼就會得到src目錄的完整路徑

include $(CLEAR_VARS) -清除之前的一些系統變量

# 當前模塊的名稱/編譯的目標對象。編譯系統會自動產生合適的前綴和後綴
LOCAL_MODULE
-編譯生成的目標對象

# 包含將要編譯打包進模塊中的C或者C++源代碼文件(無需列出頭文件和包含文件)
LOCAL_SRC_FILES
-編譯的源文件

LOCAL_C_INCLUDES
-需要包含的頭文件目錄
LOCAL_SHARED_LIBRARIES
-鏈接時需要的外部庫
LOCAL_PRELINK_MODULE
-是否需要prelink處理

# BUILD_SHARED_LIBRARY表示編譯生成共享庫,是編譯系統提供的變量,指向一個GNU Makefile腳本,
# 負責收集自從上次調用'include ($CLEAR_VARS)'以來,定義在LOCA_*變量中的所有信息,並且決定編譯什麼,如果正確去編譯。
# 另: BUILD_STATIC_LIBRARY表示生成靜態庫: lib$(LOCAL_MODULE).a; BUILD_EXECUTABLE表示生成可執行文件。
include $(BUILD_SHARED_LIBRARY)
-指明要編譯成動態庫

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

 

以上就是我的總結,附上完整源代碼:

MainActivity.java:

package com.sj.ndktest;

import android.app.Activity;
import android.os.Bundle;
import android.os.Process;
import android.util.Log;
import android.view.Menu;
import android.widget.TextView;

public class MainActivity extends Activity {

	static {
		System.loadLibrary("myjni");
	}

	public native String stringFromJni();

	public native String produceString(String string);

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		TextView textView = new TextView(this);
		String tranString = stringFromJni();
		String produString = produceString("hello ");
		textView.setText(tranString + "\n" + produString);
		setContentView(textView);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.activity_main, menu);
		return true;
	}

	@Override
	public void onBackPressed() {
		super.onBackPressed();

		finish();
		Process.killProcess(Process.myPid());
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
	}
}


myjni.c :

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

#ifndef __JNILOGGER_H_
#define __JNILOGGER_H_
#include <android/log.h>
#ifdef _cplusplus
extern "C" {
#endif
#ifndef LOG_TAG
#define LOG_TAG   "MY_LOG_TAG"
#endif
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)
#ifdef __cplusplus
}
#endif
#endif
/* __JNILOGGER_H_ */



jstring Java_com_sj_ndktest_MainActivity_stringFromJni(JNIEnv * env,
		jobject this) {

	LOGE("123");
	return (*env)->NewStringUTF(env, "Hello from My-JNI !");
}

jstring C_produceString(JNIEnv * env,
		jobject this, jstring str) {

	//從jstring類型取得c語言環境下的char*類型
	const char* name = (*env)->GetStringUTFChars(env, str, 0);
	LOGE(name);
	//output:hello
	//本地常量字符串
	char* hello = "YEAH";
	//動態分配目標字符串空間
	char* result = malloc((strlen(name) + strlen(hello) + 1) * sizeof(char));
	memset(result, 0, sizeof(result));
	//字符串鏈接
	strcat(result, hello);
	strcat(result, name);
	//釋放jni分配的內存
	(*env)->ReleaseStringUTFChars(env, str, name);
	//生成返回值對象
	str = (*env)->NewStringUTF(env, " JNI~!");
	//strcat(str, result);
	//釋放動態分配的內存
	LOGE(result);
	//output:YEAHhello
	free(result);
	return str;
}

//定義目標類名稱
static const char *className = "com/sj/ndktest/MainActivity";
//定義方法隱射關係
static JNINativeMethod methods[] = { { "produceString",
		"(Ljava/lang/String;)Ljava/lang/String;", (void*) C_produceString } };

//onLoad方法,在System.loadLibrary()執行時被調用
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
	LOGI("JNI_OnLoad startup~~!");

	//聲明變量
	jint result = JNI_ERR;
	JNIEnv* env = NULL;
	jclass clazz;
	int methodsLenght;

	//獲取JNI環境對象
	if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
		LOGE("ERROR: GetEnv failed\n");
		return JNI_ERR;
	}
	assert(env != NULL);

	//註冊本地方法.Load 目標類
	clazz = (*env)->FindClass(env, className);
	if (clazz == NULL) {
		LOGE("Native registration unable to find class '%s'", className);
		return JNI_ERR;
	}
	//建立方法隱射關係
	//取得方法長度
	methodsLenght = sizeof(methods) / sizeof(methods[0]);
	if ((*env)->RegisterNatives(env, clazz, methods, methodsLenght) < 0) {
		LOGE("RegisterNatives failed for '%s'", className);
		return JNI_ERR;
	}
	result = JNI_VERSION_1_4;
	return result;
}

//onUnLoad方法,在JNI組件被釋放時調用
void JNI_OnUnload(JavaVM* vm, void* reserved) {
	LOGE("call JNI_OnUnload ~~!!");
}


Android.mk :

# Copyright (C) 2009 The Android Open Source Project

#

# Licensed under the Apache License, Version 2.0 (the "License");

# you may not use this file except in compliance with the License.

# You may obtain a copy of the License at

#

#      http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS,

# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

# See the License for the specific language governing permissions and

# limitations under the License.

#

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := myjni

LOCAL_SRC_FILES := myjni.c

LOCAL_LDLIBS    := -llog


include $(BUILD_SHARED_LIBRARY)

 

參考地址,在此表示感謝:

http://mobile.51cto.com/android-267538_2.htm

http://blog.sina.com.cn/s/blog_4c451e0e0101339i.html

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

http://jykenan.iteye.com/blog/1140965

http://blog.csdn.net/gongyangyang100/article/details/7418436

http://www.rosoo.net/a/201204/15925.html

http://www.huidawang.info/?p=47

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