开发环境Windows平台(Unity2017.1.0f3 Personal ,Android Studio 2.3.3)
在Unity项目中构建和使用Android Plugin
Unity支持几种类型的Android plug-ins:
AAR 插件 和 Android Library
JAR 插件
继承UnityPlayerActivity
Native(C++) 插件
Unity Android runtime
Untiy Android runtime 通过继承自FrameLayout 的UnityPlayer 实现,UnityPlayer 实现了触控,键盘输入,相机,位置等特性。虽然这个UnityPlayer 实现了大部分的native 功能,但它不是应用程序的入口。
在通用的Android Unity应用程序中,程序的入口是UnityPlayerActivity 。如果你看一下APK文件反编译后的AndroidManifest.xml文件,可以看到它是如何标记UnityPlayerActivity 作为应用程序的Launcher的。
查看Unity安装目录发现,UnityPlayerActivity 的源码可以在C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Source 中查看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class UnityPlayerActivity extends Activity { protected UnityPlayer mUnityPlayer; @Override protected void onCreate (Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); super .onCreate(savedInstanceState); getWindow().setFormat(PixelFormat.RGBX_8888); mUnityPlayer = new UnityPlayer (this ); setContentView(mUnityPlayer); mUnityPlayer.requestFocus(); } ......... @Override public boolean dispatchKeyEvent (KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_MULTIPLE) return mUnityPlayer.injectEvent(event); return super .dispatchKeyEvent(event); } ......... }
可以看到UnityPlayerActivity 继承自Activity ,并且UnityPlayerActivity 持有一个UnityPlayer 实例。UnityPlayerActivity 通过UnityPlayer 分派native 事件。
通用的Android插件开发,通过继承UnityPlayerActivity ,并使子类成为整个应用程序的LAUNCHER Activity ,接下来将介绍继承UnityPlayerActivity 方式的Android Plugin 。
继承UnityPlayerActivity 方式的Android Plugin
Android Plugin 需要包含Android项目中build 后得到的app-debug.aar 和Manifest.xml 以及资源文件等,提供给Unity项目使用。文件存放在Unity项目中的**/Assets/Plugins/Android。Unity项目中的代码通过 app-debug.aar**与封装在其中的Android代码进行交互。For more details about .aar , see Android Developer Doc . And for more information about “How Unity produces the Android Manifest” , see Unity Developer Doc
那么,接下来新建Android项目,进而生成app-debug.aar 文件和Manifest.xml 文件。
Android端的操作 新建Android 项目 1.将项目切换成project的视图,打开app目录下的build.gradle文件,
1.将apply plugin: ‘com.android.application’,改成 apply plugin: ‘com.android.library’
2.然后删除applicationId
2.修改Manifest.xml 文件
在activity中添加
<meta-data android:name="unityplaer.UnityActivity" android:value="true"/>
引入Unity的classes.jar 包 从Unity 的安装目录找到unity的classes.jar包。 Windows目录:
C:\ProgramFiles\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\classes.jar Mac下目录:
/Applications/Unity/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes\classes.jar 将其拷贝到UnityAndroid项目app目录下的libs目录下,右键Add as library ,导入之后可以发现在build.gradle中就有他的引入了。
编写Android项目与Unity项目交互的代码 首先需要让MainActivity继承UnityPlayerActivity ,同时删除onCreate方法中的setContentView()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class MainActivity extends UnityPlayerActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); } public void ShowToast (final String msg) { runOnUiThread(new Runnable () { @Override public void run () { Toast.makeText(getApplicationContext(),msg, Toast.LENGTH_SHORT).show(); new AlertDialog .Builder(MainActivity.this ).setMessage(msg).show(); } }); } public void setUnityText () { runOnUiThread(new Runnable () { @Override public void run () { Toast.makeText(getApplicationContext(),"Android 端调用setText" , Toast.LENGTH_SHORT).show(); UnityPlayer.UnitySendMessage("Canvas" ,"setText" ,"Android 端调用setText" ); } }); } }
Build得到app-debug.aar 文件和Manifest.xml 文件 分别在/app/build/outputs/aar和/app/src/main目录下
接下来需要将app-debug.aar 用解压软件打开,并且删除libs 目录下的classes.jar
Unity端的操作 创建Unity项目 新建Unity项目,并新建如下目录将Android端得到的app-debug.aar 文件和Manifest.xml 文件放在**/Plugins/Android**目录下,同时在Hierarchy下按照图示新建Canvas,Button和Text:
编写C#脚本 同时新建名为AndroidTest.cs的C#脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI;public class AndroidTest : MonoBehaviour { void Start () { } void Update () { } public void BtnShowMessage () { using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer" )) { using (AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity" )) { jo.Call("ShowToast" , "Unity调用了Android中的AlertDialog" ); } } } public void BtnSetText () { using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer" )) { using (AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity" )) { jo.Call("setUnityText" ); } } } public void setText (string result ) { Text text = GameObject.Find ("UnityText" ).GetComponent<Text> (); text.text = result; } }
编译运行 按照标号步骤进行1. 选择Build的平台->->2. 添加Scenes ->-> 3. 设置Identification ->-> 4. 设置包名和 API Level ->-> 5. 编译运行
效果展示
使用AAR 或JAR 方式的Android Plugin Android端的操作 在Android Studio中新建项目 1.选择Add No Activity
新建Modue ,选择Android Library
添加AndroidPlugin.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import android.content.Context;import android.content.Intent;import android.content.IntentFilter;import android.os.BatteryManager;import java.text.SimpleDateFormat;import java.util.Date;public class AndroidPlugin { private Context context; public AndroidPlugin (Context context) { this .context = context; } public float GetBatteryPct () { Intent batteryStatus = GetBatteryStatusIntent(); int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1 ); int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1 ); float batteryPct = level / (float )scale; return batteryPct; } public boolean IsBatteryCharging () { Intent batteryStatus = GetBatteryStatusIntent(); int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1 ); return status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL; } private Intent GetBatteryStatusIntent () { IntentFilter ifilter = new IntentFilter (Intent.ACTION_BATTERY_CHANGED); return context.registerReceiver(null , ifilter); } public String getSysTime () { return new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ).format(new Date ()).toString(); } }
Build Module之后,在模块的子目录**/build/outputs/arr中得到 .aar** 和 .jar
Unity端的操作 创建Unity项目 将得到的**.arr**(将**.arr解压就可以得到 .jar**) 放在Assert 目录下,可以放在任意的目录下,官方推荐使用**.aar** 。并且按照如下目录新建Canvas和Text。
编写C#脚本 编写BatteryLevelPlugin.cs :
Unity提供了两种方式与java进行交互
AndroidJNI 和 AndroidJNIHelper
AndroidJavaClass , AndroidJavaObject 和 AndroidJavaProxy
同时,官方推荐如下调用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 using System;using System.Collections;using System.Collections.Generic;using UnityEngine;public class BatteryLevelPlugin { public static float GetBatteryLevel () { if (Application.platform == RuntimePlatform.Android) { using (var javaUnityPlayer = new AndroidJavaClass ("com.unity3d.player.UnityPlayer" )) { using (var currentActivity = javaUnityPlayer.GetStatic <AndroidJavaObject>("currentActivity" )) { using (var androidPlugin = new AndroidJavaObject ("com.hiscene.androidsysinfo.AndroidPlugin" , currentActivity)) { return androidPlugin.Call <float >("GetBatteryPct" ); } } } } return 1 f; } public static string GetSysTime () { AndroidJNI.AttachCurrentThread (); IntPtr javaClass = AndroidJNI.FindClass ("com/unity3d/player/UnityPlayer" ); IntPtr fid = AndroidJNI.GetStaticFieldID (javaClass, "currentActivity" , "Landroid/app/Activity;" ); IntPtr obj = AndroidJNI.GetStaticObjectField (javaClass, fid); IntPtr pluginClass = AndroidJNI.FindClass ("com/hiscene/androidsysinfo/AndroidPlugin" ); IntPtr initMethod = AndroidJNI.GetMethodID (pluginClass, "<init>" , "(Landroid/content/Context;)V" ); jvalue[] jv = new jvalue[1 ]; IntPtr pobj = AndroidJNI.NewObject (pluginClass, initMethod,jv); IntPtr enableMethod = AndroidJNI.GetMethodID (pluginClass, "getSysTime" , "()Ljava/lang/String;" ); return AndroidJNI.CallStringMethod (pobj, enableMethod, new jvalue[1 ]); } public static string GetSysTimeAndroidJNI () { AndroidJNI.AttachCurrentThread (); IntPtr javaClass = AndroidJNI.FindClass ("com/hiscene/androidsysinfo/SysTime" ); IntPtr initMethod = AndroidJNI.GetMethodID (javaClass, "<init>" , "()V" ); IntPtr pobj = AndroidJNI.NewObject (javaClass, initMethod, AndroidJNIHelper.CreateJNIArgArray (new object[1 ])); IntPtr enableMethod = AndroidJNI.GetMethodID (javaClass, "getSysTime" , "()Ljava/lang/String;" ); return AndroidJNI.CallStringMethod (pobj, enableMethod, new jvalue[1 ]); } public static string GetSysTimeAndroidJavaClass () { if (Application.platform == RuntimePlatform.Android) { using (var javaUnityPlayer = new AndroidJavaClass ("com.unity3d.player.UnityPlayer" )) { using (var currentActivity = javaUnityPlayer.GetStatic <AndroidJavaObject>("currentActivity" )) { using (var androidPlugin = new AndroidJavaObject ("com.hiscene.androidsysinfo.AndroidPlugin" , currentActivity)) { return androidPlugin.Call <string>("getSysTime" ); } } } } return "Time yyyy-MM-dd HH:mm:ss" ; } }
编写BatteryMonitor.cs :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 using System;using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI;public class BatteryMonitor : MonoBehaviour { public Text batteryLevelText; public Text batteryLevelIcon; public Text sysTime; static readonly string BATTERY_LEVEL_100 = Char.ConvertFromUtf32 (0xf240 ); static readonly string BATTERY_LEVEL_75 = Char.ConvertFromUtf32 (0xf241 ); static readonly string BATTERY_LEVEL_50 = Char.ConvertFromUtf32 (0xf242 ); static readonly string BATTERY_LEVEL_25 = Char.ConvertFromUtf32 (0xf243 ); static readonly string BATTERY_LEVEL_0 = Char.ConvertFromUtf32 (0xf244 ); void Update () { UpdateStatusIndicators (); sysTime.text = BatteryLevelPlugin.GetSysTime (); } void UpdateStatusIndicators () { var currentBatteryLevel = BatteryLevelPlugin.GetBatteryLevel () * 100 f; batteryLevelText.text = String.Format ("{0}%" , currentBatteryLevel); if (currentBatteryLevel >= 88 ) { batteryLevelIcon.text = BATTERY_LEVEL_100; } else if (currentBatteryLevel >= 63 ) { batteryLevelIcon.text = BATTERY_LEVEL_75; } else if (currentBatteryLevel >= 38 ) { batteryLevelIcon.text = BATTERY_LEVEL_50; } else if (currentBatteryLevel >= 13 ) { batteryLevelIcon.text = BATTERY_LEVEL_25; } else { batteryLevelIcon.text = BATTERY_LEVEL_0; } } }
按照步骤编译运行 步骤:1. 选择Build的平台->->2. 添加Scenes ->-> 3. 设置Identification ->-> 4. 设置包名和 API Level ->-> 5. 编译运行
效果展示
源代码 https://github.com/rolyyu/UnityAndroidPlugin
参考文献 [1] https://docs.unity3d.com/Manual/PluginsForAndroid.html
[2] http://www.voidcn.com/blog/Silk2018/article/p-6632911.html
[3] http://blog.csdn.net/zhangdi2017/article/details/65629589
[4] https://www.yangzhenlin.com/unity-android-plugin/
[5] https://medium.com/@datdeveloper/how-to-make-android-plugin-for-unity-take-photo-from-camera-and-gallery-c12fe247c770
[6] http://addcomponent.com/android-native-plugin-unity/