Android NDK用法

一、前言:

         以前做過一個NDK相關的項目,通常用NDK的應用,主要是目的在於數據保密(比如私有通信協議,或安全加密數據),音視頻編解碼相關等。

先上個流程圖,讓大家瞭解這個例子的調用關係:

二、代碼講解:

2.1 Java層代碼:

MainActivity.class代碼:

package com.ndk.chris.test;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {

	private static final String libSoName = "ChrisTest";
	private Button btnClick = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        btnClick = (Button) this.findViewById(R.id.btn_click);
        btnClick.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				helloWorld();
			}
		});
    }

    public native void helloWorld() ;
    /**
     * 載入JNI生成的so庫文件
     */
    static {
        System.loadLibrary(libSoName);
    }
}

MainActivity很簡單,聲明一個native的方法,並且在一開始就將本地庫加載起來,佈局裏定義了一個Button,點擊按鈕,調用NDK中的helloWorld方法。
CalledByNDK.class

package com.ndk.chris.test;

import android.util.Log;

public class CalledByNDK {
	private final static String TAG = "ChrisTest";
	public static String helloWorld1() {
		Log.d(TAG, "NDK(C)調用Java靜態方法, 返回當前時間");
		return String.valueOf(System.currentTimeMillis());
	}

	public void helloWorld2(String msg) {
		Log.d(TAG, "NDK(C)調用Java普通方法:" + msg);
	}
}

這個類中,實現了兩個方法,一個是靜態,一個是非靜態的,這兩個方法將會被NDK即C調用。

2.2 NDK層C代碼
CalledByJava.c

#include <string.h>
#include <android/log.h>
#include <jni.h>
#include "CallJava.h"

JNIEnv* jniEnv;

/**
 *  Java 中 聲明的native helloWorld 方法的實現
 *
 *	環境變量env是由JVM傳遞過來的;
 *	thiz是Java對象的引用
 */
void Java_com_ndk_chris_test_MainActivity_helloWorld(JNIEnv* env, jobject thiz)
{
	if (jniEnv == NULL) {
		jniEnv = env;
	}

	doHelloWorld();
}

JNI中接口的定義必需按照Java_包名_類名_方法名;的形式來定義,這樣JVM才能找到JAVA中調用Native的方法。

doHelloWorld在CallJava.h中聲明:

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

void doHelloWorld();

CallJava.c

#include "CallJava.h"
#include <android/log.h>

extern JNIEnv* jniEnv;

jclass CalledByNDK;
jobject mCalledByNDK;
jmethodID helloWorld1;
jmethodID helloWorld2;

int GetCallJavaClassInstance(jclass obj_class) {
	if(obj_class == NULL) {
		return 0;
	}

	jmethodID construction_id = (*jniEnv)->GetMethodID(jniEnv, obj_class,
			"<init>", "()V");

	if (construction_id == 0) {
		return -1;
	}

	mCalledByNDK = (*jniEnv)->NewObject(jniEnv, obj_class,
			construction_id);

	if (mCalledByNDK == NULL) {
		return -2;
	}

	return 1;
}

/**
 * 初始化 類、對象、方法
 */
int InitProvider() {

	__android_log_print(ANDROID_LOG_INFO, "ChrisTest", "InitProvider Enter" );

	if(jniEnv == NULL) {
		__android_log_print(ANDROID_LOG_INFO, "ChrisTest", 
			"InitProvider jniEnv is Null!");
		return 0;
	}

	if(CalledByNDK == NULL) {
		CalledByNDK = (*jniEnv)->FindClass(jniEnv,"com/ndk/chris/test/CalledByNDK");
		if(CalledByNDK == NULL){
			__android_log_print(ANDROID_LOG_INFO, "ChrisTest", 
				"InitProvider CalledByNDK is Null!");
			return -1;
		}
	}

	if (mCalledByNDK == NULL) {
		if (GetCallJavaClassInstance(CalledByNDK) != 1) {
			__android_log_print(ANDROID_LOG_INFO, "ChrisTest", 
				"InitProvider getInstanceError");
			(*jniEnv)->DeleteLocalRef(jniEnv, CalledByNDK);
			return -1;
		}
	}

	if (helloWorld1 == NULL) {
		helloWorld1 = (*jniEnv)->GetStaticMethodID(jniEnv, CalledByNDK, 
			"helloWorld1","()Ljava/lang/String;");
		if (helloWorld1 == NULL) {
			__android_log_print(ANDROID_LOG_INFO, "ChrisTest", 
				"InitProvider get helloWorld1 static methord error!");
			(*jniEnv)->DeleteLocalRef(jniEnv, CalledByNDK);
			(*jniEnv)->DeleteLocalRef(jniEnv, mCalledByNDK);
			return -2;
		}
	}

	if (helloWorld2 == NULL) {
		helloWorld2 = (*jniEnv)->GetMethodID(jniEnv, CalledByNDK, 
			"helloWorld2","(Ljava/lang/String;)V");
		if (helloWorld2 == NULL) {
			__android_log_print(ANDROID_LOG_INFO, "ChrisTest", 
				"InitProvider get helloWorld2 non-static methord error!");
			(*jniEnv)->DeleteLocalRef(jniEnv, CalledByNDK);
			(*jniEnv)->DeleteLocalRef(jniEnv, mCalledByNDK);
			(*jniEnv)->DeleteLocalRef(jniEnv, helloWorld1);
			return -3;
		}
	}

	__android_log_print(ANDROID_LOG_INFO, "ChrisTest", 
		"InitProvider Leave");
	return 1;
}

/**
 * 調用 Java 方法
 */
void doHelloWorld() {
	if(CalledByNDK == NULL || mCalledByNDK == NULL || 
		helloWorld1 == NULL || helloWorld2 == NULL) {
		int result = InitProvider() ;
		if(result != 1) {
			return;
		}
	}
	
	jstring jstr = NULL;
	char* cstr = NULL;
	jstr = (*jniEnv)->CallStaticObjectMethod(jniEnv, CalledByNDK, helloWorld1);
	cstr = (char*) (*jniEnv)->GetStringUTFChars(jniEnv,jstr, 0);
	__android_log_print(ANDROID_LOG_INFO, "ChrisTest", 
		"[NDK]time from java = %s",cstr);
	(*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, cstr);
	(*jniEnv)->DeleteLocalRef(jniEnv, jstr);
	
	jstring jstrMSG = NULL;
	jstrMSG =(*jniEnv)->NewStringUTF(jniEnv, "Hi,I'm From C");
	(*jniEnv)->CallVoidMethod(jniEnv, mCalledByNDK, helloWorld2, jstrMSG);
	(*jniEnv)->DeleteLocalRef(jniEnv, jstrMSG);
}

這裏,我們需要注意幾點:

1. C調用JAVA中的方法,需要先初始化類,對象和方法,在FindClass中,一定要寫全類所在的包名,在初始化方法時,需要注意該方法在JAVA中是靜態還是非靜態,所用的接口也是不一樣的,靜態的是GetStaticMethodID,非靜態的是GetMethodID,別忘記寫明參數類型和方法返回類型;

2. 調用JAVA方法,根據靜態與非靜態,方法有:CallStaticObjectMethod, CallVoidMethod 等返回類型;

 

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