Unity Android Plugin 開發教程

這裏寫圖片描述

開發環境Windows平臺(Unity2017.1.0f3 Personal ,Android Studio 2.3.3)

在Unity項目中構建和使用Android Plugin

Unity支持幾種類型的Android plug-ins:

這裏寫圖片描述

  1. AAR 插件 和 Android Library
  2. JAR 插件
  3. 繼承UnityPlayerActivity
  4. Native(C++) 插件

Unity Android runtime

Untiy Android runtime通過繼承自FrameLayoutUnityPlayer實現,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.aarManifest.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. 編譯運行

這裏寫圖片描述

效果展示

這裏寫圖片描述

使用AARJAR方式的Android Plugin

Android端的操作

在Android Studio中新建項目

1.選擇Add No Activity

這裏寫圖片描述

  1. 新建Modue,選擇Android Library

這裏寫圖片描述

  1. 添加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();
       }
    }
  2. Build Module之後,在模塊的子目錄/build/outputs/arr中得到.aar.jar

    這裏寫圖片描述

Unity端的操作

創建Unity項目

將得到的.arr(將.arr解壓就可以得到.jar) 放在Assert目錄下,可以放在任意的目錄下,官方推薦使用.aar 。並且按照如下目錄新建Canvas和Text。

這裏寫圖片描述

編寫C#腳本

編寫BatteryLevelPlugin.cs :

Unity提供了兩種方式與java進行交互

  1. AndroidJNI 和 AndroidJNIHelper

  2. 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

[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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章