Unity call each other and Android

https://www.jianshu.com/p/b5e3cfcdf081

 

Unity projects that require access to the Android operating system features, such as access to electricity, wifi status, we need to start the Unity Android's BroadcastReceiverlistening state, and after the status update notification to the Unity interface. This requires a mechanism called each other with the Android Unity, intuitively see that C # and Java method calls to each other.

There Unity and Android needs to call each other simultaneously in two projects require development environment, create two projects, then it comes to how to connect the two projects, there are two ways to connect:

  • Android project generator aar / jar file, copied to the Unity project, end-use mechanism to Build Unity generation apk.
  • Unity project will be exported to all content and code Android gradle a project, then open Android Studio project development, end-use Android Studio package apk.

Compare the advantages and disadvantages of both:

  Unity using the jar / aar library Export gradle Unity Project
Unity and dependence Android Unity rely only Android libraries, Segcieaned, need to synchronize files only library files Android relies Unity export scene data, too many files need to be synchronized
Development and debugging speed Android library file is relatively small, fast commissioning Unity project large, slow sync, debug cycle is long
Build mechanisms Android Build Unity built-in mechanism, similar to the eclipse compiler Android project Android Studio gradle
Build flexibility Poor, not the depth of customization, there are libraries need to rely entirely dependent explicitly copied to the Unity project Very free, you can use the latest Android Build mechanism
How to package apk Unity Build mechanisms for direct packaging Android Studio package

This project is the first method used, because the project is particularly large project in Unity, Unity project cost of exporting too much. But also encountered a library file dependency problems, but due to dependencies is not a lot, you can manually resolve. The following are the Solutions:

  • Run task Gradle dependencies, you can find help in the project's target directory "Gradle projects" window, this task will print out a dependency tree structure.
  • All dependencies separately downloaded to the local, into the Unity project.

As can be seen from these two steps, if less dependent on the level, the number is relatively small, is acceptable, but if there are a large number of deep dependence becomes particularly troublesome.

 
View dependency tree

Unity call Android

Unity official documentation need to call Java code via Plugin way, but in fact no need to introduce any Plugin can call Java code. Just need to call the library are packaged under normal circumstances, then he needs to put the jar or aar Unity project and to access its contents via C #.

aar file jar or can be placed in any directory under Unity, for the convenience of management, are placed in the Assets/Plugins/Androiddirectory.

C # call Java methods, access to Java fields

C # Java calls the underlying principle is to use JNI calls, Unity has provided a very convenient interface:

  • Create Object: C # using AndroidJavaObjectclass encapsulates Java objects, new a AndroidJavaObjecttarget object corresponding to the constructor call the corresponding Java. C # variable argument lists means, any number of parameters can be passed to the constructor of Java objects.
// 第一个参数是 Java 类的完整包名,剩下的其他参数会传递给构造方法。
AndroidJavaObject jo = new AndroidJavaObject("java.lang.String", "some_string"); 
  • Call the object methods: Use the AndroidJavaObjectCall Method in class, there are two versions of generic and non-generic.
// 泛型版本,目的是指定返回值的类型
int hash = jo.Call<int>("hashCode");
// 非泛型版本,处理返回值是void的情况。 jo.Call("aMethodReturnVoid"); // String中没有返回void的简单方法。。。 
  • Get the class, mainly used to obtain static fields or call the static method used to obtain UnityPlayer.
// 传入类的完整包名
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
  • To get a static field, only the generic version, because there will be no void type of field. . .
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity"); 

Setting field, get the object field, the code calls the static method is similar, slightly.

Type Mapping

When you call Java methods directly to C # variables / constants passed to the Java method, automatically handles type C # to Java type conversion. Through C # generics, Java can specify the type of the return value, which is to convert the Java type of type C #. C # and Java type is the type of automatic conversion rules are as follows:

Java type C # type
Basic types, such as int,boolean Value corresponding type int,bool
String string
Array type Array types (can not be a multi-dimensional array)
Inherited from other Objecttypes of AndroidJavaObject

Android calls Unity

Android from the end and can not be called directly Unity script, but the interface a callback or send a message by the way.

Way messaging

Message is a very simple invocation mechanism, built on a messaging interface:

// objectName: Unity 对象的名称
// methodName: Unity 对象绑定的脚本方法名
// message: 自定义消息
UnityPlayer.UnitySendMessage(String objectName, String methodName, String message);

Make a brief package:

import com.unity3d.player.UnityPlayer;
public class UnityPlayerCallback { public final String objectName; public final String methodName; public UnityPlayerCallback(String objectName, String methodName) { this.objectName = objectName; this.methodName = methodName; } public void invoke(String message) { UnityPlayer.UnitySendMessage(objectName, methodName, message); } } 

Send a message needs to know the name of the object and method names Unity, and this information is not known at the end of Android, it should not be hard-coded in Java code. Because Unity script code with respect to the upper Android client code, called the Android library functions provided by the library file is not supposed to know any specific information of its client code.

The correct approach is in some way to inject this information into the library, most simply, using C # Java-side code to call these two strings to save Java objects.

The following example provides a simple message format: message type = / data.

// Java 代码
public class Downloader { private UnityPlayerCallback mUnityCallback; public void setDownloadCallback(String objectName, String methodName) { mUnityCallback = new UnityPlayerCallback(objectName, methodName); } ... void onDownloaded(File file, String url) { if (mUnityCallback != null) { mUnityCallback.invoke("downloaded/" + file.getName()); } } } 
// C# 脚本:
void OnStart() { AndroidJavaObject downloader = new AndroidJavaObject("my.package.Downloader"); downloader.Call("setDownloadCallback", gameObject.name, "OnJavaMessage"); } void OnJavaMessage(string message) { // 这里解析 message,例:"download/filename.txt" if (message.StartsWith("downloaded/") { // 处理下载完成的逻辑... } } 

Since this method is rough, and the message about the processing method is not open, if there are a plurality of callback method, data transfer complex, you need to define complex messaging formats.

Interface invocation

This method uses up more natural, defined in terms of style, good Java Java callback interface, then through inheritance in C # script AndroidJavaProxyto implement this Java class interface. By setting the callback method provides the Java-side object interface implements C # to Java code is provided, to complete the process of setting Java C # callback.

This method is exemplified below:

Java code defines a class download tool, using a download progress and status interface to inform the caller:

// 回调接口
public interface DownloadListener { void onProgress(String name, int progress); void onDownloaded(String name); void onError(String name, String message); } // 下载工具类 public class DownloadHelper { public void download(String url, String name) {...} public void setDownloadListener(DownloadListener listener) {...} } 

C # code of the same name also defines a DownloadHelperclass for Java objects encapsulate the call:

public class DownloadHelper {
    // 定义 C# 端的接口,对外隐藏 Java 相关代码
    public interface IDownloadListener {
        void OnProgress(string name, int progress);
        void OnDownloaded(string name); void OnError(string name, string message); } // 定义个 Adapter 来适配 AndroidJavaProxy 对象和 IDownloadListener private class ListenerAdapter : AndroidJavaProxy { private readonly IDownloadListener listener; public ListenerAdapter(IDownloadListener listener) : base("my.package.DownloadListener") { this.listener = listener; } // 继承自 AndroidJavaProxy 的对象可以直接按照 Java 中的方法签名 // 写出对应的 C# 方法,参数类型遵循上文提到的数据类型转换规则。 // 当 Java 调用接口方法时,对应的 C# 方法会自动调用,非常方便。 void onProgress(string name, int progress) { listener.OnProgress(name, progress); } void onDownloaded(string name) { listener.OnDownloaded(name); } void onError(string name, string message) { listener.OnError(name, message); } } private readonly AndroidJavaObject javaObject; private ListenerAdapter listenerAdapter; public DownloadHelper() { javaObject = new AndroidJavaObject("my.package.DownloadHelper", DefaultDirectory); } public void SetDownloadListener(IDownloadListener listener) { if (listener != null) { listenerAdapter = new ListenerAdapter(listener); javaObject.Call("setDownloadListener", listenerAdapter); } else { listenerAdapter = null; javaObject.Call("setDownloadListener", null); } } public void Download(string url, string name) { javaObject.Call("download", url, name); } // 初始化下载目录 private static string DefaultDirectory; static DownloadHelper() { AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity"); string path = jo.Call<AndroidJavaObject>("getExternalFilesDir", "videos").Call<string>("getCanonicalPath"); DefaultDirectory = path; } } 

When in use, direct use of C # DownloadHelperclasses with DownloadHelper.IDownloadListenercan.

Postscript:
The second way to achieve a colleague found a problem after writing the script of Unity: Due to the callback download module is executed in the Android UI thread, which is not the main thread of Unity, Unity callback to the environment can not be performed each modes of operation of the object. Unity therefore need to notify the main thread and executed callback.

C # code is modified as follows, using the Loomclass, somewhat similar to Android Handler, you can refer to the article Unity Loom plug-ins

private class ListenerAdapter : AndroidJavaProxy {
    ...
    void onProgress(string name, int progress) { Loom.QueueOnMainThread((param) => { listener.OnProgress(name, progress); }, null); } void onDownloaded(string name) { Loom.QueueOnMainThread((param) => { listener.OnDownloaded(name); }, null); } void onError(string name, string message) { Loom.QueueOnMainThread((param) => { listener.OnError(name, message); }, null); } } 

How to get direct broadcast Andrews

Although Andrews layer may be used BroadcastReceiverto receive a broadcast, and transmitted to the layer C # custom method. But if you can receive even more convenient directly in C # side, then later he wrote a versatile broadcast-receiving layer.

The main purpose of this first look at how to use the broadcast-receiving layer, the design of this layer is twofold: First, to register directly in C # code Andrews broadcast, the other is the code used to be simple enough.

First the codes used:

public class BTHolder : MonoBehaviour, UnityBroadcastHelper.IBroadcastListener {
    UnityBroadcastHelper helper;
    void Start() { if (helper == null) { helper = UnityBroadcastHelper.Register( new string[] { "some_action_string" }, this); } } void OnReceive(string action, Dictionary<string, string> dictionary) { // handle the broadcast } } 

We can see the use of broadcast requires four steps:

  1. Implement UnityBroadcastHelper.IBroadcastListenerthe interface.
  2. A definition of UnityBroadcastHelperthe object and initialize.
  3. In the method of void OnReceive(string action, Dictionary<string, string> dictionary)the custom code broadcast processing.
  4. Helper.Stop call at the right time () to stop listening radio.

It can be seen with custom Java code BroadcastReceiveris almost the same steps, following analysis of principle.

  1. First using a Java object UnityBroadcastHelperto hold BroadcastReceiver, and then registered in the Context Java code.
  2. Reuse interface mode referred to above will be UnityBroadcastHelper.BroadcastListenermapped in C # UnityBroadcastHelper.IBroadcastListener. Such calls C # interfaces in the Java terminal receives the broadcast, you can notify C # broadcast has been received.
  3. Finally, the data acquisition interface using data broadcasting, which is stored in the Bundle Extra, mapped to the C # Dictionary, is passed to the OnReceivemethod to facilitate use C #. Here for simplicity to all types of data are mapped to string type, this mapping is cumbersome, there is a need to write some of the details.

Java code:

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle; import com.unity3d.player.UnityPlayer; import java.util.LinkedList; import java.util.Queue; public class UnityBroadcastHelper { private static final String TAG = "UnityBroadcastHelper"; public interface BroadcastListener { void onReceive(String action); } private final BroadcastListener listener; private Queue<String[]> keysQueue = new LinkedList<>(); private Queue<String[]> valuesQueue = new LinkedList<>(); public UnityBroadcastHelper(String[] actions, BroadcastListener listener) { MyLog.d(TAG, "UnityBroadcastHelper: actions: " + actions); MyLog.d(TAG, "UnityBroadcastHelper: listener: " + listener); this.listener = listener; IntentFilter intentFilter = new IntentFilter(); for (String action : actions) { intentFilter.addAction(action); } Context context = UnityPlayer.currentActivity; if (context == null) { return; } context.registerReceiver(broadcastReceiver, intentFilter); } public boolean hasKeyValue() { return !keysQueue.isEmpty(); } public String[] getKeys() { return keysQueue.peek(); } public String[] getValues() { return valuesQueue.peek(); } public void pop() { keysQueue.poll(); valuesQueue.poll(); } public void stop() { Context context = UnityPlayer.currentActivity; if (context == null) { return; } context.unregisterReceiver(broadcastReceiver); } private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); MyLog.d(TAG, "UnityBroadcastHelper: action: " + action); Bundle bundle = intent.getExtras(); if (bundle == null) { bundle = new Bundle(); } int n = bundle.size(); String[] keys = new String[n]; String[] values = new String[n]; int i = 0; for (String key : bundle.keySet()) { keys[i] = key; Object value = bundle.get(key); values[i] = value != null ? value.toString() : null; MyLog.d(TAG, "UnityBroadcastHelper: key[" + i + "]: " + key); MyLog.d(TAG, "UnityBroadcastHelper: value[" + i + "]: " + value); i++; } keysQueue.offer(keys); valuesQueue.offer(values); listener.onReceive(action); } }; } 

C # code:

using System.Collections.Generic;
using UnityEngine;
public class UnityBroadcastHelper {
    public interface IBroadcastListener {
        void OnReceive(string action, Dictionary<string, string> dictionary);
    }
    private class ListenerAdapter : AndroidJavaProxy {
        readonly IBroadcastListener listener;
        readonly UnityBroadcastHelper helper; public ListenerAdapter(IBroadcastListener listener, UnityBroadcastHelper helper) : base("UnityBroadcastHelper$BroadcastListener") { this.listener = listener; this.helper = helper; } void onReceive(string action) { AndroidJavaObject javaObject = helper.javaObject; if (!javaObject.Call<bool>("hasKeyValue")) { return; } string[] keys = javaObject.Call<string[]>("getKeys"); string[] values = javaObject.Call<string[]>("getValues"); javaObject.Call("pop"); Dictionary<string, string> dictionary = new Dictionary<string, string>(); Debug.Log("onReceive: dictionary: " + dictionary); int n = keys.Length; for (int i = 0; i < n; i++) { dictionary[keys[i]] = values[i]; } listener.OnReceive(action, dictionary); } } private readonly AndroidJavaObject javaObject; private UnityBroadcastHelper(string[] actions, IBroadcastListener listener) { ListenerAdapter adapter = new ListenerAdapter(listener, this); javaObject = new AndroidJavaObject("UnityBroadcastHelper", actions, adapter); } public static UnityBroadcastHelper Register(string[] actions, IBroadcastListener listener) { return new UnityBroadcastHelper(actions, listener); } public void Stop() { javaObject.Call("stop"); } }

Guess you like

Origin www.cnblogs.com/alps/p/11206465.html