Wi-Fi peer-to-peer (P2P) 允许安卓4.0或更高的设备与适当的硬件连接直接通过Wi-Fi无线网络连接,而没有一个中间接入点。使用这些APIs,当设备都支持Wi-Fi时,可以发现并连接到这些设备上。然后建立一个快速连接,该连接距离比蓝牙连接远。可以实现用户之间的数据共享,如多人游戏或照片共享应用程序。
WiFi API 包含以下主要的方面:
- WifiP2pManager类中包含了一些方法允许去发现,请求和连接节点。
- WifiP2pManager方法调用成功还是失败,可以通过listeners获得通知。当调用WifiP2pManager方法,每个方法可以接受一个特定的listener作为一个参数。
- Intent会通知你由WI-FI P2P框架检测的特定事件。例如,断开一个连接或者发现一个新的节点。
这是经常使用到的API三个主要组件。例如,提供一个WifiP2pManager.ActionListener去调用discoverPeers()。因此可以获得ActionListenr.onSuccess()和ActionListener.onFailure()方法的通知。如果discoverPeers()方法发现节点列表发生改变,一个包含WIFI_P2P_PEERS_CHANGED_ACTION的intent将被广播出去。
API Overview
WifiPpManager类提供一些方法去允许你设备上的wifi硬件去做类似于发现和连接节点的事。
Creating a Broadcast Receiver for Wi-Fi P2P Intents
一个broadcast receiver允许去接受intents broadcast,因此应用程序可以回应你感兴趣的事件。创建一个广播接收机处理Wi-Fi P2P意图的基本步骤如下:
- 创建一个继承BroadcastReceiver的类。在该类的构造函数中,需要有WifiP2pManager,WifiP2pManager.Channel,和broadcast receiver将注册的activity。这允许broadcast receiver给activity发送更新,如果需要,可以访问wifi硬件和通信信道。
- 在broadcast receiver中,在onReceive()中检查你感兴趣的intents。完成任何必要的actions都依赖于接受到的Intent。例如,如果broadcast receiver接受一个包含WIFI_P2P_PEERS_CHANGED_ACTION的intent,你可以调用requestPeers()方法获取一系列当前被发现的节点。
下面的代码显示了怎样去创建一个典型的broadcast receiver。
public class WiFiDirectBroadcastReceiver extends BroadcastReceiver{
private WifiP2pManager mManager;
private WifiP2pManager.Channel mCannel;
private WiFiDirectActivity mActivity;
public WiFiDirectBroadcastReceiver(WifiP2pManager manager,WifiP2pManager.Channel channel,WiFiDirectActivity activity)
{
super();
this.mManager=manager;
this.mCannel=channel;
this.mActivity=activity;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
// 检查是否启用Wi-Fi,并通知适当的活动
} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
// 调用WifiP2pManager.requestPeers()去获取当前节点的列表
} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
// 连接或断开连接时
} else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
// 设备的WiFi状态改变时
}
}
}
Creating a Wi-Fi P2P Application
创建一个Wi-Fi P2P应用涉及到为应用创建和注册一个broadcast receiver,discovering peers, connecting to a peer, and transferring data to a peer。
Initial setup
在使用Wi-Fi P2P APIs,必须确保你的应用程序访问硬件并且该设备支持Wi-Fi P2P协议。如果支持Wi-Fi P2P,可以获得一个WifiP2pManager的实例,创建和注册你的broadcast receiver,同时使用Wi-Fi P2P APIs。
1.在Android manifest中添加Wi-Fi硬件的权限并声明正确的最小的SDK版本。
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
2.检查Wi-Fi P2P是否被支持。检查的地方是当broadcast receiver接受到WIFI_P2P_STATE_CHANGED_ACTION intent。通知你的Wi-Fi网络状态的活动,并作出相应的反应:
@Override
public void onReceive(Context context, Intent intent) {
...
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
// Wifi P2P is enabled
} else {
// Wi-Fi P2P is not enabled
}
}
...
}
3.在activity的onCreate()方法,获得一个WifiPpManager实例并且通过initialize()方法使用Wi-Fi P2P框架注册你的应用程序。该方法返回一个wifiP2pManager.Channel,用来将你的应用程序与Wi-Fi P2P框架的连接。
WifiP2pManager mManager;
Channel mChannel;
BroadcastReceiver mReceiver;
...
@Override
protected void onCreate(Bundle savedInstanceState){
...
mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, getMainLooper(), null);
mReceiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this);
...
}
4.创建一个Intent filter并且添加一些broadcast receiver需要的intents。
IntentFilter mIntentFilter;
...
@Override
protected void onCreate(Bundle savedInstanceState){
...
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
...
}
5.在你的activity的onResume()方法中注册你的broadcast receiver在onPause()方法中注销
@Override
protected void onResume() {
super.onResume();
registerReceiver(mReceiver, mIntentFilter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mReceiver);
}
当你获得一个WiFiP2pManager.Channel同时配置一个broadcast receiver,你的应用程序可以调用Wi-Fi P2P方法并且接受Wi-Fi P2P intents。
Discovering peers
为了发现可连接的节点,调用discoverPeers()去检测范围内可用的节点。该功能的实现是异步的,并且如果你创建了WifiP2pManager.ActionListener,将通过onSuccess()和onFailure()方法将success或failure信息返回给你的应用程序。
mManager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
...
}
@Override
public void onFailure(int reasonCode) {
...
}
});
如果发现过程成功并检测到节点,系统将会广播WIFI_P2P_PEERS_CHANGED_ACTION intent,你可以通过监听broadcast receiver获得一系列节点。当你的应用程序接收到WIFI_P2P_PEERS_CHANGED_ACTION intent,你可以通过requestPeers()获得一系列发现的节点。
PeerListListener myPeerListListener;
...
if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
if (mManager != null) {
mManager.requestPeers(mChannel, new WifiP2pManager.PeerListListener() {
@Override
public void onPeersAvailable(WifiP2pDeviceList peers) {
//peers为wifiP2pDevice的集合,保存了寻找到的所有设备的信息
}
});
}
}
requestPeers()方法也是异步,当一系列节点可用时,onPeersAvailable()方法会通知你的activity,onPeersAvailable()定义在WifiP2pManager.PeerListListener接口中。该方法将提供一个WifiP2pDeviceList,你可以通过遍历WifiP2pDeviceList找到你想要连接的节点。
Connecting to peers
当你找到你想连接的设备,调用connect()方法去连接设备。该方法会返回一个WifiP2pConfig对象,该对象包含了连接的目标设备的信息。
//从WifiP2pDeviceList中获得一个节点
WifiP2pDevice device;
WifiP2pConfig config = new WifiP2pConfig();
//设置连接地址
config.deviceAddress = device.deviceAddress;
mManager.connect(mChannel, config, new ActionListener() {
@Override
public void onSuccess() {
//success logic
}
@Override
public void onFailure(int reason) {
//failure logic
}
});
Transferring data
当连接建立,你可以通过socket在设备之间传递数据。传递数据的基本步骤如下:
1.创建一个ServerSocket。该socket在指定的端口等待来自client的连接,运行在后台线程中。
2.创建一个客户端socket。该客户端使用server socket的端口和IP地址去连接服务设备。
3.从客户端中发送数据给服务端。当客户端socket成功与server socket连接,客户端以字节流的方式将数据发送给服务端。
4.Server socket等待来自客户端的连接(通过accept()方法)。该方法会阻塞直到客户端连接,因此在线程中调用。当连接发生,服务端可以获得来自客户端的数据。
下面的实例代码来自Wi-Fi P2P Demo,显示怎么创建一个client-server
socket连接,将JPEG图片通过service从client端传递给server。
public static class FileServerAsyncTask extends AsyncTask {
private Context context;
private TextView statusText;
public FileServerAsyncTask(Context context, View statusText) {
this.context = context;
this.statusText = (TextView) statusText;
}
@Override
protected String doInBackground(Void... params) {
try {
/**
* 创建一个server socket同时等待client的连接
*/
ServerSocket serverSocket = new ServerSocket(8888);
Socket client = serverSocket.accept();
/**
*获得来自client的连接,将来自client的inputStream保
*存到对应的文件中
*/
final File f = new File(Environment.getExternalStorageDirectory() + "/"
+ context.getPackageName() + "/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.e(WiFiDirectActivity.TAG, e.getMessage());
return null;
}
}
@Override
protected void onPostExecute(String result) {
if (result != null) {
statusText.setText("File copied - " + result);
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" + result), "image/*");
context.startActivity(intent);
}
}
}
在客户端,通过一个socket与server socket相连,并传输数据
Context context = this.getApplicationContext();
String host;
int port;
int len;
Socket socket = new Socket();
byte buf[] = new byte[1024];
...
try {
/**
* 创建一个client socket,设置需连接的服务端的IP地址,端口号和
* 超时时间
*/
socket.bind(null);
socket.connect((new InetSocketAddress(host, port)), 500);
OutputStream outputStream = socket.getOutputStream();
ContentResolver cr = context.getContentResolver();
InputStream inputStream = null;
inputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg"));
while ((len = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, len);
}
outputStream.close();
inputStream.close();
} catch (FileNotFoundException e) {
//catch logic
} catch (IOException e) {
//catch logic
}
/**
* 在完成传输或发生异常时清理任何打开的socket。
*/
finally {
if (socket != null) {
if (socket.isConnected()) {
try {
socket.close();
} catch (IOException e) {
//catch logic
}
}
}
}
总结
使用Wi-Fi P2P的大致流程:
- 实现一个BroadcastReceiver,用于接收系统广播的intent。
- 通过discoverPeers方法查找可用的节点。
- 通过connect方法连接选择的节点。
- 通过client-server socket 实现数据的传递。
一个简单的例子:android手机之间通过Wifi传递图片示例