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.xxx.xxx.connection.UnityMessage");
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("Instance");
jo.Call ("showToast","我是Unity传来的消息");
按钮二:
AndroidJavaObject jc =
new AndroidJavaClass ("com.xxx.xxx.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
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"
/>