Android平台下的JNI封装

Android平台下的JNI封装 Hilbert 2024-01-03 13:31:40 686

Android平台下的JNI封装



JNI(Java Native Interface)知识体系


JNINativeMethod 结构体的官方定义

typedef struct {
    const char* name; //Java里调用的函数名
    const char* signature; //JNI字段描述符, 用来表示Java里调用的函数的参数和返回值类型
    void* fnPtr; //C语言实现的本地函数
} JNINativeMethod;

 Java访问C库

1、在JNIDemo.java中通过 System.loadLibrary方法加载C库

public class JNIDemo {
    static { /* 1. load */
        System.loadLibrary("native"); /* libnative.so */
    }
    public native void hello();
    public static void main (String args[]) {
        JNIDemo d = new JNIDemo();

        /* 2. map java hello <-->c c_hello */

        /* 3. call */
        d.hello();
    }
}

 2、在native.c中映射java与C的函数关系

#include <jni.h> /* /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ */
#include <stdio.h>

void c_hello(JNIEnv *env, jobject cls) //C语言实现的本地函数
{
    printf("Hello, world!\n");
}


static const JNINativeMethod methods[] = { //填充JNINativeMethod结构体
    {"hello", "()V", (void *)c_hello},
};

/* 1. System.loadLibrary */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
    JNIEnv *env;
    jclass cls;

    if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) { //获取Java运行环境
        return JNI_ERR; /* JNI version not supported */
    }
    cls = (*env)->FindClass(env, "JNIDemo"); //从Java运行环境中获取类
    if (cls == NULL) {
        return JNI_ERR;
    }

    /* 2. map java hello <-->c c_hello */
    if ((*env)->RegisterNatives(env, cls, methods, 1) < 0) //将JNINativeMethod结构体注册到Java运行环境中,完成java与C的方法映射
    return JNI_ERR;

    return JNI_VERSION_1_4;
}

快速生成JNI描述字段

  • 在Java文件中用public native void hello();声明本地方法
  • 编译JNIDemo.java—-> javac JNIDemo.java =====> JNIDemo.class
  • 通过javah -jni JNIDemo 生成JNIDemo.h

JNIDemo.h如下:

注:C语言的函数比Java的方法中要多两个参数

编译C文件生成动态库

gcc -I/usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -fPIC -shared -o libnative.so native.c

导入环境变量并执行 export LD_LIBRARY_PATH=. java JNIDemo 执行结果:

传递参数

  • int型参数

Java中的声明如下:
public native int hello(int m);
native.c中对应的函数及JNINativeMethod 结构体的填充:

jint c_hello(JNIEnv *env, jobject cls, jint m) //基本参数直接使用,直接返回
{
    printf("Hello, world! val = %d\n", m);
    return 100;
}


static const JNINativeMethod methods[] = {
    {"hello", "(I)I", (void *)c_hello},
};
  • String型参数

    Java中的声明如下:
    public native String hello(String str);
    native.c中对应的函数及JNINativeMethod 结构体的填充:

jstring JNICALL c_hello(JNIEnv *env, jobject cls, jstring str)
{
    //printf("this is c : %s\n", str); /*错误方法*/
    //return "return from C"; /*错误方法*/

    const jbyte *cstr;
    cstr = (*env)->GetStringUTFChars(env, str, NULL); /*通过JVM提供的方法获取*/
    if (cstr == NULL) {
        return NULL; /* OutOfMemoryError already thrown */
    }
    printf("Get string from java :%s\n", cstr);
    (*env)->ReleaseStringUTFChars(env, str, cstr);

    return (*env)->NewStringUTF(env, "return from c");/*通过JVM提供的方法返回*/
}


static const JNINativeMethod methods[] = {
{"hello", "(Ljava/lang/String;)Ljava/lang/String;", (void *)c_hello},
};
  • 数组型参数

    Java中的声明如下:
    public native int[] hello(int[] a);
    native.c中对应的函数及JNINativeMethod 结构体的填充:

jintArray c_hello(JNIEnv *env, jobject cls, jintArray arr)
{
    jint *carr;
    jint *oarr;
    jintArray rarr;

    jint i, n = 0;
    carr = (*env)->GetIntArrayElements(env, arr, NULL);/*通过JVM提供的方法获取*/
    if (carr == NULL) {
        return 0; /* exception occurred */
    }

    n = (*env)->GetArrayLength(env, arr);
    oarr = malloc(sizeof(jint) * n);
    if (oarr == NULL)
    {
        (*env)->ReleaseIntArrayElements(env, arr, carr, 0);
        return 0;
    }

    for (i = 0; i < n; i++)
    {
        oarr[i] = carr[n-1-i];
    }

    (*env)->ReleaseIntArrayElements(env, arr, carr, 0);

    /* create jintArray */
    rarr = (*env)->NewIntArray(env, n);
    if (rarr == NULL)
    {
        return 0;
    }

    (*env)->SetIntArrayRegion(env, rarr, 0, n, oarr);/*通过JVM提供的方法返回*/
    free(oarr);

    return rarr;
}


static const JNINativeMethod methods[] = {
    {"hello", "([I)[I", (void *)c_hello},
};

 Java访问C++的相互调用

  • 在JNIDemo.java中通过 System.loadLibrary方法加载C++ 编译生成的动态库
public class JNIDemo {

    static {
        /* 1. load */
        System.loadLibrary("native"); /* libnative.so */
    }

    public static void callback(int status){
        System.out.println(status);
    }

    public static native int [] hello(int []a); /* */
    public static void main(String args[]) {
        int []a = {1, 2, 3};
        int []b = null;
        int i;

        JNIDemo d = new JNIDemo();
        /* 2. map java hello<--->c hello */
        /* 3. call */
        b = d.hello(a);
        for (i = 0; i < b.length; i++)
        {
            System.out.println(b[i]);
        }
    }
}
  • 在native.cpp中映射java与C++的函数关系
#include <jni.h> /* /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ */
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include "test.h"

using namespace std;

jclass cls;
JNIEnv *env;

class java_cb:public observer
{
    public:
    java_cb(){};
    ~java_cb(){};
    void phe_status(int status);
};

void java_cb::phe_status(int status)
{
    jmethodID jmid = env->GetStaticMethodID(cls, "callback", "(I)V"); //通过JVM提供GetStaticMethodID方法来获取Java中的callback方法

    if(NULL == jmid)
    {
    cout<<"jmid error"<<endl;
    return ;
    }

    //jobject obj = getInstance(env, cls);
    env->CallStaticVoidMethod(cls, jmid, status);//通过JVM提供CallStaticVoidMethod方法来调用Java中的callback方法

    //return status;
}

jintArray JNICALL c_hello (JNIEnv *env, jclass cls, jintArray arr)
{
    jint *carr;
    jint *oarr;
    jintArray rarr;

    jint i, n = 0;
    carr = env->GetIntArrayElements(arr, NULL);
    if (carr == NULL) {
        return 0; /* exception occurred */
    }
    n = env->GetArrayLength(arr);
    oarr = (jint*)malloc(sizeof(jint) * n);
    if(oarr == NULL)
    {
        env->ReleaseIntArrayElements(arr, carr, 0);
        return 0;
    }
    for(i = 0;i < n; i ++)
    {
        oarr[i] = carr[n-i-1];
    }

    env->ReleaseIntArrayElements( arr, carr, 0);

    /* Create jintArray */
    rarr = env->NewIntArray(n);
    if(rarr == NULL)
    {
        return 0;
    }
    env->SetIntArrayRegion(rarr, 0, n, oarr);
    free(oarr);


    /*用于测试c++调用Java中的方法*/

    java_cb * j_cb = new java_cb();

    test T(j_cb); //注册回调
    T.get_status(); //触发回调机制

    return rarr;
}


static const JNINativeMethod methods[] = {
    {"hello", "([I)[I", (void *)c_hello},
};

/* System.loadLibarary */

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{

    if (jvm->GetEnv((void **)&env, JNI_VERSION_1_4)) {
    return JNI_ERR; /* JNI version not supported */
    }
    cls = env->FindClass("JNIDemo");
    if (cls == NULL) {
    return JNI_ERR;
    }

    /* 2. map java hello<--->c hello */
    if(env->RegisterNatives(cls, methods, 1) < 0)
    return JNI_ERR;

    return JNI_VERSION_1_4;
}
  • 编译C++文件生成动态库
g++ -I/usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -fPIC -shared -o libnative.so native.cpp -L ./ -ltest

导入环境变量并执行

 export LD_LIBRARY_PATH=.
   java JNIDemo

注:JVM为c语言和C++语言提供的方法参数有差别

JNI封装的几点注意事项

Android基础知识:

一、访问权限

权限的目的是为了保护Android用户的隐私。Android应用程序必须请求允许访问敏感用户数据(如联系人和 SMS),以及某些系统特征(如摄像头和 Internet)。根据该特征,系统可以自动授予许可,或者提示用户批准请求。

二、权限分类

在Android 6.0(M)之后,对权限进行了分类,大致有这三种:

  • 普通权限:
    涵盖应用需要访问其沙盒外部数据或资源,但对用户隐私或其他应用操作风险很小的区域。这些权限在应用安装时授予,运行时不再询问用户。例如: 网络访问、WIFI状态、音量设置等。
  • 危险权限:
    涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域。例如: 读取通讯录、读写存储器数据、获取用户位置等。如果应用声明需要这些危险权限,则必须在运行时明确告诉用户,让用户手动授予。
  • 特殊权限:
    SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 这两个权限比较特殊,不能通过代码申请方式获取,必须得用户打开软件设置页手动打开,才能授权。
    三、常用权限设置
    在AndroidManifest.xml中申请权限:

    //允许程序访问网络连接,可能产生GPRS流量
    //允许获取网络信息状态
    //允许获取当前WiFi接入的状态以及WLAN热点的信息
    //允许程序录制声音通过手机或耳机的麦克

    详细权限列表见:https://www.jianshu.com/p/24f79a70025b
    四、安卓硬件管理框架
    由于JIN直接访问硬件,可能导致多个应用同时访问一个驱动,就可能导致驱动出现问题,安卓中可以只让一个应用程序来访问硬件,这个应用程序成为“SystemServer”,APP有应用请求统一发给它,由它统一管理所有的service。这个服务称之为硬件访问服务。
  • 硬件访问服务的流程
  1. 通过LoadLibrary来加载C库,
  2. C库的JNI_Onload函数里面注册本地方法,分别调用各个硬件的函数来注册本地方法,比如LED、振动器、串口等等
  3. SystemServer:对每个硬件addService(Server里面有很多个Service,由Server提供Service),每个硬件都需构造Service(即注册本地方法)
  4. APP使用:
    首先获得服务:get Service
    然后使用服务:执行Service方法
  • 硬件访问服务框图(以led为例)

App通过JNI访问底层的流程(忽略硬件访问服务)

JNI的多线程调用几点注意

  1. 在其他线程里面回调到java层,通过NewGlobalRef,保存全局变量。
  2. 在回调java层时要获取当前native线程有没有被附加到jvm环境中(通过GetEnv(g_VM, (void **) &env,JNI_VERSION_1_6)方法),如果没有要通过AttachCurrentThread(g_VM, &env, NULL)主动附加到jvm环境中,获取到env,回调完成后要通过DetachCurrentThread(g_VM);释放当前线程所持有的JVM的环境。
    参考:https://www.jianshu.com/p/e576c7e1c403

参考文档《The Java Native Interface Programmer’s Guide and Specification.pdf》

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
Hilbert
红包 1 1 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
Hilbert
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区