Android studio jni

lsc183發表於2015-09-11

  首先我們要明確幾個概念,jni,ndk,共享庫(.so)。

  jni是java native interface的縮寫,java 本地介面。它提供了若干的API實現了Java和其他語言的通訊(主要是C/C++)。從Java1.1開始,jni標準成為java平臺的一部分,它允許Java程式碼和其他語言寫的程式碼進行互動。

  ndk:Android NDK 是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google稱為“NDK”。

  .so:共享函式庫,在可執行程式啟動的時候載入,所有程式重新執行時都可自動載入共享函式庫中的函式。

 

  為何要使用ndk?

    1. 程式碼的保護,由於apk的java層程式碼很容易被反編譯,而C/C++庫反匯難度較大。
    2. 在NDK中呼叫第三方C/C++庫,因為大部分的開源庫都是用C/C++程式碼編寫的。
    3. 便於移植,用C/C++寫的庫可以方便在其他的嵌入式平臺上再次使用。
 

  通俗來說,jni提供一套標準,包括定義了一些資料型別,引用型別,對應於Java中的資料型別,引用型別。還有一些轉換函式,這些都定義在jni.h中。      

  例如,java傳入的String引數,在c檔案中被jni轉換為jstring的資料型別,在c檔案中宣告char* test,然後test = (char*)(*env)->GetStringUTFChars(env, jstring, NULL);注意:test使用完後,需要釋放指標變數:(*env)->ReleaseStringUTFChars(env, jstring, test);將char* test轉換為jstring 用 (*env)->NewStringUTF(env,const char* );(const 指啥意思?)

  Android 函式庫是用c/c++寫的,框架層不能直接呼叫它,而是通過jni呼叫的。我們也可以自己用jni呼叫native層。

  實戰。

  1,配置NDK環境,需要下載NDK開發包並配置。

  2,在app build.gradle裡面配置ndk屬性。

  3,靜態載入動態庫,編寫naive方法,和普通java方法基本沒區別。

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

    public native String getStringFromNative();

   4,生成標頭檔案。在android studio 的命令列介面中,進入/app/src/main/java目錄下,執行命令:

javah -d ../jni com.example.shengchanglu.test.MainActivity

   這樣就在src/main/目錄中新增了jni目錄,以及jni/com_example_shengchanglu_test_MainActivity.h標頭檔案。

  com_example_shengchanglu_test_MainActivity.h標頭檔案內容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_shengchanglu_test_MainActivity */

#ifndef _Included_com_example_shengchanglu_test_MainActivity
#define _Included_com_example_shengchanglu_test_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_shengchanglu_test_MainActivity
 * Method:    getStringFromNative
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_shengchanglu_test_MainActivity_getStringFromNative
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

   看返回值jstring對應於java中得String。

  5,在jni目錄中新增main.c檔案,去實現com_example_shengchanglu_test_MainActivity.h標頭檔案中定義的方法。

//
// Created by shengchang lu on 15/9/2.
//

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include <android/log.h>

#ifndef LOG_TAG
#define LOG_TAG "ANDROID_LAB"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#endif


#ifndef _Included_com_example_shengchanglu_test_MainActivity
#define _Included_com_example_shengchanglu_test_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class: com_example_shengchanglu_test_MainActivity
 * Method: getStringFromNative
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_shengchanglu_test_MainActivity_getStringFromNative(JNIEnv * env , jobject j)
{
    LOGE("log string from ndk.");
    return (*env)->NewStringUTF(env,"Hello From JNI!");
}

#ifdef __cplusplus
}
#endif
#endif

   6,Android中呼叫native方法。

  7,編譯,執行。在app/build/intermediates/下出現ndk目錄,生成了動態庫so檔案和mk檔案。

 

  上面講的只是ndk開發最基本的。Java不僅可以呼叫jni方法,jni也可以呼叫Java中屬性(靜態和非靜態),方法(靜態和非靜態)。

void AppAction::setUndoRedoState(bool undo, bool redo) {
		JNIEnv* jniEnv = EnvManager::shareInstance()->getEnv();
		if (jniEnv == NULL) {
			return;
		}
		jclass jclz = NULL;
		jclz = jniEnv->FindClass(
				"com/fotoable/fotoproedit/activity/ProEditLightPenActivity");
		if (jniEnv->ExceptionCheck() == JNI_TRUE) {
			jniEnv->ExceptionClear();
			jniEnv->DeleteLocalRef(jclz);
			return;
		}
		jmethodID checkUndoRedoState = jniEnv->GetStaticMethodID(jclz,
				"checkUndoRedoState", "(ZZ)V");
		if (checkUndoRedoState == NULL) {
			jniEnv->DeleteLocalRef(jclz);
			return;
		}
		jniEnv->CallStaticVoidMethod(jclz, checkUndoRedoState,
				undo,redo);
		if (jniEnv->ExceptionCheck() == JNI_TRUE) {
			jniEnv->ExceptionClear();
		}

		jniEnv->DeleteLocalRef(jclz);
}

   根據上面的jni程式碼,我們可以反推出在當前專案中有且只有一個類中有這個方法。

   類名:com.fotoable.fotoproedit.activity.ProEditLightPenActivity

   方法:public void static checkUndoRedoState(boolean b1,boolean b1);

 

  呼叫方法完畢後,指標變數應該釋放,要不然會引起記憶體洩露,程式崩潰。難點是異常捕獲,ExceptionCheck只能捕獲上一行程式碼引發的異常,且不能向Android層丟擲,所以必須多加小心。

  關於儲存jni環境: EnvManager::shareInstance():

/*
 * UtilManager.h
 *
 *  Created on: 2013-12-31
 *      Author: Administrator
 */


#ifndef UTILMANAGER_H_
#define UTILMANAGER_H_
#include <jni.h>
class EnvManager {
public:
	EnvManager();
	virtual ~EnvManager();

	static EnvManager* shareInstance();
	static void destroy();

    JNIEnv* getEnv();

	void setEnv(JNIEnv* jniEnv);

private:
	JNIEnv* env;
};

#endif /* UTILMANAGER_H_ */

 

/*
 * UtilManager.cpp
 *
 *  Created on: 2013-12-31
 *      Author: Administrator
 */
#include <stdio.h>
#include <stdlib.h>
#include "EnvManager.h"

static EnvManager * sEnvManager = NULL;

EnvManager * EnvManager::shareInstance()
{
    if (!sEnvManager) {
    	sEnvManager = new EnvManager();
    }
    return sEnvManager;
}

void EnvManager::destroy()
{
	delete sEnvManager;
	sEnvManager = NULL;
}

JNIEnv* EnvManager::getEnv(){
	return env;
}

void EnvManager::setEnv(JNIEnv* jniEnv){
	env = jniEnv;
}

EnvManager::EnvManager() {
	env = NULL;
}

EnvManager::~EnvManager() {
	delete env;
}

 後續:跨平臺開發方面。譬如在圖片處理方面,Android 和 iOS底層可以使用cocos2dx ,Android 通過 jni 與上層介面互動,iOS可以呼叫Cocos2dx,這樣底層的程式碼是一樣的,iOS和Android只用各寫自己的介面就行。開發速度就比較快。

      如果底層有記憶體洩露引起崩潰,整個引用也會崩潰,不會丟擲異常。所以應該儘量少做底層和Android層的來回頻繁切換,避免崩潰。

相關文章