问题
I'm fairly new to Android and I'm working on an application that will exchange data over WIFI direct and I would like to use DNS Service Discovery instead of pure P2P discovery to get more useful information like device "nick names" etc.
So far I've followed the tutorials at http://developer.android.com/training/connect-devices-wirelessly/index.html and read through the documentation.
My application seems to register it's local service successfully and the onSuccess callback from discoverServices is always called, however the txtServiceRecord from other devices (I'm using 2 devices) is never received.
Here's my full code:
package com.caballero.marco.dnssd_test;
import android.graphics.Color;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager.Channel;
import android.net.wifi.p2p.WifiP2pManager.DnsSdTxtRecordListener;
import android.net.wifi.p2p.WifiP2pManager.DnsSdServiceResponseListener;
import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceInfo;
import android.net.wifi.p2p.WifiP2pManager.ActionListener;
import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceRequest;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.TextView;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class MainActivity extends ActionBarActivity
{
public static String SERVICE_NAME = "_marcotest";
private int server_port = 0;
private WifiP2pManager mManager;
private Channel channel;
private ServerSocket serverSocket;
private TextView txtServiceStatus;
private Button btnDiscover;
final HashMap<String, String> buddies = new HashMap<String, String>();
private ArrayAdapter<String> adapter;
//private ActionListener serviceDiscoveryListener;
private WifiP2pDnsSdServiceRequest serviceRequest;
private DnsSdServiceResponseListener serviceResponseListener;
private DnsSdTxtRecordListener txtRecordListener;
private WifiP2pDnsSdServiceInfo serviceInfo;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Load controls */
txtServiceStatus = (TextView)findViewById(R.id.txtStatus);
btnDiscover = (Button)findViewById(R.id.btnDiscover);
btnDiscover.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startServiceDiscovery();
}
});
mManager = (WifiP2pManager)getSystemService(WIFI_P2P_SERVICE);
channel = mManager.initialize(this, getMainLooper(), null);
adapter = new ArrayAdapter<String>( this,
android.R.layout.simple_list_item_1,
new ArrayList<String>());
/* Initialize Socket Server */
try {
initializeSocketServer();
}
catch (IOException e)
{
e.printStackTrace();
}
startRegistration();
setupDiscoverServices();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
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.
serviceInfo = WifiP2pDnsSdServiceInfo.newInstance(SERVICE_NAME, "._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 WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
// Command successful! Code isn't necessarily needed here,
// Unless you want to update the UI or add logging statements.
SpannableString text = new SpannableString("Online");
text.setSpan(new ForegroundColorSpan(Color.GREEN), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
txtServiceStatus.setText(text);
Log.v("DnsSDTest", "Service added.");
}
@Override
public void onFailure(int arg0)
{
// Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
SpannableString text = new SpannableString("Offline");
text.setSpan(new ForegroundColorSpan(Color.RED), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
txtServiceStatus.setText(text);
}
});
}
private void initializeSocketServer() throws IOException
{
serverSocket = new ServerSocket(0);
server_port = serverSocket.getLocalPort();
}
private void setupDiscoverServices()
{
txtRecordListener = new WifiP2pManager.DnsSdTxtRecordListener() {
@Override
public void onDnsSdTxtRecordAvailable(String fullDomainName, Map<String, String> txtRecordMap, WifiP2pDevice srcDevice)
{
Log.v("DnsSDTest", "DnsSdTxtRecord available -" + txtRecordMap.toString());
buddies.put(srcDevice.deviceAddress, txtRecordMap.get("buddyname"));
}
};
serviceResponseListener = new DnsSdServiceResponseListener() {
@Override
public void onDnsSdServiceAvailable(String instanceName, String registrationType, WifiP2pDevice srcDevice)
{
// Update the device name with the human-friendly version from
// the DnsTxtRecord, assuming one arrived.
srcDevice.deviceName = buddies
.containsKey(srcDevice.deviceAddress) ? buddies
.get(srcDevice.deviceAddress) : srcDevice.deviceName;
// Add to the custom adapter defined specifically for showing
// wifi devices.
adapter.add(srcDevice.toString());
adapter.notifyDataSetChanged();
Log.v("DnsSDTest", "onBonjourServiceAvailable " + instanceName);
}
};
mManager.setDnsSdResponseListeners(channel, serviceResponseListener, txtRecordListener);
Log.v("DnsSDTest", "Added DNS SD response listeners.");
}
private void startServiceDiscovery()
{
serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
mManager.addServiceRequest(channel, serviceRequest, new ActionListener() {
@Override
public void onSuccess() {
Log.v("DnsSDTest", "Service Request added successfully!");
}
@Override
public void onFailure(int reason) {
Log.v("DnsSDTest", "Failed to add service request!");
}
});
mManager.discoverServices(channel, new ActionListener() {
@Override
public void onSuccess() {
Log.v("DnsSDTest", "Service discovery successfull!");
}
@Override
public void onFailure(int reason) {
Log.v("DnsSDTest", "Service discovery failed :(");
}
});
}
@Override
protected void onPause()
{
super.onPause();
mManager.removeLocalService(channel, serviceInfo, new ActionListener() {
@Override
public void onSuccess() {
Log.v("DnsSDTest", "Removed service");
serviceInfo = null;
}
@Override
public void onFailure(int reason) {
Log.v("DnsSDTest", "Failed to remove service");
}
});
}
@Override
protected void onResume()
{
super.onResume();
if(serviceInfo == null)
{
startRegistration();
}
}
@Override
protected void onDestroy()
{
super.onDestroy();
try {
if(!serverSocket.isClosed())
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
回答1:
Finding 1:
Only after you attach the both the listeners(Service listener and the txtrecord listener) using setupDiscoverServices()
, you need to create a Service Request for these attached listners and initiate the discovery. You approach to manually create a Service Request at any time and trigger discover services using a button , is error prone. There is no guarantee that the listeners are actually attached before calling startServiceDiscovery()
using a button.
Solution1: Try to call the method, startServiceDiscovery(), after setting up the DNS SD Response listeners i.e. after mManager.setDnsSdResponseListeners(), instead of calling on the button click Listener.
Solution2: Else, I would suggest, move your
'mManager.addServiceRequest(channel, serviceRequest, new ActionListener() {....}'
part into the 'setupDiscoverServices()'
method and try to activate the button(startServiceDiscovery()
) in the 'onSuccess()'
of the 'mManager.addServiceRequest'
. By this way you could achieve a synchronization point between attaching listeners, registering service and starting discovery(manually)
The below link demonstrates a WiFiDirect project wherein the peers connect with each other through the network service discovery for message exchange. This order of the methods calls for network service discovery worked for me. https://github.com/Hariharan-Gandhi/WiFriends/blob/master/app/src/main/java/tud/cnlab/wifriends/WiFriends.java
Finding 2: I also noticed that that service type used in the following line is ".tcp".
serviceInfo = WifiP2pDnsSdServiceInfo.newInstance(SERVICE_NAME, "._tcp", record);
However as per Android developer guide, the second parameter(service type) should be of the format: "_protocol._transportlayer". http://developer.android.com/training/connect-devices-wirelessly/nsd.html. In the demo code here, developer.android.com/training/connect-devices-wirelessly/nsd-wifi-direct.html, they have used "_presence._tcp" where "presence" is a messaging protocol of XMPP. Refer the detailed explanation here, Available service types in WifiP2pDnsSdServiceInfo.newInstance.
I am not sure if it is acceptable to omit the Service name in the SVR record [Anybody with knowledge abt this part kindly update me if I am wrong]
来源:https://stackoverflow.com/questions/30158340/android-p2p-service-discovery-callbacks-not-being-called