Unity与Android交互通信系列(2)

  在上一篇文章中,我们介绍了Unity和Android交互通信的原理及在Unity中直接调用Java代码的方式,但没有给出代码示例,下面通过实际例子演示上篇文章中AndroidJavaClass、AndroidJavaObject两个类的基本用法,由于交互通信涉及到两端,我们先使用Android Studio创建Unity2Java类,Java代码如下:

//代码片断1
package com.example.davidwang;

public class Unity2Java {
    public static void StaticPrint(String str){
        System.out.println(str);
    }
    public static int StaticAdd(int a,int b)
    {
        return a+b;
    }

    public void DynamicPrint(String str){
        System.out.println(str);
    }
    public int DynamicAdd(int a,int b)
    {
        return a+b;
    }
}

  找到该类所在的.java文件,并将该文件复制到Unity工程Assets/Plugins/Android目录或其子目录下,然后在Unity工程窗口(Project窗口)中选中该java文件,在属性窗口(Inspector窗口)中查看其导入设置,确保Android平台被选择,如图1所示。
在这里插入图片描述

图1 在Java文件导入设置中勾选Android多选框



  为在Unity端调用Java代码,通过Unity创建Android2Unity.cs脚本文件,代码如下:

//代码片断2
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Android2Unity : MonoBehaviour
{
    void Start()
    {
        using (AndroidJavaClass Unity2JavaClass = new AndroidJavaClass("com.example.davidwang.Unity2Java"))
        {
            Unity2JavaClass.CallStatic("StaticPrint", "Hello World from Android static method ");
            int result1 = Unity2JavaClass.CallStatic<int>("StaticAdd", 1, 1);
            Debug.Log("结果1:" + result1);

            Unity2JavaClass.Call("DynamicPrint", "Hello World from Android dynamic method ");
            int result2 = Unity2JavaClass.Call<int>("DynamicAdd", 1, 2);
            Debug.Log("结果2:" + result2);
        }

        using (AndroidJavaObject Unity2JavaObject = new AndroidJavaObject("com.example.davidwang.Unity2Java"))
        {
            Unity2JavaObject.CallStatic("StaticPrint", "Hello World from Android static method ");
            int result3 = Unity2JavaObject.CallStatic<int>("StaticAdd", 1, 3);
            Debug.Log("结果3:" + result3);

            Unity2JavaObject.Call("DynamicPrint", "Hello World from Android dynamic method ");
            int result4 = Unity2JavaObject.Call<int>("DynamicAdd", 1, 4);
            Debug.Log("结果4:" + result4);
        }
    }
}

  在Unity中,将Android2Unity脚本挂载到场景中的任意对象上,连接手机,打包运行[ Java代码编译后运行于dalvik / art虚拟机,其控制台输出不能输出到Unity Debug窗口,所以只能通过真机运行,Logcat查看输出。],输出结果如下:

行号  类型       输出信息     
1   System.out Hello World from Android static method 
2   Unity       结果12
3   Unity  
4   Unity       结果20
5   System.out Hello World from Android static method 
6   Unity       结果34
7   System.out Hello World from Android dynamic method 
8   Unity       结果45

  上述代码首先演示了Java端无返回值、有返回值方法的调用,通过泛型方法定义返回值类型获取Java端方法执行结果,由于数据类型的不同,Java端与C#端交互支持的类型有string、int、float、bool、AndroidJavaObject共5类(也可以返回这些数据类型的数组);其次演示了静态方法和实例方法的调用,类静态方法使用带static后辍的方法访问,而对象实例方法则使用不带Static后辍的方法调用;再次演示了AndroidJavaClass和AndroidJavaObject类使用上的区别,通过输出结果,可以看到,AndroidJavaClass调用对象实例方法既不报错,也不执行;AndroidJavaObject类调用类静态方法可以正常执行[ Java语言支持实例对象调用类静态方法或者获取类静态属性,这与C#语言不同。],虽然我们使用时都使用了new关键字,但AndroidJavaClass类不会生成实例对象,而AndroidJavaObject类会实例化对象。

  类与实例的属性获取/设置与上述方法使用基本一致,通过这种方式,就可以直接在C#代码中调用Java端的原生类,代码如下[ 为了简化排版,后续Java代码与C#代码将放置于同一个代码片断中,并使用注释进行说明。]:

//代码片断3
//Java端代码
package com.example.davidwang;
import android.app.Activity;
import android.widget.Toast;

public class Unity2Java {
	public boolean ShowToast(Activity activity, String str){
        Toast.makeText(activity,str,Toast.LENGTH_SHORT).show();
        return true;
    }
}

//C#端代码
//获取设备UUID
private string GetAndroidID()
{
    string androidID = "NONE";
#if UNITY_ANDROID && !UNITY_EDITOR
    using (AndroidJavaObject contentResolver = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic<AndroidJavaObject>("currentActivity").Call<AndroidJavaObject>("getContentResolver"))
    {
        using (AndroidJavaClass secure = new AndroidJavaClass("android.provider.Settings$Secure"))
        {
            androidID = secure.CallStatic<string>("getString", contentResolver, "android_id");
        }
    }
#endif
    return androidID;
}

//调用Andriod端Toast
private void ShowToast()
{
    if (Application.platform == RuntimePlatform.Android)
        using (AndroidJavaObject Unity2JavaObject = new AndroidJavaObject("com.example.davidwang.Unity2Java"))
        {
            using (AndroidJavaObject activity = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic<AndroidJavaObject>("currentActivity"))
            {
                bool isSuccess = Unity2JavaObject.Call<bool>("ShowToast", activity, "From Unity");
                Debug.Log("ShowToast Status :" + isSuccess);
            }
        }
}

  在上述代码片断3中,我们演示了两种直接调用Android端原生类的方式,第一种方法是通过直接获取Android端的原生类,调用其静态方法;第二种是通过调用自定义的Java类间接调用Android原生类方法。同时,由于C#代码运行平台不确定,为确保代码兼容多平台,我们也使用了两种判断代码执行平台的方法,第一种使用预编译指令区分平台,另一种通过Application类直接判断当前运行平台,这也是在多平台开发中经常使用的技巧。

  除此之外,代码还演示了获取当前活动Activity的方法,即通过com.unity3d.player.UnityPlayer类获取当前Activity,Android很多类都需要传递活动的Activity或者Context上下文对象,通过这种方式获取当前Activity是一种常用方法。

  使用AndroidJavaClass和AndroidJavaObject类直接调用Java代码或Android原生类非常方便,但只能是单向由C#调用Java代码,Java没办法反向调用C#代码,实现反向调用则必须使用AndroidJavaProxy类,正如其名,这是个代理类,负责在Java和C#代码之间桥接,后文我们还会详细介绍该类。

  如前文所述,Java端与C#端交互支持的类型有string、int、float、bool、AndroidJavaObject共5类,AndroidJavaObject可以表示所有对象类型,因此,除string、int、float、bool 4种基本类型,其余对象都可由AndroidJavaObject表达。C#端调用Java端方法千差万别,但Java端调用C#端就以上5类(包括数组则共10类),因此我们可以编写一个通用的框架,因为结构稍微有点复杂,涉及到C#端AndroidCallbackManager.cs、AndoridCallbackInterface.cs两个脚本文件,Java端CallUnityInterface.java、Java2Unity.java两个代码文件。

  其中,AndroidCallbackManager.cs文件代码如下:

//代码片断4  
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AndroidCallbackManager : MonoBehaviour
{
    void Start()
    {
        using (AndroidJavaObject activity = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic<AndroidJavaObject>("currentActivity"))
        {
            using (AndroidJavaObject appController = new AndroidJavaObject("com.example.davidwang.Java2Unity"))
            {
                AndoridCallbackInterface callback = new AndoridCallbackInterface("com.example.davidwang.CallUnityInterface");
                callback.stringCallBack = StringProcess;
                callback.intCallBack = IntProcess;
                callback.floatArrayCallBack = FloatArrayProcess;
                appController.Call("Init", activity, callback);
                Debug.Log("In Start");
            }
        }
    }

    public void StringProcess(string str)
    {
        Debug.Log("string callback :"+str);
    }
    public void IntProcess(int value)
    {
        Debug.Log("int callback :" + value);
    }
    public void FloatArrayProcess(float[] arr)
    {
        foreach (var value in arr)
            Debug.Log("float in arr:" + value);
    }
}

  在代码片断4中,AndroidCallbackManager类是Unity端的使用类,是调用入口,其首先获取到当前Activity,实例化Java端的Java2Unity类,并且同时实例化了一个C#端的AndoridCallbackInterface类。然后设置AndoridCallbackInterface类的回调方法之后调用了Java端的初始化方法。

  AndoridCallbackInterface.cs文件代码如下:

//代码片断5 
using System;
using UnityEngine;
public class AndoridCallbackInterface : AndroidJavaProxy
{
    public Action<AndroidJavaObject> javaObjectCallBack;
    public Action<bool> boolCallBack;
    public Action<string> stringCallBack; 
    public Action<int> intCallBack;
    public Action<float> floatCallBack;
    public Action<float[]> floatArrayCallBack;   
    //构造方法
    public AndoridCallbackInterface(string interfaceName) : base(interfaceName)
    {
    }
    public void JavaObjectCallBack(AndroidJavaObject _data)
    {
        if (javaObjectCallBack != null)
            javaObjectCallBack(_data);
    }
    public void BoolCallBack(bool _data)
    {
        if (boolCallBack != null)
            boolCallBack(_data);
    }
    public void StringCallBack(string _data)
    {
        if (stringCallBack != null)
            stringCallBack(_data);
    }
    public void IntCallBack(int _data)
    {
        if (intCallBack != null)
            intCallBack(_data);
    }
    public void FloatCallBack(float _data)
    {
        if (floatCallBack != null)
            floatCallBack(_data);
    }
    public void FloatArrayCallBack(float[] _data)
    {
        if (floatArrayCallBack != null)
            floatArrayCallBack(_data);
    }
}

  在代码片断5中,AndoridCallbackInterface类继承自AndroidJavaProxy类,定义了回调方法,如前文所述,Java端与C#端交互支持的类型共有5种,这里为了通用将这5种回调方法都进行了演示(还包括一个Float数组的演示方法)。该类构造方法很重要,这里需要通过AndroidJavaProxy类将C#端实例与Java端的CallUnityInterface接口对应起来[ 可以理解为建立了一个从Java端到C#端AndoridCallbackInterface类实例的指针,这样Java端就可以通过这个指针访问到C#端的实例。],这样Java端就可以调用C#端实例的方法。

  CallUnityInterface.java文件代码如下:

//代码片断6  
package com.example.davidwang;

public interface CallUnityInterface {
 public void JavaObjectCallBack(Object _data);
 public void BoolCallBack(boolean _data);
 public void StringCallBack(String _data);
 public void IntCallBack(int _data);
 public void FloatCallBack(float _data);
 public void FloatArrayCallBack(float[] _data);
}

  在代码片断6中,CallUnityInterface接口方法签名与AndoridCallbackInterface类中对应方法签名需要完全一致,这样才能确保正确相互调用。

  CallUnityInterface.java文件代码如下:

//代码片断7 
package com.example.davidwang;

import android.content.Context;
public class Java2Unity {
    private Context context = null;                //上下文对象
    private CallUnityInterface callback = null;  //缓存回调
    public  void  Init(Context context , CallUnityInterface callback)
    {
        this.context = context;
        this.callback = callback;
        try {
            java.lang.Thread.sleep(5000);
            Run();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
    private  void Run()
    {
        int i = 100;
        String str = "From Java";
        float[] floatArr = {1.01f,1.02f,1.03f};
        this.callback.IntCallBack(i);
        this.callback.StringCallBack(str);
        this.callback.FloatArrayCallBack(floatArr);
    }
}

  在代码片断7中,Java2Unity类首先通过Init()方法获取到当前上下文对象[ 在本示意中,上下文对象并没有使用,但很多时间都需要上下文对象或者当前Activity。]和回调实例,通过回调接口即可以调用C#端的方法,这里通过延时5秒触发回调方法。

  在这个通用框架中,这4个类的相互关系如图2所示。
在这里插入图片描述

图2 Java端与C#端相互调用关系示意图
  在Unity中,将AndroidCallbackManager脚本挂载到场景中的任意对象上,连接手机,打包运行[ Java代码编译后运行于dalvik / art虚拟机,其控制台输出不能输出到Unity Debug窗口,所以只能通过真机运行,Logcat查看输出。],输出结果如下:
行号 类型       输出信息     
1   Unity     int callback :100
2   Unity     string callback :From Java
3   Unity     float in arr:1.01
4   Unity     float in arr:1.02
5   Unity     float in arr:1.03
6   Unity     In Start

  通过结果看到,Java端正确的向C#端回调了相应方法并且参数传递无误。而且也可以看到,这种调用是同步的,会阻塞当前线程。


提示

         在开发中,通常我们都是在Android Studio中进行Java代码编写,然后将代码复制到Assets/Plugins/Android目录中供Unity使用,如果改动了Android Studio中的Java代码,则需要再次复盖Unity中的对应文件,而且同时存在两份一样的文件,文件内容同步维护会是非常大的问题,这时我们可以通过创建文件/文件夹链接确保只有一份文件。文件夹链接类似于Windows操作系统中的快捷方式,但可供第三方软件使用。

         Windows下创建方式:mklink /D    LINK_PATH    SOURCE_PATH

         Linux下创建方式:ln -s   SOURCE_PATH    LINK_PATH

         其中参数LINK_PATH为链接的路径,这个路径无真实文件夹或者文件;SOURCE_PATH为源文件/文件夹路径,即真实的文件路径。通过创建文件/文件夹链接,就可以只在Assets/Plugins/Android目录下保留一份代码文件,Android Studio通过链接连接到相应的文件或文件夹,从而可以保持文件同步,无需进行复制代码文件的操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/255341.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

查看知乎数学公式Tex源码的方法

首先使用F12打开开发者工具&#xff0c;再使用元素选择器选中要查看的公式。 在源码对应位置附近可以看到一个类型为 math/tex 的 script&#xff0c;如果没找到可以展开目录查找。

网络时间服务器

本章主要介绍网络时间服务器。 使用chrony配置时间服务器 配置chrony客户端向服务器同步时间 1 时间同步的必要性 一些服务对时间要求非常严格&#xff0c;例如&#xff0c;图所示的由三台服务器搭建的ceph集群。 这三台服务器的时间必须保持一致&#xff0c;如果不一致&#…

若依打包将vue放到.jar里面部署

1.vue静态文件&#xff0c;以及单页面 ruoyi-admin\src\main\resources\static \ruoyi-admin\src\main\resources\templates 2.后台开放白名单 "/cms", "/cms#/login" 3. mvc访问vue页面入口&#xff0c;接口 package com.ruoyi.web.controller.syst…

08_CSS定位与综合案例开发

day08_CSS定位与&综合案例开发 Objective&#xff08;本课目标&#xff09; 理解什么是定位能说出为什么要用定位 1. 为什么使用定位 标准流在最底层 (海底) ------- 浮动的盒子在中间层 (海面) ------- 定位的盒子 在 最上层 &#xff08;天空&#xff09; 小黄色块在…

软件测试面试八股文,最常见的7个高频面试题(附答案,建议收藏)

问题1&#xff1a;请自我介绍下&#xff1f; 核心要素&#xff1a;个人技能优势工作背景经验亮点 参考回答&#xff1a; 第一种&#xff1a;基本信息离职理由 面试官您好&#xff0c;我叫张三&#xff0c;来自番茄市&#xff0c;在软件测试⾏业有 3 年的⼯作经验。做过 Web…

算法——动态规划(DP,Dynamic Programming)

一、基础概念 DP的思想&#xff1a; 把问题分成子问题&#xff0c;前面子问题的解决结果被后面的子问题使用DP与分治法的区别&#xff1a; 分治法把问题分成独立的子问题&#xff0c;各个子问题能独立解决 自顶向下DP前面子问题的解决结果被后面的子问题使用&#xff0c;子问题…

3090K MOSFET N通道沟槽功率 PWM应用

3090K 采用沟槽技术&#xff0c;提供活x氧(导通)&#xff0c;低栅J电荷和栅J电压低至4.5V的工作。3090K 设备适用于各种应用。 3090K 特性&#xff1a; ● VDS 30V,ID 86A RDS(ON) < 5 mΩ VGS 10V RDS(ON) < 9.5mΩ VGS 4.5V ● 高功率和电流处理能力 ● 获得无…

【性能测试】基础知识篇-压力模型

常见压力模式 并发模式&#xff08;即虚拟用户模式&#xff09;和RPS模式&#xff08;即Requests Per Second&#xff0c;每秒请求数&#xff0c;吞吐量模式&#xff09;。 本文介绍这两种压力模式的区别&#xff0c;以便根据自身业务场景选择更合适的压力模式。 并发模式 …

C++面向对象(OOP)编程-模板

本文主要讲解C的模板&#xff0c;其中包括模板的分类&#xff0c;函数模板和类模板&#xff0c;以及类模板与友元函数关系引起的几种关系。强调提供代码来搞懂C模板这一泛型编程手段。 目录 1 C模板 2 模板的本质 3 模板分类 4 函数模板 4.1 函数模板定义格式 4.2 函数模…

下午好~ 我的论文【遥感】(第一期)

写在前面&#xff1a;下午浑浑噩噩&#xff0c;泡杯茶&#xff0c;读篇论文吧 首先说明&#xff0c;时间有限没有那么精力一一回复了&#xff0c;对不起各位了TAT 文章目录 遥感Bi-Dilation-formerCNN-GNN-FusionMulti-hierarchical cross transformerCoupled CNNs 遥感 Bi-D…

Linux---Ubuntu软件安装

1. 软件安装的介绍 Ubuntu软件安装有两种方式: 离线安装(deb文件格式安装&#xff09;在线安装(apt-get方式安装) 2. deb文件格式安装 是 Ubuntu 的安装包格式&#xff0c;可以使用 dpkg 命令进行软件的安装和卸载。 命令说明dpkg安装和卸载deb安装包 dpkg命令选项: 选项…

TestSSLServer4.exe工具使用方法简单介绍(查SSL的加密版本SSL3或是TLS1.2)

一、工具使用方法介绍 工具使用方法参照&#xff1a;http://www.bolet.org/TestSSLServer/ 全篇英文看不懂&#xff0c;翻译了下&#xff0c;能用到的简单介绍如下&#xff1a; 将下载的TestSSLServer4.exe工具放到桌面上&#xff0c;CMD命令行进入到桌面目录&#xff0c;执…

Kafka--从Zookeeper数据理解Kafka集群工作机制

从Zookeeper数据理解Kafka集群工作机制 这一部分主要是理解Kafka的服务端重要原理。但是Kafka为了保证高吞吐&#xff0c;高性能&#xff0c;高可扩展的三高架构&#xff0c;很多具体设计都是相当复杂的。如果直接跳进去学习研究&#xff0c;很快就会晕头转向。所以&#xff0c…

Java小案例-RocketMQ的11种消息类型,你知道几种?(死信消息)

前言 在RocketMQ中&#xff0c;死信消息&#xff08;Dead-Letter Message&#xff09;是指那些在正常情况下无法被消费者消费的消息。这些消息会被存储在死信队列&#xff08;Dead-Letter Queue&#xff0c;简称DLQ&#xff09;中。 死信消息的特性包括&#xff1a; 不会再被…

如何免费搭建私人电影网站(一)

前言&#xff1a;在线看电影经常会出现烦人的广告&#xff0c;为了不浪费时间看广告&#xff0c;有必要做自己的专属网站。 准备工作&#xff1a; 1、申请免费域名&#xff08;也可以花钱注册域名相对稳定&#xff09;链接: 申请免费域名方法 2、申请免费主机&#xff08;也可以…

详解 Jeecg-boot 框架如何配置 elasticsearch

目录 一、下载安装 Elasticsearch 1、 地址&#xff1a;https://www.elastic.co/cn/downloads/elasticsearch 2、下载完成后&#xff0c;解压缩&#xff0c;进入config目录更改配置文件 3、 修改配置完成后&#xff0c;前往bin目录启动el 4、访问&#xff1a;localhost:92…

FreeRTOS202212 ‐ 定时器结构体定义

FreeRTOS202212-定时器结构体 /*-----------------------------------------------------------*/ /* The definition of the timers themselves. */ /*定时器本身的定义*/ typedef struct tmrTimerControl /* The old naming convention is used to prevent breaking kernel a…

分布式事务--TC服务的高可用和异地容灾

1.模拟异地容灾的TC集群 计划启动两台seata的tc服务节点&#xff1a; 节点名称ip地址端口号集群名称seata127.0.0.18091SHseata2127.0.0.18092HZ 之前我们已经启动了一台seata服务&#xff0c;端口是8091&#xff0c;集群名为SH。 现在&#xff0c;将seata目录复制一份&…

音视频学习(二十一)——rtmp收流(tcp方式)

前言 本文主要介绍rtmp协议收流流程&#xff0c;在linux上搭建rtmp服务器&#xff0c;通过自研的rtmp收流库发起取流请求&#xff0c;使用ffmpegqt实现视频流的解码与播放。 关于rtmp协议基础介绍可查看&#xff1a;https://blog.csdn.net/www_dong/article/details/13102607…

《点云处理》平面拟合

前言 在众多点云处理算法中&#xff0c;其中关于平面拟合的算法十分广泛。本篇内容主要是希望总结归纳各类点云平面拟合算法&#xff0c;并且将代码进行梳理保存。 环境&#xff1a; VS2019 PCL1.11.1 1.RANSAC 使用ransac对平面进行拟合是非常常见的用法&#xff0c;PCL…