天天看點

[unity3d]Unity3D與android互動----建構android插件

為android建構一個插件

extern "C"   

{  

  float FooPluginFunction ();  

}   

通過C#腳本使用插件

建構了共享庫後,必須把共享庫複制到unity3d工程中的Assets->Plugins->Android目錄下。(沒有該目錄的話,自己依次建立。)

當你在unity3d中在C#腳本中定義如下的函數時,unity3d就能通過名稱找到共享庫

[DllImport ("PluginName")]  

private static extern float FooPluginFunction ();   

部署

對于要部署到多個平台的項目,項目工程中必須包含各個平台所需要的插件(例如:libPlugin.so用于android平台,Plugin.bundle用于mac平台,Plugin.dll用于windows平台)。unity3d會自動為目标平台選擇正确的插件。

使用java插件

android插件機制同樣允許使用java來與android系統進行互動。

為android建構一個java插件

在native代碼中使用java插件

要找到你的java代碼,必須要能通路到java虛拟機。幸運的是,可以通過在c/c++代碼中添加如下函數來很容易的實作這種通路:

jint JNI_OnLoad(JavaVM* vm, void* reserved)   

  JNIEnv* jni_env = 0;  

  vm->AttachCurrentThread(&jni_env, 0);  

這個是從c/c++調用java所必需的。JNI超越了本文檔的範疇,不做詳細解釋。通常情況下,先找到類的定義,然後解析類的構造方法(<init>)并建立類的執行個體,如下面例子所示:

jobject createJavaObject(JNIEnv* jni_env)   

  jclass cls_JavaClass = jni_env->FindClass("com/your/java/Class");          // 找到類定義  

  jmethodID mid_JavaClass = jni_env->GetMethodID (cls_JavaClass, "<init>",  "()V");        // 找到構造方法  

  jobject obj_JavaClass = jni_env->NewObject(cls_JavaClass, mid_JavaClass);      // 建立對象執行個體  

  return jni_env->NewGlobalRef(obj_JavaClass);                       // return object with a global reference  

通過幫助類來使用java插件

使用AndroidJNIHelper 和AndroidJNI會減輕些使用原始JNI的痛苦。

AndroidJNIHelper 和AndroidJNI自動完成了很多任務(指找到類定義,構造方法等),并且使用緩存使調用java速度更快。AndroidJavaObject和AndroidJavaClass基于AndroidJNIHelper 和AndroidJNI建立,但在處理自動完成部分也有很多自己的邏輯,這些類也有靜态的版本,用來通路java類的靜态成員。

你可以選擇任意你喜歡的方式來替代這種原始JNI的做法,可以通過 AndroidJNI類,也可以通過AndroidJNIHelper和AndroidJNI, 最後也可以使用 AndroidJavaObject/AndroidJavaClass,這樣會有最大程度的自動完成和最大的便利性。

Call方法

Get域的值

Set域的值

Call分為兩類,調用void方法和調用非void傳回類型的方法,會使用一個泛型類型來表示這些非void傳回類型的方法的傳回類型;Get和Set也經常帶一個泛型類型用以表示域的類型。

例子1:

//注釋表示是使用原始JNI方法必須做的工作  

 AndroidJavaObject jo = new AndroidJavaObject("java.lang.String", "some_string");   

 // jni.FindClass("java.lang.String");   

 // jni.GetMethodID(classID, "<init>", "(Ljava/lang/String;)V");   

 // jni.NewStringUTF("some_string");   

 // jni.NewObject(classID, methodID, javaString);   

 int hash = jo.Call<int>("hashCode");   

 // jni.GetMethodID(classID, "hashCode", "()I");   

 // jni.CallIntMethod(objectID, methodID);  

 AndroidJavaObject的構造方法至少需要一個參數----你想要執行個體化的類的名稱。類名之後的參數會被對象的構造函數所使用,如上例種的字元串“some_string”,随後的對hashCode方法的Call會傳回一個int型值,這也是為什麼我們會傳一個泛型參數給Call方法。

注意:不能使用點.來初始化一個嵌套類型,内部類必須使用$分隔符,在斜線/或點.分隔的類名中都可以使用。是以當類LayoutParams嵌套在ViewGroup類中時,像android.view.ViewGroup$LayoutParams或者android/view/ViewGroup$LayoutParams,這兩種方式都是可行的。

例子2:

上面有個插件的例子是說擷取目前程式的緩存目錄的,下面這個例子直接用c#代碼做同樣的事情,而不需要任何插件:

AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");   

// jni.FindClass("com.unity3d.player.UnityPlayer");   

AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");   

// jni.GetStaticFieldID(classID, "Ljava/lang/Object;");   

// jni.GetStaticObjectField(classID, fieldID);   

// jni.FindClass("java.lang.Object");   

Debug.Log(jo.Call<AndroidJavaObject>("getCacheDir").Call<string>("getCanonicalPath"));   

// jni.GetMethodID(classID, "getCacheDir", "()Ljava/io/File;"); // or any baseclass thereof!   

// jni.CallObjectMethod(objectID, methodID);   

// jni.FindClass("java.io.File");   

// jni.GetMethodID(classID, "getCanonicalPath", "()Ljava/lang/String;");   

// jni.GetStringUTFChars(javaString);  

例子3:

最後,是一個通過UnitySendMessage方法從java代碼向腳本代碼傳遞資料的小竅門。

using UnityEngine;   

public class NewBehaviourScript : MonoBehaviour   

{   

<span style="white-space:pre">  </span>void Start ()   

<span style="white-space:pre">  </span>{   

    <span style="white-space:pre">  </span>JNIHelper.debug = true;   

    <span style="white-space:pre">  </span>using (JavaClass jc = new JavaClass("com.unity3d.player.UnityPlayer"))   

<span style="white-space:pre">      </span>{   

<span style="white-space:pre">  </span>     jc.CallStatic("UnitySendMessage", "Main Camera", "JavaMessage", "whoowhoo");   

    <span style="white-space:pre">  </span>}   

    }   

    void JavaMessage(string message)   

        Debug.Log("message from java: " + message);   

    }  

這裡我們直接從腳本中調用的,但它确實是在java端發送的消息,它會調回到unity3d的native代碼,傳遞消息到名為"Main Camera"的遊戲對象上去,該對象上綁定的某個腳本中包含有名為"JavaMessage"的方法。

在unity3d中使用java插件的最佳實踐

這一節主要針對那些沒有足夠jni,java和android經驗的人。假設我們在unity3d中使用AndroidJavaObject/AndroidJavaClass來與java互動。

首先就是要注意對AndroidJavaObject/AndroidJavaClass的任何操作都是很費時的(是通過JNI來進行的)。是以為了代碼性能和代碼清晰性,我們強烈建議托管代碼與native/java代碼間的轉換次數保持在最小數量。

你可以定義一個java方法完成所有的事情,然後我們通過AndroidJavaObject/AndroidJavaClass來與這個方法通信和擷取結果,我們的JNI幫助類會盡可能多的緩存資料已提高性能。

//第一次像這樣調用java函數  

AndroidJavaObject jo = new AndroidJavaObject("java.lang.String", "some_string");  // 有點費時  

int hash = jo.Call<int>("hashCode");  //第一次 - 費時  

int hash = jo.Call<int>("hashCode");  // 第二次 - 不那麼費時, 因為我們已經知道了這個java方法,可以直接調用它。  

在使用過後,Mono垃圾回收器會釋放所有建立的AndroidJavaObject和AndroidJavaClass執行個體,但我們還是建議把它們放到using(){}塊中,以保證它們能被盡快的清除掉。除此之外,你無法保證它們會被銷毀掉。如果你設定了AndroidJNIHelper.debug為true,你會在log輸出中看到垃圾回收器的活動記錄。

//擷取系統語言的安全方法  

void Start ()   

    using (AndroidJavaClass cls = new AndroidJavaClass("java.util.Locale"))   

        using(AndroidJavaObject locale = cls.CallStatic<AndroidJavaObject>("getDefault"))   

            Debug.Log("current lang = " + locale.Call<string>("getDisplayLanguage"));   

        }   

}  

也可以直接調用.Dispose()方法確定沒有java對象殘留,c#對象會存活長一點,最終還是會被mono的垃圾回收器回收。

繼承UnityPlayerActivity java代碼

在Unity Android上,我們可以繼承标準的UnityPlayerActivity類(android上Unity Player的主要java類,類似于Unity iOS上的AppController.mm)。

繼承UnityPlayerActivity的一個例子,OverrideExample.java:

package com.company.product;  

import com.unity3d.player.UnityPlayerActivity;  

import android.os.Bundle;  

import android.util.Log;  

public class OverrideExample extends UnityPlayerActivity {  

  protected void onCreate(Bundle savedInstanceState) {  

    // call UnityPlayerActivity.onCreate()  

    super.onCreate(savedInstanceState);  

    // print debug message to logcat  

    Log.d("OverrideActivity", "onCreate called!");  

  }  

  public void onBackPressed()  

  {  

    // instead of calling UnityPlayerActivity.onBackPressed() we just ignore the back button event  

    // super.onBackPressed();  

相關的AndroidManifest.xml檔案如下:

<?xml version="1.0" encoding="utf-8"?>  

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.product">  

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

    <activity android:name=".OverrideExample"  

              android:label="@string/app_name"  

              android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">  

        <intent-filter>  

            <action android:name="android.intent.action.MAIN" />  

            <category android:name="android.intent.category.LAUNCHER" />  

        </intent-filter>  

    </activity>  

  </application>  

</manifest>   

UnityPlayerNativeActivity

同樣我們可以建立UnityPlayerNativeActivity的子類,這與建立UnityPlayerActivity的子類具有相同的效果,但是會有較小的輸入延遲。但是,需要明白的是,NativeActivity是在Gingerbread中引入的(即android 2.3),老的android版本沒有這個特性,因為在NativeActivity中,觸摸事件都是在native代碼中處理的,java視圖正常情況下是無法擷取這些事件的,不過在unity3d中,有允許将事件傳到DalvikVM的轉發機制,要應用這個轉發機制,必須修改manifest檔案如下:

    <activity android:name=".OverrideExampleNative"  

  <meta-data android:name="android.app.lib_name" android:value="unity" />  

  <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />  

要注意activity元素中的.OverrideExampleNative屬性,還有兩條meta-data元素,第一條meta-data元素指明使用unity3d庫libunity.so,第二條meta-data元素使事件能傳遞到你建立的UnityPlayerNativeActivity子類中。

例子

native插件例子

java插件例子

本文轉蓬萊仙羽51CTO部落格,原文連結:http://blog.51cto.com/dingxiaowei/1366208,如需轉載請自行聯系原作者

繼續閱讀