首页 > 移动开发 > Android > UNITY与ANDORID交互的那些坑
2022
08-10

UNITY与ANDORID交互的那些坑

近期接触到需要Unity与andorid交互的项目,我负责andorid开发,记录一下开发过程遇到的坑,代码偏向于android端处理,unity端其他操作自行百度

 一.unity工程师导出andorid项目

有两种方式,推荐用Gradle方式导出(unity同事工作),导出的工程结构如下
UNITY与ANDORID交互的那些坑 - 第1张  | 逗分享开发经验

二.合并进主项目

UNITY与ANDORID交互的那些坑 - 第2张  | 逗分享开发经验

    上图红框中文件都可在导出的unity工程文件中找到,添加进合并的原生项目里,并在AndroidManifest.xml以中添加

    <uses-feature android:glEsVersion="0x00020000" />
    <uses-feature android:name="android.hardware.vulkan" android:required="false" />
    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
    <uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false" />
    <uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="false" />
<application ....>
<activity android:label="@string/app_name"
            android:hardwareAccelerated="true"
            android:launchMode="singleTask"
            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection"
            android:name=".ui.activity.UnityPlayerActivity"
            >
            <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
        </activity>
</application>

build.gradle(app)中添加

    implementation files('libs/unity-classes.jar')
    implementation(name: 'UniWebView', ext: 'aar')

启动Unity项目

//原生中调用
startActivity(Intent(this, UnityPlayerActivity.class));

三.数据交互 

/**
  * andorid 端原生调用unity
  * */
 UnityPlayer.UnitySendMessage("Unity项目C中的类名", "类的方法名", "params");


/**
 * unity调用的原生方法
 */
C#中的代码
1.新建两个按钮
2.点击调用
按钮一:
AndroidJavaObject jc = 
new AndroidJavaObject("com.android.smartbath.connection.UnityMessage");
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("Instance");
jo.Call ("showToast","我是Unity传来的消息");

按钮二:
AndroidJavaObject jc = 
new AndroidJavaClass ("com.android.smartbath.ui.activity.UnityPlayerActivity");
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
jo.Call ("showToast","我是Unity传来的消息");

以上是网上各处都会提到的方法,具体参数含义看看其他博客说明,此处只说明需要注意的点

    1.'UnityMessage'  'UnityPlayerActivity' 需要已经实例化

    2.'Instance'  'currentActivity' 需要在类中声明

    3.'showToast' 方法无法直接实现 ,需要 handle / runOnUiThread 实现

 

UnityPlayerActivity.java中添加以下代码

public static UnityPlayerActivity currentActivity;

    @Override
    protected void onCreate (Bundle savedInstanceState){
        ....

        currentActivity = this;

        UnityMessage.getInstance();

    }

public void showToast(String msg){
        ThreadUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                ToastUtils.showLong(msg);
                //andorid 端调用unity
                UnityPlayer.UnitySendMessage("Main", "OnLogin", "Test Login"); //unity方法
                UnityPlayer.UnitySendMessage("Main", "TestSend", "TestSend"); //unity方法
            }
        });
    }

UnityMessage.java

package com.android.smartbath.connection.unity;

import com.blankj.utilcode.util.ThreadUtils;
import com.blankj.utilcode.util.ToastUtils;

/**
 * <pre>
 * Created by DengDongQi on 2020/4/23
 * </pre>
 */
public class UnityMessage {
    private static UnityMessage Instance = null;

    private UnityMessage() {
    }

    public static UnityMessage getInstance() {
        if(Instance == null){
            synchronized (UnityMessage.class){
                if(Instance == null){
                    Instance = new UnityMessage();
                }
            }
        }
        return Instance;
    }

    public void showToast(String msg){
        ThreadUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                ToastUtils.showLong(msg);
            }
        });
    }
}

测试交互

UNITY与ANDORID交互的那些坑 - 第3张  | 逗分享开发经验

点击按钮

UNITY与ANDORID交互的那些坑 - 第4张  | 逗分享开发经验

至此双向交互完成

四.后记

测试完交互后发现,启动unity页面后,按系统返回键无法退回原生界面,只能在UnityPlayerActivity 中某处调用finish()方法结束页面

调用finish之后会出现整个进程也会随着页面结束而被杀掉,原因是UnityPlayerActivityonDestroy()中调用了mUnityPlayer.quit(); quit中调用了this.kill();

为了退出unity界面而不结束进程,需要在AndroidManifestUnityPlayerActivity的声明中添加android:process=":unity3d"

声明为子进程后双向交互有出现问题,之后需要使用跨进程交互方式才能在原生主进程中接收交互信息

五.跨进程数据交互

选用比较简单的Messenger方式实现:修改后具体代码

UnityMessage.java

/**
 * <pre>
 * Created by DengDongQi on 2020/4/23
 * Unity 直接发送数据至 UnityMessage 再由UnityMessage 传递进 HandleUnityMessengerService ,Service处理并返回结果
 *
 * Unity项目为客户端 , 原生APP为服务端
 *
 * 原生APP内 Unity进程为客户端 ,主进程为服务端
 *
 * </pre>
 */
public class UnityMessage {
    // 客户端请求码
    public static final int CLIENT_REQUEST_CODE = 0X1001;
    // 客户端请求字段
    public static final String CLIENT_REQUEST_MSG = "client_request";
    // 单例
    private static UnityMessage Instance = null;
    // 上下文
    private Context mContext;
    // 客户端handle
    private ClientHandler mhandle = new ClientHandler();
    // 客户端送信者
    private Messenger mMessenger = new Messenger(mhandle);
    // 服务端送信者
    private Messenger mServiceMessenger = null;
    // 服务连接对象
    private UnityMsgServiceConnection mServiceConnected;
    // 避免内存泄露 弱引用获取context
    private static WeakReference<Context> weakReference;

    private static void initWeakReferenceContext(Context context) {
        weakReference = new WeakReference<>(context);
    }

    private Context getWeakReferenceContext() {
        return weakReference.get();
    }

    private UnityMessage() {
    }

    public static UnityMessage getInstance() {
        if (Instance == null) {
            synchronized (UnityMessage.class) {
                if (Instance == null) {
                    Instance = new UnityMessage();
                }
            }
        }
        return Instance;
    }

    public void init(Context context){
        initWeakReferenceContext(context);
        this.mContext = getWeakReferenceContext();
        mServiceConnected = new UnityMsgServiceConnection();
        bindService();
    }

    public void sendMsgToAndroid(String msg) {
        ThreadUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                sendMsgToService(msg);
            }
        });
    }

    public void showToast(String msg){
        ThreadUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                LogUtils.d("unity进程收到信息:"+msg);
                sendMsgToService(msg);
            }
        });
    }

    /**
     * 发送消息到服务端
     * @param msg
     */
    private void sendMsgToService(String msg) {
        Message message = mhandle.obtainMessage();
        message.what = CLIENT_REQUEST_CODE;
        Bundle bundle = new Bundle();
        bundle.putString(CLIENT_REQUEST_MSG, msg);
        message.setData(bundle);
        message.replyTo = mMessenger;
        if (mServiceMessenger != null) {
            try {
                LogUtils.d("发送至主进程:"+msg);
                mServiceMessenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 客户端handler
     * 处理服务端(主进程)返回的数据
     * */
    private class ClientHandler extends Handler {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if (msg != null) {
                if (msg.what == HandleUnityMessengerService.SERVICE_RESULT_CODE) {
                    // 服务端返回的数据
                    String data = msg.getData().getString(HandleUnityMessengerService.SERVICE_RESULT_MSG);
                    // 返回给unity项目
                    if(data!=null) {
                        LogUtils.e(data);
                        UnityPlayer.UnitySendMessage("Main", "TestSend", data);
                    }
                }
            }
        }
    }

    /**
     * 绑定服务
     */
    private void bindService() {
        Intent intent = new Intent(mContext, HandleUnityMessengerService.class);
        mContext.bindService(intent, mServiceConnected, Service.BIND_AUTO_CREATE);
    }

    private class UnityMsgServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            LogUtils.d("绑定-->" + name.getClassName());
            mServiceMessenger = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            LogUtils.d("解绑-->" + name.getClassName());
            mServiceMessenger = null;
        }
    }

    /**
     * 释放资源
     * */
    public void release(){
        unbindService();
        if(mhandle!=null){
            mhandle.removeCallbacksAndMessages(null);
        }
    }

    /**
     * 解绑服务
     * */
    private void unbindService() {
        if (mServiceConnected != null && mContext != null) {
            mContext.unbindService(mServiceConnected);
        }
    }

}
HandleUnityMessengerService.kt
/**
 * 服务端创建一个 Service 来处理客户端请求,同时通过一个 Handler 对象来实例化一个 Messenger 对象,
 * 然后在 Service 的 onBind 中返回这个 Messenger 对象底层的 Binder 即可。
 * */
class HandleUnityMessengerService : Service() {

    companion object{
        //服务端的返回码
        const val SERVICE_RESULT_CODE = 0X1002
        //服务端的返回数据字段
        const val SERVICE_RESULT_MSG = "service_result"
    }

    private class MessageHandler : Handler(){
        override fun handleMessage(msg: Message?) {
            super.handleMessage(msg)
            when(msg!!.what){
                UnityMessage.CLIENT_REQUEST_CODE -> {
                    LogUtils.e("服务端收到客服端信息:${msg.data.getString(UnityMessage.CLIENT_REQUEST_MSG)}")

                    val client = msg.replyTo
                    val replyMsg = Message()
                    replyMsg.what = SERVICE_RESULT_CODE
                    val bundle = Bundle()
                    bundle.putString(SERVICE_RESULT_MSG,"客服端你好!服务端已经收到你的信息了!")
                    replyMsg.data = bundle
                    client.send(replyMsg)
                }
            }
        }
    }

    private val messenger = Messenger(MessageHandler())

    override fun onBind(intent: Intent): IBinder {
        return messenger.binder
    }

    override fun onUnbind(intent: Intent?): Boolean {
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
    }
}

UnityPlayerActivity中的onCreate()  onDestroy()中

//Unity消息接收
UnityMessage.getInstance().init(this);
    @Override
    protected void onDestroy ()
    {
        UnityMessage.getInstance().release();

        mUnityPlayer.quit();
        super.onDestroy();
    }

最后AndroidManifest添加

<service
            android:name=".connection.HandleUnityMessengerService"
            android:enabled="true"
            android:exported="true"
            />

 

最后编辑:
作者:搬运工

留下一个回复

你的email不会被公开。