Develop>Training(19)---设备连接无线网_网络上任何服务的名称都必须是唯一的,并且 android 会自动处理冲突解决-程序员宅基地

技术标签: 通讯  android  局域网  Android 官网文档  api  应用  

官方链接:https://developer.android.com/training/connect-devices-wirelessly/index.html

除了使用云通讯,android无线API也能够让处在同一个局域网的设备相互通讯,甚至设备可以不再网络上,但是在物理附近上。此外,网络服务发现(NSD)能够进一步的允许一个应用程序寻找附近运行服务的设备,在它们是能够通信的。把这些功能整合在你的应用程序中,能够帮助你扩大应用程序的功能,比如玩游戏的时候,用户可以在一个相同的房子里面,从网络摄像头可以获取一些图像,或者记录一些他们的机器信息,在相同的网络上。

这节课描述了API的关键字,在你的应用程序中找到,然后连接其他设备。特别地,它描述了NSD API,发现可用的服务和Wi-Fi Peer-to-Peer (P2P) API,来做对等的无线网络连接。这节课也教你怎样使用NSD和Wi-Fi P2P组合起来,查找可提供的服务来在设备之间的连接,当设备都不在一个网络上的时候。

如果你的应用程序不传输敏感的或者私密数据,但是要求信息可以可靠的转移,可以考虑使用Nearby Connections API。

使用网络服务发现(NSD)

NSD给你的应用程序有对服务的访问,从其他设备提供的一个本地网络。设备支持NSD,包括像打印机,网络摄像头,https服务,和其他的移动设备。

NSD实现了DNS-SD机制,它允许你的应用程序通过指定服务类型和提供所需服务类型的设备实例的名称来请求服务,DNS-SD对于android和其他移动平台都支持。

在你的应用程序中添加NSD,用户就能够识别本地网络上的其他设备,通过应用程序请求该支持的服务。对于各种各样的对等的应用程序来说,这是非常有用的,像文件分享或者多玩家游戏。android NSD API简化了你实现这些特性所需要的工作量。

这节课将教你怎样构建一个应用程序,广播出它的名称和本地网络信息和扫描其他做相同事情的应用程序。最终,这节课展示如何连接到另一个设备上运行的同一应用程序。

注册你的网络服务

这一步是可选的。如果你不关心通过本地网络广播你应用程序的服务,你可以跳过这一步。

注册本地网络服务,第一步需要创建 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("nsdchat._tcp");
    serviceInfo.setPort(port);
    ....
}

这段代码设置了这个服务的名字为NsdChat,服务的名字也就是实例的名字,对于网络上的其他设备来说,这是一个看得见的名字。网络上的任何一个设备都能看得见这个名字,通过使用NSD本地服务查找。记住,网络上的服务名称必须是唯一的,android会自动处理解决冲突。如果网络上的两个设备都安装了NsdChat应用程序,其中一个服务名称就会自动改变,可能就会是”NsdChat (1)”。

第二个参数设置了服务的类型,指定应用程序使用的协议和传输层。该语法是”< protocol >.< transportlayer >”格式。上述代码中,该服务使用HTTP协议运行在TCP。一个应用程序提供一个打印服务,它能够设置服务类型为”_ipp._tcp”。

当你的服务设置了端口,避免硬编码它与其他应用程序冲突。对于这个实例,假设你的应用程序总是使用 1337端口,与其他安装的应用程序使用相同的端口,就可能存在潜在的冲突。相反的,使用设备下一个可用的端口。因为它的信息是可以通过广播服务提供给其他应用程序的,你的应用程序不要再编译时知道其他应用程序的端口了。相反的,应用程序能够通过广播服务得到它的信息,在连接服务之前。

如果你是使用sockets,下面是初始化代码:

public void initializeServerSocket() {
    // Initialize a server socket on the next available port.
    mServerSocket = new ServerSocket(0);

    // Store the chosen port.
    mLocalPort =  mServerSocket.getLocalPort();
    ...
}

现在,你已经定义NsdServiceInfo对象,你需要实现RegistrationListener 接口。该接口包括了回调使用,android会提示应用程序的服务注册和注销的成功或者失败情况。

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.
        }
    };
}

现在你有一系列的注册服务方法了。会回调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);
}

在网络上发现服务

网络是有充满生命的,从糟糕的网络打印机到温顺的网络摄像头,从可恶的到附近的井字游戏玩家火热的战斗。让你的应用程序看到这个充满活力的功能的生态系统的关键是服务发现。你的应用程序需要监听网络上的服务广播,查找哪些可用的网络,过滤掉应用程序不能使用的。

服务发现,像服务注册,有两步,设置服务监听和相关的回调,写一个单一的异步API回调discoverServices()。

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

NSD API使用这个方法通过接口来回调给你的应用程序,从发现服务就开始了。当它失败的时候,当服务被发现和丢失(丢失的意思是不再可用)。注意,当服务被发现的时候,需要检查几个点。

  • 将所找到的服务的服务名称与本地服务的服务名称进行比较,以确定设备是否刚刚提取了自己的广播(这是有效的)。
  • 服务类型检查,已验证你的应用程序是否连接到这个类型的服务。
  • 服务名称检查,已验证应用程序连接是否正确。

服务名称检查不是必须的,如果你想连接到指定的应用程序,它只是相关联的。例如,应用程序只想连接到其他设备上来运行这个实例。然而,如果你的应用程序想连接网络打印机,它查看服务类型就得是”_ipp._tcp”。

设置完监听之后,会回调discoverServices()方法,应用程序应该通过服务类型来查找,发现使用的协议,就可以创建一个监听器。

mNsdManager.discoverServices(
        SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);

连接到网络上的服务

当你的应用程序在网络上找到一个服务并连接它的时候,它必须第一个决定连接的服务信息,使用resolveService()方法。实现 NsdManager.ResolveListener ,通过它的一些方法,可以获得NsdServiceInfo对象,包含连接的信息。

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功能的开启和关闭时很重要的。要注销你的应用程序当它关闭的时候,可以防止其他应用程序认为它还是存活的,从而尝试去连接它。所以,服务发现是一个昂贵的操作,当Activity暂停的时候,应该关闭它,当Activity重新启动的时候,在打开它。你可以重写Activity生命周期的方法,在气冲插入操作服务发现的相关代码。

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

创建p2p连接wifi

wifi p2p允许你的应用程序快速地发现、互动附近的设备,它超出了蓝牙功能的范围。

wifi p2p APIs 允许你的应用程序连接附近的设备,而不需要连接网络或者热点。如果你的应用程序被设计成安全的一部分,附近范围的网络,wifi直连,是一个更合适的选择比传统的Wi-Fi ad-hoc网络,原因如下:
- wifi直连支持WPA2加密。(像ad-hoc网络只支持WEP加密)
- 设备可以广播他们提供的服务,这有助于其他设备更容易地找到相对等的。
- 当确定哪个设备应该是哪个网络的组所有者时候,wifi直连会检查每个设备的电源管理,UI和服务能力,使用这些信息选择设备,能够最有效地处理服务器的响应能力。
- android不提供wifi ad-hoc模式。

这节课将告诉你如何找到使用wifi p2p连接到附近的设备。

设置应用程序权限

为了使用wifi p2p,要在清单文件中添加CHANGE_WIFI_STATE,ACCESS_WIFI_STATE,INTERNET三个权限。wifi p2p不要求网络连接,但是要使用标准的 java socket,所以要求网络权限。

<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"/>
    ...

设置广播接收者和点对点管理器

为了使用wifi p2p,你需要监听一些intent的广播,当某些事情发生时要告诉你的应用程序。在你的应用程序中,实例化IntentFilter,添加下面这些Action:

  • WIFI_P2P_STATE_CHANGED_ACTION:表明wifi p2p是否启用。
  • WIFI_P2P_PEERS_CHANGED_ACTION:表明点对点列表已经被改变。
  • WIFI_P2P_CONNECTION_CHANGED_ACTION:表明wifi p2p连接状态已经被改变。
  • WIFI_P2P_THIS_DEVICE_CHANGED_ACTION:表明设备配置详情已经被改变。
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);

    ...
}

在onCreate()方法中,实例化WifiP2pManager对象,然后调用initialize()方法。这个方法返回了WifiP2pManager.Channel对象,稍后你将使用你的应用程序连接到wifi p2p框架。

@Override

Channel mChannel;

public void onCreate(Bundle savedInstanceState) {
    ....
    mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
    mChannel = mManager.initialize(this, getMainLooper(), null);
}

现在,要创建一个新的 BroadcastReceiver 类,你将监听系统的wifi p2p状态的变化。在onReceive()方法中,添加一个条件来处理p2p状态的变化:

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

        }
    }

最后,在你的主Activiy中注册这个广播,子活动暂停的时候取消注册。

    /** 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);
    }

初始化点对点发现

开始用wifi 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()方法启动发现进程,然后立即返回。系统会通知你如果点对点发现过程是成功的,是通过监听器回调的。此外,发现将保持活跃,直到连接开始或形成一个p2p组。

获取对等列表

现在需要写代码来获取和处理对等列表了。首先需要实现 WifiP2pManager.PeerListListener 接口,提供一些关于检测wifi p2p的信息。次信息还允许有你的应用程序来决定,当有其他的设备来加入或者离开网络的时候。

    private List<WifiP2pDevice> peers = new ArrayList<WifiP2pDevice>();
    ...

    private PeerListListener peerListListener = new PeerListListener() {
        @Override
        public void onPeersAvailable(WifiP2pDeviceList peerList) {

            List<WifiP2pDevice> refreshedPeers = peerList.getDeviceList();
            if (!refreshedPeers.equals(peers)) {
                peers.clear();
                peers.addAll(refreshedPeers);

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

                // Perform any other updates needed based on the new list of
                // peers connected to the Wi-Fi P2P network.
            }

            if (peers.size() == 0) {
                Log.d(WiFiDirectActivity.TAG, "No devices found");
                return;
            }
        }
    }

现在,修改你广播接收者 onReceive() 中的方法,当接收到action为WIFI_P2P_PEERS_CHANGED_ACTION时,调用 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");
    }...
}

现在,当有一个action为 WIFI_P2P_PEERS_CHANGED_ACTION 的时候,就会触发一个请求更新对等列表的操作。

连接到一个对等体

为了连接到对等体,需要创建一个 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();
            }
        });
    }

如果你的组中每个设备都支持wifi直连,你就不需要在连接的时候询问密码了。允许不支持wifi直连的设备加入这个组,然而,你需要通过requestGroupInfo()来检索密码。

mManager.requestGroupInfo(mChannel, new GroupInfoListener() {
  @Override
  public void onGroupInfoAvailable(WifiP2pGroup group) {
      String groupPassword = group.getPassphrase();
  }
});

注意,WifiP2pManager.ActionListener的实现实在connect()方法中,当初始化成功或失败的时候会通知你。监听连接状态的变化,实现 WifiP2pManager.ConnectionInfoListener 接口,当连接状态变化的时候,会回调 onConnectionInfoAvailable() 方法。在多个设备将连接到单个设备的情况下,一个设备将被指定为”组所有者”。你能够指定特定的设备作为网络的组所有者,通过创建一个组的部分。

    @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
        // (server).
        if (info.groupFormed && info.isGroupOwner) {
            // Do whatever tasks are specific to the group owner.
            // One common case is creating a group owner thread and accepting
            // incoming connections.
        } else if (info.groupFormed) {
            // The other device acts as the peer (client). In this case,
            // you'll want to create a peer thread that connects
            // to the group owner.
        }
    }

现在回到广播的 onReceive() 方法,改变 WIFI_P2P_CONNECTION_CHANGED_ACTION action的代码。当接收到这个intent的时候,调用requestConnectionInfo()方法。这是一个异步的调用,结果将由连接的监听器作为参数接收。

        ...
        } 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直连,你根据 Connect to a Peer 做同样的步骤,使用createGroup()来创建一个 WifiP2pManager.ActionListener,代替 connect() 方法。关于WifiP2pManager.ActionListener 的回调处理也是一样的。

mManager.createGroup(mChannel, new WifiP2pManager.ActionListener() {
    @Override
    public void onSuccess() {
        // Device is ready to accept incoming connections from peers.
    }

    @Override
    public void onFailure(int reason) {
        Toast.makeText(WiFiDirectActivity.this, "P2P group creation failed. Retry.",
                Toast.LENGTH_SHORT).show();
    }
});

在创建了一个组之后,调用 requestGroupInfo() 方法检索网络上对等连接的详细信息,包括设备名称和连接状态。

使用wifi p2p服务发现

在这节课的第一节,Using Network Service Discovery,教你怎样发现服务和连接到本地网络。然而,使用wifi p2p服务发现允许你发现附近服务设备直连,而不需要连接到一个网络上。你也可以在设备上运行服务的广告。这些功能可以帮助应用程序之间进行通信,即使没有本地网络或热点可用。

虽然这一组API类似于上一节中网络服务发现API,但实现的代码是完全不同的。这节课将教你怎样在其他设备上发现服务可用,使用wifi p2p。这个课假设你已经熟wifi p2p的API。

设置清单文件

为了使用wifi p2p,需要在清单文件添加 CHANGE_WIFI_STATE,ACCESS_WIFI_STATE和INTERNET权限,尽管wifi p2p不需要网络请求,但是要使用标准的java socket,使用这些的话,android需要请求权限。

<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"/>
    ...

添加一个本地服务

如果你提供了一个本地服务,你需要注册它作为服务发现。一旦你的本地服务注册,framework将自动响应服务发现请求。
创建一个本地服务

  • 创建一个 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来监听进入的记录。这个记录能够随意的广播给其他设备。当一个新的进入时,复制设备地址和一些其他的相关信息,你想要的数据结构在当前的方法中,所以,你可以在这之后访问它。下面的代码假定记录中有”buddyname”变量,填充了用户的身份。

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 。接收了实际的描述和连接信息。上一节代码实现了一个 map 对象来代表一个设备地址和好友姓名这样的键值对。该服务响应监听器使用此DNS记录与相应的服务信息链接。一旦两个监听器都实现了,它们将通过 WifiP2pManager 来调用setDnsSdResponseListeners()方法。

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()方法。

        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(...)
                    ...
            }
        });

如果一切顺利,万岁,你做的很好!如果你遇到一些问题,记住这是异步调用,你已经把WifiP2pManager.ActionListener 作为一个参数,它将回调你成功还是失败。对于诊断问题,把调试代码放在 onFailure() 中,在这个方法中,错误将被提示出来。这里是可能的错误值和它们的意思:

  • P2P_UNSUPPORTED:在设备上运行的app不支持wifi p2p
  • BUSY:系统处理请求繁忙。
  • ERROR:操作失败,由于内部错误。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012301841/article/details/60977976

智能推荐

从零开始搭建Hadoop_创建一个hadoop项目-程序员宅基地

文章浏览阅读331次。第一部分:准备工作1 安装虚拟机2 安装centos73 安装JDK以上三步是准备工作,至此已经完成一台已安装JDK的主机第二部分:准备3台虚拟机以下所有工作最好都在root权限下操作1 克隆上面已经有一台虚拟机了,现在对master进行克隆,克隆出另外2台子机;1.1 进行克隆21.2 下一步1.3 下一步1.4 下一步1.5 根据子机需要,命名和安装路径1.6 ..._创建一个hadoop项目

心脏滴血漏洞HeartBleed CVE-2014-0160深入代码层面的分析_heartbleed代码分析-程序员宅基地

文章浏览阅读1.7k次。心脏滴血漏洞HeartBleed CVE-2014-0160 是由heartbeat功能引入的,本文从深入码层面的分析该漏洞产生的原因_heartbleed代码分析

java读取ofd文档内容_ofd电子文档内容分析工具(分析文档、签章和证书)-程序员宅基地

文章浏览阅读1.4k次。前言ofd是国家文档标准,其对标的文档格式是pdf。ofd文档是容器格式文件,ofd其实就是压缩包。将ofd文件后缀改为.zip,解压后可看到文件包含的内容。ofd文件分析工具下载:点我下载。ofd文件解压后,可以看到如下内容: 对于xml文件,可以用文本工具查看。但是对于印章文件(Seal.esl)、签名文件(SignedValue.dat)就无法查看其内容了。本人开发一款ofd内容查看器,..._signedvalue.dat

基于FPGA的数据采集系统(一)_基于fpga的信息采集-程序员宅基地

文章浏览阅读1.8w次,点赞29次,收藏313次。整体系统设计本设计主要是对ADC和DAC的使用,主要实现功能流程为:首先通过串口向FPGA发送控制信号,控制DAC芯片tlv5618进行DA装换,转换的数据存在ROM中,转换开始时读取ROM中数据进行读取转换。其次用按键控制adc128s052进行模数转换100次,模数转换数据存储到FIFO中,再从FIFO中读取数据通过串口输出显示在pc上。其整体系统框图如下:图1:FPGA数据采集系统框图从图中可以看出,该系统主要包括9个模块:串口接收模块、按键消抖模块、按键控制模块、ROM模块、D.._基于fpga的信息采集

微服务 spring cloud zuul com.netflix.zuul.exception.ZuulException GENERAL-程序员宅基地

文章浏览阅读2.5w次。1.背景错误信息:-- [http-nio-9904-exec-5] o.s.c.n.z.filters.post.SendErrorFilter : Error during filteringcom.netflix.zuul.exception.ZuulException: Forwarding error at org.springframework.cloud..._com.netflix.zuul.exception.zuulexception

邻接矩阵-建立图-程序员宅基地

文章浏览阅读358次。1.介绍图的相关概念  图是由顶点的有穷非空集和一个描述顶点之间关系-边(或者弧)的集合组成。通常,图中的数据元素被称为顶点,顶点间的关系用边表示,图通常用字母G表示,图的顶点通常用字母V表示,所以图可以定义为:  G=(V,E)其中,V(G)是图中顶点的有穷非空集合,E(G)是V(G)中顶点的边的有穷集合1.1 无向图:图中任意两个顶点构成的边是没有方向的1.2 有向图:图中..._给定一个邻接矩阵未必能够造出一个图

随便推点

MDT2012部署系列之11 WDS安装与配置-程序员宅基地

文章浏览阅读321次。(十二)、WDS服务器安装通过前面的测试我们会发现,每次安装的时候需要加域光盘映像,这是一个比较麻烦的事情,试想一个上万个的公司,你天天带着一个光盘与光驱去给别人装系统,这将是一个多么痛苦的事情啊,有什么方法可以解决这个问题了?答案是肯定的,下面我们就来简单说一下。WDS服务器,它是Windows自带的一个免费的基于系统本身角色的一个功能,它主要提供一种简单、安全的通过网络快速、远程将Window..._doc server2012上通过wds+mdt无人值守部署win11系统.doc

python--xlrd/xlwt/xlutils_xlutils模块可以读xlsx吗-程序员宅基地

文章浏览阅读219次。python–xlrd/xlwt/xlutilsxlrd只能读取,不能改,支持 xlsx和xls 格式xlwt只能改,不能读xlwt只能保存为.xls格式xlutils能将xlrd.Book转为xlwt.Workbook,从而得以在现有xls的基础上修改数据,并创建一个新的xls,实现修改xlrd打开文件import xlrdexcel=xlrd.open_workbook('E:/test.xlsx') 返回值为xlrd.book.Book对象,不能修改获取sheett_xlutils模块可以读xlsx吗

关于新版本selenium定位元素报错:‘WebDriver‘ object has no attribute ‘find_element_by_id‘等问题_unresolved attribute reference 'find_element_by_id-程序员宅基地

文章浏览阅读8.2w次,点赞267次,收藏656次。运行Selenium出现'WebDriver' object has no attribute 'find_element_by_id'或AttributeError: 'WebDriver' object has no attribute 'find_element_by_xpath'等定位元素代码错误,是因为selenium更新到了新的版本,以前的一些语法经过改动。..............._unresolved attribute reference 'find_element_by_id' for class 'webdriver

DOM对象转换成jQuery对象转换与子页面获取父页面DOM对象-程序员宅基地

文章浏览阅读198次。一:模态窗口//父页面JSwindow.showModalDialog(ifrmehref, window, 'dialogWidth:550px;dialogHeight:150px;help:no;resizable:no;status:no');//子页面获取父页面DOM对象//window.showModalDialog的DOM对象var v=parentWin..._jquery获取父window下的dom对象

什么是算法?-程序员宅基地

文章浏览阅读1.7w次,点赞15次,收藏129次。算法(algorithm)是解决一系列问题的清晰指令,也就是,能对一定规范的输入,在有限的时间内获得所要求的输出。 简单来说,算法就是解决一个问题的具体方法和步骤。算法是程序的灵 魂。二、算法的特征1.可行性 算法中执行的任何计算步骤都可以分解为基本可执行的操作步,即每个计算步都可以在有限时间里完成(也称之为有效性) 算法的每一步都要有确切的意义,不能有二义性。例如“增加x的值”,并没有说增加多少,计算机就无法执行明确的运算。 _算法

【网络安全】网络安全的标准和规范_网络安全标准规范-程序员宅基地

文章浏览阅读1.5k次,点赞18次,收藏26次。网络安全的标准和规范是网络安全领域的重要组成部分。它们为网络安全提供了技术依据,规定了网络安全的技术要求和操作方式,帮助我们构建安全的网络环境。下面,我们将详细介绍一些主要的网络安全标准和规范,以及它们在实际操作中的应用。_网络安全标准规范

推荐文章

热门文章

相关标签