A) 至 jmdns.sourceforge.net 取得 JmDNS 並利用 ant 進行編譯:
1) 以 osx 平台為例,在 terminal 輸入:
svn checkout https://jmdns.svn.sourceforge.net/svnroot/jmdns/tags/jmdns-3.4.1 jmdns
2) 待程式碼下載完成後進入 jmdns 資料夾輸入 :
ant jar
3) 完成之後進入資料夾 ./build/lib/ (此時應該找到檔案 jmdns.jar),並輸入底下指令:
mkdir unjar cd unjar jar xf ../jmdns.jar jar cfm ../jmdns.jar META-INF/MANIFEST.MF javax/
目的在於去除重覆的 class 檔案,以避免 Android dex 工具編譯時出現錯誤。
B) 建立 Unity 專案並設定權限:
1) 將 jmdns.jar 檔案復製到 Unity 專案底下資料夾 (Assets/Plugins/Android)
2) 在 Assets/Plugins/Android 資料夾底下新增檔案 AndroidManifest.xml 並設定內容如下:
<?xml version="1.0" encoding="utf-8"?> <manifest android:versionCode="1" android:versionName="1.0" android:installLocation="preferExternal" package="com.macaronics.jmdns" xmlns:android="http://schemas.android.com/apk/res/android"> <supports-screens android:anyDensity="true" android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" /> <application android:label="@string/app_name" android:icon="@drawable/app_icon" android:debuggable="false"> <activity android:label="@string/app_name" android:name="com.unity3d.player.UnityPlayerProxyActivity" android:screenOrientation="portrait" android:configChanges= "locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:label="@string/app_name" android:name="com.unity3d.player.UnityPlayerActivity" android:screenOrientation="portrait" android:configChanges= "locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale" /> <activity android:label="@string/app_name" android:name="com.unity3d.player.UnityPlayerNativeActivity" android:screenOrientation="portrait" android:configChanges= "locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale" > <meta-data android:name="android.app.lib_name" android:value="unity" /> <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false" /> </activity> <activity android:label="@string/app_name" android:name="com.unity3d.player.VideoPlayer" android:screenOrientation="behind" android:configChanges= "locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale" /> </application> <uses-feature android:glEsVersion="0x20000" /> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/> </manifest>
值得注意的是 uses-permission 的部分,其設定網路使用權限使 JmDNS 在 Android 系統下能夠作用。
C) 撰寫使用 JmDNS 之 Java Class ( jmDNSWrapper.java ):
這裡不直接從 C# 透過 JNI 呼叫 JmDNS 之函數,而是先撰寫一個名為 JmDNSWrapper 的 Java Class 將 JmDNS 的使用簡化,再讓 Unity 透過 JNI 呼叫使用此 JmDNSWrapper Class,目的是簡化 JNI 的使用。此 Class 大略分為五個部分分別敘述如下
1) 初始化 jmDNS 及 wifi service:
public jmDNSWrapper() { Log.i("jmDNSWrapper", "onStart()..."); mActivity =UnityPlayer.currentActivity; if (wifi ==null){ Log.i("jmDNSWrapper", "get wifi manager..."); wifi = (android.net.wifi.WifiManager)mActivity.getSystemService( android.content.Context.WIFI_SERVICE ); } if (lock ==null && wifi !=null){ Log.i("jmDNSWrapper", "lock wifi multicast..."); lock = wifi.createMulticastLock("mylockthereturn"); lock.setReferenceCounted(true); lock.acquire(); } if (mJmDNS ==null){ try{ mJmDNS =JmDNS.create(); } catch(IOException e){ e.printStackTrace(); } } }
2) 結束此 class(停止 jmDNS):
public void dispose() { Log.i("jmDNSWrapper", "onStop()..."); stopListener(); stopService(); if (mJmDNS !=null){ try{ mJmDNS.close(); } catch(IOException e){ e.printStackTrace(); } mJmDNS =null; } if (lock !=null){ Log.i("jmDNSWrapper", "unlock wifi multicast..."); lock.release(); } lock =null; }
3) 發佈/停止 Service :
public void publishService( String strService, String strDeviceName, String strDesc, int port) { if (mJmDNS ==null) return; stopService(); Log.i("jmDNSWrapper", "trying to create service..."); try{ ServiceInfo serviceInfo = ServiceInfo.create(strService, strDeviceName, port, strDesc); mJmDNS.registerService(serviceInfo); } catch(IOException e){ e.printStackTrace(); } } public void stopService() { if (mJmDNS !=null){ Log.i("jmDNSWrapper", "unregisterAllServices()..."); mJmDNS.unregisterAllServices(); } }
4) 搜尋/停止 Service :
public void startListener( String strServiceType, String strEventHandlerObjName) { if (mJmDNS ==null) return; stopListener(); mStrEventHandlerName =strEventHandlerObjName; mListener =new ServiceListener() { @Override public void serviceResolved(ServiceEvent e) { String additions = ""; if (e.getInfo().getInetAddresses() != null && e.getInfo().getInetAddresses().length > 0) { additions = e.getInfo().getInetAddresses()[0].getHostAddress(); } Log.i("jmDNSWrapper", "service resolved: "+ e.getInfo().getQualifiedName()+", port: "+ e.getInfo().getPort()+", ip: "+additions); UnityPlayer.UnitySendMessage( mStrEventHandlerName, "serviceResolved", "{\"name\":\""+ e.getInfo().getQualifiedName()+"\",\"ip\":\""+additions+ "\",\"port\":\""+e.getInfo().getPort()+"\"}"); } @Override public void serviceRemoved(ServiceEvent e) { String additions = ""; if (e.getInfo().getInetAddresses() != null && e.getInfo().getInetAddresses().length > 0) { additions = e.getInfo().getInetAddresses()[0].getHostAddress(); } Log.i("jmDNSWrapper", "service removed: "+e.getName()); UnityPlayer.UnitySendMessage(mStrEventHandlerName, "serviceRemoved", "{\"name\":\""+e.getInfo().getQualifiedName()+ "\",\"ip\":\""+additions+"\",\"port\":\""+ e.getInfo().getPort()+"\"}"); } @Override public void serviceAdded(ServiceEvent e) { mJmDNS.requestServiceInfo(e.getType(), e.getName(), 1); } }; Log.i("jmDNSWrapper", "add service listener..."); mStrServiceType =strServiceType; mJmDNS.addServiceListener( mStrServiceType, mListener); } public void stopListener() { if (mJmDNS !=null && mListener !=null){ Log.i("jmDNSWrapper", "removeServiceListener()..."); mJmDNS.removeServiceListener( mStrServiceType, mListener ); } mListener =null; mStrServiceType =null; }其中使用 UnitySendMessage 來傳送字串資訊給 Unity,字串格式為 JSON 格式。
D) 編譯 jmDNSWrapper.java:
與 jmDNS 相同,將 jmDNSWrapper 編譯成 jar 檔案,此文章假設使用者已安裝 Android SDK 且安裝之 Android 平台版本為 4.0.3 (API Level 15)。
1) 輸入指令,將 java 編譯成 jmDNSWrapper.class :
javac jmDNSWrapper.java -cp ~/Downloads/android-sdk-macosx/platforms/android-15/android.jar:../jmDNS.jar:/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/bin/classes.jar -d .
此指令分別提供三個 jar 檔在編譯時使用。
2) 將 com 資料夾(資料夾按照 jmDNSWrapper.java 裡 "package com.macaronics" 產生)底下的 class 檔壓成 jar 檔案:
jar cvfM jmDNSWrapper.jar com/
3) 將 jmDNSWrapper.jar 檔案復製到 Unity 專案底下資料夾 (Assets/Plugins/Android)
E) Unity 引用 jmDNSWrapper :
這邊的目的是讓 jmDNSWrapper.java 裡的函數能在 C# 環境下呼叫。
public class jmDNSWrapper { AndroidJavaObject mJmDNSWrapperObj = null; public void init() { if (mJmDNSWrapperObj != null){ mJmDNSWrapperObj.Call("dispose"); mJmDNSWrapperObj.Dispose(); mJmDNSWrapperObj = null; } mJmDNSWrapperObj = new AndroidJavaObject("com.macaronics.jmDNSWrapper"); } public void dispose() { if (mJmDNSWrapperObj != null){ mJmDNSWrapperObj.Call("dispose"); mJmDNSWrapperObj.Dispose(); } mJmDNSWrapperObj = null; } public void publishService( string strService, string strDeviceName, string strDesc, int port) { if (mJmDNSWrapperObj == null) return; mJmDNSWrapperObj.Call("publishService", new object[4] { strService, strDeviceName, strDesc, port }); } public void stopService() { if (mJmDNSWrapperObj == null) return; mJmDNSWrapperObj.Call("stopService"); } public void startListener( string strServiceType, string strEventHandlerObjName){ if (mJmDNSWrapperObj == null) return; mJmDNSWrapperObj.Call("startListener", new object[2] { strServiceType, strEventHandlerObjName }); } public void stopListener() { if (mJmDNSWrapperObj == null) return; mJmDNSWrapperObj.Call("stopListener"); } }其利用 Unity 提供的 AndroidJavaObject 來呼叫連結外部 Java 程式碼。
F) 實際範例
最後撰寫一個簡單的範例來實際測試一下 jmDNS,此範例有兩個按鈕,分別是發佈與停止服務,還有將搜尋到的服務顯示在 Console Window。
public class main : MonoBehaviour { string strServiceType = "_macaronics._tcp.local."; jmDNSWrapper pp = null; void Start() { pp = new jmDNSWrapper(); pp.init(); //設定服務名稱及 jmDNS 回呼之物件名稱 pp.startListener(strServiceType, gameObject.name); } void OnDisable() { if (pp != null) { pp.dispose(); pp = null; } } void OnGUI() { if (GUI.Button(new Rect(15, 125, 450, 100), "publish service")) { pp.publishService(strServiceType, "MACARONICS", "BONJOUR TEST FROM MACARONICS", 9000); } if (GUI.Button(new Rect(15, 125 + 100, 450, 100), "stop service")) { pp.stopService(); } } public void serviceResolved(string msg) { //顯示發現的服務 Debug.Log(msg); } public void serviceRemoved(string msg) { //顯示移除的服務 Debug.Log(msg); } }
G) 最後實機測試
很不幸的在 Android Emulator 上無法做 wifi 連線測試,因此需要一台真正的 Android 手機。在 Unity 上設定好 Android 編譯參數後按下 Build And Run,順利的話會看到程式在手機上跑起來了。此時在 Android SDK 裡找到 adb 執行檔,執行 adb logcat 之後就可以看到 Unity 的 Log 訊息。
请问可以把jmDNSWrapper.java源码给一份吗?
回覆刪除ok, 在近日內會把整個專案擺在 github 上
刪除刚才已经把 jmDNSWrapper.java 补全了,没用过 java,费了点时间。
刪除但是我在真机上测试不成功,没有能发现服务,也许是服务发布的地方出了问题,正在查原因。
有一个问题 strServiceType = "_macaronics._tcp.local."
这个 type 是 bonjour 里面的 type 和 replyDomain 合在一起吗?
测试成功了,发现一个问题,如果主机名字里面含有.的话,是无法被发现了,比如:
刪除publishService(strServiceType,
"MACARONICS.lan",
"BONJOUR TEST FROM MACARONICS",
9000);