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

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

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脚本文件,代码如下:

csharp 复制代码
//代码片断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查看输出。],输出结果如下:

java 复制代码
行号  类型       输出信息     
1   System.out Hello World from Android static method 
2   Unity       结果1:2
3   Unity  
4   Unity       结果2:0
5   System.out Hello World from Android static method 
6   Unity       结果3:4
7   System.out Hello World from Android dynamic method 
8   Unity       结果4:5

上述代码首先演示了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#代码将放置于同一个代码片断中,并使用注释进行说明。]:

java 复制代码
//代码片断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文件代码如下:

csharp 复制代码
//代码片断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文件代码如下:

csharp 复制代码
//代码片断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文件代码如下:

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文件代码如下:

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查看输出。],输出结果如下:

java 复制代码
行号 类型       输出信息     
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通过链接连接到相应的文件或文件夹,从而可以保持文件同步,无需进行复制代码文件的操作。

相关推荐
阿巴斯甜11 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker11 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952712 小时前
Andorid Google 登录接入文档
android
黄林晴13 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android