在 Unity 中使用 Android SDK

如果你想知道什么是爱、我们从哪里来、生命的意义、宇宙的起源,那么请不要看这篇文章。

这只是一篇无聊的文章,除非你是一只正在被折磨的猿猴,否则请跳过。

 

将 Java 代码做成 Unity 插件

下载 Android sdk、在 Unity 中配置好路径,保证 Unity 可以正常导出 apk;下载 JDK 、配置好环境变量,保证 Eclipse 可以正常打开。另外你需要知道使用Eclipse的logcat查看调试log。

1.打开 Eclipse,建立一个 Android 空项目,注意 package name 一栏,这里写的名称会添加到项目 manifest 中,仅影响 gen 中自动生成的代码所在的包,由于 Android 项目仅作为插件项目,最好与自己代码包名、应用包名区别开。设置好最低sdk、目标sdk版本,点击Next。

2.取消图标、Activity,标记为 library 项目,创建项目目录,点击 finish。

3.再继续之前,先解释一下Android项目的结构:

src:源代码目录,java代码是按照包名划分目录的,根目录就是src;

gen:自动生成代码的目录,比如 R.java 文件(这个class为每个资源定义一个唯一id);

assets:一个可以存放任何其他资源的目录;

bin:生成的二进制文件目录;

libs:存放引用的其他包文件;

res:项目标准资源目录,图标啦、字符串啦等等;

AndroidManifest.xml:配置清单。

好了只需要了解这些。

4.打开 Unity 安装目录,复制 Editor\Data\PlaybackEngines\androidplayer\release\bin\classes.jar 到 Android 项目的 libs 里面,这个 jar 包仅用于辅助 Eclipse 代码检查和提示,不用于最后导出.

5.开始码起来。右键src,创建 java 类 UnityPluginTest.java。点击finish。

6.写点有用的东西。比如显示一个 Android UI 风格的小提示的功能。

复制代码
package unityplugin;

import android.widget.Toast;
import com.unity3d.player.UnityPlayer;

public class UnityPluginTest
{
    public static void ShowToast(final String content, final boolean isLong)
    {
        UnityPlayer.currentActivity.runOnUiThread(new Runnable()
        {
            @Override
            public void run()
            {
                Toast.makeText(UnityPlayer.currentActivity, content, isLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show();
            }
        });
    }
}
复制代码

注意你的项目不应该含有任何错误,否则请检查 classes.jar 是否成功的被引用了。

关于这段代码,有必要做一下解释。首先这个方法是 static 类型的。接下来我们几乎把所有需要与 Unity 交互的代码都会写成静态的。不为什么,就是任性。然后注意在这个方法里面我们调用了 UnityPlayer.currentActivity.runOnUiThread 方法,参数是一个 Runnable 对象。很多与Android UI 相关的代码都必须写在 UI 线程里,这与 windows 的消息队列类似,事情必须一件一件来做才不会乱掉。最后你还需要了解函数参数的关键字 final,它可以让这个参数穿越时空——哦不,是穿越到 Runnable 的 run 里面(如果你知道C#的 delegate 可以引用外部变量应该会感到熟悉,只是这里需要显示的标记为 final)。

7.打开 Unity,创建一个新的空 Unity 项目,切换目标平台为Android。创建如图所示的目录结构。

8.切换到 Eclipse,右键项目菜单,选择 “Export...”,导出类型选择jar file,点击next,导出内容仅选择bin/classes目录。导出路径选择刚才的unity项目里的libs目录。其他选项看图。然后点击finish。

9.至此Eclipse上的任务完成了。切换到 Unity,在 AndroidScripts 目录添加脚本 AndroidBehaviour.cs, 代码如下:

复制代码
    public abstract class AndroidBehaviour<T> : MonoBehaviour where T : AndroidBehaviour<T>
    {
        protected abstract string javaClassName { get; }

        private static T _instance;
        protected static T instance { get { return _instance; } }


        protected AndroidBehaviour() { _instance = this as T; }


        protected void CallStatic(string methodName, params object[] args)
        {
            using (AndroidJavaClass ajc = new AndroidJavaClass(javaClassName))
            {
                ajc.CallStatic(methodName, args);
            }
        }


        protected ReturnType CallStatic<ReturnType>(string methodName, params object[] args)
        {
            using (AndroidJavaClass ajc = new AndroidJavaClass(javaClassName))
            {
                return ajc.CallStatic<ReturnType>(methodName, args);
            }
        }


        protected ReturnType GetStatic<ReturnType>(string fieldName)
        {
            using (AndroidJavaClass ajc = new AndroidJavaClass(javaClassName))
            {
                return ajc.GetStatic<ReturnType>(fieldName);
            }
        }
    }
复制代码

我们以后就使用这个类来与 Java 代码交互了,可以调用 java 类中的静态方法。现在知道为什么 java 类里的方法要写成静态的吗?不为什么,还是任性。注意这个类是抽象的,必须继承它;并且有一个泛型的 instance 保存唯一实例。不要管那么多,继续。
10. AndroidScripts 目录添加脚本 UnityPluginTest.cs, 代码如下:

复制代码
    public class UnityPluginTest : AndroidBehaviour<UnityPluginTest>
    {
        protected override string javaClassName
        {
            get { return "unityplugin.UnityPluginTest"; }
        }


        public static void ShowToast(string message, bool isLong)
        {
            instance.CallStatic("ShowToast", message, isLong);
        }
    }
复制代码

这个是继承AndroidBehaviour的,并且需要实现 javaClassName,注意这个返回值是包名+类名,包名是啥?看上面java代码开头写的包。然后我们写个静态方法使用CallStatic调用java代码里的 ShowToast。注意CallStatic的参数,第一个是静态方法的字符串名称,后面的参数与对应的java方法参数个数、类型保持一致。这里的参数类型仅支持int,float,bool,string等简单类型。
11.好了,插件写完了。接下来写个测试代码看看。

复制代码
    public class Test : MonoBehaviour
    {
        void Update()
        {
            if(Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began)
            {
                UnityPluginTest.ShowToast("拿开你的臭手!", false);
            }
        }
    }
复制代码

12.在场景里创建一个空物体AndroidBridge,把UnityPluginTest.cs脚本挂上去。然后把测试脚本挂到任何一个物体上。添加场景到build settings里面。完成playersettings各种设置,公司名、产品名、包名。

13.连接你的手机,大胆的build and run吧。

14.测试通过后,你就可以把插件导出为unitypackage了,仅需要导出Plugins文件夹,这样在以后就可以方便的重用了。

 

将各种社交、计费、统计 等 SDK 做成 Unity 插件

请确保自己已经完全理解了第一部分的内容再继续。现在,你已经掌握了如何让自己的 java 代码可以在unity中执行了。你已经可以做很多有意思的事情了,比如浏览文件、弹出对话框、调用系统分享......但是你的老板想要的更多。下面我们通过一个实际的例子来看如何把第三方sdk做成unity的插件。再讲例子的过程中我会把各种sdk可能遇到的问题都提出来,这篇文章适用范围几乎是所有的sdk。思来想去,为了感谢小企鹅日复一日为我弹的广告,接下来就拿小米统计做个例子,一方面它比较简单,做例子不会太麻烦,另一方面任何人都可以很方便的做测试。这个我也没用过,我们一起开始吧。

1.下载小米统计 SDK:http://dev.xiaomi.com/doc?page_id=4023,解压后看看里面有什么东西:demo,doc,sdk。打开doc里面的html,看看说了什么东西。

2.创建Eclipse Android项目MiStats(参考第一部分)。复制刚才解压后sdk目录里的jar文件到项目的libs目录。有的其他sdk要求添加src,res,assets 等等,同样复制过来。有的sdk还会以library的形式提供sdk,就是含有一个library项目,然后要求你导入这个项目并引用它。当然不要理它了,直接把这个library项目里所有有价值的东西都复制过来。由于library项目的包名和你的项目包名可能不同,导致自动生成的R.java包名发生变化,你可以手动修改一下使用这个类的那些类的导入位置,或者到Manifest里把package改成library项目相同的包名,重新生成R.java就好了。如果任何现有源代码发现了乱码,请注意修复项目编码。哎,我在说什么呢?

3.根据文档要求配置 Manifest。此sdk仅要求添加几个权限。最后Manifest是这样的:

复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:theme="@android:style/Theme.NoTitleBar" package="gen.MiStats">
    <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="19" />

    
    <!-- MiStats -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    

</manifest>
复制代码

Manifest中,你最好为sdk要求添加的内容写上注释,因为我们最后并不使用这个Manifest文件,而仅使用sdk要求的内容。某些sdk还会要求添加 Activity,service,meta-data......把他们都复制过来就行了。

4.开码,创建MiStats类。把文档中提到的可能在Unity中使用的东西都包装起来。这里情况比较简单,基本只需要挨个调用现成的方法。此sdk中统计页面功能在游戏中用不了,因为游戏基本就是一个Activity,所以没写了。

复制代码
package unityplugin;

import android.util.Log;
import com.unity3d.player.UnityPlayer;
import com.xiaomi.mistatistic.sdk.MiStatInterface;

public class MiStats
{
    // 初始化
    public static void Initialize(String appId, String appKey, String channel, int policy, int interval)
    {
        MiStatInterface.initialize(UnityPlayer.currentActivity, appId, appKey, channel);
        MiStatInterface.setUploadPolicy(policy, (long)interval * 1000);
        
        Log.i("MiStats Device ID", MiStatInterface.getDeviceID(UnityPlayer.currentActivity));
    }
    
    
    // 上传(仅开发模式有效)
    public static void Upload()
    {
        MiStatInterface.triggerUploadManually();
    }
    
    
    // 计数(value = 1)
    public static void Count(String category, String key)
    {
        MiStatInterface.recordCountEvent(category, key);
    }
    
    
    // 计算
    public static void Calculate(String category, String key, int value)
    {
        MiStatInterface.recordCalculateEvent(category, key, value);
    }
    
    
    // 设置属性(String类型)
    public static void SetProperty(String category, String key, String value)
    {
        MiStatInterface.recordStringPropertyEvent(category, key, value);
    }
    
    
    // 设置属性(int类型)
    public static void SetProperty(String category, String key, int value) 
    {
        MiStatInterface.recordNumericPropertyEvent(category, key, value);
    }
}
复制代码

有的sdk里,经常会有各种listener、event之类的回调,回调结果必须传递给unity,那么可以使用 UnityPlayer.UnitySendMessage(gameObjectName, method, param) 方法。
5.接下来创建一个Unity项目,用来制作、测试和导出MiStats插件。切换平台为android,创建目录结构如下。

相比于第一部分,多了AndroidManifests 目录。第一部分我们并没有用到manifest,但是由于此sdk需要添加权限,所以必须含有manifest。但是,一个unity项目仅可以含有一个manifest文件,如果多个sdk都要求添加manifest该如何解决?解决办法就是每个sdk都把自己需要的manifest放到这个AndroidManifests 目录里面,只要使用此sdk就将其对应的manifest合并到最终的manifest里面。

6.把eclipse项目里的有用的东西都搬过来。本sdk有用的东西就是MiStats_SDK_Client_1_0_0.jar、AndroidManifest.xml 和 你写的java代码。MiStats_SDK_Client_1_0_0.jar 直接复制到unity项目的libs目录,AndroidManifest.xml改名为 MiStats.xml 复制到 AndroidManifests目录——不要问为什么,一如既往的任性。你写的java代码通过导出jar的方式来使用,参考第一部分。其他sdk可能还有res、assets这些资源,直接复制这些文件夹到unity的Android目录。

7.使用解压软件打开MiStats_SDK_Client_1_0_0.jar,检查是否含有assets目录。如果有的话,复制出来放到Android目录。对所有不是你导出的jar都执行此操作。Unity导出包的时候会丢掉jar中非代码的资源。如果你一目十行没看到这句话,祝你好运!

8.为插件写代码。把第一部分的AndroidBehaviour脚本复制到AndroidScripts,然后创建新脚本MiStats.cs, 代码如下。

复制代码
    public class MiStats : AndroidBehaviour
    {
        public enum UploadPolicy : int { realTime = 0, wifiOnly = 1, batch = 2, whileInitialize = 3, interval = 4, development = 5 }

        [SerializeField] string appId;
        [SerializeField] string appKey;
        [SerializeField] string channel;
        [SerializeField] UploadPolicy policy;
        [SerializeField][Range(300, 86400)] int interval = 300;


        protected override string javaClassName { get { return "unityplugin.MiStats"; } }


        // 初始化
        void Awake()
        {
            CallStatic("Initialize", appId, appKey, channel, (int)policy, interval);
        }


        // 开发模式下手动上传数据(非开发模式自动禁用)
        public static void Upload()
        {
            if (instance.policy == UploadPolicy.development)
                instance.CallStatic("Upload");
        }


        // 计数(value = 1)
        public static void Count(string category, string key)
        {
            instance.CallStatic("Count", category, key);
        }


        // 计算
        public static void Calculate(string category, string key, int value)
        {
            instance.CallStatic("Calculate", category, key, value);
        }


        // 设置属性(string类型)
        public static void SetProperty(string category, string key, string value)
        {
            instance.CallStatic("SetProperty", category, key, value);
        }


        // 设置属性(int类型)
        public static void SetProperty(string category, string key, int value)
        {
            instance.CallStatic("SetProperty", category, key, value);
        }
    }
复制代码

除了需要把某些东西显示到inspector,与第一部分的代码没有什么区别。需要注意的是,callstatic仅支持有限的类型,枚举需要强制转换为int再传递。
9.复制一份MiStats.xml到Android目录,改名为AndroidManifest.xml,这样是为了进行测试,就当是合并Manifest了。添加Application和启动Activity。有的sdk或文章要求你必须实现一个自己的Activity,那是错误的。下面我们直接使用unity的nityPlayerNativeActivity。注意Application的icon和label,一定要按下面这样写,否则无法在unity中设置他们。还有manifest package属性同样需要移除掉。最后关于输入事件传递给java虚拟机,如果你使用了Android标准控件,需要设置为true才能响应用户输入。

复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:theme="@android:style/Theme.NoTitleBar">
    <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="19" />
  
<!-- MiStats 要求的权限 -->
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.READ_PHONE_STATE" />


  <application android:icon="@drawable/app_icon" android:label="@string/app_name" >

    <activity android:name="com.unity3d.player.UnityPlayerNativeActivity" android:label="@string/app_name" android:launchMode="singleTask" >
      <meta-data android:name="android.app.lib_name" android:value="unity" />
      <!-- 将输入事件传递给虚拟机 -->
      <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>

  </application>


</manifest>
复制代码

10.然后写个测试脚本。

复制代码
using UnityEngine;

public class Test : MonoBehaviour
{
    Rect[] rects;


    void Awake()
    {
        rects = new Rect[4];

        for(int i=0; i<4; i++)
        {
            rects[i] = new Rect(0, i * Screen.height * 0.25f, Screen.width, Screen.height * 0.25f);
        }
    }


    void OnGUI()
    {
        if (GUI.Button(rects[0], "计数"))
        {
            MiStats.Count("计数", "分享");
        }

        if (GUI.Button(rects[1], "计算"))
        {
            MiStats.Calculate("计算", "消费", Random.Range(1, 101));
        }

        if (GUI.Button(rects[2], "文本属性"))
        {
            MiStats.SetProperty("文本属性", "昵称", "白猫");
        }

        if (GUI.Button(rects[3], "数值属性"))
        {
            MiStats.SetProperty("数值属性", "智商", 250);
        }
    }

}
复制代码

11.最后再确认一下我们是不是在做同一件事。

12.开始测试。你需要有一个小米开发者帐号,然后创建一个应用,获得appid和appkey,填写到inspector。policy选realtime方便测试。其他参考第一部分。打包后手机运行,打开logcat查看设备id,到小米后台添加测试设备。

13. 导出插件,注意用于测试的 AndroidManifest不要导出,否则以后使用时会覆盖项目的Manifest。

 

终于填完了。相信你们看了这篇文章后一定不会一帆风顺的,嘻嘻。

关于合并Manifest,目前我是手工做的,反正一个项目也就需要做一次。当然你也可以研究一下自动完成的事情,我是没有兴趣了。

 

如果你觉得我的文章有价值,点个“推荐”、加个“关注”什么的我也不会介意的......

【白猫,博客园首页:http://www.cnblogs.com/whitecat/

转载:http://www.cnblogs.com/whitecat/p/4156000.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值