開發 Unity Android Plugin – 從零開始

這篇文章記錄了我所知道的基本 Unity Android Plugin 開發流程,目的是補充我所看過的網路資源,在細節上的說明不足。會特別針對幾個我遭遇過的疏漏,或因開發需求不同會有的細節變化進行說明。

整篇文章將流程分為幾段來描述:

  • 建立 Android 專案
  • 找到並匯入 Unity Java classes
  • 編寫你的 Java classes
  • 處理 build.gradle file
  • 編譯 .jar 並放到該去的位置
  • 編寫 Manifest.xml
  • 編寫 C# Script 作為 Plugin API

另一篇關於 Android Plugin 的文章:開發 Unity Android Plugin – 基本認識

建立 Android 專案

我使用 Android Studio 來開發 JAVA 部分,建立一個新專案,設定好 Package Name 以及 Add No Activity,就可以前往下一步了。

變化:在 Android 中每一個畫面或視窗都會與一個 Activity 相關,通常 Plugin 不會需要內建運作時的 UI 介面,所以選擇 Add No Activity。但這也不是絕對的,也可依照需求選擇有預設 Activity 的專案。

找到並匯入 Unity Java classes

要在專案中使用 Unity 的 Java Classes,就要找出相關檔案匯入目前的  Android 專案之中,你可以在 Unity 安裝位置找到它:

Windows
C:Program FilesUnityEditorDataPlaybackEnginesAndroidPlayersrccomunity3dplayerclasses.jar
Mac OSX
/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/src/com/unity3d/player/classes.jar

找到之後 classes.jar 複製到上圖的紅色區塊, MyPlugin/app/libs 資料夾之中,並對著檔案點右鍵選單中的 Add As Library… 便完成匯入的動作。

變化:匯入 Unity Classes 不外乎是為了在待會的開發有下列動作,所以如果沒有下列的需求,其實也不必匯入這個 classes.jar。如果不確定,先匯入就對了。

  • 繼承 UnityPlayerActivity
  • 使用 UnityPlayer 中的靜態成員 (static method & member variable)

地雷:Unity 的安裝處有一個以上的 classes.jar,我曾經因為拿錯檔案而使專案無法編譯,我們所要的檔案中必須要有 UnityPlayer 及 UnityPlayerActivity。

編寫你的 Java classes

接下來,在上圖的紅色區塊中,MyPlugin/app/src/main/java/com.name.plugin 點右鍵選單 New > Java Class 新增文件,可以 coding 囉!

package com.douduck08.myplugin;
/**
 * Created by douduck08 on 2016/6/9.
 */
import android.app.Activity;
import android.content.Intent;
import com.unity3d.player.UnityPlayer;
public class PluginInterface {

    public static void StartPluginActivity(Activity activity) {
        Intent myIntent = new Intent(activity, PluginActivity.class);
        activity.startActivity(myIntent);
    }

    public static void SendMessage(String objectName, String methodName, String message) {
        try {
            UnityPlayer.UnitySendMessage(objectName, methodName, message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

變化:這邊的 Java Class 可以繼承 UnityPlayerActivity,來開發影響核心的 Plugin。

地雷:我第一次撰寫 Plugin 所依循的網路教學中,繼承了 UnityPlayerActivity 來進行開發,卻沒有進一步說明原理,讓我曾以為繼承是必要動作。事實上,我認為開發時應該盡量避免去繼承 UnityPlayerActivity,而透過其他方式來達成目的,可以減少開發上的繁瑣,以及增加與其他 Plugin 的相容空間。

處理 build.gradle file

apply plugin: 'com.android.library'
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"
    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 23
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    lintOptions {
        abortOnError false
    }
    sourceSets {
        main {
            java.srcDir 'src/main/java'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile files('libs/classes.jar')
}

// task to delete the old jar
task deleteOldJar(type: Delete) {
    delete 'release/MyPlugin.jar'
}
// task to export contents as jar
task exportJar(type: Copy) {
    from('build/intermediates/bundles/release/')
    into('release/')
    include('classes.jar')
    rename('classes.jar', 'MyPlugin.jar')
}
exportJar.dependsOn(deleteOldJar, build)
  • Line 1:將 ‘com.android.application’ 改成 ‘com.android.library’。
  • Line 6~9:移除 applicationId、versionCode、versionName 三個參數。
  • Line 15~17:避免預期外的錯誤拖垮整個程式。
  • Line 18~22:原始碼的位置。因為這設定,表示你可以開發一個更複雜的專案,或者方便將專案進行拆分後重複使用部分原始碼。
  • Line 29:這是告訴專案在編譯時必須參考的 libarary,可以發現 Unity Classes 已經自動被寫在這了。如果有匯入其他 library 或在匯入後搬運過 library 檔案的位置,可以再一次檢查這裡有沒有遺漏或錯誤。
  • Line 32~43:這段設定是建立一個 task 用來建構我們所需要的 jar 檔。如果你想變更輸出後的檔案名稱,可以修改 Line 34 & 41。

地雷:整個專案會有超過一個 build.gradle 檔案,千萬記得你要修改的是位於 App 資料夾底下的那個。如 上圖紅框內所標示。

地雷:Line 1 的 ‘com.android.library’ 如果忘了,你將看不到建構後的 jar 檔。

變化:如果你了解 build.gradle 的運作,可以依照需求進行調整。

編譯 .jar 並放到該去的位置

對 build.gradle 做出任何變動之後,檔案上方會浮出一個訊息欄問你是否要同步專案,將 Sync Now 按下去就對了。然後可以打開右側 Gradle 視窗 (如上圖黃框),尋找剛剛建立的 exportJar task,你可能會在 MyPlugin/Tasks/other 中找到它。

雙擊 exportJar task 進行建置,如果建置成功,你可以在 專案資料夾 底下的 app/release 找到成果。複製到 Unity 專案中,便完成了 50% 的開發動作。

Assets/Plugins/Android/MyPlugin.jar

變化:你也可以放在 Assets/Plugin 底下自訂的子資料夾中,但別忘了建立 project.properties。

編寫 Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.douduck08.myplugin"
        android:versionCode="1"
        android:versionName="1.0">
    <uses-sdk android:minSdkVersion="15" />
    <!-- Permissions -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <!-- Application -->
    <application>
        <activity android:name=".PluginActivity"/>
    </application>
</manifest>
  • Line 3:填上 package 名稱
  • Line 6:Plugin 的最小 SDK 版本必須與 Unity 專案不衝突,較簡單的做法是直接設定相同。
  • Line 7~9:Plugin 的權限會由 Unity 在最後合併到 apk 的 Manifest 之中,需要的權限便是在此宣告。
  • Line 12:如果在 Plugin 有自訂的 Activity,必須宣告在這,之後才能在 App 中使用。

變化:如果不需要權限,也沒有自訂 Activity,直接省略 Manifest 是可行的。

變化:如果 Plugin 的 Java 部分繼承了 UnityPlayerActivity,設計了自己的 Unity player 來取代原本的 Player,Manifest.xml 會需要宣告許多其他的資訊,詳細內容參考官方文件

編寫 C# Script 作為 Plugin API

下方的程式碼對應上方 Java class 中的 StartPluginActivity()

using UnityEngine;
using System.Collections;
public class PluginAPI
{
  public static void StartActivity(string ProductName, double Price)
  {
    AndroidJavaClass unity = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
    AndroidJavaObject unityActivity = unity.GetStatic<AndroidJavaObject> ("currentActivity");

    AndroidJavaClass 使用 AndroidJavaClass 取用 com.unity3d.player.UnityPlayer 這個 class = new AndroidJavaClass("com.douduck08.myplugin.PluginInterface");
    plugin.CallStatic("StartPluginActivity", unityActivity);
  }
}
  • Line 7:使用 AndroidJavaClass 取用 com.unity3d.player.UnityPlayer 這個 class,這行程式碼只會找出對應的 class,不會進行實體化。因為沒有實體化,所以待會只能使用靜態成員。
  • Line 8:使用 GetStatic<AndroidJavaObject> 取得 UnityPlayer 中的靜態成員 currentActivity。通常除了 int / double / string 等一般變數,都統一用 AndroidJavaObject 來接收回傳的 class 型別物件。AndroidJavaObject 通常用於有實體的對象,接收函式的回傳結果便是一個有實體的物件。
  • Line 10:使用 AndroidJavaClass 取用 MyPlugin 自訂的 Java class。
  • Line 11:呼叫 PluginInterface 中的靜態函式 StartPluginActivity。

地雷:使用 AndroidJavaClass/AndroidJavaObject 進行呼叫時,常常會找不到對應的 class/method,強烈建議有隨時檢查 ErrorLog 來得知定應的問題,可能性有:

  • 名稱錯誤:包括前方的 com.package.name 也有可能錯誤,必須對應在 Android Studio 中的檔案結構。
  • 回傳型態錯誤: CallStatic/Call 皆能透過多型指定回傳型態,如果型態錯誤也會找不到對應方法。
  • Manifest 未宣告:如果找不到的對象是 Activity,或許錯誤是在於 Manifest.xml 沒有宣告或名稱有誤。

地雷:直接使用 new AndroidJavaObject(string) 建構 Java 物件時,有可能因為 Android 的流程而要求一個額外的執行緒,這在單執行緒為主體的 Unity 中可能會出現錯誤,需要在 Java 中手動宣告新的執行緒。

變化:直接使用 new AndroidJavaObject(string) 時,會使用對應的建構子建構 Java 物件,跟使用 Call() 一樣,可以傳入不定數量的參數來使用不同的建構子。

變化:如果不完全有自訂類別的必要,甚至可以完全只透過 AndroidJavaClass/AndroidJavaObject 來實現一部份的 Android 功能呼叫,而不需要編寫 Plugin。

寫在結尾

我試著記下我所記得的,關於我在開發 Unity Android Plugin 的過程中,所注意到的細節。有些是因為我對學習資源有所誤解,有些則是一般的教學範例不合我的需求,也有些是我不小心遺漏,卻導致我要花上許多時間檢查的錯誤。

希望這篇記錄可以幫助其他人更容易解決跟我相同或相似的疑問。

 

參考

官方文件 http://docs.unity3d.com/Manual/PluginsForAndroid.html

廣告

2 thoughts on “開發 Unity Android Plugin – 從零開始

  1. hi~ 想請問一下
    > 編寫你的 Java classes 裡面的一段code
    public static void StartPluginActivity(Activity activity) {
    Intent myIntent = new Intent(activity, PluginActivity.class);
    activity.startActivity(myIntent);
    }

    這裡的 PluginActivity 是甚麼東西?
    跟著步驟到這裡就卡住了QQ

    喜歡

    • PluginActivity 我是指 Plugin 中所需要的 Android Activity,需要另外編寫,StartPluginActivity 是為了啟動這個 Activity 而存在的 static 函式。
      PluginActivity 存在的目的是為了避免繼承 UnityPlayerActivity 的同時又可以使用 Android Activity 來設計功能。如果你的功能不需要有 Activity 運作的話,這個函式就不需要了,可以直接移除。

      我這篇文章中的 code 並無法構成一個有功能的 Plugin,只是列出一個可行架構。

      喜歡

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s