n1cef1sh's Blog

前言#

初步学习Android NDK开发,尝试用as编写so文件并在java层调用它,从而更好地理解Android JNI(java native interface)技术。关于so文件的格式解析工作还没有做好,过几天再补上文档。

JNI介绍#

What##

JNI其实就是java和c/cpp之间进行通信的一个接口规范,java可以调用c/cpp里面的函数,同样,c/cpp也可以调用java类的方法。

Why##

基本数据类型对应##

Demo编写#

关于ndk的相关配置和项目内的一些设置不再赘述,直接看教程贴即可。主要记录一下代码实现部分及原理,还有一些过程中踩的坑。

简单输出字符串##

myJNI.java

package com.fish.demo6;

/**
 * Created by 75723 on 2018/7/31.
 */

public class myJNI {
static{
System.loadLibrary("JniTest");
}
public static native String sayHello();
}

加载so文件,声明sayHello()方法。

com_ fish_ demo6_myJNI.h

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

#ifndef _Included_com_fish_demo6_myJNI
#define _Included_com_fish_demo6_myJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class: com_fish_demo6_myJNI
 * Method:sayHello
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_fish_demo6_myJNI_sayHello
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif   

将myJNI.java编译,再使用javah生成.h头文件(要在java目录下)

main.c

JNIEXPORT jstring JNICALL Java_com_fish_demo6_myJNI_sayHello
(JNIEnv *env, jclass jobj){
    return (*env)->NewStringUTF(env, "hello nicefish !");
}	 main.c的内容就是把头文件的内容拷贝过来,把函数声明实现。		

MainActivity.java

package com.fish.demo6;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Log.i("开始调用so中的sayHello方法", myJNI.sayHello());
   }
}

调用myJNI的sayHello方法并用log打印出来。在logcat中查看

简单整型加法操作##

myJNI.java中添加

public static native int Add(int a, int b);

对应生成的头文件也多了这个函数声明,然后main.c中实现

/*
 * Class: com_fish_demo6_myJNI
 * Method:Add
 * Signature: (II)I
 */
  JNIEXPORT jint JNICALL Java_com_fish_demo6_myJNI_Add
(JNIEnv *env, jclass jobj, jint a, jint b){
return a+b;
} MainActivity.java打印结果。需要注意的是log.i()两个参数都是string类型的,myJNI.Add返回的结果是int,所以要转成string格式不然会报错。

Log.i("a+b"," "+myJNI.Add(6,7));

打印Java对象信息##

首先创建个实体类User,定义了三个属性name,age,sex。以及get方法。

在myJNI.java里添加方法声明

public static native String printUser(User user); 编译User.java,然后编译myJNI.java这时候会报错,说找不到符号User。因为myJNI里用到了另一个类User,所以把这两个.java一块编译即可

javac User.java myJNI.java

main.c中实现方法。获得obj对象的类,然后获取对象中特定方法的id,调用该方法。这里的话只返回了个js_name,看一下打印效果即可。

 jclass cls_objClass =(*env)->GetObjectClass(env, obj);
jmethodID nameMethodId = (*env)->GetMethodID(env, cls_objClass, "getName","()Ljava/lang/String;");
jstring js_name = (jstring)(*env)->CallObjectMethod(env, obj, nameMethodId);
char name = (char)(*env)->GetStringUTFChars(env, js_name, 0);

jmethodID ageMethodId = (*env)->GetMethodID(env, cls_objClass, "getAge", "()I");
jint ji_age = (*env)->CallIntMethod(env, obj, ageMethodId);

jmethodID sexMethodId = (*env)->GetMethodID(env, cls_objClass, "getSex", "()Ljava/lang/String;");
jstring js_sex = (jstring)(*env)->CallObjectMethod(env, obj, sexMethodId);
char  sex = (char )(*env)->GetStringUTFChars(env, js_sex, 0);

(*env)->ReleaseStringUTFChars(env, js_name, name);
(*env)->ReleaseStringUTFChars(env, js_sex, sex);
return js_name;

这里GetMehodID的第四个参数是signature,用字符串描述了函数的参数和返回值 。例如:

”()V”

“(II)V”

“(Ljava/lang/String;Ljava/lang/String;)V”

实际上这些字符是与函数的参数类型一一对应的。 “()” 中的字符表示参数,后面的则代表返回值。例如”()V” 就表示void Func(); “(II)V” 表示 void Func(int, int);

另外的补充:

MainActivity.java里调用printUser()方法,log出name

踩坑#

在实际创建android项目,配置及编写过程中出现了很多错误,有一些是因为缺乏经验的低级错误,也一并记录当作教训。

Android:JNI 与 NDK到底是什么?

简单JNI使用demo

Android NDK开发之Jni调用Java对象