初步学习Android NDK开发,尝试用as编写so文件并在java层调用它,从而更好地理解Android JNI(java native interface)技术。关于so文件的格式解析工作还没有做好,过几天再补上文档。
JNI其实就是java和c/cpp之间进行通信的一个接口规范,java可以调用c/cpp里面的函数,同样,c/cpp也可以调用java类的方法。
关于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));
首先创建个实体类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项目,配置及编写过程中出现了很多错误,有一些是因为缺乏经验的低级错误,也一并记录当作教训。
as部署:部署项目时出现错误:Instant Run requires ‘Tools | Android | Enable ADB integration’ to be enabled。这个是由于Android Studio2.x版本中的Instant Run (即时运行)引起的,点击工具栏中的Tools,选中Android,最后点击Enable ADB Integration,使其前面出现✔号就OK了。 |