Android JNI复杂用法,回调,C++中调用Java方法

这篇具有很好参考价值的文章主要介绍了Android JNI复杂用法,回调,C++中调用Java方法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Android JNI复杂用法,回调,C++中调用Java方法

一、前言

Android JNI的 普通用法估计很多人都会,但是C++中调用Java方法很多人不熟悉,并且网上很多介绍都是片段的。

虽然C/C++调用Java不常用,但是掌握多一点还是有好处的。

Android JNI的基础知识介绍,之前已经有介绍,不熟悉的可以先看看:

Android Jni的介绍和简单Demo实现:

https://blog.csdn.net/wenzhi20102321/article/details/136291126

本文主要介绍JNI C++调用Java代码实现和相关知识,有兴趣的可以看看。

二、C++调用Java方法实现代码

1、上层代码 MainAcitvity.java

package com.demo.jnicallback;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    String TAG = "MainActivity.java";

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i(TAG, "conCreate");
        TextView tv = findViewById(R.id.sample_text);
        String jniString = stringFromJNI();
        Log.i(TAG, "conCreate cppCallBackMethod jniString = " + jniString);
        tv.setText("" + jniString);
    }

    //C++调用Java 的方法,定义成private方法,cpp也是可以调用到的,因为是通过反射过来的
    public void cppCallBackMethod(String name, int age) {
        Log.i(TAG, "cppCallBackMethod name = " + name + ",age = " + age);
    }

	//Java 调用到 cpp 的native方法
    public native String stringFromJNI();

}

布局上未做修改,运行后的默认字符串"Hello from C++"。

Java代码这里加了一个给C++调用过来的方法,具体实现效果可以看是日志。

2、cpp代码 native-lib.cpp 代码:

#include <jni.h>
#include <string>

#include <android/log.h> //添加头文件
#define LOG_TAG "native-lib.cpp" //定义TAG
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

#include <iostream>
#include <chrono>
#include <thread>

extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_jnicallback_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject thiz /* this */) {
    std::string hello = "Hello from C++";
    LOGI("stringFromJNI hello = %s", hello.c_str());
    
    //c++调用Java方法:public void cppCallBackMethod(String name, int age)
    jobject m_object = env->NewGlobalRef(thiz);//创建对象的本地变量
    jclass mainActivityCls=env->FindClass("com/demo/jnicallback/MainActivity");//获取类对象
    jmethodID cppCallBackMethod = env->GetMethodID(mainActivityCls, "cppCallBackMethod", "(Ljava/lang/String;I)V");
    const char *message = "cppA";
    int age = 10;
    env->CallVoidMethod(m_object, cppCallBackMethod, env->NewStringUTF(message), age);
    while (age < 50) {
        //睡眠1秒
        std::chrono::seconds duration(1); // 休眠一秒钟
        std::this_thread::sleep_for(duration);
        age = age + 10;
        env->CallVoidMethod(m_object, cppCallBackMethod, env->NewStringUTF(message), age);
    }

    return env->NewStringUTF(hello.c_str());
}


上面代码可以看到获取类对象,是为了获取方法id;获取对象的本地变量是为了调用方法。

网上有些示例可能写法不一样,熟悉c++代码的应该知道,"env->“的写法和”(*env)."是一个意思。

3、效果日志:

//Java打印最开始日志
2024-03-01 16:27:50.401  I/MainActivity.java: conCreate
//cpp文件打印,开始的日志
2024-03-01 16:27:50.402  I/native-lib.cpp: stringFromJNI hello = Hello from C++
//cpp调用Java部分日志,在Java代码每隔一秒的打印
2024-03-01 16:27:50.402  I/MainActivity.java: cppCallBackMethod name = cppA,age = 10
2024-03-01 16:27:51.403  I/MainActivity.java: cppCallBackMethod name = cppA,age = 20
2024-03-01 16:27:52.403  I/MainActivity.java: cppCallBackMethod name = cppA,age = 30
2024-03-01 16:27:53.403  I/MainActivity.java: cppCallBackMethod name = cppA,age = 40
2024-03-01 16:27:54.404  I/MainActivity.java: cppCallBackMethod name = cppA,age = 50
//Java onCreate最后的日志,打印C++返回的字符串
2024-03-01 16:27:54.404  I/MainActivity.java: conCreate cppCallBackMethod jniString = Hello from C++

上面的代码就有Java --> C++和C++ --> Java的代码流程。

注意,这里的示例代码添加了睡眠代码,如果在主线程长时间执行任务是有可能导致ANR的。

4、cpp代码 native-lib.cpp 代码另一种写法

下面这种写法不用NewGlobalRef创建对象的本地变量。

中间的区别就是这里函数的调用没有使用"->“,使用的”(*env)."

extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_jnicallback_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject thiz /* this */) {
    std::string hello = "Hello from C++";
    LOGI("stringFromJNI hello = %s", hello.c_str());
    //c++调用Java方法:public void cppCallBackMethod(String name, int age)
    jclass mainActivityCls=(*env).FindClass("com/demo/jnicallback/MainActivity");//获取类对象
    jmethodID cppCallBackMethod = (*env).GetMethodID(mainActivityCls, "cppCallBackMethod", "(Ljava/lang/String;I)V");
    const char *message = "cppA";
    int age = 10;
    (*env).CallVoidMethod(thiz, cppCallBackMethod, env->NewStringUTF(message), age);
    while (age < 50) {
        //睡眠1秒
        std::chrono::seconds duration(1); // 休眠一秒钟
        std::this_thread::sleep_for(duration);
        age = age + 10;
        (*env).CallVoidMethod(thiz, cppCallBackMethod, env->NewStringUTF(message), age);
    }

    return env->NewStringUTF(hello.c_str());
}

上面的代码运行也是一样的效果。

"->“和”(*env)."有啥区别?因为不是很熟悉,还还说不清。

上面不同写法调用方法的参数是有区别的,其实就是函数api的参数要求不同,具体可以看到jni.h的源码。

三、其他

1、C++到Java 相关api函数介绍

上面示例中使用用到的api:

(1)jobject m_object = env->NewGlobalRef(thiz);//创建对象的本地变量
(2)jclass mainActivityCls=env->FindClass("com/demo/jnicallback/MainActivity");//获取类对象

(3)jmethodID cppCallBackMethod = env->GetMethodID(mainActivityCls, "cppCallBackMethod", "(Ljava/lang/String;I)V");

(4)env->CallVoidMethod(m_object, cppCallBackMethod, env->NewStringUTF(message), age);

上面(1)和(2)是没什么研究价值的,NewGlobalRef和FindClass都是固定的写法。

(3)和(4)的不用方法的调用区别就比较大了,使用不同的api函数还可以修改Java的变量属性。

静态方法和动态方法调用的api函数也不一样,有返回值的方法和没有返回值的方法调用的api函数也是不一样的。

并且Java方法或者变量即使是private修饰的也不影响cpp调用过去,因为反射是不受修饰符影响的。

第3步里面的签名字符串“(Ljava/lang/String;I)V”,表示的是Java的方法和返回值的签名,唯一性;

这里面的签名字符串都是根据Java方法和方法的参数进行变化的。

下面对3、4步的代码相关知识做展开介绍。

2、调用获取不同方法和变量的api

方法、变量修饰类型表格
函数描述 描述
GetFieldID 得到一个实例的域的ID
GetStaticFieldID 得到一个静态的域的ID
GetMethodID 得到一个实例的方法的ID
GetStaticMethodID 得到一个静态方法的ID

上面Jni.cpp调用Java代码已经用到部分api方法,并且从字面含义也是比较容易里面这个表格的api的具体作用。

这个表格的用于就是为了获取到方法的修饰类型,比如方法,静态方法,变量,静态变量。

毕竟不同的修饰类型,在编译过程是有差异的。所以要区分。

3、Java签名类型字符串 常用的数据类型及对应字符:

上面示例中的"(Ljava/lang/String;I)V");字符串都是根据Java的方法通过下面这个表格转换来的。

Java 类型 Jni中表示的符号 备注
boolean Z 不是类型首字母大写
byte B
char C
short S
int I
long L
float F
double D
void V
objects对象 Lfully-qualified-class-name;L全类名; 记得最后是有分号的
Arrays数组 [array-type [数组类型
methods方法 (argument-types)return-type(参数类型)返回类型

这个表格是有有啥用?就更多人懵逼了。

其实这些类型符号表示的是Java方法或者属性的一个签名,唯一性,目前就是为了让Jni.cpp调用到Java代码。

举个例子就很容易清楚了:

//XXX.Java
  int age;
  String name;
  public  int add(int number1,int number2){
        System.out.println("c/C++居然调用了我");
        return number1+number2;
    }

//jni.cpp 修改Java属性值和调用Java方法示例

//获取类对象
jclass mainActivityCls=env->FindClass("com/zmw/jnitest/MainActivity");

//获取属性的fieldId,--》这里就用到了签名类型
jfieldID ageFid = env->GetFieldID(mainActivityCls,"age","I");
jfieldID nameFid=env->GetFieldID(mainActivityCls, "name", "Ljava/lang/String;");
//获取属性值
jint  age = env->GetIntField(mainActivityThis,ageFid);
jstring name = (jstring)env->GetObjectField(thiz,nameFid);//此处有编码转换问题未解决

//修改属性值,C++中修改变量值后,Java重新获取打印发现是修改过的
env->SetIntField(mainActivityThis, ageFid , 11);
env->SetObjectField(thiz, nameFid,Stringvalue);

//获取方法的methodId,--》这里就用到了签名类型
jmethodID addMid=env->GetMethodID(mainActivityCls, "add", "(II)I");
int result=env->CallIntMethod(mainActivityThis, addMid, 1, 1); //这里就能获取到2的值。

仔细看一下上面的代码,就大致能理解这个签名表格的具体作用:为了找到Java方法的参数和返回值的形式。

Java签名类型小结:文章来源地址https://www.toymoban.com/news/detail-843576.html

(1)基础类型签名那些转换都是很容易记住的,基础类型中,特别留意一下boolean类型 是 Z 就行
(2)对象Object类型的转换是:L+全包名(包名直接用 /间隔)+类名+分号
(3)数组类型签名转换:[数组类型,比如[I,表示Java的 int[]

(4)方法签名的转换:(参数类型)返回类型,中间多个参数类型依此填写就行,
比如:Jni中的代码:env->GetMethodID("add", "(IILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;")
如果不清楚上面的表格转换,看起来就头大,特别是那些有三四个以上参数的情况,但是学习过后就不难了,
查看表格对应关系可以知道,Java中的对应方法是:public String add(int a,int b,String c,String d)
其实就是先看括号后面的返回值,然后再一个个确定括号内的形参变量

共勉: 这短短的一生,我们最终都会过去,你不妨大胆一些,爱一个人、攀一座山、追一个梦

到了这里,关于Android JNI复杂用法,回调,C++中调用Java方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android JNI传递CallBack接口并接收回调

    在JNI中,可以通过传递一个Java接口对象的引用给C++代码,并在C++代码中调用该接口对象的方法,实现JAVA层监听C++数据变化,下面是一个简单的示例: 在Java代码中定义一个CallBack接口和JNI方法 使用JNI实现将Java接口对象传递给C++代码,并在C++代码中调用接口方法。 在C++代码中

    2024年02月12日
    浏览(13)
  • unity桥接调用Android方法及回调完整流程

    作为一个完整的游戏,在unity开发完成后,需要接入SDK。SDK就是手游渠道(如应用宝、小米应用商店、华为应用商店等)提供的集成了账号注册登录、充值、防沉迷、游戏公告、分享、社区入口、push消息、数据上报、礼包或折扣券等功能的一个集合。 而大多数情况下,作为

    2024年02月08日
    浏览(7)
  • Android studio配置OpenCV的JNI接口,实现C++代码编程

    目录 一、下载OpenCV-android-sdk 二、新建项目 三、导入OpenCV包  四、配置OpenCV的JNI接口,拷贝OpenCV所需的头文件和库文件 五、修改Cmake文件  六、配置app的build.gradle文件  七、测试 OpenCV官网地址:https://opencv.org/  点击Library--Releases,下载Android版本,解压得到 OpenCV-android-sdk文件

    2024年02月10日
    浏览(7)
  • Java生成jni.h头文件,java调用C方法 图文详解

    环境搭建 1. android studio2021.2.1 2. JDK版本1.8 File —— New —— New Project —— Empty Activity 创建后如下图所示 大概需求:①java传两个整数给C                   ②C接收到来自java的两个整数,并且相加                   ③C把两个数之和的结果返回给java (1)AS打开Terminal窗口

    2024年02月05日
    浏览(10)
  • Android Java代码与JNI交互 JNI访问Java类方法 (七)

    🔥 Android Studio 版本 🔥    🔥 创建包含JNI的类 JNIAccessMethod.java 🔥  🔥 Java方法对应Native层方法名称 🔥 🔥 配置动态库名称 🔥  🔥 生成可关联的库链接 🔥  为了让Java能够调用 access-method-lib 库中的函数,您需要使用 CMake 构建脚本中的 target_link_libraries() 命令来关联 

    2024年02月16日
    浏览(8)
  • Android Java代码与JNI交互 JNI方法Java类字段 (六)

    🔥 Android Studio 版本 🔥    🔥 Java 基础类型数据对应 Native层的字母 🔥  通过 jni 查找java某个类中相应字段对应的数据类型 , 需要使用到 jni 中的 GetFieldID() 函数 🔥 Java 引用类型数据对应 Native层字符串 🔥   🔥 创建 JNIAccessField 文件 🔥 🔥 配置动态库名称 🔥  🔥 生成

    2024年02月16日
    浏览(8)
  • Android Java代码与JNI交互 JNI访问Java构造方法(九)

    🔥 Android Studio 版本 🔥    🔥 创建包含JNI相关函数类 JNIConstructorClass.java 🔥  🔥 配置动态库名称 🔥   🔥 生成可关联的库链接 🔥  为了让Java能够调用 constructor-class-lib 库中的函数,您需要使用 CMake 构建脚本中的 target_link_libraries() 命令来关联constructor-class-lib 库  🔥

    2024年02月15日
    浏览(9)
  • Android Java代码与JNI交互 JNI子线程访问Java方法 (八)

    🔥 Android Studio 版本 🔥   🔥 创建包含JNI相关函数类 JNIInvokeMethod.java 🔥  🔥 配置动态库名称 🔥  🔥 生成可关联的库链接 🔥  为了让Java能够调用 invoke-method-lib 库中的函数,您需要使用 CMake 构建脚本中的 target_link_libraries() 命令来关联 invoke-method-lib 库  🔥 提供给N

    2024年02月16日
    浏览(10)
  • 关于 Delphi 11.3跨平台开发Android调用 JNI JAR java 的说明和注意事项

    关于 调用 JNI JAR 的说明和注意事项,调用第三方 JAR SDK 和 翻译 安卓 JAVA 代码 的说明 V2017.10.18 (* ************************************************ *) (* *) (* *) (* 设计:爱吃猪头肉 Flying Wang 2015-04-15  *) (* 上面的版权声明请不要移除。 *) (* *) (* ************************************************ *) 本人所

    2024年02月07日
    浏览(11)
  • C++回调函数 匿名函数,类中的方法做为回调函数

    C++中的回调函数和匿名函数都是函数指针或函数对象的使用形式。下面分别介绍它们的使用方法。 回调函数是一种函数指针,它允许将函数作为参数传递给另一个函数,并在需要时调用它。这种技术通常用于事件处理、异步处理和状态机等应用中。 下面是一个简单的示例,

    2024年02月01日
    浏览(12)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包