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之後會出現整個進程也會隨着頁面結束而被殺掉,原因是UnityPlayerActivity 的 onDestroy()中調用了mUnityPlayer.quit(); quit中調用了this.kill();
爲了退出unity界面而不結束進程,需要在AndroidManifest中UnityPlayerActivity的聲明中添加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"
/>