說在前面的話:
“WIFI”—工作這麼久以來第一次接觸WIFI的知識
在之前翻譯Google 官方課程所記錄的《Web Apps》《Volley》時,就深感計算機網絡知識的欠缺,在翻譯到這一章的時候更感力不從心
因爲是學習資料看的是英文,很多專業單詞藉助外力(必應詞典,有道,谷歌翻譯)查,結果還是不名所以,模模糊糊的詞義多了,想理解句子含義就更別提了,有的時候學習一篇Goolg的課程,粗略看一遍挺簡單的,翻譯成通順的中文句子,難,將段落翻譯的有條有理,按着作者的行文邏輯將文章複述,更難,更別說消化吸收每一篇知識舉一反三了
從五月份已經開始刷計算機網絡相關書籍,希望捋順一遍計算機網絡的知識,再回過頭來把這裏的章節弄懂。
補充令人慶幸的一點:
還好,代碼是相通的,每次翻譯Google 的教程,各個渠道查,英文句子還是看不懂的,猜,猜不透的,看到代碼部分,就豁然開朗了
嗯,代碼是相通的,一切的疑難,從代碼入手是最快的;但我認爲這個階段慢下來最好,捋順邏輯,跟着Google的示例學習:
寫代碼的思路(學習任何編程都是從最簡單的入手,比如Volley,先學會發送最簡單的Request,接着使用標準的Request,最後自定義Request時候,有什麼注意事項,跟Google學習使用單例Request等等)
模塊的設計(分層思想,數據層,控制層,視圖層,每一層如何增刪改查)
分層的思想可以追溯到物理世界計算機網絡系統的設計,大到全球ISP和國家ISP,地區ISP,小到校園,公司,個人等等,爲什麼不提供一層ISP來服務於不同的端系統?網絡層級爲什麼劃分這麼多層?目的都是爲了服務於整個系統不同級別的“端”系統,抽象到軟件編程中,不管MVC MVP,MVVM等分層模式,其實都是爲了寫出”機器執行的快又好,後續的同事能更好的開發和維護“的項目,如果心中有“層”的概念,那麼作爲一個優秀的開發者,不論接手“爛”代碼,還是“好”代碼,都可以遊刃有餘,從容開發和維護了。
想改變世界,先改變自己的適應能力。
本文介紹
本章是翻譯自Google 官方課程 Building Apps with Connectivity & the Cloud 第一節
限於篇幅和文章連續性,本文只翻譯Connecting Devices Wirelessly 連接無線設備篇
本篇文章 的demo爲 NSDChat,是一款使用無線網絡服務所做的聊天室
文末有代碼示例
連接無線網絡
這一節所有代碼,最低版本4.1 對應API 16
使用網絡發現服務
這節將包括什麼
- 註冊網絡服務
- 發現網絡服務
- 連接服務
- 解除服務
本節目的旨在如何在不同設備連接同一個應用
註冊服務
1. 創建NsdServiceInfo對象,這個對象有什麼用呢 ?
看下段示例:
public void registerService(int port) {
// Create the NsdServiceInfo object, and populate it.
NsdServiceInfo serviceInfo = new NsdServiceInfo();
// The name is subject to change based on conflicts
// with other services advertised on the same network.
serviceInfo.setServiceName("NsdChat");
serviceInfo.setServiceType("_http._tcp.");
serviceInfo.setPort(port);
....
}
通過NsdServiceInfo設置了服務名,服務類型和端口號,這就決定了如何連接此服務
如果我們使用socket編程接口,我們初始該socket的時候可以設置端口
```
public void initializeServerSocket() {
// Initialize a server socket on the next available port.
mServerSocket = new ServerSocket(0);
// Store the chosen port.
mLocalPort = mServerSocket.getLocalPort();
...
}
```
定義好NsdServiceInfo對象後,接着我們得去實現一個接口,哪個接口呢?
2. 實現RegistrationListenter接口
這個接口包含了上一步我們註冊成功或失敗的回調
public void initializeRegistrationListener() {
mRegistrationListener = new NsdManager.RegistrationListener() {
@Override
public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) {
// Save the service name. Android may have changed it in order to
// resolve a conflict, so update the name you initially requested
// with the name Android actually used.
mServiceName = NsdServiceInfo.getServiceName();
}
@Override
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Registration failed! Put debugging code here to determine why.
}
@Override
public void onServiceUnregistered(NsdServiceInfo arg0) {
// Service has been unregistered. This only happens when you call
// NsdManager.unregisterService() and pass in this listener.
}
@Override
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Unregistration failed. Put debugging code here to determine why.
}
};
}
現在我們已經完成註冊的步驟了(創建NsdService對象,設置註冊成功失敗的監聽)
3. 調用registerService()方法
注意這個方法是 異步的,所以,所有需要執行在 此服務 啓動 之後的操作,都必須在 onServiceRegistered()中
public void registerService(int port) {
NsdServiceInfo serviceInfo = new NsdServiceInfo();
serviceInfo.setServiceName("NsdChat");
serviceInfo.setServiceType("_http._tcp.");
serviceInfo.setPort(port);
mNsdManager = Context.getSystemService(Context.NSD_SERVICE);
mNsdManager.registerService(
serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
}
2. 發現網絡上的服務
網絡上運行着各式各樣的服務,想在app中發現那些服務,我們需要監聽服務廣播,去分析哪些服務是可用的,並且過濾掉那些不能正常工作的服務
發現服務 只需兩步:
- 設置發現監聽
- 加入單例的異步編程接口discoverServices()
1. 設置內部類實現NsdManager.DiscoveryListener
public void initializeDiscoveryListener() {
// Instantiate a new DiscoveryListener
mDiscoveryListener = new NsdManager.DiscoveryListener() {
// Called as soon as service discovery begins.
@Override
public void onDiscoveryStarted(String regType) {
Log.d(TAG, "Service discovery started");
}
@Override
public void onServiceFound(NsdServiceInfo service) {
// A service was found! Do something with it.
Log.d(TAG, "Service discovery success" + service);
if (!service.getServiceType().equals(SERVICE_TYPE)) {
// Service type is the string containing the protocol and
// transport layer for this service.
Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
} else if (service.getServiceName().equals(mServiceName)) {
// The name of the service tells the user what they'd be
// connecting to. It could be "Bob's Chat App".
Log.d(TAG, "Same machine: " + mServiceName);
} else if (service.getServiceName().contains("NsdChat")){
mNsdManager.resolveService(service, mResolveListener);
}
}
@Override
public void onServiceLost(NsdServiceInfo service) {
// When the network service is no longer available.
// Internal bookkeeping code goes here.
Log.e(TAG, "service lost" + service);
}
@Override
public void onDiscoveryStopped(String serviceType) {
Log.i(TAG, "Discovery stopped: " + serviceType);
}
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
};
}
注意上面的示例在onServiceFound()回調中所做的三次檢查:
1. !service.getServiceType().equals(SERVICE_TYPE)
2. service.getServiceName().equals(mServiceName)
3. service.getServiceName().contains(“NsdChat”)
當設置好監聽後,調用discoverServices(),通過此前應用中所設置的尋找參數:類型,協議,和監聽
mNsdManager.discoverServices(
SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
3. 連接網絡上的服務
ok,當app發現了網絡上的某個服務,想連接該項服務的時候,
需要使用resolveService()方法,接着實現NsdManager.ResolveListener接口
public void initializeResolveListener() {
mResolveListener = new NsdManager.ResolveListener() {
@Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Called when the resolve fails. Use the error code to debug.
Log.e(TAG, "Resolve failed" + errorCode);
}
@Override
public void onServiceResolved(NsdServiceInfo serviceInfo) {
Log.e(TAG, "Resolve Succeeded. " + serviceInfo);
if (serviceInfo.getServiceName().equals(mServiceName)) {
Log.d(TAG, "Same IP.");
return;
}
mService = serviceInfo;
int port = mService.getPort();
InetAddress host = mService.getHost();
}
};
}
一旦連接上該服務,我們的應用將接受到服務的信息,包括端口號和IP地址
應用關閉的時候解除服務
當應用關閉或退出至後臺的時候,我們就需要停止NSD功能。不關閉?ok,那應用將一直去發現符合要求的服務並嘗試連接,費電不說,關鍵是手機燙啊!
預覽整個activity生命週期,並在onstart和onstop中插入服務相關的廣播:
//In your application's Activity
@Override
protected void onPause() {
if (mNsdHelper != null) {
mNsdHelper.tearDown();
}
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
if (mNsdHelper != null) {
mNsdHelper.registerService(mConnection.getLocalPort());
mNsdHelper.discoverServices();
}
}
@Override
protected void onDestroy() {
mNsdHelper.tearDown();
mConnection.tearDown();
super.onDestroy();
}
// NsdHelper's tearDown method
public void tearDown() {
mNsdManager.unregisterService(mRegistrationListener);
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
}
使用WI-FI創建P2P連接
這一節將瞭解:
- 如何設置權限
- 設置廣播接收者和Peer-to-Peer管理器
- 初始化Peer發現
- 拿Peers的列表
- 連接Peer
1. 設置應用權限
爲了使用WI-FI P2P功能,需要添加如下權限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.nsdchat"
...
<uses-permission
android:required="true"
android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission
android:required="true"
android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission
android:required="true"
android:name="android.permission.INTERNET"/>
...
2. 設置廣播接收者和文件管理器
爲了使用WI-FI P2P功能,還需要監聽廣播事件:
WIFI_P2P_STATE_CHANGED_ACTION 標明WI-FI P2P 功能是否可用
WIFI_P2P_PEERS_CHANGED_ACTION 標明文件列表已經改變
WIFI_P2P_CONNECTION_CHANGED_ACTION 標明WI-FI P2P連接已經改變
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 標明設備配置已經改變
接着只需在IntentFilter中加入這些行爲,就可以過濾到其他不需要的行爲,接收我們感興趣的Intent了
private final IntentFilter intentFilter = new IntentFilter(); ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Indicates a change in the Wi-Fi P2P status. intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); // Indicates a change in the list of available peers. intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); // Indicates the state of Wi-Fi P2P connectivity has changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); // Indicates this device's details have changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); ... }
獲得WifiP2pManager對象
在oncreate()方法的最後,初始化WifiP2pManager對象,接着獲取WifiP2pManager.Channel對象,通過Channel可以連接WI-FI P2P 服務
@Override Channel mChannel; public void onCreate(Bundle savedInstanceState) { .... mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); mChannel = mManager.initialize(this, getMainLooper(), null); }
創建BroadcastReceiver
創建廣播可以監聽WI-FI P2P的狀態, 當WI-FI 的狀態發生改變時,我們在onReceive()中做相應的處理:
@Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { // Determine if Wifi P2P mode is enabled or not, alert // the Activity. int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1); if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { activity.setIsWifiP2pEnabled(true); } else { activity.setIsWifiP2pEnabled(false); } } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // The peer list has changed! We should probably do something about // that. } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { // Connection state changed! We should probably do something about // that. } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { DeviceListFragment fragment = (DeviceListFragment) activity.getFragmentManager() .findFragmentById(R.id.frag_list); fragment.updateThisDevice((WifiP2pDevice) intent.getParcelableExtra( WifiP2pManager.EXTRA_WIFI_P2P_DEVICE)); } }
註冊和取消廣播接收
在onResume()和onPause()中分別註冊和取消廣播
/** register the BroadcastReceiver with the intent values to be matched */ @Override public void onResume() { super.onResume(); receiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this); registerReceiver(receiver, intentFilter); } @Override public void onPause() { super.onPause(); unregisterReceiver(receiver); }
3. 初始化文件發現服務
通過discoverPeers()可以開啓 查找附近支持WI-FI P2P的設備
discoverPeers()裏有倆重要的參數:
- 上一步創建的WifiP2pManager.Channel
WifiP2pManager.ActionListener 系統級回調,發現成功與否
mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { // Code for when the discovery initiation is successful goes here. // No services have actually been discovered yet, so this method // can often be left blank. Code for peer discovery goes in the // onReceive method, detailed below. } @Override public void onFailure(int reasonCode) { // Code for when the discovery initiation fails goes here. // Alert the user that something went wrong. } });
通過上述兩步只是初始化了文件發現服務,discoverPeers()方法啓動發現進程,並且會立刻得到返回結果,
獲得文件列表
1. 第一步實現WifiP2pManager.PeerListListener 接口,它可以提供已連接的WI-FI
P2P 信息:
private List peers = new ArrayList();
...
private PeerListListener peerListListener = new PeerListListener() {
@Override
public void onPeersAvailable(WifiP2pDeviceList peerList) {
// Out with the old, in with the new.
peers.clear();
peers.addAll(peerList.getDeviceList());
// If an AdapterView is backed by this data, notify it
// of the change. For instance, if you have a ListView of available
// peers, trigger an update.
((WiFiPeerListAdapter) getListAdapter()).notifyDataSetChanged();
if (peers.size() == 0) {
Log.d(WiFiDirectActivity.TAG, "No devices found");
return;
}
}
}
2. 第二步當接收到匹配WIFI_P2P_PEERS_CHANGED_ACTION 行爲的Intent後,在廣播的onReceiver()中調用用requestPeers(),
public void onReceive(Context context, Intent intent) {
...
else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
// Request available peers from the wifi p2p manager. This is an
// asynchronous call and the calling activity is notified with a
// callback on PeerListListener.onPeersAvailable()
if (mManager != null) {
mManager.requestPeers(mChannel, peerListListener);
}
Log.d(WiFiDirectActivity.TAG, "P2P peers changed");
}...
}
連接一個網絡
爲了連接一個同級網絡,創建WifiP2pConfig 對象,並且複製數據至WifiP2pDevice (你想連接到哪個設備),接着調用connect()方法
@Override
public void connect() {
// Picking the first device found on the network.
WifiP2pDevice device = peers.get(0);
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
config.wps.setup = WpsInfo.PBC;
mManager.connect(mChannel, config, new ActionListener() {
@Override
public void onSuccess() {
// WiFiDirectBroadcastReceiver will notify us. Ignore for now.
}
@Override
public void onFailure(int reason) {
Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.",
Toast.LENGTH_SHORT).show();
}
});
}
如果想監聽連接狀態,需要實現 WifiP2pManager.ConnectionInfoListener,在onConnectionInfoAvailable()中將返回WI-FI P2P連接狀態
@Override
public void onConnectionInfoAvailable(final WifiP2pInfo info) {
// InetAddress from WifiP2pInfo struct.
InetAddress groupOwnerAddress = info.groupOwnerAddress.getHostAddress());
// After the group negotiation, we can determine the group owner.
if (info.groupFormed && info.isGroupOwner) {
// Do whatever tasks are specific to the group owner.
// One common case is creating a server thread and accepting
// incoming connections.
} else if (info.groupFormed) {
// The other device acts as the client. In this case,
// you'll want to create a client thread that connects to the group
// owner.
}
}
現在返回到onReceive()方法中,當接收到匹配 WIFI_P2P_CONNECTION_CHANGED_ACTION的Intent,調requestConnectionInfo(),這是異步的調用,所以結果將被上一部設置的連接監聽(ConnectionInfoListener)所接收;
...
} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
if (mManager == null) {
return;
}
NetworkInfo networkInfo = (NetworkInfo) intent
.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
if (networkInfo.isConnected()) {
// We are connected with the other device, request connection
// info to find group owner IP
mManager.requestConnectionInfo(mChannel, connectionListener);
}
...
使用WIFI P2P 發現服務
這節將涉及:
- 設置Mainfest文件
- 加入本地服務
- 發現附近的服務
在前面兩節,我們已經學會如何發現服務並連接本地網絡,然後,使用WI-FI P2P發現服務允許你發現附近的設備,不需要連接網絡,這樣就可以不需要網絡從而連接兩個服務。
設置Manifest文件
爲了使用WI-FI P2P功能,加入如下權限:
<uses-permission
android:required="true"
android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission
android:required="true"
android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission
android:required="true"
android:name="android.permission.INTERNET"/>
加入本地服務
爲了提供本地服務,還需要註冊發現服務,一旦註冊了發現服務,系統將自動響應來自文件的發現請求
現在來創建一個本地服務:
- 創建WifiP2pServiceInfo 對象
- 計算關於服務的信息
- 調用 addLocalService()方法去註冊本地服務
private void startRegistration() {
// Create a string map containing information about your service.
Map record = new HashMap();
record.put("listenport", String.valueOf(SERVER_PORT));
record.put("buddyname", "John Doe" + (int) (Math.random() * 1000));
record.put("available", "visible");
// Service information. Pass it an instance name, service type
// _protocol._transportlayer , and the map containing
// information other devices will want once they connect to this one.
WifiP2pDnsSdServiceInfo serviceInfo =
WifiP2pDnsSdServiceInfo.newInstance("_test", "_presence._tcp", record);
// Add the local service, sending the service info, network channel,
// and listener that will be used to indicate success or failure of
// the request.
mManager.addLocalService(channel, serviceInfo, new ActionListener() {
@Override
public void onSuccess() {
// Command successful! Code isn't necessarily needed here,
// Unless you want to update the UI or add logging statements.
}
@Override
public void onFailure(int arg0) {
// Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
}
});
}
發現附近的服務
Android使用回調方法通知客戶端哪些是可用的服務,所以首先得設置這些回調接口:
WifiP2pManager.DnsSdTxtRecordListener
final HashMap<String, String> buddies = new HashMap<String, String>(); ... private void discoverService() { DnsSdTxtRecordListener txtListener = new DnsSdTxtRecordListener() { @Override /* Callback includes: * fullDomain: full domain name: e.g "printer._ipp._tcp.local." * record: TXT record dta as a map of key/value pairs. * device: The device running the advertised service. */ public void onDnsSdTxtRecordAvailable( String fullDomain, Map record, WifiP2pDevice device) { Log.d(TAG, "DnsSdTxtRecord available -" + record.toString()); buddies.put(device.deviceAddress, record.get("buddyname")); } }; ... }
爲了獲得服務信息,創建WifiP2pManager.DnsSdServiceResponseListener接口
private void discoverService() { ... DnsSdServiceResponseListener servListener = new DnsSdServiceResponseListener() { @Override public void onDnsSdServiceAvailable(String instanceName, String registrationType, WifiP2pDevice resourceType) { // Update the device name with the human-friendly version from // the DnsTxtRecord, assuming one arrived. resourceType.deviceName = buddies .containsKey(resourceType.deviceAddress) ? buddies .get(resourceType.deviceAddress) : resourceType.deviceName; // Add to the custom adapter defined specifically for showing // wifi devices. WiFiDirectServicesList fragment = (WiFiDirectServicesList) getFragmentManager() .findFragmentById(R.id.frag_peerlist); WiFiDevicesAdapter adapter = ((WiFiDevicesAdapter) fragment .getListAdapter()); adapter.add(resourceType); adapter.notifyDataSetChanged(); Log.d(TAG, "onBonjourServiceAvailable " + instanceName); } }; mManager.setDnsSdResponseListeners(channel, servListener, txtListener); ... }
現在創建服務請求並且調用addServiceRequest(),這個方法需要傳遞一個監聽接口 ActionListener
serviceRequest = WifiP2pDnsSdServiceRequest.newInstance(); mManager.addServiceRequest(channel, serviceRequest, new ActionListener() { @Override public void onSuccess() { // Success! } @Override public void onFailure(int code) { // Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY } });
最後調用discoverServices()
mManager.discoverServices(channel, new ActionListener() { @Override public void onSuccess() { // Success! } @Override public void onFailure(int code) { // Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY if (code == WifiP2pManager.P2P_UNSUPPORTED) { Log.d(TAG, "P2P isn't supported on this device."); else if(...) ... } });
全文代碼
1. ChatConnection
ChatConnection 是最核心的地方,有兩個內部類ChatServer服務端和ChatClient客戶端,作用是初始化NsdServiceInfo信息,刷新客戶端的聊天界面
public class ChatConnection {
private Handler mUpdateHandler;
private ChatServer mChatServer;
private ChatClient mChatClient;
private static final String TAG = "ChatConnection";
private Socket mSocket;
private int mPort = -1;
public ChatConnection(Handler handler) {
mUpdateHandler = handler;
mChatServer = new ChatServer(handler);
}
public void tearDown() {
mChatServer.tearDown();
mChatClient.tearDown();
}
public void connectToServer(InetAddress address, int port) {
mChatClient = new ChatClient(address, port);
}
public void sendMessage(String msg) {
if (mChatClient != null) {
mChatClient.sendMessage(msg);
}
}
public int getLocalPort() {
return mPort;
}
public void setLocalPort(int port) {
mPort = port;
}
public synchronized void updateMessages(String msg, boolean local) {
Log.e(TAG, "Updating message: " + msg);
if (local) {
msg = "me: " + msg;
} else {
msg = "them: " + msg;
}
Bundle messageBundle = new Bundle();
messageBundle.putString("msg", msg);
Message message = new Message();
message.setData(messageBundle);
mUpdateHandler.sendMessage(message);
}
private synchronized void setSocket(Socket socket) {
Log.d(TAG, "setSocket being called.");
if (socket == null) {
Log.d(TAG, "Setting a null socket.");
}
if (mSocket != null) {
if (mSocket.isConnected()) {
try {
mSocket.close();
} catch (IOException e) {
// TODO(alexlucas): Auto-generated catch block
e.printStackTrace();
}
}
}
mSocket = socket;
}
private Socket getSocket() {
return mSocket;
}
private class ChatServer {
ServerSocket mServerSocket = null;
Thread mThread = null;
public ChatServer(Handler handler) {
mThread = new Thread(new ServerThread());
mThread.start();
}
public void tearDown() {
mThread.interrupt();
try {
mServerSocket.close();
} catch (IOException ioe) {
Log.e(TAG, "Error when closing server socket.");
}
}
class ServerThread implements Runnable {
@Override
public void run() {
try {
// Since discovery will happen via Nsd, we don't need to care which port is
// used. Just grab an available one and advertise it via Nsd.
mServerSocket = new ServerSocket(0);
setLocalPort(mServerSocket.getLocalPort());
while (!Thread.currentThread().isInterrupted()) {
Log.d(TAG, "ServerSocket Created, awaiting connection");
setSocket(mServerSocket.accept());
Log.d(TAG, "Connected.");
if (mChatClient == null) {
int port = mSocket.getPort();
InetAddress address = mSocket.getInetAddress();
connectToServer(address, port);
}
}
} catch (IOException e) {
Log.e(TAG, "Error creating ServerSocket: ", e);
e.printStackTrace();
}
}
}
}
private class ChatClient {
private InetAddress mAddress;
private int PORT;
private final String CLIENT_TAG = "ChatClient";
private Thread mSendThread;
private Thread mRecThread;
public ChatClient(InetAddress address, int port) {
Log.d(CLIENT_TAG, "Creating chatClient");
this.mAddress = address;
this.PORT = port;
mSendThread = new Thread(new SendingThread());
mSendThread.start();
}
class SendingThread implements Runnable {
BlockingQueue<String> mMessageQueue;
private int QUEUE_CAPACITY = 10;
public SendingThread() {
mMessageQueue = new ArrayBlockingQueue<String>(QUEUE_CAPACITY);
}
@Override
public void run() {
try {
if (getSocket() == null) {
setSocket(new Socket(mAddress, PORT));
Log.d(CLIENT_TAG, "Client-side socket initialized.");
} else {
Log.d(CLIENT_TAG, "Socket already initialized. skipping!");
}
mRecThread = new Thread(new ReceivingThread());
mRecThread.start();
} catch (UnknownHostException e) {
Log.d(CLIENT_TAG, "Initializing socket failed, UHE", e);
} catch (IOException e) {
Log.d(CLIENT_TAG, "Initializing socket failed, IOE.", e);
}
while (true) {
try {
String msg = mMessageQueue.take();
sendMessage(msg);
} catch (InterruptedException ie) {
Log.d(CLIENT_TAG, "Message sending loop interrupted, exiting");
}
}
}
}
class ReceivingThread implements Runnable {
@Override
public void run() {
BufferedReader input;
try {
input = new BufferedReader(new InputStreamReader(
mSocket.getInputStream()));
while (!Thread.currentThread().isInterrupted()) {
String messageStr = null;
messageStr = input.readLine();
if (messageStr != null) {
Log.d(CLIENT_TAG, "Read from the stream: " + messageStr);
updateMessages(messageStr, false);
} else {
Log.d(CLIENT_TAG, "The nulls! The nulls!");
break;
}
}
input.close();
} catch (IOException e) {
Log.e(CLIENT_TAG, "Server loop error: ", e);
}
}
}
public void tearDown() {
try {
getSocket().close();
} catch (IOException ioe) {
Log.e(CLIENT_TAG, "Error when closing server socket.");
}
}
public void sendMessage(String msg) {
try {
Socket socket = getSocket();
if (socket == null) {
Log.d(CLIENT_TAG, "Socket is null, wtf?");
} else if (socket.getOutputStream() == null) {
Log.d(CLIENT_TAG, "Socket output stream is null, wtf?");
}
PrintWriter out = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(getSocket().getOutputStream())), true);
out.println(msg);
out.flush();
updateMessages(msg, true);
} catch (UnknownHostException e) {
Log.d(CLIENT_TAG, "Unknown Host", e);
} catch (IOException e) {
Log.d(CLIENT_TAG, "I/O Exception", e);
} catch (Exception e) {
Log.d(CLIENT_TAG, "Error3", e);
}
Log.d(CLIENT_TAG, "Client sent message: " + msg);
}
}
}
2. NsdHelper
NsdHelper是這個聊天室app 中第二重要的類
它的作用是設置監聽器,提供接口給Activity交給用戶控制:中斷服務,啓動服務,發現服務,連接服務等
public class NsdHelper {
Context mContext;
NsdManager mNsdManager;
NsdManager.ResolveListener mResolveListener;
NsdManager.DiscoveryListener mDiscoveryListener;
NsdManager.RegistrationListener mRegistrationListener;
public static final String SERVICE_TYPE = "_http._tcp.";
public static final String TAG = "NsdHelper";
public String mServiceName = "NsdChat";
NsdServiceInfo mService;
public NsdHelper(Context context) {
mContext = context;
mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
}
public void initializeNsd() {
initializeResolveListener();
initializeDiscoveryListener();
initializeRegistrationListener();
// mNsdManager.init(mContext.getMainLooper(), this);
}
public void initializeDiscoveryListener() {
mDiscoveryListener = new NsdManager.DiscoveryListener() {
@Override
public void onDiscoveryStarted(String regType) {
Log.d(TAG, "Service discovery started");
}
@Override
public void onServiceFound(NsdServiceInfo service) {
Log.d(TAG, "Service discovery success" + service);
if (!service.getServiceType().equals(SERVICE_TYPE)) {
Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
} else if (service.getServiceName().equals(mServiceName)) {
Log.d(TAG, "Same machine: " + mServiceName);
} else if (service.getServiceName().contains(mServiceName)){
mNsdManager.resolveService(service, mResolveListener);
}
}
@Override
public void onServiceLost(NsdServiceInfo service) {
Log.e(TAG, "service lost" + service);
if (mService == service) {
mService = null;
}
}
@Override
public void onDiscoveryStopped(String serviceType) {
Log.i(TAG, "Discovery stopped: " + serviceType);
}
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
};
}
public void initializeResolveListener() {
mResolveListener = new NsdManager.ResolveListener() {
@Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
Log.e(TAG, "Resolve failed" + errorCode);
}
@Override
public void onServiceResolved(NsdServiceInfo serviceInfo) {
Log.e(TAG, "Resolve Succeeded. " + serviceInfo);
if (serviceInfo.getServiceName().equals(mServiceName)) {
Log.d(TAG, "Same IP.");
return;
}
mService = serviceInfo;
}
};
}
public void initializeRegistrationListener() {
mRegistrationListener = new NsdManager.RegistrationListener() {
@Override
public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) {
mServiceName = NsdServiceInfo.getServiceName();
}
@Override
public void onRegistrationFailed(NsdServiceInfo arg0, int arg1) {
}
@Override
public void onServiceUnregistered(NsdServiceInfo arg0) {
}
@Override
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
}
};
}
public void registerService(int port) {
NsdServiceInfo serviceInfo = new NsdServiceInfo();
serviceInfo.setPort(port);
serviceInfo.setServiceName(mServiceName);
serviceInfo.setServiceType(SERVICE_TYPE);
mNsdManager.registerService(
serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
}
public void discoverServices() {
mNsdManager.discoverServices(
SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
}
public void stopDiscovery() {
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
}
public NsdServiceInfo getChosenServiceInfo() {
return mService;
}
public void tearDown() {
mNsdManager.unregisterService(mRegistrationListener);
}
}
NsdActivity
響應用戶的操作,根據activity的生命週期做出一些響應
public class NsdChatActivity extends AppCompatActivity {
NsdHelper mNsdHelper;
private TextView mStatusView;
private Handler mUpdateHandler;
public static final String TAG = "NsdChat";
ChatConnection mConnection;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mStatusView = (TextView) findViewById(R.id.status);
mUpdateHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
String chatLine = msg.getData().getString("msg");
addChatLine(chatLine);
}
};
mConnection = new ChatConnection(mUpdateHandler);
mNsdHelper = new NsdHelper(this);
mNsdHelper.initializeNsd();
}
public void clickAdvertise(View v) {
// Register service
if(mConnection.getLocalPort() > -1) {
mNsdHelper.registerService(mConnection.getLocalPort());
} else {
Log.d(TAG, "ServerSocket isn't bound.");
}
}
public void clickDiscover(View v) {
mNsdHelper.discoverServices();
}
public void clickConnect(View v) {
NsdServiceInfo service = mNsdHelper.getChosenServiceInfo();
if (service != null) {
Log.d(TAG, "Connecting.");
mConnection.connectToServer(service.getHost(),
service.getPort());
} else {
Log.d(TAG, "No service to connect to!");
}
}
public void clickSend(View v) {
EditText messageView = (EditText) this.findViewById(R.id.chatInput);
if (messageView != null) {
String messageString = messageView.getText().toString();
if (!messageString.isEmpty()) {
mConnection.sendMessage(messageString);
}
messageView.setText("");
}
}
public void addChatLine(String line) {
mStatusView.append("\n" + line);
}
@Override
protected void onPause() {
if (mNsdHelper != null) {
mNsdHelper.stopDiscovery();
}
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
if (mNsdHelper != null) {
mNsdHelper.discoverServices();
}
}
@Override
protected void onDestroy() {
mNsdHelper.tearDown();
mConnection.tearDown();
super.onDestroy();
}
}
參考
結尾
這一篇翻譯所爛的程度和之前《 OpenGL ES 》有一拼,但還是硬着頭皮翻譯了6-7成的樣子,以博客的形式寫出,意味着終歸要回來審視,這一遍翻譯得不夠好,下一遍,下下遍的時候是不是能更好一些,這是我更加需要反思的地方
寫在結尾,勉勵自己!