跟Google 学代码Building Apps with Connectivity & the Cloud【连接无线设备】

来源:互联网 发布:帝国cms写进销存系统 编辑:程序博客网 时间:2024/06/06 16:31

说在前面的话:

“WIFI”—工作这么久以来第一次接触WIFI的知识

在之前翻译Google 官方课程所记录的《Web Apps》《Volley》时,就深感计算机网络知识的欠缺,在翻译到这一章的时候更感力不从心

因为是学习资料看的是英文,很多专业单词借助外力(必应词典,有道,谷歌翻译)查,结果还是不名所以,模模糊糊的词义多了,想理解句子含义就更别提了,有的时候学习一篇Goolg的课程,粗略看一遍挺简单的,翻译成通顺的中文句子,难,将段落翻译的有条有理,按着作者的行文逻辑将文章复述,更难,更别说消化吸收每一篇知识举一反三了

从五月份已经开始刷计算机网络相关书籍,希望捋顺一遍计算机网络的知识,再回过头来把这里的章节弄懂。

补充令人庆幸的一点:

还好,代码是相通的,每次翻译Google 的教程,各个渠道查,英文句子还是看不懂的,猜,猜不透的,看到代码部分,就豁然开朗了

嗯,代码是相通的,一切的疑难,从代码入手是最快的;但我认为这个阶段慢下来最好,捋顺逻辑,跟着Google的示例学习:

  1. 写代码的思路(学习任何编程都是从最简单的入手,比如Volley,先学会发送最简单的Request,接着使用标准的Request,最后自定义Request时候,有什么注意事项,跟Google学习使用单例Request等等)

  2. 模块的设计(分层思想,数据层,控制层,视图层,每一层如何增删改查)

分层的思想可以追溯到物理世界计算机网络系统的设计,大到全球ISP和国家ISP,地区ISP,小到校园,公司,个人等等,为什么不提供一层ISP来服务于不同的端系统?网络层级为什么划分这么多层?目的都是为了服务于整个系统不同级别的“端”系统,抽象到软件编程中,不管MVC MVP,MVVM等分层模式,其实都是为了写出”机器执行的快又好,后续的同事能更好的开发和维护“的项目,如果心中有“层”的概念,那么作为一个优秀的开发者,不论接手“烂”代码,还是“好”代码,都可以游刃有余,从容开发和维护了。

想改变世界,先改变自己的适应能力。

本文介绍

本章是翻译自Google 官方课程 Building Apps with Connectivity & the Cloud 第一节

限于篇幅和文章连续性,本文只翻译Connecting Devices Wirelessly 连接无线设备篇

本篇文章 的demo为 NSDChat,是一款使用无线网络服务所做的聊天室

文末有代码示例

这里写图片描述

连接无线网络

这一节所有代码,最低版本4.1 对应API 16

使用网络发现服务


这节将包括什么

  1. 注册网络服务
  2. 发现网络服务
  3. 连接服务
  4. 解除服务

本节目的旨在如何在不同设备连接同一个应用

注册服务

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中发现那些服务,我们需要监听服务广播,去分析哪些服务是可用的,并且过滤掉那些不能正常工作的服务

发现服务 只需两步:

  1. 设置发现监听
  2. 加入单例的异步编程接口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连接

这一节将了解:

  1. 如何设置权限
  2. 设置广播接收者和Peer-to-Peer管理器
  3. 初始化Peer发现
  4. 拿Peers的列表
  5. 连接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. 设置广播接收者和文件管理器

  1. 为了使用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 标明设备配置已经改变

  2. 接着只需在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);        ...    }
  3. 获得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);    }
  4. 创建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));            }        }
  5. 注册和取消广播接收

    在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()里有俩重要的参数:

  1. 上一步创建的WifiP2pManager.Channel
  2. 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 发现服务

这节将涉及:

  1. 设置Mainfest文件
  2. 加入本地服务
  3. 发现附近的服务

在前面两节,我们已经学会如何发现服务并连接本地网络,然后,使用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"/>

加入本地服务

为了提供本地服务,还需要注册发现服务,一旦注册了发现服务,系统将自动响应来自文件的发现请求

现在来创建一个本地服务:

  1. 创建WifiP2pServiceInfo 对象
  2. 计算关于服务的信息
  3. 调用 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使用回调方法通知客户端哪些是可用的服务,所以首先得设置这些回调接口:

  1. 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"));                }            };        ...    }
  2. 为了获得服务信息,创建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);        ...    }
  3. 现在创建服务请求并且调用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                        }                    });
  4. 最后调用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();    }}

参考

  1. 这里写链接内容
  2. demo下载

结尾

这一篇翻译所烂的程度和之前《 OpenGL ES 》有一拼,但还是硬着头皮翻译了6-7成的样子,以博客的形式写出,意味着终归要回来审视,这一遍翻译得不够好,下一遍,下下遍的时候是不是能更好一些,这是我更加需要反思的地方

写在结尾,勉励自己!

0 0