技术标签: 通讯 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,包括像打印机,网络摄像头,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);
}
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:
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() 方法,这个方法有下面几个参数:
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() 方法检索网络上对等连接的详细信息,包括设备名称和连接状态。
在这节课的第一节,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将自动响应服务发现请求。
创建一个本地服务
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() 中,在这个方法中,错误将被提示出来。这里是可能的错误值和它们的意思:
leecode++理解:本文介绍了一些LeetCode题目的解析和思路,包括两数相加、寻找有序数组的中位数、最长字串、整数反转等。还讨论了一些下标规律和正则表达式匹配问题。
文章浏览阅读8.5k次,点赞2次,收藏28次。UDPServer.javaimport java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetAddress;/* * 服务器端,实现基于UDP的用户登陆 */public class UDPServer { public s_java xsocket jar包,实现udp服务端
文章浏览阅读692次,点赞2次,收藏3次。规划采购管理是记录项目采购决策、明确采购方法,及识别潜在卖方的过程。本过程的主要作用 是,确定是否从项目外部获取货物和服务,如果是,则还要确定将在什么时间、以什么方式获取什 么货物和服务。货物和服务可从执行组织的其他部门采购,或者从外部渠道采购。本过程仅开展一 次或仅在项目的预定义点开展。图 12-2 描述本过程的输入、工具与技术和输出。图 12-3 是本过程的 数据流向图。..._pmp项目管理实施
文章浏览阅读5.7k次,点赞4次,收藏15次。linux bash脚本Fatmawati Achmad Zaenuri/Shutterstock.comFatmawati Achmad Zaenuri / Shutterstock.com The date command is found in the Bash shell, which is the default shell in most Linux distributions and..._linux terminal显示时间
文章浏览阅读3.6k次。npm报错处理_npm安装报错
文章浏览阅读2.5k次。问题本文主要针对的问题是在Unity中对Button类进行Onclick事件绑定的时候出现的函数参数错误进行分析解决,具体例子如下: Button[] button = GetComponentsInChildren<Button>(); int buttonCnt = 3; for (int i = 0; i < buttonCnt; i++) { button[i].SetActive(true); Debug.Log("i: " + i);_unity 按钮onclick参数类型
文章浏览阅读154次。如果是通过chrome调用摄像头的话:navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(errorCallback);...function gotDevices(deviceInfos) {...for (var i = 0; i !== deviceInfos.length; ++i) {var deviceInf..._navigator.mediadevices.enumeratedevices() 将摄像头渲染到 video
文章浏览阅读337次。TPM(Trusted Platform Module)安全芯片,是指符合TPM(可信赖平台模块)标准的安全芯片。标准由TCG(可信赖计算组织,Trusted Computing Group)提出,目前最新版本为2.0。符合TPM的芯片首先必须具有产生加解密密钥的功能,此外还必须能够进行高速的资料加密和解密,以及充当保护BIOS和操作系统不被修改的辅助处理器。TPM的可信基础来源于可信根,可信根(..._tpm 总线监听
文章浏览阅读6.7w次。也就更容易获得更高的浏览量。,我们称为午高峰,这个时间段主要是针对一二线城市的上班族,因为玩抖音的一二线城市的人比较多,所以这个时间段他们基本都是下班的时间段,刷抖音的人也很多。,我们成为晚高峰,这个时间段的人基本都忙完工作在休息了,这个时间段可以说是一天中抖音流量最高的时间段,是高峰中的高峰。,这一个时间段的人大都是刚刚睡醒,躺在床上刷一刷抖音,或者在上班的路上没事看看抖音,坐公交的路上刷着抖音。,我们称之为午高峰,这个时间段是人们的午休时间,这个时间段刷抖音的人也很多,吃完饭午休,拿着手机刷刷抖音。_几点发抖音浏览量最高
文章浏览阅读6.1k次,点赞7次,收藏41次。图数据可视化_R语言ggplot2包和tidybayes包绘制小提琴图进阶概述:绘制小提琴图时按数据分布的密度填充不同透明度的颜色(渐变填充)。使用工具:R语言中的ggplot2和tidybayes工具包本文使用的数据及计算方式与之前的博文一致:数据可视化——R语言ggplot2包绘制精美的小提琴图(并箱线图或误差条图组合)。本文采用tidybayes包中stat_eye()绘制小提琴图,通过设置aes(alpha = stat(f)可实现渐变填充。由于stat_eye()会默认采用中位数及分位数作_分半小提琴图
文章浏览阅读1.4k次。江苏省专转本计算机_计算机硬件系统是执行软件程序的物质基础,其中能执行程序指令的是( )
文章浏览阅读4.4k次。在Robocode中,坦克分为3个部件: 身体(Body)、炮塔(Gun)、雷达(Radar)。 因此,在Robot类(还记得吗,它是任何坦克的父类)中,有对这些部件操作的方法。要查看Robocode提供的API,可以在robocode目录下的javadoc下找到,也可以在Robocode程序的帮助菜单中找到: 对于Body来说,Robot类提供了4个方法:_robocode炮和机身的运动分离