android NDK(native development kit)不被认为是对开发者最友好的工具之一。在本文中,我提出了一个解决方案,使使用它更容易。
介绍
为什么要在Android项目中使用C?我可以给你两个很好的理由:
性能。如果你正在开发一个计算量很大的应用程序(游戏、CAD、图像处理、密码学等),你可能会考虑在C库中实现其中的一些计算。
跨平台开发。你可以在Android和iOS应用程序中集成C库。
实际上,将C代码集成到用Swift编写的iOS应用程序中非常简单。关于这一点,您可以在这里阅读更多:
导入C和Objective-C API
另一方面,在Android中,集成C库是一项更为复杂的任务。在本文中,我将向您展示如何简化此任务。
背景
这不是对Android NDK的介绍。如果您刚刚开始熟悉它,最好从官方文档开始:
幸运的是,Java编译器中有一个内置工具,可以从Java类生成本机绑定。在撰写本文时,Kotlin还没有得到这个工具的正式支持,但是由于Java和Kotlin具有极好的互操作性,因此您也可以在Kotlin项目中使用它。让我们看看这是怎么回事。下面是一个小示例,其中有一个消息类:
public class Message {
public String subject;
public String text;
}
以及发送消息函数,该函数应绑定到本机C函数:
public class HelloJNI {
static {
System.loadLibrary("hello");
}
private native boolean sendMessage(Message message);
}
让我们运行以下命令:
javac -h . HelloJNI.java
这将为Java本机接口(JNI)生成以下C绑定:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */
#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJNI
* Method: sendMessage
* Signature: (LMessage;)Z
*/
JNIEXPORT jboolean JNICALL Java_HelloJNI_sendMessage(JNIEnv *, jobject, jobject);
#ifdef __cplusplus
}
#endif
#endif
生成的代码的重要部分是以下函数定义:
JNIEXPORT jboolean JNICALL Java_HelloJNI_sendMessage(JNIEnv *, jobject, jobject);
第一个参数是JNI环境,第二个是HelloJNI实例,第三个是消息实例。请注意,这些类型根本没有映射,因此对于第三个参数,您需要知道它是一个消息实例,并且它有一个名为“subject”的成员,这是一个字符串。如果你想读subject的值,你需要这样写:
jclass messageClass = (*jenv)->FindClass((JNIEnv *) jenv, "com/jnigen/model/Message");
jfieldID fieldId = (*jenv)->GetFieldID((JNIEnv *) jenv,
messageClass, "subject", "Ljava/lang/String;");
char* value = (*jenv)->GetStringUTFChars((JNIEnv *) jenv,
(*jenv)->GetObjectField((JNIEnv *) jenv, obj, fieldId), 0);
是的,您理解正确,此代码相当于:
String value = message.subject;
一定有更好的办法吧?
生成代码的更好方法
我编写了一个代码生成器,它的工作方式正好相反:它生成Kotlin代码,并从C代码生成C绑定。此项目可在以下位置找到:
让我们看看同样的例子,但现在有了新的发电机。使用此生成器,我们将从编写C头开始:
#ifndef MESSAGE_H
#define MESSAGE_H
struct Message {
char* subject;
char* text;
};
int sendMessage(struct Message message);
#endif
从这个头文件,我的生成器将创建三个文件://Generated code. Do not edit!
package com.jnigen.model
data class Message (var subject: String = String(), var text: String = String())
本机接口:
//Generated code. Do not edit!
package com.jnigen
import com.jnigen.model.*
class JniApi {
external fun sendMessage(message: Message): Int
companion object {
init {
System.loadLibrary("native-lib")
}
}
}
以及JNI的绑定代码:
// Generated code. Do not edit!
#include <stdlib.h>
#include "../example/example.h"
#include "jni.h"
jclass getMessageClass(JNIEnv const *jenv) {
return (*jenv)->FindClass((JNIEnv *) jenv, "com/jnigen/model/Message");
}
jmethodID getMessageInitMethodId(JNIEnv const *jenv) {
return (*jenv)->GetMethodID((JNIEnv *) jenv, getMessageClass(jenv), "<init>", "()V");
}
jobject createMessage(JNIEnv const *jenv) {
return (*jenv)->NewObject((JNIEnv *) jenv, getMessageClass(jenv),
getMessageInitMethodId(jenv));
}
jfieldID getMessageFieldID(JNIEnv const *jenv, char *field, char *type) {
return (*jenv)->GetFieldID((JNIEnv *) jenv,
getMessageClass(
jenv),
field, type);
}
jobject convertMessageToJobject(JNIEnv const *jenv, struct Message value) {
jobject obj = createMessage(jenv);
(*jenv)->SetObjectField((JNIEnv*)jenv, obj,
getMessageFieldID(jenv, "subject", "Ljava/lang/String;"),
(*jenv)->NewStringUTF((JNIEnv *) jenv, value.subject));
(*jenv)->SetObjectField((JNIEnv*)jenv, obj,
getMessageFieldID(jenv, "text", "Ljava/lang/String;"),
(*jenv)->NewStringUTF((JNIEnv *) jenv, value.text));
return obj;
}
struct Message convertJobjectToMessage(JNIEnv const *jenv, jobject obj) {
struct Message result;
result.subject = (*jenv)->GetStringUTFChars((JNIEnv *) jenv,
(*jenv)->GetObjectField((JNIEnv *) jenv, obj,
getMessageFieldID(jenv, "subject", "Ljava/lang/String;")), 0);
result.text = (*jenv)->GetStringUTFChars((JNIEnv *) jenv,
(*jenv)->GetObjectField((JNIEnv *) jenv, obj,
getMessageFieldID(jenv, "text", "Ljava/lang/String;")), 0);
return result;
}
JNIEXPORT
jint JNICALL
Java_com_jnigen_JniApi_sendMessage(JNIEnv *jenv, jobject instance,
jobject message) {
int result = sendMessage(
convertJobjectToMessage(jenv, message)
);
return result;
}
请注意,这个代码生成过程的结果要好得多。由于C结构正确地映射到Kotlin数据类,因此基本上不需要手工编写任何JNI代码。出处:https://www.codeproject.com/Tips/5295028/Using-C-for-Android-Development-the-Easy-Way
原作者:Gábor Angyal