• Android中关于JNI 的学习(四)简单的样例,温故而知新


    在第零篇文章简单地介绍了JNI编程的模式之后。后面两三篇文章,我们又针对JNI中的一些概念做了一些简单的介绍,也不知道我究竟说的清楚没有。但相信非常多童鞋跟我一样。在刚開始学习一个东西的时候,入门最好的方式就是一个现成的样例来參考,慢慢研究,再学习概念。再回过来研究代码,加深印象,从而開始慢慢掌握。

    今天我们就再来做一个小Demo。这个样例会比前面略微复杂一点。可是假设阅读过前面几篇文章的话,理解起来也还是非常easy的。

    非常多东西就是这样。未知的时候非常可怕。理解了就非常easy了。

    1)我们首先定义一个Java类,里面包括几个native方法,例如以下:

    public class ParamTransferTest {
    
    	public static int testval = 1;
    	
    	public native void changeTestVal();
    	
    	public native int add(int x, int y);
    	
    	public native String addTail(String tail);
    	
    	public native int[] changeArray(int[] arr);
    }
    

    2)利用javah工具生成相应的头文件。例如以下:

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_lms_jni_ParamTransferTest */
    
    #ifndef _Included_com_lms_jni_ParamTransferTest
    #define _Included_com_lms_jni_ParamTransferTest
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_lms_jni_ParamTransferTest
     * Method:    changeTestVal
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_com_lms_jni_ParamTransferTest_changeTestVal
      (JNIEnv *, jobject);
    
    /*
     * Class:     com_lms_jni_ParamTransferTest
     * Method:    add
     * Signature: (II)I
     */
    JNIEXPORT jint JNICALL Java_com_lms_jni_ParamTransferTest_add
      (JNIEnv *, jobject, jint, jint);
    
    /*
     * Class:     com_lms_jni_ParamTransferTest
     * Method:    addTail
     * Signature: (Ljava/lang/String;)Ljava/lang/String;
     */
    JNIEXPORT jstring JNICALL Java_com_lms_jni_ParamTransferTest_addTail
      (JNIEnv *, jobject, jstring);
    
    /*
     * Class:     com_lms_jni_ParamTransferTest
     * Method:    changeArray
     * Signature: ([I)[I
     */
    JNIEXPORT jintArray JNICALL Java_com_lms_jni_ParamTransferTest_changeArray
      (JNIEnv *, jobject, jintArray);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    上面就生成了相应的方法,在上面我们能够看到前面文章所介绍过的方法名称以Java开头,方法签名等信息。对吧。

    3)编写 C 文件。例如以下:

    #include <stdio.h>
    #include <stdlib.h>
    #include "com_lms_jni_ParamTransferTest.h"
    #include <android/log.h>
    #include <jni.h>
    #include <malloc.h>
    
    #define LOG_TAG "System.out"
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
    
    char* Jstring2CStr(JNIEnv * env, jstring str){
    	char * rtn = NULL;
    
    	jclass clsstring = (*env)->FindClass(env, "java/lang/String");//通过FindClass方法获得Java的String类
    	jstring strencode = (*env)->NewStringUTF(env, "UTF-8");//调用NewStringUTF方法,获得"UTF-8"字符串,作为编码
    	jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");//获取String类的getBytes方法
    	jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, str, mid, strencode);//调用String类的getBytes方法
    
    	jsize alen = (*env)->GetArrayLength(env, barr);//获得数组长度
    	jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);//获得数组的首地址。C/C++中数组的首元素就是一个指针
    	if(alen > 0){
    		rtn = (char*) malloc(alen + 1);
    		memcpy(rtn, ba, alen);
    		rtn[alen] = 0;
    	}//上面这一步是将数组的值拷贝到一个char*的数组中,也就是C/C++的char数组。由于C/C++没有字符串概念,最后以0结尾。
    	(*env)->ReleaseByteArrayElements(env, barr, ba, 0);//释放内存
    	return rtn;
    }
    
    /*
     * Class:     com_lms_jni_ParamTransferTest
     * Method:    changeTestVal
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_com_lms_jni_ParamTransferTest_changeTestVal
      (JNIEnv * env, jobject obj){
    	jclass clazz = (*env)->GetObjectClass(env,obj);//获得obj相应的类。也就是ParamTransferTest
    	jint val = (*env)->GetStaticIntField(env, clazz,
    						(*env)->GetStaticFieldID(env, clazz,"testval","I"));//获取字段testval的值
    	LOGI("before change testval = %d", val);//加入Log信息
    	val = val + 1;
    	LOGI("after change testval = %d", val);
    	(*env)->SetStaticIntField(env, clazz,(*env)->GetStaticFieldID(env, clazz,"testval","I"),val);//设置字段testval的值
    }
    
    /*
     * Class:     com_lms_jni_ParamTransferTest
     * Method:    add
     * Signature: (II)I
     */
    JNIEXPORT jint JNICALL Java_com_lms_jni_ParamTransferTest_add
      (JNIEnv * env, jobject obj, jint x, jint y){
    	LOGI("x = %d", x);
    	LOGI("y = %d", y);
    	return x + y;       //返回參数x和y的和
    }
    
    /*
     * Class:     com_lms_jni_ParamTransferTest
     * Method:    addTail
     * Signature: (Ljava/lang/String;)Ljava/lang/String;
     */
    JNIEXPORT jstring JNICALL Java_com_lms_jni_ParamTransferTest_addTail
      (JNIEnv * env, jobject obj, jstring str){
    
    	char* p = Jstring2CStr(env,str);//将Java中的string转化为C/C++中的char数组
    	LOGI("str = %s", p);
    	char* newStr = " Tail ";
    	return (*env)->NewStringUTF(env, strcat(p, newStr));//调用strcat函数连接两个char数组,将通过NewStringUTF返回。
    }
    
    /*
     * Class:     com_lms_jni_ParamTransferTest
     * Method:    changeArray
     * Signature: ([I)[I
     */
    JNIEXPORT jintArray JNICALL Java_com_lms_jni_ParamTransferTest_changeArray
      (JNIEnv * env, jobject obj, jintArray ja){
    	int len = (*env)->GetArrayLength(env, ja);/获取数组长度
    	LOGI("len = %d", len);
    	LOGI("address = %#x", &ja);
    	
    	jint* arr = (*env)->GetIntArrayElements(env, ja, 0);//将数组中的全部元素存入以jint*为首地址的数组中(数组的首地址就是数组名)
    
    	int i = 0;
    	for(;i < len; i++){
    		LOGI("arr[%d] = %d", i, *(arr + i));
    		*(arr + i) += 10;
    	}
        //数组中每一个元素的值加上10return ja;由于GetIntArrayElements的最后一个參数是0,即表明获取的数组是不复制的。即它们操作同一块内存,所以返回哪个数组都是ok的
    	return ja;
    }
    


    相应这四个方法,在上面都加入了一些凝视。大家看着凝视。自己研究一下逻辑。也就清楚了这几个方法实现的功能是什么,非常easy的。

    4)编写Android.mk文件,例如以下:

    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE := com_lms_jni_HwDemo
    
    LOCAL_SRC_FILES := 
    HwDemo.c 
    JniTest.c 
    ParamTransferTest.c
    
    LOCAL_LDLIBS += -llog
    
    include $(BUILD_SHARED_LIBRARY)

    在这里有一点的注意的是,编译多个文件的时候,要利用反斜杠 ""来进行换行,区分不同的C/C++文件。

    而这里LOCAL_LDLIBS是JNI中运用log所须要加入的动态包。下一篇文章会讲。

    5)编写好Android.mk文件之后,就利用ndk-build文件进行编译。文件结构例如以下:



    在jni文件夹下执行ndk-build,例如以下:


    到这里,关于JNI层的实现就结事了。以下就是Java端的事情。

    6)Activity中调用,例如以下:

    public class HwDemo extends Activity {
    
    	static {
    		System.loadLibrary("com_lms_jni_HwDemo");//载入动态包,名称就是Android.mk中的Module名称。
    	}
    	
    	public native String printHello();	
    
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);		
    		setContentView(R.layout.main);
    		...
    		
    		TextView tvAdd = (TextView)findViewById(R.id.tvAdd);
    		TextView tvString = (TextView)findViewById(R.id.tvString);
    		TextView tvArray = (TextView)findViewById(R.id.tvArray);
    		
    		TextView tvChangeTestVal = (TextView)findViewById(R.id.tvChangeTestVal);		
    		ParamTransferTest ptt = new ParamTransferTest();
    		//调用changeTestVa()方法
    		ptt.changeTestVal();		
    		tvChangeTestVal.setText("" + ptt.testval);
    		
    		//调用add方法
    		int sum = ptt.add(1, 2);
    		tvAdd.setText(String.valueOf(sum));
    		//调用addTail方法
    		tvString.setText(ptt.addTail("lms"));
    		//调用changeArray方法
    		int[] newArr = ptt.changeArray(new int[]{1,2});
    		
    		StringBuilder sb = new StringBuilder();
    		sb.append("[");
    		sb.append(newArr[0]).append(",").append(newArr[1]);
    		sb.append("]");
    		tvArray.setText(sb.toString());
    				
    	}
    
    }
    

    7)结果显示:

    关于假设在Java层调用JNI方法,还有在JNI层调用Java的方法,并操作Java的对象,相信经过这一个样例,再结合前面几篇文章所讲的东西,大家应该可以对JNI的作用和应用有一个比較主要的了解了。

    Android中底层框架的实现,尤其是在启动的时候,Native层去载入一个系统核心服务,或者启动Zygote虚拟机的时候,用到了大量的JNI层面的框架。大家假设对JNI熟悉了解了,再去了解这些框架的东西。会有非常大的帮助的。

    结束。

  • 相关阅读:
    vmware 上安装 gentoo
    有关网络编程
    【记录】Linux API钩子-文件打开
    在linux上实现DllMain + 共享库创建方法
    让程序在指定路径寻找库文件 + 库文件搜索顺序
    自定义协议解析
    修改bashrc,如何立即刷新
    Autoconf学习笔记
    ps命令输出,进程状态
    npm 使用 taobao 的镜像后,无法 login & publish
  • 原文地址:https://www.cnblogs.com/liguangsunls/p/7271889.html
Copyright © 2020-2023  润新知