Android studio jni

  首先我们要明确几个概念,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层的来回频繁切换,避免崩溃。

posted @ 2015-09-11 16:08  lsc183  阅读(1983)  评论(0编辑  收藏  举报