Unity與andorid交互的那些坑(偏andorid)

Unity與andorid交互的那些坑

    近期接觸到需要Unity與andorid交互的項目,我負責andorid開發,記錄一下開發過程遇到的坑,代碼偏向於android端處理,unity端其他操作自行百度

 一.unity工程師導出andorid項目

        有兩種方式,推薦用Gradle方式導出(unity同事工作),導出的工程結構如下
        

二.合併進主項目

    上圖紅框中文件都可在導出的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頁面後,按系統返回鍵無法退回原生界面,只能在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"
            />

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章