技术标签: 通讯 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() 中,在这个方法中,错误将被提示出来。这里是可能的错误值和它们的意思:
文章浏览阅读1.6k次。例如你的原路径是 http://localhost/test/index.php/index/add那么现在的地址是 http://localhost/test/index/add如何去掉index.php呢?1.httpd.conf配置文件中加载了mod_rewrite.so模块 //在APACHE里面去配置#LoadModule rewrite_module modu
文章浏览阅读3.8k次,点赞5次,收藏39次。★了解Qt和C++的关系★掌握Qt的信号/槽机制的原理和使用方法★了解Qt的元对象系统★掌握Qt的架构★理解Qt的事件模型,掌握其使用的时机信号与槽、元对象系统、事件模型是Qt机制的核心,如果您想要掌握Qt编程,就需要对它们有比较深入的了解。本章重点介绍了信号与槽的基本概念和用法、元对象系统、Qt的事件模型,以及它们在实际使用过程中应注意的一些问题。Qt对标准C++的扩展标准C++对象模型为面向对象编程提供了有效的实时支持,但是它的静态特性在一些领域中表现的不够灵活。事实上,GUI应用程序_qt原理
文章浏览阅读8.2k次,点赞3次,收藏25次。TI-RTOS概述TI-RTOS是CC2640R2F设备上蓝牙低能耗项目的运行环境。TI-RTOS内核是传统SYS/BIOS内核的定制版本,可作为具有驱动程序,同步和调度工具的实时抢占式多线程操作系统。线程模块TI-RTOS内核管理线程执行的四个不同的任务级别,如图21所示。线程模块列表如下图所示,按照优先级降序排列。硬件中断软件中断任务后台空闲功能的空闲任务_ti rtos 总中断
文章浏览阅读2k次,点赞2次,收藏4次。在开发过程中我们可以通过按需引入的方式引入所需要的组件,以达到减小项目体积的目的:步骤一:使用babel-plugin-component插件。运行命令行npm install babel-plugin-component -D2、修改babel.config.js文件module.exports = { presets: ['@vue/cli-plugin-babel/preset'], plugins: [ [ 'component',_项目里面没找的.babelrc文件怎么按需引入elment
文章浏览阅读7.9k次,点赞7次,收藏11次。再做闪屏页广告的时候,如果是视频媒体,通常用户都不想听广告到底在播什么。如果是 MediaPlayer 的话设置静音模式mediaPlayer.setVolume(0f, 0f);设置有声模式mediaPlayer.setVolume(1, 1);假如是VideoView呢,MediaPlayer对象是私有成员,没办法直接获取到,咋办videoView.setOn..._android standardgsyvideoplayer 设置静音播放
文章浏览阅读8.2k次,点赞2次,收藏3次。使用lambda表达式分别 根据 单个字段、多个字段,分组求和示意图:1、根据 单个字段,分组求和:根据2019这个字段,计算一个list集合里,同属于2019的某个字段累加和2、根据 多个字段,分组求和:(1)先根据2019这个字段,再根据1这个字段,计算一个list集合里,同属于2019和1的某个字段累加和;(2)先根据2019这个字段,再根据2这个字段,计算一个list集合里,同属于2019..._jdk8分组求和
文章浏览阅读8w次,点赞111次,收藏691次。参考链接https://www.bilibili.com/video/BV1JE411g7XF?p=54https://arxiv.org/abs/1706.03762https://blog.csdn.net/qq_36653505/article/details/83375160简述自注意力机制(self-attention)self-attention可以视为一个特征提取层,给定输入特征a1,a2,⋅⋅⋅ana^{1},a^{2},\cdot \cdot \cdot a^{n}a1,a2_自注意力机制代码
文章浏览阅读1.6k次。在Vue中应用cornerstone并且跟随dicom实时更新缩放比例等数据最近需要在cornerstone的显示界面中显示一些数据,比如缩放比例以及渲染时间等,但是这些是需要根据鼠标事件进行实时更新的,想想肯定是有这个接口的,于是便试了一下。1、获取到当前视窗的对象获取到视窗这个对象因为它里面肯定会有许多属性可以直接被使用,刚好我们的缩放比例就是里面的scale,还有一些属性可以直接取用。..._浏览器缩放倍数变化时 更新数据 vue
文章浏览阅读3.1w次,点赞135次,收藏1k次。C语言中常用的函数1、putchar()函数2、getchar()函数3、pow( a , b )函数4、sqrt( a )函数5、fabs(a)函数6、puts(字符数组)函数——输出字符串的函数7、gets(字符数组)——输入字符串的函数8、strcat(a , b)函数——字符串连接函数9、strcpy函数——字符串复制函数10、strncpy函数——字符串复制函数11、strcmp函数——字符串比较函数12、strlen函数——测量字符串长度的函数13、strlwr函数——转换为小写的函数14、_c语言常用函数
文章浏览阅读5k次。webpack打包时如何修改文件名在使用webpack进行项目打包的时候,我们可通过以下方式对不同类型的资源,进行文件名或文件路径的修改_webpack打包文件名称设置
文章浏览阅读5.7k次。自定义dialog/** * Created by zhaoxiaoyu on 2019/10/31 0031. */public class CardDialog extends Dialog{ private DialogCardBinding cardBinding; private CardViewModel cardViewModel; private int num; public CardDialog(@NonNull Context context_android dialog中使用datebinding
文章浏览阅读6.3w次,点赞18次,收藏78次。零基础搭建服务器(型号:DELL PowerEdge R740)step1:开机按Ctrl+R删除默认的磁盘组step2:创建虚拟磁盘step3:选择RAID5,分三个硬盘(raid-5最少需要三个),VD-size我给100G用来装系统注意有时候是现实TB如下图step4:再分个盘,用来存储数据把身下的几T都给它,直接OK就行。step5:创建热盘,选中OK,按ESC回车保存退..._r740安装server2016