Android基础总结之九:JNI

版权声明:本文为博主原创文章,转载请务必注明作者与原文链接。 https://blog.csdn.net/jingerppp/article/details/25715751

文章出处:https://blog.csdn.net/shift_wwx/article/details/25715751

前言:一直没有时间研究这个小玩意,但是近期会碰到这一块,那么就总结一下,方便后期查看。其中很多知识点还没有完善,因为时间有限,后期一定要详细分析。

详细可以看: JNI 再分析

一.JNI的原理图

image

直接到官方教程那里截的….

二. JNI简介

1. jni的调用流程

众所周知,Android的应用层的类都是以Java写的,这些Java类编译为Dex文件之后,必须靠Dalvik虚拟机( Virtual Machine)来执行。假如在执行java程序时,需要载入C&C++函数时,Dalvik虚拟机就会去加载C&C++的库,(System.loadLibrary("libName");)让java层能顺利地调用这些本地函数。需要清楚一点,这些C&C++的函数并不是在Dalvik虚拟机中运行的,所以效率和速度要比在Dalvik虚拟机中运行得快很多。

Dalvik虚拟机成功加载库之后,就会自动地寻找库里面的JNI_OnLoad函数,这个函数用途如下:

(1)告诉Dalvik虚拟机此C库使用哪一个JNI版本。如果你的库里面没有写明JNI_OnLoad()函数,VM会默认该库使用最老的JNI 1.1版本。但是新版的JNI做了很多的扩充,也优化了一些内容,如果需要使用JNI的新版功能,就必须在JNI_OnLoad()函数声明JNI的版本。如

result = JNI_VERSION_1_4; 

当没有JNI_OnLoad()函数时,Android调试信息会做出如下提示(No JNI_OnLoad found)

04-29 13:53:12.184: D/dalvikvm(361): Trying to load lib /data/data/com.shift.helloworld/lib/libHelloWorld.so 0x44edea98  
04-29 13:53:12.204: D/dalvikvm(361): Added shared lib /data/data/com.shift.helloworld/lib/libHelloWorld.so 0x44edea98 
04-29 13:53:12.204: D/dalvikvm(361): No JNI_OnLoad found in /data/data/com.shift.helloworld/lib/libHelloWorld.so 0x44edea98, skipping init
 

(2)因为Dalvik虚拟机加载C库时,第一件事是调用JNI_OnLoad()函数,所以我们可以在JNI_OnLoad()里面进行一些初始化工作,如注册JNI函数等等。注册本地函数,可以加快java层调用本地函数的效率。

另外:与JNI_OnLoad()函数相对应的有JNI_OnUnload()函数,当虚拟机释放该C库时,则会调用JNI_OnUnload()函数来进行善后的请出动作。

三. 开始使用JNI

java通过jni调用底层函数,目前据我分析是两种情况,第一种是通过javah得到头文件,然后对头文件中的函数进行实现;另一种,是通过JNI_OnLoad中对需要对接的函数进行注册,注册中包含了接口函数的class name等。自我感觉,应该还是第二种比较好一点,code容易管理,结构也比较清晰。第一种的机制是怎么解析的,还是需要后期进行进一步的研究。

下面来介绍这两种方法的实现:

1. java中会使用到的jni接口

package com.shift.testjni;

public class TestJNI {
    static{
        System.loadLibrary("shift_jni");
    }
    private native int printJNI();
    
    private int test(){
        return printJNI();
    }
}


2. 通过javac命令得到class文件

在cmd命令窗口,cd到当前TestJNI.java的目录,然后调用命令:

或者用eclipse进行编译,会在bin/classes下面生成同样的TestJNI.class文件

3. 通过javah生成c中需要的头文件

这一步比较关键,首先要设置环境变量classpath:

注意:这里的classpath是TestJNI.class所在的包的全路径,不是TestJNI.class所在的路径哦,是包。

如果这个设置不对的话,就会出现这样的一个错误:

如果,classpath设置ok的话,就能生成头文件:

另外,javah还有几个参数或选项是需要注意的,

可以事先通过set classpath命令设置环境变量,也可以通过选项-classpath来指定这个环境变量;

可以通过-d指定生成的头文件的路径,如果不指定应该是在当前的目录下产生;

4. 生成的jni头文件

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

#ifndef _Included_com_shift_testjni_TestJNI
#define _Included_com_shift_testjni_TestJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_shift_testjni_TestJNI
 * Method:    printJNI
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_shift_testjni_TestJNI_printJNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

其中的这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。
详细介绍后期补充。。。

5. 写本地的c程序

#include "com_shift_testjni_TestJNI.h"
#include <stdio.h>

JNIEXPORT jint JNICALL
Java_com_shift_testjni_TestJNI_printJNI (JNIEnv *env, jobject obj)
{
	printf("====jni test successfully===");
	return 100;
}

这里编译可以在eclipse上编译(可以通过NDK),也可以在linux下编译(通过Android.mk)出so。

这里提供Androi.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := libshift_jni
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := TestForJni.c
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) 
include $(BUILD_SHARED_LIBRARY)


6. 将so添加到eclipse上,运行测试程序

package com.shift.testjni;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.app.Activity;

public class TestJNIActivity extends Activity {
	private static final String TAG = "TestJNIActivity";
	TestJNI jni = new TestJNI();
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		Button test1 = (Button)findViewById(R.id.test1);
		test1.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				Log.d(TAG, "=====call JNI======"+jni.test());
			}
		});
	}	
}


7. 结果

8. 另一种方法

java层的code不变,c层所有的method通过register进行统一管理,:

#define LOG_NDEBUG 0
#define LOG_TAG "Shift_Test_JNI"

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

#include "utils/Log.h"

#ifndef NELEM
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif

JNIEXPORT jint JNICALL printForTest (JNIEnv *env, jobject obj)
{
    ALOGE("====jni test successfully===");
    return 0;
}
  
static JNINativeMethod methods[] = {
  { "printJNI", "()I",        (void*)printForTest},
  
};
  
  
/*
 * Register methods for one class.
 */
static int registerNativeMethods(JNIEnv* env, const char* className,
                                 const JNINativeMethod* methods, int numMethods)
{
    int rc;
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        ALOGE("Native registration unable to find class '%s'\n", className);
        return JNI_FALSE;
    }
    if (rc = ((*env)->RegisterNatives(env, clazz, methods, numMethods)) < 0) {
        ALOGE("RegisterNatives failed for '%s' %d\n", className, rc);
        return JNI_FALSE;
    }

    return JNI_TRUE;
}
  
 
/*  
 * Register methods for all classes.
 * 
 * returns JNI_TRUE on success.
 */
static int registerNatives(JNIEnv* env)
{
    if (!registerNativeMethods(env, "com/shift/testjni/TestJNI", methods, NELEM(methods))){
        return JNI_FALSE;    
    }
    return JNI_TRUE;
}

/*  
 * Called by the VM when the shared library is loaded.  
 */  
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;
    
    ALOGE("=====JNI_OnLoad=====\n");
    
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK)
        goto bail;
        
    assert(env != NULL);
        
    if (!registerNatives(env))
        goto bail;
        
    /* success -- return valid version number */
    result = JNI_VERSION_1_6;
  
bail:
    ALOGE("Leaving JNI_OnLoad (result=0x%x)\n", result);
    return result;
}

注意jni方法的使用,C&C++格式是不一样的,如下:

                  C 格式:(*env) -> <jni function> (env, <parameters>)

                  返回jstring:return (*env)->NewStringUTF(env, "XXX");

                  C++ 格式:env -> <jni function> (<parameters>)

                  返回jstring:return env->NewStringUTF("XXX");

其中log输出部分需要依赖系统库,Android.mk更新为:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := libshift_jni
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := TestForJni.c

LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)

LOCAL_SHARED_LIBRARIES := \
	libcutils \
	libutils

include $(BUILD_SHARED_LIBRARY)


输出结果:

--------- beginning of /dev/log/main
--------- beginning of /dev/log/system
E/Shift_Test_JNI( 4038): =====JNI_OnLoad=====
E/Shift_Test_JNI( 4038): Leaving JNI_OnLoad (result=0x10006)
E/Shift_Test_JNI( 4038): ====jni test successfully===
D/TestJNIActivity( 4038): =====call JNI======0

参考文档:http://developer.android.com/training/articles/perf-jni.html

猜你喜欢

转载自blog.csdn.net/jingerppp/article/details/25715751