prefácio
-
No ecossistema Android, existem principalmente três linguagens: C/C++, Java e Kotlin, cuja relação não é de substituição, mas de complementaridade. Dentre eles, o contexto do C/C++ é algoritmo e alto desempenho, o contexto do Java é independente de plataforma e gerenciamento de memória, e o Kotlin combina as excelentes características de múltiplas linguagens, trazendo um método de programação mais moderno;
-
JNI é um recurso que realiza a interação entre o código Java e o código C/C++. Pense sobre uma questão: como a máquina virtual Java realiza a interação entre duas linguagens irrelevantes? Hoje, vamos resumir de forma abrangente a estrutura de conhecimento de desenvolvimento JNI e estabelecer as bases para o desenvolvimento do NDK.
Conheça o JNI
1. Por que usar JNI?
JNI (Java Native Interface, Java Local Interface) é um recurso do ecossistema Java, que estende os recursos da máquina virtual Java, permitindo que o código Java interaja com o código C/C++. Por meio da interface JNI, o código Java pode chamar o código C/C++ e o código C/C++ também pode chamar o código Java.
2. O processo básico de desenvolvimento do JNI
Um processo de desenvolvimento JNI padrão inclui principalmente as seguintes etapas:
- 1. Crie
HelloWorld.java
e declare o método nativo sayHi(); - 2. Use o comando javac para compilar o arquivo fonte e gerar um arquivo
HelloWorld.class
bytecode ; - 3. Use o comando javah para exportar
HelloWorld.h
o arquivo de cabeçalho (o arquivo de cabeçalho contém o protótipo da função do método local); - 4. Implemente o protótipo da função
HelloWorld.cpp
no ; - 5. Compilar código local e gerar arquivos
Hello-World.so
dinâmicos de biblioteca nativa; - 6. Chame System.loadLibrary(…) no código Java para carregar o arquivo so;
- 7. Use o comando Java para executar o programa HelloWorld.
código de modelo JNI
JNIEXPORT void JNICALL Java_com_xurui_hellojni_HelloWorld_sayHi (JNIEnv *, jobject);
1. Nome da função JNI
Por que o nome da função JNI Java_com_xurui_HelloWorld_sayHi
adota o método de nomenclatura? —— Esta é a regra de nomenclatura de função da convenção de registro estático de função JNI. Há um relacionamento de mapeamento um-para-um entre métodos nativos Java e funções JNI, e há dois métodos de registro para estabelecer esse relacionamento de mapeamento: registro estático + registro dinâmico.
Entre eles, o registro estático é um relacionamento de mapeamento estabelecido com base em convenções de nomenclatura, e a função JNI correspondente a um método nativo Java usará o nome de função acordado, ou seja, Java_[类的全限定名 (带下划线)]_[方法名]
.
2. Palavras-chave JNIEXPORT
JNIEXPORT
É uma definição de macro, indicando que uma função precisa ser exposta ao uso externo da biblioteca compartilhada. JNIEXPORT é definido de forma diferente no Windows e no Linux:
// Windows 平台 :
#define JNIEXPORT __declspec(dllexport)
#define JNIIMPORT __declspec(dllimport)
// Linux 平台:
#define JNIIMPORT
#define JNIEXPORT __attribute__ ((visibility ("default")))
3. Palavra-chave JNICALL
JNICALL
É uma definição de macro, indicando que uma função é uma função JNI. JNICALL é definido de forma diferente no Windows e no Linux:
// Windows 平台 :
#define JNICALL __stdcall // __stdcall 是一种函数调用参数的约定 ,表示函数的调用参数是从右往左。
// Linux 平台:
#define JNICALL
4. O parâmetro jobject
O tipo jobject é a representação da camada JNI do objeto de tipo de aplicativo da camada Java. Todo método nativo chamado de Java passa uma referência ao objeto atual na função JNI. Distinguem-se dois casos:
- 1. Método nativo estático : O segundo parâmetro é o tipo jclass, apontando para o objeto Class da classe onde o método nativo está localizado;
- 2. Método nativo da instância : O segundo parâmetro é o tipo jobject, apontando para o objeto que chama o método nativo.
5. O papel de JavaVM e JNIEnv
JavaVM
e JNIEnv
são as duas estruturas de dados mais críticas definidas no arquivo de cabeçalho jni.h:
- JavaVM : representa a máquina virtual Java.Cada processo Java possui e possui apenas um objeto JavaVM global, e o JavaVM pode ser compartilhado entre threads;
- JNIEnv : representa o ambiente de tempo de execução Java, cada encadeamento Java tem seu próprio objeto JNIEnv independente e JNIEnv não pode ser compartilhado entre encadeamentos.
As definições de tipo de JavaVM e JNIEnv são ligeiramente diferentes em C e C++, mas essencialmente as mesmas, consistindo internamente em uma série de ponteiros de função apontando para o interior da máquina virtual.
exemplo
1. Crie HelloWorld.java
e declare o método nativo hello_world();
public class JNIExample {
static {
// 函数System.loadLibrary()是加载dll(windows)或so(Linux)库,只需名称即可,
// 无需加入文件名后缀(.dll或.so)
System.loadLibrary("JNIExample");
init_native();
}
private static native void init_native();
public static native void hello_world();
public static void main(String...args) {
JNIExample.hello_world();
}
}
2. Use o comando javac para compilar o arquivo fonte e gerar um arquivo HelloWorld.class
bytecode ;
javac com/kevin/jni/JNIExample.java
javah com.kevin.jni.JNIExample
3. Use o comando javah para exportar HelloWorld.h
o arquivo de cabeçalho (o arquivo de cabeçalho contém o protótipo da função do método local);
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_kevin_jni_JNIExample */
#ifndef _Included_com_kevin_jni_JNIExample
#define _Included_com_kevin_jni_JNIExample
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_kevin_jni_JNIExample
* Method: init_native
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_kevin_jni_JNIExample_init_1native
(JNIEnv *, jclass);
/*
* Class: com_kevin_jni_JNIExample
* Method: hello_world
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_kevin_jni_JNIExample_hello_1world
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
4. Implemente o protótipo da função HelloWorld.cpp
no ;
void init_native(JNIEnv *env, jobject thiz) {
printf("native_init\n");
return;
}
void hello_world(JNIEnv *env, jobject thiz) {
printf("Hello World!");
return;
}
static const JNINativeMethod gMethods[] = {
{
"init_native", "()V", (void*)init_native},
{
"hello_world", "()V", (void*)hello_world}
};
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
__android_log_print(ANDROID_LOG_INFO, "native", "Jni_OnLoad");
JNIEnv* env = NULL;
if(vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) // 从 JavaVM 获取JNIEnv,一般使用 1.4 的版本
return -1;
jclass clazz = env->FindClass("me/shouheng/jni/JNIExample");
if (!clazz){
__android_log_print(ANDROID_LOG_INFO, "native", "cannot get class: com/example/efan/jni_learn2/MainActivity");
return -1;
}
if(env->RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])))
{
__android_log_print(ANDROID_LOG_INFO, "native", "register native method failed!\n");
return -1;
}
return JNI_VERSION_1_4;
}
5. Compilar código local e gerar arquivos Hello-World.so
dinâmicos de biblioteca nativa;
gcc -c -I"E:\JDK\include" -I"E:\JDK\include\win32" jni/JNIExample.c
6. Chame System.loadLibrary(…) no código Java para carregar o arquivo so;
gcc -Wl,--add-stdcall-alias -shared -o JNIExample.dll JNIExample.o
7. Use o comando Java para executar o programa HelloWorld.