本實作主要參考 nsdchat,出處應來自 Android Developers 官方,但據下方文章作者提及官方好像已經移除且也找不到了,這是舊的連結,不過官方依然有 sample code 只是比較簡化就是。
本人參考後無法正常運行,且範例是沒有區別 server 與 client ,在 trace 過程中有點難理解,故自行簡化重新修改成兩種版本:dns_server
與dns_client
,可用兩台不同裝置分別安裝來測試看看:
兩支不同專案實作時,記得在 AndroidManifest.xml
加上
<uses-permission android:name="android.permission.INTERNET"/>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_registger"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:textAllCaps="false"
android:text="Register Service"/>
<Button
android:id="@+id/btn_unregistger"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/btn_registger"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="Unregister Service"
android:textAllCaps="false"/>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity
package com.example.nsdchat_server_test;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
//region Properties
public static final String TAG = "MainActivity";
public static final String SERVICE_NAME = "NsdChat_server"; // 要註冊的 service 名稱
public static final String SERVICE_TYPE = "_http._tcp."; // 要註冊的 service 類型
public static final int SERVICE_PORT = 2222; // 要註冊的 service port
private TextView tv_state;
private Button btn_registger;
private Button btn_unregistger;
private NsdManager mNsdManager;
private static NsdServiceInfo mServiceInfo;
private NsdManager.RegistrationListener mRegistrationListener;
//endregion
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNsdManager = (NsdManager) getSystemService(Context.NSD_SERVICE);
initView();
initListener();
}
@Override
protected void onStop() {
tearDown();
super.onStop();
}
private void initView() {
tv_state = (TextView) findViewById(R.id.tv_state);
btn_registger = (Button) findViewById(R.id.btn_registger);
btn_unregistger = (Button) findViewById(R.id.btn_unregistger);
}
private void initListener() {
btn_registger.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mServiceInfo != null) {
updateRegisterStateUI("您已註冊過 service: " + mServiceInfo.getServiceName());
return;
}
NsdServiceInfo serviceInfo = createServiceInfo();
registerService(serviceInfo);
}
});
btn_unregistger.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tearDown();
}
});
}
private void registerService(NsdServiceInfo serviceInfo) {
mRegistrationListener = new NsdManager.RegistrationListener() {
@Override
public void onServiceRegistered(NsdServiceInfo nsdServiceInfo) {
String serviceName = nsdServiceInfo.getServiceName();
Log.d(TAG, "Service registered: " + serviceName);
mServiceInfo = nsdServiceInfo;
updateRegisterStateUI("註冊 service 成功: " + serviceName);
}
@Override
public void onRegistrationFailed(NsdServiceInfo nsdServiceInfo, int i) {
Log.d(TAG, "Service registration failed: " + i);
updateRegisterStateUI("Service registration failed");
}
@Override
public void onServiceUnregistered(NsdServiceInfo nsdServiceInfo) {
Log.d(TAG, "Service unregistered: " + nsdServiceInfo.getServiceName());
updateRegisterStateUI("您已註銷 service");
}
@Override
public void onUnregistrationFailed(NsdServiceInfo nsdServiceInfo, int i) {
Log.d(TAG, "Service unregistration failed: " + i);
updateRegisterStateUI("Service unregistration failed");
}
};
mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
}
private NsdServiceInfo createServiceInfo() {
NsdServiceInfo serviceInfo = new NsdServiceInfo();
serviceInfo.setServiceName(SERVICE_NAME);
serviceInfo.setServiceType(SERVICE_TYPE);
serviceInfo.setPort(SERVICE_PORT);
return serviceInfo;
}
private void tearDown() {
if (mRegistrationListener != null) {
try {
mNsdManager.unregisterService(mRegistrationListener);
} finally {
}
mRegistrationListener = null;
mServiceInfo = null;
}
}
private void updateRegisterStateUI(String msg) {
new Thread(new Runnable() {
@Override
public void run() {
tv_state.post(new Runnable() {
@Override
public void run() {
tv_state.setText(msg);
}
});
}
}).start();
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_serviceName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp">
<TextView
android:id="@+id/tv_serviceName_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:textAllCaps="false"
android:text="Search Service Name: "
android:gravity="start"/>
<TextView
android:id="@+id/tv_serviceName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_serviceName_title"
app:layout_constraintEnd_toEndOf="parent"
android:textAllCaps="false"
android:textStyle="bold"
android:text="SERVICE NAME"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<Button
android:id="@+id/btn_discovery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/cl_serviceName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"
android:textAllCaps="false"
android:text="Discovery"/>
<Button
android:id="@+id/btn_connectService"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/btn_discovery"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:textAllCaps="false"
android:lines="3"
android:text="Connect service\n(or Bind Service)\n(or Resolve Service)"/>
<Button
android:id="@+id/btn_disconnect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/btn_connectService"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:textAllCaps="false"
android:text="Disconnect"/>
<ScrollView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
app:layout_constraintTop_toBottomOf="@id/btn_disconnect"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/cl_connectedState"
android:background="#B5B5B5">
<TextView
android:id="@+id/tv_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</ScrollView>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_connectedState"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/tv_connectedState_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Connect state:"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="@+id/tv_connectedState"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/tv_connectedState_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:textColor="#FF0000"
android:lines="5"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity
package com.example.nsdchat_client_test;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
//region Properties
public static final String TAG = "MainActivity";
public static final String FIND_SERVICE_NAME = "NsdChat_server"; // 要尋找的 service 名稱
public static final String FIND_SERVICE_TYPE = "_http._tcp."; // 要尋找的 service 類型
private TextView tv_serviceName;
private TextView tv_state;
private Button btn_discovery;
private Button btn_connectService;
private Button btn_disconnect;
private TextView tv_connectedState;
private NsdManager mNsdManager;
private NsdManager.ResolveListener mResolveListener;
private NsdManager.DiscoveryListener mDiscoveryListener;
private static NsdServiceInfo mTryConnServiceInfo; // 符合 service 名稱可以嘗試 connect 的 service
private static NsdServiceInfo mConnServiceInfo; // 已 connect 的 service
//endregion
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNsdManager = (NsdManager) getSystemService(Context.NSD_SERVICE);
initView();
initListener();
tv_serviceName.setText(FIND_SERVICE_NAME);
}
private void initView() {
tv_serviceName = (TextView) findViewById(R.id.tv_serviceName);
tv_state = (TextView) findViewById(R.id.tv_state);
btn_discovery = (Button) findViewById(R.id.btn_discovery);
btn_connectService = (Button) findViewById(R.id.btn_connectService);
btn_disconnect = (Button) findViewById(R.id.btn_disconnect);
tv_connectedState = (TextView) findViewById(R.id.tv_connectedState);
}
private void initListener() {
//region Discovery 按鈕
btn_discovery.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
stopDiscovery();
tv_state.setText("");
tv_connectedState.setText("");
startDiscovery();
}
});
//endregion
//region Connect Service 按鈕
btn_connectService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 要找的 service name 不存在
if (mTryConnServiceInfo == null) {
tv_connectedState.setText("service name not exist.");
return;
}
// 已存在 connected service
else if (mConnServiceInfo != null) {
String msg = getConnectedServiceInfo(mConnServiceInfo);
tv_connectedState.setText("已存在 connected service: " + "\n" + msg);
return;
}
startConnectService(mTryConnServiceInfo);
}
});
//endregion
//region Disconnect 按鈕
btn_disconnect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tv_connectedState.setText("");
mTryConnServiceInfo = null;
mConnServiceInfo = null;
// 刷新列表
reloadDiscovery();
}
});
//endregion
}
private void startDiscovery() {
mDiscoveryListener = new NsdManager.DiscoveryListener() {
/** 開始搜尋 service */
@Override
public void onDiscoveryStarted(String regType) {
Log.d(TAG, "Service discovery started");
}
/** 搜尋 service 中 */
@Override
public void onServiceFound(NsdServiceInfo service) {
Log.d(TAG, "Service discovery success: " + service);
if (!service.getServiceType().equals(FIND_SERVICE_TYPE)) {
Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
}
String list_msg;
// 若已存在 connected service, 且 service 名字相同 (目前使用名稱來判斷 service 是否相同)
if (mConnServiceInfo != null && service.getServiceName().equals(FIND_SERVICE_NAME)) {
list_msg = String.valueOf(service) + " (已連接)" + "\n\n";
} else {
list_msg = String.valueOf(service) + "\n\n";
}
updateDiscoveryStateUI(list_msg);
if (service.getServiceName().equals(FIND_SERVICE_NAME)) {
mTryConnServiceInfo = service;
}
}
/** service 斷線 */
@Override
public void onServiceLost(NsdServiceInfo service) {
Log.e(TAG, "service lost: " + service);
if (mConnServiceInfo != null) {
tv_connectedState.setText("service lost");
mTryConnServiceInfo = null;
mConnServiceInfo = null;
}
// 刷新列表
reloadDiscovery();
}
/** 停止搜尋 service */
@Override
public void onDiscoveryStopped(String serviceType) {
Log.i(TAG, "Discovery stopped: " + serviceType);
}
/** 開始搜尋 service fialed */
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
}
/** 停止搜尋 service failed */
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
}
};
mNsdManager.discoverServices(FIND_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
}
private void startConnectService(NsdServiceInfo service) {
mResolveListener = new NsdManager.ResolveListener() {
/** connect service 失敗 */
@Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
Log.e(TAG, "Resolve failed" + errorCode);
tv_connectedState.setText("Resolve failed: " + serviceInfo.getServiceName());
}
/** connect service 成功 */
@Override
public void onServiceResolved(NsdServiceInfo serviceInfo) {
Log.e(TAG, "Resolve Succeeded. " + serviceInfo);
mConnServiceInfo = serviceInfo;
// 刷新 Connect state
String msg = getConnectedServiceInfo(serviceInfo);
tv_connectedState.setText("Connect Succeeded" + "\n" + msg);
// 刷新列表
reloadDiscovery();
}
};
mNsdManager.resolveService(service, mResolveListener);
}
private void stopDiscovery() {
if (mDiscoveryListener != null) {
try {
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
} finally {
}
mDiscoveryListener = null;
}
}
private void updateDiscoveryStateUI(String msg) {
new Thread(new Runnable() {
@Override
public void run() {
tv_state.post(new Runnable() {
@Override
public void run() {
tv_state.append(msg);
}
});
}
}).start();
}
/**
* 重新刷新搜尋 service 列表
*/
private void reloadDiscovery() {
new Thread(new Runnable() {
@Override
public void run() {
btn_discovery.post(new Runnable() {
@Override
public void run() {
stopDiscovery();
tv_state.setText("");
startDiscovery();
}
});
}
}).start();
}
private String getConnectedServiceInfo(NsdServiceInfo serviceInfo) {
return
"service name: " + serviceInfo.getServiceName() + "\n" +
"host_from_server: " + serviceInfo.getHost() + "\n" +
"port from server: " + serviceInfo.getPort();
}
}
在測試時注意 dns_client 的 FIND_SERVICE_NAME
須與 dns_server 的 SERVICE_NAME
一致,connect 才能 work
dns_server 的 SERVICE_PORT
隨機即可。
Listener already in use (Service Discovery)
Use network service discovery (官方)
實作相關
Android 4.4 之前,儲存空間:
Aug 21, 2023//設定隱藏標題 getSupportActionBar().hide(); //設定隱藏狀態 getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN); Ref. 【Android】隱藏標題列(Title Bar)與狀態列(Status Bar)
Jul 28, 2023角色GATT Server/ Peripheral: BLE device GATT Client/ Central: 手機 未配對情況下,BLE device 會一直廣播,直到手機發送 request 給 BLE device,等待 BLE device 回傳 response。 定義: uuid 0x2800 is Service uuid 0x2803 is Characteristics Ref.
May 25, 2023簡述 Service可以在背景不斷的工作,直到停止或是系統無法提供資源為止。 Service 需要透過某Activity 或者其他Context 物件來啟動。 Service不需要和 user 互動,所以沒有操作介面。 生命週期與Activity是各自獨立的,Activity就算關閉,Service仍然可以繼續執行。 類似 BroadcastReceiver,需要定義一個繼承 Service 的類別,並覆寫其中的生命週期函數,最後在AndroidManifest.xml中宣告才能使用 Service可以同時支援 Started 與 Bind 兩種模式。在這種情況下,Service 需要等到兩種模式都被關閉才會觸發onDestroy()事件。 Service 只有第一次被啟動時,會執行onCreate(),若重複啟動則不會執行onCreate()。 Service的運作優先權相當的高,一般來說除非系統資源耗盡,否則 Android 不會主動關閉一個已被啟動的Service。一旦系統有足夠的資源,被 Android 關閉的Service也會被重新啟動。 兩者都需要在AndroidManifest.xml宣告
May 9, 2023or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up