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);