開發環境Windows平臺(Unity2017.1.0f3 Personal ,Android Studio 2.3.3)
在Unity項目中構建和使用Android Plugin
- 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中查看。
public class UnityPlayerActivity extends Activity
{
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
// Setup activity layout
@Override protected void onCreate (Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
getWindow().setFormat(PixelFormat.RGBX_8888); // <--- This makes xperia play happy
mUnityPlayer = new UnityPlayer(this);
setContentView(mUnityPlayer);
mUnityPlayer.requestFocus();
}
.........
// For some reason the multiple keyevent type is not supported by the ndk.
// Force event injection by overriding dispatchKeyEvent().
@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()
public class MainActivity extends UnityPlayerActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
//示例1:
//Unity端調用該函數
public void ShowToast(final String msg){
// 需要在UI線程下執行
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(),msg, Toast.LENGTH_SHORT).show();
new AlertDialog.Builder(MainActivity.this).setMessage(msg).show();
}
});
}
//示例2:
//Unity端調用該函數
public void setUnityText(){
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(),"Android 端調用setText", Toast.LENGTH_SHORT).show();
//調用Unity端函數
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#腳本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AndroidTest : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
public void BtnShowMessage()
{
using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
using(AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity"))
{
// 調用Android端方法
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"))
{
// 調用Android端方法
jo.Call("setUnityText");
}
}
}
//Android端調用該方法
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
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 { // Needed to get the battery level. private Context context; public AndroidPlugin(Context context) { this.context = context; } // Return the battery level as a float between 0 and 1 (1 being fully charged, 0 fulled discharged) 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; } // Return whether or not we're currently on charge 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); } //Get System Time 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 :
AndroidJNI 和 AndroidJNIHelper
AndroidJavaClass , AndroidJavaObject 和 AndroidJavaProxy
同時,官方推薦如下調用方式:
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 1f;
}
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];
//TODO
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 :
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);
// Update is called once per frame
void Update () {
UpdateStatusIndicators();
sysTime.text = BatteryLevelPlugin.GetSysTime();
}
void UpdateStatusIndicators()
{
var currentBatteryLevel = BatteryLevelPlugin.GetBatteryLevel() * 100f;
batteryLevelText.text = String.Format("{0}%", currentBatteryLevel);
// Show the icon that matches the current level most closely.
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