p2p
技术,可以让具备相应硬件的 Android 4.0(API 级别 14)
或更高版本设备在没有中间接入点的情况下,通过 wlan
进行直接互联。使用这些 api
,您可以实现支持 wlan p2p
的设备间相互发现和连接,从而获得比蓝牙连接更远距离的高速连接通信效果。对于多人游戏或照片共享等需要在用户之间共享数据的应用而言,这一技术非常有用。
一、服务发布
一个设备上可以有多个p2p
服务存在,通过addLocalService
可以在设备上发布自己的p2p
服务,通过removeLocalService
可以移除自己的p2p
服务
// 获取 WifiP2pManager
mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
// 获取 WifiP2pManager.Channel
mChannel = mManager.initialize(this, getMainLooper(), null);
Map<String, String> map = new HashMap<>();
map.put("port", "8888");
// 构造一个 WifiP2pDnsSdServiceInfo,可通过此对象发布自己的信息,比如上面的 port 8888
p2pDnsSdServiceInfo = WifiP2pDnsSdServiceInfo.newInstance(serviceName, serviceType, map);
// 发布自己的p2p服务
mManager.addLocalService(mChannel, p2pDnsSdServiceInfo, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
Log.i(TAG, "startServer onSuccess");
}
@Override
public void onFailure(int reason) {
Log.i(TAG, "startServer onFailure:" + reason);
}
});
serviceName
是服务的名字,可以自定义,这里设置为 DON_TEST
serviceType
是服务类型,格式为 _<protocol>._<transportlayer>
,表示协议+传输类型。
例如:
-
_ipp._tcp
,协议为ipp
(打印机服务),通过tcp
传输数据 - _
presence._tcp
协议为presence
(xmpp.org的消息传递协议),通过tcp
传输数据
二、设备&服务搜索
1、注册广播
p2p
设备信息及连接状态的变化,需要通过监听系统广播来实现,常用到的广播有以下几个:
-
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION
p2p网络状态广播 -
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION
p2p设备列表修改广播 -
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
当前设备信息修改广播 -
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION
p2p连接状态广播
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
registerReceiver(broadCastReceiver, intentFilter);
2、搜索设备
通过方法discoverPeers
可搜索设备周围的p2p设备
mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
Log.i(TAG, "search onSuccess");
}
@Override
public void onFailure(int reason) {
Log.i(TAG, "search onFailure:" + reason);
}
});
discoverPeers
会触发广播 WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION
,收到此广播后,通过方法requestPeers
可获取周边p2p
设备列表
private WifiP2pManager.PeerListListener peerListListener = new WifiP2pManager.PeerListListener() {
@Override
public void onPeersAvailable(WifiP2pDeviceList wifiP2pDeviceList) {
// 获取设备列表
if (wifiP2pDeviceList != null && !wifiP2pDeviceList.getDeviceList().isEmpty()) {
mainActivity.updateDevices(wifiP2pDeviceList.getDeviceList());
}
}
};
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.i(TAG, "onReceive " + action);
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {// p2p状态
int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
// Wifi P2P is enabled
Log.i(TAG, "Wifi P2P is enabled");
} else {
// Wi-Fi P2P is not enabled
Log.i(TAG, "Wifi P2P is not enabled");
}
} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {// p2p设备变化监听
if (mManager != null) {
mManager.requestPeers(mChannel, peerListListener);
}
}
}
3、搜索服务
通过 一、服务发布,发布了一个名为 DON_TEST
的服务,通过discoverServices
方法就可以搜索到这个服务,同时需要通过setDnsSdResponseListeners
方法设置监听WifiP2pManager.DnsSdServiceResponseListener
和WifiP2pManager.DnsSdTxtRecordListener
,在搜索过程中这两个监听会回调搜索到的服务信息。
WifiP2pManager.DnsSdServiceResponseListener
用于监听搜索到的服务信息
boolean isConnecting = false;
private WifiP2pManager.DnsSdServiceResponseListener dnsListener = new WifiP2pManager.DnsSdServiceResponseListener() {
@Override
public void onDnsSdServiceAvailable(String instanceName, String registrationType, WifiP2pDevice device) {
Log.i(TAG, "onDnsSdServiceAvailable " + instanceName + " " + isConnecting);
if (isConnecting) {
return;
}
if (serviceName.equals(instanceName)) {
isConnecting = true;
connect(device);
}
}
};
WifiP2pManager.DnsSdTxtRecordListener
用于获取服务发布携带的额外信息,例如 一、服务发布 中的携带的信息 port 8888
private WifiP2pManager.DnsSdTxtRecordListener recordListener = new WifiP2pManager.DnsSdTxtRecordListener() {
@Override
public void onDnsSdTxtRecordAvailable(String fullDomain, Map record, WifiP2pDevice device) {
Log.i(TAG, "onDnsSdTxtRecordAvailable " + fullDomain);
}
};
mManager.setDnsSdResponseListeners(mChannel, dnsListener, recordListener);
serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
mManager.addServiceRequest(mChannel, serviceRequest, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
}
@Override
public void onFailure(int reason) {
}
});
// 搜索服务
mManager.discoverServices(mChannel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
Log.i(TAG, "search onSuccess");
}
@Override
public void onFailure(int reason) {
Log.i(TAG, "search onFailure:" + reason);
}
});
三、文件传输
1、设备连接
通过WifiP2pManager.DnsSdServiceResponseListener
可以搜索到可用的服务信息,通过方法 onDnsSdServiceAvailable
中的参数 WifiP2pDevice
可获取到服务所在的设备信息,通过 connect
方法可以连接该设备
public void connect(WifiP2pDevice device) {
Log.i(TAG, "connect " + device.deviceName);
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
config.wps.setup = WpsInfo.PBC;
mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
//success logic
Log.i(TAG, "connect success");
}
@Override
public void onFailure(int reason) {
//failure logic
Log.i(TAG, "connect onFailure " + reason);
isConnecting = false;
}
});
}
connect
调用成功之后,如果是首次连接,对端手机会显示一个弹窗,用户选择允许连接之后,两部手机手机才能建立连接。
建立连接之后,系统会发送广播WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION
,收到这个广播之后,可通过requestConnectionInfo
方法获取连接信息 WifiP2pInfo
,通过WifiP2pInfo
可获取对方的ip
public void requestConnectionInfo() {
Log.i(TAG, "requestConnectionInfo");
mManager.requestConnectionInfo(mChannel, new WifiP2pManager.ConnectionInfoListener() {
@Override
public void onConnectionInfoAvailable(WifiP2pInfo wifiP2pInfo) {
try {
InetAddress address = InetAddress.getByName(wifiP2pInfo.groupOwnerAddress.getHostAddress());
Log.i(TAG, "address " + address.toString());
} catch (Exception e) {
Log.w(TAG, e);
}
}
});
}
2、创建群组
连接到了一起,此时系统会自动创建一个群组(Group)
并随机指定一台设备为群主(GroupOwner)
。此时,对于两台设备来说,群主的IP
地址是可知的(系统回调函数中有提供),但客户端的IP
地址需要再来通过其他方法来主动获取。
可以通过创建群组,指定某台设备作为服务器端(群主),即指定某台设备用来接收文件,因此,服务器端要主动创建群组,并等待客户端的连接。
mManager.createGroup(mChannel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
Log.i(TAG, "createGroup onSuccess");
}
@Override
public void onFailure(int reason) {
Log.i(TAG, "createGroup onFailure: " + reason);
}
});
3、传输文件
server端
package com.don.p2pdemo;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class FileServerAsyncTask extends AsyncTask {
private final String TAG = "p2p:FileServerAsyncTask";
private Context context;
public FileServerAsyncTask(Context context) {
this.context = context;
}
@Override
protected String doInBackground(Object[] params) {
try {
/**
* Create a server socket and wait for client connections. This
* call blocks until a connection is accepted from a client
*/
ServerSocket serverSocket = new ServerSocket(8888);
Socket client = serverSocket.accept();
/**
* If this code is reached, a client has connected and transferred data
* Save the input stream from the client as a JPEG file
*/
final File f = new File(Environment.getExternalStorageDirectory() + "/wifip2pshared-" + System.currentTimeMillis()
+ ".jpg");
File dirs = new File(f.getParent());
if (!dirs.exists())
dirs.mkdirs();
f.createNewFile();
InputStream inputstream = client.getInputStream();
copyFile(inputstream, new FileOutputStream(f));
serverSocket.close();
return f.getAbsolutePath();
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
}
private void copyFile(InputStream inputStream, FileOutputStream fileStream) throws IOException {
byte[] bytes = new byte[1024];
int index = 0;
while ((index = inputStream.read(bytes)) != -1) {
fileStream.write(bytes, 0, index);
}
fileStream.flush();
}
/**
* Start activity that can handle the JPEG image
*/
@Override
protected void onPostExecute(Object result) {
if (result != null) {
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" + result), "image/*");
context.startActivity(intent);
}
}
}
客户端
boolean isSending = false;
public void sendImage(String host, int port) {
if (isSending) {
return;
}
Log.i(TAG, "sendImage " + host + " " + port);
Context context = this.getApplicationContext();
int len;
Socket socket = null;
byte buf[] = new byte[1024];
try {
socket = new Socket(host, port);
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = context.getAssets().open("icon.png");
while ((len = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, len);
}
outputStream.close();
inputStream.close();
Log.i(TAG, "sendImage close");
} catch (Exception e) {
Log.w(TAG, e);
} finally {
if (socket != null) {
if (socket.isConnected()) {
try {
socket.close();
} catch (IOException e) {
//catch logic
}
}
}
}
isSending = false;
Log.i(TAG, "sendImage end");
}
在两部手机通过p2p
连接之后,客户端传输文件到服务端
public void requestConnectionInfo() {
Log.i(TAG, "requestConnectionInfo");
mManager.requestConnectionInfo(mChannel, new WifiP2pManager.ConnectionInfoListener() {
@Override
public void onConnectionInfoAvailable(final WifiP2pInfo wifiP2pInfo) {
Log.i(TAG, "onConnectionInfoAvailable 群主:" + wifiP2pInfo.isGroupOwner);
if (wifiP2pInfo.isGroupOwner) {
AsyncTask asyncTask = new FileServerAsyncTask(MainActivity.this);
asyncTask.execute();
} else {
new Thread(new Runnable() {
@Override
public void run() {
try {
InetAddress address = InetAddress.getByName(wifiP2pInfo.groupOwnerAddress.getHostAddress());
Log.i(TAG, "address " + address.toString());
sendImage(address.toString().replace("/",""), 8888);
} catch (Exception e) {
Log.w(TAG, e);
}
}
}).start();
}
}
});
}
参考:
https://developer.android.google.cn/guide/topics/connectivity/wifip2p#java
https://blog.csdn.net/weixin_33735676/article/details/87990760