安卓开发文档翻译:无线网点对点连接,Wi-Fi Peer-to-Peer
无线点对点(P2P)使得安卓4.0 (应用编程接口级别14)及更高版本的带有相应硬件的设备可以互相之间直接通过无线网连接,而不需要一个中间的接入点(安卓的无线点对点框架已经通过了无线技术协会的无线直连(Wi-Fi Direct™)认证)。使用这些接口,妳就可以在支持无线点对点连接的设备之间互相发现对方并且连接到对方,然后以距离和速度都比蓝牙连接大得多的一个连接来互相通信。这对于那些要在用户之间传递数据的应用程序来说狠有用,比如,多人游戏,或者照片分享应用程序。
无线点对点应用编程接口由以下主要部分组成:
•. WifiP2pManager 类中定义了一些方法,用来发现、请求及连接到其它节点。
•. 一些监听器,让妳可以得知对于 WifiP2pManager 中那些方法进行调用的结果是成功还是失败。 在调用 WifiP2pManager 中的方法时,都可以带上一个特定的监听器作为其中的参数。
•. 一些意图(Intent),向妳告知由无线点对点框架检测到的特定事件,例如某个连接被断开了,或者发现了一个新节点。
妳通常会需要将应用编程接口 中的这三个主要组件一起使用。例如, 在调用 discoverPeers() 时,妳可以提供一个 WifiP2pManager.ActionListener , 这样, 当有对应的事件发生时, 会通过 ActionListener.onSuccess() 和 ActionListener.onFailure() 方法来通知妳。另外 , 当 discoverPeers() 方法发现节点列表 发生变动时,也会发出一个 WIFI_P2P_PEERS_CHANGED_ACTION 广播。
WifiP2pManager 类提供了一些方法,使得 妳可以与设备上的无线网硬件交互, 以进行一系列动作,例如 发现和连接到其它节点。 有以下动作可用:
表 1. 无线 点对点方法
方法 |
说明 |
将本应用程序注册到无线网框架中。必须先调用这个方法,才能调用任何其它的无线点对点方法。 |
|
对一个拥有特定配置信息的设备开始一个点对点连接。 |
|
启动对节点的发现过程。 |
WifiP2pManager 中的那些方法都可以传入一个监听器,这样,无线 网点对点框架就可以向妳的活动告知方法调用的状态。 下表,列出了可用 的监听器接口和对应的 WifiP2pManager 方法:
表 2. 无线 点对点监听器
监听器接口 |
相关联的动作 |
connect() 、 cancelConnect() 、 createGroup() 、 removeGroup() 和 discoverPeers() |
|
无线 点对点应用编程接口中定义了一些意图,它们会在特定的无线点对点事件发生时被广播,例如 , 发现了一个新节点,或者设备的无线网状态发生变化。 妳 可以 创建 一个广播接收器 ,以便在自己的应用程序中接收这些意图,并且处理:
表 3. 无线 网点对点意图
意图 |
说明 |
当妳调用了 discoverPeers() 之后,会收到这个广播。如果妳在自己的应用程序中处理这个意图的话,那么,妳通常需要调用 requestPeers() 以获取最新的节点列表。 |
使用广播接收器,可以接收到安卓系统发送出来的广播,这样,妳的应用程序就可以对妳所感兴趣的事件作出响应。以下是创建用于处理无线点对点意图的广播接收器的基本步骤:
1. 创建 一个类,它继承自 BroadcastReceiver 类。 妳可能需要在这个类的构造函数中加入三个参数:对应 的 WifiP2pManager 、 WifiP2pManager.Channel 和 要在其中注册这个广播接收器的活动。 这样,该广播接收器就可以向该活动发送更新了,并且 还能够 在必要的时候 访问到无线 网硬件 和通信信道。
2. 在广播接收器的 onReceive() 方法里,检查当前所收到的意图是不是妳所感兴趣的。根据 所收到的意图,做出任何必要的动作。例如,假设 该广播接收器接收到了一个 WIFI_P2P_PEERS_CHANGED_ACTION 意图,则, 妳可以调用 requestPeers() 方法,以获取当前发现的节点的列表。
以下代码展示了,如何创建一个典型的广播接收器。 这个广播接收器需要两个参数 :一个 WifiP2pManager 对象和一个活动。 它会使用这两个类来在接收到意图时做出适当的动作 :
/**
* 会针对重要的无线点对点事件发出通知的 BroadcastReceiver 。
*/
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 )) {
// 检查无线 网是否被启用, 如果启用 了,则 向适当的活动发出通知
} 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 )) {
// 对于 本设备的无线网状态变动做出响应
}
}
}
创建无线点对点应用,需要以下步骤:为妳的应用程序创建及注册一个广播接收器;发现节点;连接到某个节点;向连接的节点传递数据。以下各个小节说明了这一点。
在使用无线点对点应用编程接口之前, 妳必须确保,妳的应用程序能够访问到对应的硬件,并且该设备支持无线点对点协议。如果支持无线 点对点的话,则, 妳可以获取 WifiP2pManager 的一个实例,创建 并注册一个广播接收器,然后开始使用无线点对点应用编程接口。
1.请求获取对应的权限,以使用设备上的无线网硬件,并且在安卓清单文件中声明妳的应用程序所要求的最低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_P2P_STATE_CHANGED_ACTION 意图时进行这项检查。 向妳的活动告知无线网点对点状态,以便做出相应的反应:
@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 ) {
// 无线 网点对点处于启用状态
} else {
// 无线 网点对点未启用
}
}
...
}
3. 在妳的活动的 onCreate() 方法中,获取 一个 WifiP2pManager 实例,并且调用 其 initialize() 方法,以将妳的应用程序注册到无线网点对点框架中。 这个方法会返回一个 WifiP2pManager.Channel ,它的作用就是 将妳的应用程序与无线网点对点框架连接起来。另外 ,妳还应当创建一个广播接收器实例, 向它传入此处获取 到的 WifiP2pManager 和 WifiP2pManager.Channel 对象 ,以及妳的活动的一个引用。 这样,妳的广播接收器就可以 向妳的活动告知各个事件,以便做出对应的响应。 另外也可以在必要的时候对设备的无线网状态进行操作:
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.创建一个意图过滤器,向其中加入妳的广播接收器会检查的那些意图:
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. 在妳的活动的 onResume() 方法中注册这个广播接收器,在 onPause() 方法中解除注册:
/* 使用妳所关心的那些意图值来注册这个广播接收器 */
@Override
protected void onResume () {
super . onResume ();
registerReceiver ( mReceiver , mIntentFilter );
}
/* 解除广播接收器的注册 */
@Override
protected void onPause () {
super . onPause ();
unregisterReceiver ( mReceiver );
}
当妳获取了 WifiP2pManager.Channel ,并且设置好了广播接收器之后, 妳的应用程序就可以调用那些无线网点对点方法,并且接收无线网点对点意图了。
好了,妳可以继续编写妳的应用程序了,通过调用 WifiP2pManager 的各个方法来使用无线网点对点功能。 下一小节将说明,如何进行常用操作,例如 发现及连接到节点。
调用 discoverPeers() 以探测那些处于信号范围内的可用节点。 对这个函数的调用是异步的,如果 妳为之创建了一个 WifiP2pManager.ActionListener 的话,则 它会通过 onSuccess() 和 onFailure() 来向妳的应用程序告知调用结果。 onSuccess() 方法只是用来 向妳告知,发现节点的过程已经成功完成, 但不会提供关于已经发现的节点的任何信息:
mManager . discoverPeers ( channel , new WifiP2pManager . ActionListener () {
@Override
public void onSuccess () {
...
}
@Override
public void onFailure ( int reasonCode ) {
...
}
});
如果 发现过程成功完成并且探测到了节点,则,系统 会发出 WIFI_P2P_PEERS_CHANGED_ACTION 广播, 妳可以在一个广播接收器里监听这个广播, 以获取到节点列表。 当妳的应用程序接收到 WIFI_P2P_PEERS_CHANGED_ACTION 意图时, 妳就可以通过 requestPeers() 来请求获取已发现的节点列表了。 以下是代码示例:
PeerListListener myPeerListListener ;
...
if ( WifiP2pManager . WIFI_P2P_PEERS_CHANGED_ACTION . equals ( action )) {
// 从点对点管理器请求获取可用节点列表。 这是一个异步调用,
// 会通过回调函数PeerListListener.onPeersAvailable()来通知调用这个方法的活动
if ( mManager != null ) {
mManager . requestPeers ( mChannel , myPeerListListener );
}
}
requestPeers() 方法 也是异步的, 当节点列表准备好了之后, 会通过 onPeersAvailable() 回调函数来通知妳的活动, 这个回调函数是在 WifiP2pManager.PeerListListener 接口中定义的。 onPeersAvailable() 函数 会向妳提供一个 WifiP2pDeviceList , 妳可以对其进行遍历,以找到妳想连接的那个节点。
当妳获取 到可连接的节点,并且找到了要连接的设备之后,就可以调用 connect() 方法来连接到那个设备了。 这个方法需要一个 WifiP2pConfig 对象, 它包含着要连接的设备的信息。连接结果 会通过 WifiP2pManager.ActionListener 告知给妳。 以下代码片断展示了如何连接到指定的设备:
//从WifiP2pDeviceList中获取一个节点
WifiP2pDevice device ;
WifiP2pConfig config = new WifiP2pConfig ();
config . deviceAddress = device . deviceAddress ;
mManager . connect ( mChannel , config , new ActionListener () {
@Override
public void onSuccess () {
//连接成功之后 的处理
}
@Override
public void onFailure ( int reason ) {
//连接失败之后 的处理
}
});
当连接建立成功之后,妳就可以通过套接字在设备之间传输数据了。传输数据的基本步骤是:
1. 创建 一个 ServerSocket 。 这个套接字会在指定的端口等待客户端连接,并且处于阻塞状态,所以 ,请在后台线程中做这件事。
2. 创建 一个客户端 Socket 。客户端使用服务器 套接字的IP 地址和端口来连接到服务器设备。
3.从客户端向服务器发送数据。当客户端套接字成功连接到服务器套接字之后,妳就可以使用字节流从客户端向服务器发送数据了。
4. 服务器套接字等待客户端连接(具体 就是调用 accept() 方法 )。 这个调用会阻塞,直到有客户端连接上来为止,因此,应当在另一个线程中调用这个方法。 当有连接建立之后,服务器设备 就可以从客户端接收数据了。然后 ,对这个数据做出对应的动作,例如, 将它保存到文件中,或者显示给用户。
以下代码是从 无线 网点对点演示 示例修改过来的,展示 了,如何建立 这样的客户端/服务器通信,并且 将JPEG 图片从一个客户端传输到一个带有服务的服务器上。 若想看一个完整的正常工作的示例,则编译运行 无线 网点对点演示 示例 。
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 {
/**
* 创建 一个服务器套接字,并且等待客户端连接。 这个调用会阻塞,
* 直到 从客户端接收到了一个连接
*/
ServerSocket serverSocket = new ServerSocket ( 8888 );
Socket client = serverSocket . accept ();
/**
* 如果代码执行 到了这里,则表示,已经有一个客户端连接上来,并且
* 开始传输数据 了。 把从客户端处读取的输入流保存为一个 JPEG文件
*/
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 ;
}
}
/**
* 启动能够处理 此JPEG 图片的活动
*/
@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 );
}
}
}
在客户端上,使用一个客户端套接字连接到服务器套接字,并且传输数据。在这个示例中,会将客户端设备的文件系统上的一个JPEG 图片传输过去。
Context context = this . getApplicationContext ();
String host ;
int port ;
int len ;
Socket socket = new Socket ();
byte buf [] = new byte [ 1024 ];
...
try {
/**
* 创建 一个客户端套接字,指定主机、端口和超时信息。
*/
socket . bind ( null );
socket . connect (( new InetSocketAddress ( host , port )), 500 );
/**
* 从 JPEG 文件创建一个字节流, 并将它转接到套接字的输出流上。
* 这些数据会被服务器设备接收到。
*/
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 ( IOException e ) {
//捕获异常
}
/**
* 在传送完毕之后,或者在出现异常时,清理处于打开状态的套接字。
*/
finally {
if ( socket != null ) {
if ( socket . isConnected ()) {
try {
socket . close ();
} catch ( IOException e ) {
//捕获异常
}
}
}
}
HxLauncher: Launch Android applications by voice commands