Wifi peer-to-peer允许Android 4.0(API level14)或以后的拥有wifi硬件的设备通过wifi直接互相连接,而不用中间媒介(Android的wifi P2p框架符合wifi联盟的认证计划)。通过使用该框架的api,你可以在相互间都支持wifi p2p时发现并连接其他设备,并且比蓝牙更远距离且更高速度地互相通信。这对于用户之间共享数据是非常有用的,例如多玩家游戏或者照片分享app。
为了Wifi P2p框架可以通知你的Activity方法调用的状态,WifiP2pManager的方法要求你传进一个监听器。可用的监听器接口和对应的WifiP2pMethod方法,列举如下:
wifi p2p模块的api由一下部分组成:
1允许你发现,请求和连接到点的方法(定义在WifiP2pManager类中);
2允许你监听WifiP2pManager类中方法是否成功执行的监听器。当调用WifiP2pManager的方法时,每个方法都会收到一个指定的监听器(通过函数参数传入)。
3通知你特定事件的Intent(由wifi P2p框架通知),例如连接失败,发现新的连接点等
你经常需要一起使用这三个部分的api。例如,你可以提供一个WifiP2pManager.ActionListener给discoverPeers()方法,这样你就会被 ActionListener.onSuccess()
and ActionListener.onFailure() 方法通知。如果discoverPeers()方法发现可连接点的列表改变时一个
WIFI_P2P_PEERS_CHANGED_ACTION
intent也会被广播。
API Overview
WifiP2pManager对象为你提供许多方法,这使你能够和wifi设备通信(例如发现和连接点)。下面列举方法:
Table 1.Wi-Fi P2P Methods
Method | Description |
---|---|
initialize() | Registers the application with the Wi-Fi framework. This must be called before calling any other Wi-Fi P2P method. |
connect() | Starts a peer-to-peer connection with a device with the specified configuration. |
cancelConnect() | Cancels any ongoing peer-to-peer group negotiation. |
requestConnectInfo() | Requests a device's connection information. |
createGroup() | Creates a peer-to-peer group with the current device as the group owner. |
removeGroup() | Removes the current peer-to-peer group. |
requestGroupInfo() | Requests peer-to-peer group information. |
discoverPeers() | Initiates peer discovery |
requestPeers() | Requests the current list of discovered peers. |
Table 2. Wi-Fi P2P Listeners
Table 3. Wi-Fi P2P Intents
Intent | Description |
---|---|
WIFI_P2P_CONNECTION_CHANGED_ACTION | Broadcast when the state of the device's Wi-Fi connection changes. |
WIFI_P2P_PEERS_CHANGED_ACTION | Broadcast when you call discoverPeers() . You usually want to call requestPeers() to get an updated list of peers if you handle this intent in your application. |
WIFI_P2P_STATE_CHANGED_ACTION | Broadcast when Wi-Fi P2P is enabled or disabled on the device. |
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION | Broadcast when a device's details have changed, such as the device's name. |
Creating a Broadcast Receiver for Wi-Fi P2P Intents
广播接收器允许你对系统的广播进行接收,这样你的app可以对你感兴趣的事件进行处理。基本的广播接收器创建步骤列举如下:
1 创建一个class继承自BroadcastReceiver。对于接收器的构造函数,你很可能想要WifiP2pManager,WifiP2pManager.Channel作为参数和注册接收器的Activity。这个允许你的广播接收器发送更新给你的Activity,并且访问wifi设备,访问通信通道。
2在广播接收器中,挑出你感兴趣的意图并在onReceive方法中进行处理。根据收到的意图执行需要的操作。例如,如果接收器收到
WIFI_P2P_PEERS_CHANGED_ACTION
的意图,你可以调用requestPeers()方法来获取当前可连接点的表。
下面的代码向你展示了如何创建一个典型的广播接收器。在接收到意图时,广播接收器用WifiP2pManager和Activity对象作为参数,并用这两个对象来适当的执行需要的操作:
* A BroadcastReceiver that notifies of important Wi-Fi p2p events. */ public class WiFiDirectBroadcastReceiver extends BroadcastReceiver { private WifiP2pManager mManager; private Channel mChannel; private MyWiFiActivity mActivity; public WiFiDirectBroadcastReceiver(WifiP2pManager manager, Channel channel, MyWifiActivity activity) { super(); this.mManager = manager; this.mChannel = 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)) { // Check to see if Wi-Fi is enabled and notify appropriate activity } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // Call WifiP2pManager.requestPeers() to get a list of current peers } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { // Respond to new connection or disconnections } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { // Respond to this device's wifi state changing } } }
Creating a Wi-Fi P2P Application
创建一个wifi P2p app涉及为你的app创建创建和注册广播接收器,发现连接点,连接和发送数据到一个点。下面的部分介绍如何做到:
Initial setup
在使用wifi- P2p api之间,你必须确保你的app可以访问硬件,并且设备支持wifi功能。如果支持wifi你可以获取WifiP2pManager对象,创建并注册的的广播接收器,并开始使用api。
1在Android manifest中请求使用wifi硬件的权限并声明你的app拥有正确的最小sdk版本:
<uses-sdk android:minSdkVersion="14" /> <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检查是否支持wifi。一个确认这个的好地方是当你的广播接收器接收到
WIFI_P2P_STATE_CHANGED_ACTION
intent.时,把wifi状态通知你的activity:
@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方法中,获取wifiP2pManager的实例并调用initialize()注册wifi框架。这个方法返回一个
WifiP2pManager.Channel
,这是用来连接你的app和wifi框架的。你应该创建一个广播接收器的实例(用wifiP2pManager和WiFiP2pManager.channel 做构造器参数),这使你的广播接收器把感兴趣的事情通知你的activity并根据它更新。这也使你在必要时能够操作设备的wifi状态:
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); ... }创建一个intent filter并添加与广播接收器要检查的相同的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); ... }在onResume()方法中注册你的广播接收器,并在onPause()方法中unregister它:
/* register the broadcast receiver with the intent values to be matched */ @Override protected void onResume() { super.onResume(); registerReceiver(mReceiver, mIntentFilter); } /* unregister the broadcast receiver */ @Override protected void onPause() { super.onPause(); unregisterReceiver(mReceiver); }当你获得了wifiP2pManager.Channel并设置了广播接收器,你的app就可以调用wifi模块的app并接受wifi intents。 你现在可以实现你app并通过调用WiFiP2pManager中的方法来 使用wifi特性。下一个部分将介绍如何执行常见的操作,如发现和连接热点。
Discovering peers
为了发现可以连接的热点,你要调用discoverPeers()方法来发现一定范围内可用的热点。调用这个方法是不同步的, 如果你创建了一个WifiP2pManager.ActionListener那么
会在
onSuccess()和
onFailure()方法中对成功和失败时进行相应处理。onSuccess()方法只在discovery()操作成功执行,并且不会提供任何关于实际发现的热点信息,如果有的话:
mManager.discoverPeers(channel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { ... } @Override public void onFailure(int reasonCode) { ... } });如果discover过程成功并发现热点,系统将会广播
WIFI_P2P_PEERS_CHANGED_ACTION
intent,你可以在广播接收器里面监听并获取一个热点列表。当你的app接收 WIFI_P2P_PEERS_CHANGED_ACTION
intent时,你可以通过requestPeers()请求一个发现的热点的列表。下面的代码展示如何操作:
PeerListListener myPeerListListener; ... 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, myPeerListListener); } }requestPeers()方法也是不同步的,并会在热点表可用时用过onPeersAvailable(
WifiP2pManager.PeerListListener
interface中定义
)(在)方法通知你的activity。onPeersAvailable()方法为你提供一个
WifiP2pDeviceList (你可以通过迭代找出你想要的热点);
Connecting to peers
当你获得热点表并找出你要连接的设备后,调用connect()方法来连接到该设备。这个方法需要一个
WifiP2pConfig
对象(该对象拥有要连接的设备的信息)。连接的成功与失败会在WifiP2pManager.ActionListener
. 中通知。下面的代码展示如何创建一个连接到一个目标设备:
//obtain a peer from the 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
一旦连接被建立,你可以用sockets在设备之间传输数据,传输数据的基本步骤如下:
1创建一个ServerSocket。这个socket会等待来自客户端的连接(在指定的端口并阻塞线程直到连接成功),所以这在一个后台线程中执行。
2创建一个客户端端socket,客户端用户使用ip地址和服务端的端口号来连接到服务端。
3从客户端发送数据到服务端,当客户端socket成功连接到服务端的socket时,你可以用字节流的方式向服务端发送数据。
4服务端socket通过accept()方法来等待客户端的连接。这个方法是阻塞式的,所以请在另一个线程里面调用该方法。当一个连接发生时,服务端设备会接收到客户端发来的数据。可以对该数据执行任何操作,如把他存储到一个文件或显示给用户。
下面的例子,根据
Wi-Fi P2P Demo
sample修改而来,想你展示怎样创建这样一个客户端-服务端之间socket通信,并用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 { /** * 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() + "/" + 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; } } /** * Start activity that can handle the JPEG image */ @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 { /** * Create a client socket with the host, * port, and timeout information. */ socket.bind(null); socket.connect((new InetSocketAddress(host, port)), 500); /** * Create a byte stream from a JPEG file and pipe it to the output stream * of the socket. This data will be retrieved by the server device. */ 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 } /** * Clean up any open sockets when done * transferring or if an exception occurred. */ finally { if (socket != null) { if (socket.isConnected()) { try { socket.close(); } catch (IOException e) { //catch logic } } } }