Unity 与 Android (Android Studio)的交互问题研究 (一)

Unity 与 Android (Android Studio)的交互问题研究 (一)

之前公司有个AR相关的项目,里面用到的3D模型是用Unity搭建的。
由于Unity程序在手机端独立运行于自己的虚拟机中(mono/il2cpp),因此具备了强大的跨平台能力,我们可以用它直接编译生成能够运行在IOS与Android两大平台的程序。
事实上整个项目绝大部分都是由Unity的C#编写的,这样好处很多,主要是不用针对不同平台做移植,节省了很大的资源。
不过事情也没有绝对,Andorid版后期需要开发一个录屏功能,可能是因为涉及到底层C/C++的库,所以必须要用原生的代码(Java)来写一个方法实现,然后由Unity来调用,操纵摄像头。
在网上查了很多资料,自己也踩了很多的坑,不过磕磕绊绊总算是有点头绪,所以决定趁这次机会总结归纳一下。

关键在于Unity与Android之间的交互机制

我们知道,Android程序都是运行在dalvik/art虚拟机上的,而Unity程序是在(mono/il2cpp)上。当一个Unity应用想要用到Andorid的方法的话,毫无疑问,这个应用就需要两套虚拟机同时运行,即两个虚拟机运行在同一个进程中。
那么,Unity与Android之间的交互问题,其实就是两个VM之间的相互调用:如下图

虚拟机之间相互调用
如上图所示,Unity通过UnityEngine提供的API调用Android的方法;Android借助com.unity.player包提供的API调用Unity的方法。
此次我们主要讨论Unity调用Android,Android调用Unity不过多深入。

Android部分

Unity调用Android,需要在Android项目中封装一系列方法,然后打成Jar包供Unity使用。
由于AS没有直接导出Jar包的方法,所以这次我们利用了AS的Module Build会生成Jar包的原理。
打开AndroidStudio,New一个Project,我们这里命名为AndroidPlugin。之后我们再New一个Module出来,类型为Android Library,命名为android2u3d
在此Module里,New一个Activity,并勾选上Launcher Activity选项。
接下来,我们会编写这个Activity,主要是在里面写一些方法供Unity调用。
在此之前,我们还要用到一个Unity提供给Android的包,这个包提供了支持该平台的Player。根据Unity版本的不同,包的位置会略有不同。笔者用的是5.3.4,位置在Unity安装目录\Editor\Data\PlaybackEngines\Androidplayer\Variations\il2cpp\Release\Classes\classes.jar(注意到Variations下有两个目录:il2cppmono,这是Unity项目脚本执行器的类型,有mono和il2cpp两种,与Unity项目的”Script Backend”一致)。接着将其放置到Module下的libs目录,右键点击Add As Library,将其置为Module的File Dependency。
开始编写Activity:

package com.example.yaoobs.android2u3d;

import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Vibrator;
import android.widget.Toast;

/* 引入Unity的包,在之前Unity的classes.jar里  */
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;

/* 如果需要Activity与Unity对接,可以通过继承UnityPlayerActivity来实现  */
public class MainActivity extends UnityPlayerActivity {
    
    

    private Context mContext = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
    }

    /* 定义一个调用Unity方法的方法 ,基于UnitySendMessage实现 。
    * 由于没有布局文件,我们在这里通过Unity调用这个方法*/
    public void InvokeUnity(String mStr) {
        UnityPlayer.UnitySendMessage("Unity2Android", "SetCameraColor", "");
    }

    // 定义一个显示对话框的方法,在Unity中调用此方法
    public void ShowDialog(final String mTitle, final String mContent) {
        // 在UI线程下执行
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                AlertDialog.Builder mBuilder = new AlertDialog.Builder(MainActivity.this);
                mBuilder.setTitle(mTitle).setMessage(mContent).setPositiveButton("OK", null);
                mBuilder.show();
            }
        });
    }

    // 定义一个显示Toast的方法,在Unity中调用此方法
    public void ShowToast(final String mStr2Show) {
        // 同样需要在UI线程下执行
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), mStr2Show, Toast.LENGTH_LONG).show();
            }
        });
    }

    //  定义一个手机振动的方法,在Unity中调用此方法
    public void SetVibrator(final long[] mTime) {
        Vibrator mVibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
        mVibrator.vibrate(mTime, -1); //-1:表示不重复 0:循环的震动
    }
}

如上所示,我们删掉了布局文件,然后定义了4个方法,显示Dialog跟Toast,手机振动,还有一个调用Unity方法的。别忘了在AndroidManifest.xml添加权限:

<uses-permission android:name="android.permission.VIBRATE"></uses-permission>

为了导出Jar包,我们需要在Module下的build.gradle里添加一个task,如下:

task makeJar(type: Copy) {
    delete 'build/libs/android2u3d.jar'
    from('build/intermediates/bundles/release/')
    into('build/libs/')
    include('classes.jar')
    rename('classes.jar', 'android2u3d.jar')
}

makeJar.dependsOn(build)

上面的命令无非就是拷贝删除重命名而已,就不多说了。
打开AS的Terminal,输入:gradlew makeJar
等待出现BUILD SUCCESSFUL字样,就可以在Module/build/libs下找到android2u3.jar
如果出现lint检查错误,在Module下的build.gradle里添加:

android{
...
lintOptions{

        abortOnError false

    }
...
}

就没事了。
最好把src下的androidTesttest,即UI测试与单元测试相关的目录删掉,再把没有用到的譬如

testCompilejunit:junit:4.12compilecom.android.support:appcompat-v7:23.1.0

的Dependency删掉。

Unity部分

打开Unity,New一个Project,命名为Unity2Android,在Project/Assets/Plugins/Android/bin(标黑的是需要自己Create的目录)下放入之前的android2u3d.jar。Android下放入Module的AndroidManifest.xml文件。
接着在Plugins的同级目录下Create一个Script目录,在其下Create一个C# Script,我们将其命名为:AndroidAPI.cs,编写代码如下:

using UnityEngine;
using System.Collections;

public class AndroidAPI : MonoBehaviour {

    //在Android中我们将使用这个名字
    void Start () {
        this.name="Unity2Android"; 
    }

    void SetCameraColor()  
    {  
        //设置摄像头背景颜色  
        Camera.main.backgroundColor=new Color(1.0F,0.5F,0.5F);  
    } 

    void Update () {
        // 返回键退出
        if(Input.GetKey(KeyCode.Escape))
            Application.Quit();
    }

    void OnGUI ()   
    {  
        // 通过API调用对话框  
        if(GUILayout.Button("调用安卓Jar中的方法ShowDialog",GUILayout.Height(50)))  
        {  
            //获取Android的Java接口  
            AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer");  
            AndroidJavaObject jo=jc.GetStatic<AndroidJavaObject>("currentActivity");  
            //构造参数  
            string[] mString=new string[2];  
            mString[0]="Unity2Android";  
            mString[1]="Talk is cheap,Show me the code!";  
            //调用方法  
            jo.Call("ShowDialog",mString);  
        }  
        // 通过API调用Toast
        if(GUILayout.Button("调用安卓Jar中的方法ShowToast",GUILayout.Height(50)))  
        { 
            AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer");  
            AndroidJavaObject jo=jc.GetStatic<AndroidJavaObject>("currentActivity"); 
            jo.Call("ShowToast","HELLO WORLD!"); 
        }
        // 通过API调用手机震动的方法
        if(GUILayout.Button("调用安卓Jar中的方法SetVibrator",GUILayout.Height(50)))  
        { 
            long[] mTime = new long[]{ 200, 2000, 2000, 200, 200, 200 };
            AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer");  
            AndroidJavaObject jo=jc.GetStatic<AndroidJavaObject>("currentActivity"); 
            jo.Call("SetVibrator",mTime); 
        }
        //通过API调用Toast  
        if(GUILayout.Button("通过SendMessage调用Unity中的方法",GUILayout.Height(50)))  
        {  
            //获取Android的Java接口  
            AndroidJavaClass jc=new AndroidJavaClass("com.unity3d.player.UnityPlayer");  
            AndroidJavaObject jo=jc.GetStatic<AndroidJavaObject>("currentActivity");  

            jo.Call("InvokeUnity","");  
        } 
    } 
}

可以看到,在Unity里调用Android的方法是通过AndroidJavaObjectAndroidJavaClass这两个类来实现的。首先获取到MainActivity,接着调用指定方法(关于具体原理可以去看UnityPlayerUnityPlayerActivity的源码,我们将放在之后讲解)。值得注意的是Android的InvokeUnity这个方法,Unity调用它后,它又去调用Unity的SetCameraColor这个方法。通过UnitySendMessage这个方法,可以调用Unity中指定GameObject所挂载的脚本的方法,而GameObject的名字,就是这里

void Start () {
        this.name="Unity2Android"; 
    }

指定的名字,必须与Android代码中的

 public void InvokeUnity(String mStr) {
        UnityPlayer.UnitySendMessage("Unity2Android", "SetCameraColor", "");
    }

第一个参数保持统一,第二个参数是方法名,第三个参数应该是传参,没有验证。
之后将这个 AndroidAPI.cs 拖动到 Main Camera 上进行绑定。
如果是第一次使用,还需配置AndroidSDK的Location。在菜单栏 ->Edit -> Preferences -> External Tools里设置。
之后是BuildSettings设置,切换到Android平台,勾选Development buildAutoconnect profilerTexture Compression选择 ETC(default)Player Settings设置BundleID最小APIlevel,最后Create New Keystore,最后连接设备,BuildAndRun
真机运行

项目已上传GitHub,地址:https://github.com/Yaoobs/UnityAndroidPluginDemo

参考文章:
http://mp.weixin.qq.com/s?__biz=MzI1NjEwMTM4OA==&mid=2651231917&idx=1&sn=ce2e0f7251e26f7b5a9b2fddadf96bcc&scene=23&srcid=0705qPTKw0LXSdy8hRQfBegv#rd
http://blog.csdn.net/kuerjinjin/article/details/50177633
http://blog.csdn.net/qinyuanpei/article/details/39348677

猜你喜欢

转载自blog.csdn.net/Yaoobs/article/details/51566480