之前做了个WiFi直连的小应用,忙于之前没时间整理,趁着周末有空整理一下。
WiFi直连可以在不通过网络或热点的情况下,直接与周围的设备进行连接并进行信息交换。WiFi直连是在Android 4.0(API level 14)或更高的版本中才加入的新功能。本文主要介绍通过Wi-Fi Direct查找附近的设备,实现连接并完成图片的发送。一般包括如下几个步骤:
• 在AndroidManifest文件中声明相关权限
• 创建对等网络管理器(WifiP2pManager),注册相关广播监听WiFi直连状态
• 创建MyAdapter,用于显示搜索到的对等点列表,监听点击连接事件
• 开始对对等点的搜索,获取对等点列表
• 连接一个对等点
• 判断一方为组拥有者(GroupOwner),提交接收图片的异步任务
• 调用系统图库选择图片,开启后台服务将其发送
• 分别设作为组拥有者,完成图片的互发
整体的流程图如下:
接下来进行分部说明
在AndroidManifest文件中声明相关权限
WiFi直连是在api level 14及更高的版本才能使用,所以要声明
android:minSdkVersion="14"。
创建对等网络管理器(WifiP2pManager),注册相关广播监听WiFi直连状态
使用WiFi直连,需要监听如下动作:
WIFI_P2P_STATE_CHANGED_ACTION — 表明Wi-Fi对等网络(P2P)是否已经启用
WIFI_P2P_PEERS_CHANGED_ACTION — 表明可用的对等点的列表发生了改变
WIFI_P2P_CONNECTION_CHANGED_ACTION — 表明Wi-Fi对等网络的连接状态发生了改变
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION — 表明该设备的配置信息发生了改变
private BroadcastReceiver mReceiver;
private IntentFilter mFilter;
private void initIntentFilter() {
mFilter = new IntentFilter();
mFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
mFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
mFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
mFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
}
mReceiver = new WifiDirectBroadcastReceiver(mManager, mChannel, this, mPeerListListerner, mInfoListener);
@Override
protected void onResume() {
super.onResume();
registerReceiver(mReceiver, mFilter);
}
@Override
public void onPause() {
super.onPause();
unregisterReceiver(mReceiver);
}
广播接收
public class WifiDirectBroadcastReceiver extends BroadcastReceiver {
private WifiP2pManager mManager;
private WifiP2pManager.Channel mChannel;
private Activity mActivity;
private WifiP2pManager.PeerListListener mPeerListListener;
private WifiP2pManager.ConnectionInfoListener mInfoListener;
public WifiDirectBroadcastReceiver(WifiP2pManager manager, WifiP2pManager.Channel channel, Activity activity,
WifiP2pManager.PeerListListener peerListListener,
WifiP2pManager.ConnectionInfoListener infoListener
) {
this.mManager = manager;
this.mChannel = channel;
this.mPeerListListener = peerListListener;
this.mActivity = activity;
this.mInfoListener = infoListener;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
/*check if the wifi is enable*/
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
}
/*get the list*/
else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
mManager.requestPeers(mChannel, mPeerListListener);
} /*Respond to new connection or disconnections*/
else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
if (mManager == null) {
return;
}
NetworkInfo networkInfo = (NetworkInfo) intent
.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
if (networkInfo.isConnected()) {
mManager.requestConnectionInfo(mChannel, mInfoListener);
} else {return;
}
}
/*Respond to this device's wifi state changing*/
else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
}
}
}
创建MyAdapter,用于显示接下来搜索到的对等点列表,监听点击连接事件
继承RecyclerView.Adapter,实现获取对等点列表的显示和响应事件
public class MyAdapter extends RecyclerView.Adapter {
private List> mList;
/*
* 设置回调接口,实现点击
* */
public interface OnItemClickListener {
void OnItemClick(View view, int position);
}
public OnItemClickListener mOnItemClickListener;
public void SetOnItemClickListener(OnItemClickListener listener) {
this.mOnItemClickListener = listener;
}
public MyAdapter(List> list) {
super();
this.mList = list;
}
@Override
public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.card_item,
parent, false);
MyHolder myHolder = new MyHolder(view);
return myHolder;
}
@Override
public void onBindViewHolder(final MyHolder holder, final int position) {
holder.tvname.setText(mList.get(position).get("name"));
holder.tvaddress.setText(mList.get(position).get("address"));
if (mOnItemClickListener != null) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnItemClickListener.OnItemClick(holder.itemView, position);
}
});
}
}
@Override
public int getItemCount() {
return mList.size();
}
class MyHolder extends RecyclerView.ViewHolder {
public TextView tvname;
public TextView tvaddress;
public MyHolder(View View) {
super(View);
tvname = (TextView) View.findViewById(R.id.tv_name);
tvaddress = (TextView) View.findViewById(R.id.tv_address);
}
}
}
开始对对等点的搜索,获取对等点列表
首先在 onCreate() 方法中获得了WifiP2pManager 的一个实例,调用它的 initialize() 方法。这个方法返回一个 WifiP2pManager.Channel 对象,并触发WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION
的广播,把应用程序连接到Wi-Fi Direct框架中。
protected void onCreate(Bundle savedInstanceState) {
...
mManager = (WifiP2pManager) getSystemService(WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, Looper.myLooper(), null);
}
点击开启搜索,调用discoverPeers()方法。其参数如下:
WifiP2pManager.Channel 即从上一步调用initialize()的 方法得到
WifiP2pManager.ActionListener 实现了系统在查找成功或失败时会调用的方法
mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
//Register for WIFI_P2P_PEERS_CHANGED_ACTION
@Override
public void onSuccess() {
}
@Override
public void onFailure(int reason) {
}
});
如果发现设备会回调上面的onSuccess()
方法,讲触发WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION
这个广播,在onReceive()中可以调用
requestPeers()
方法,然后在MainActivity的PeerListListerner接口中,调用onPeersAvailable()回调方法,获取到对等列表的详细信息,通过MyAdapter在RecyclerView列表中显示。
WifiP2pManager.PeerListListener mPeerListListerner = new WifiP2pManager.PeerListListener() {
@Override
public void onPeersAvailable(WifiP2pDeviceList peersList) {
peers.clear();
peersshow.clear();
Collection aList = peersList.getDeviceList();
peers.addAll(aList);
for (int i = 0; i < aList.size(); i++) {
WifiP2pDevice a = (WifiP2pDevice) peers.get(i);
HashMap map = new HashMap();
map.put("name", a.deviceName);
map.put("address", a.deviceAddress);
peersshow.add(map);
}
mAdapter = new MyAdapter(peersshow);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager
(MainActivity.this));
}
};
连接一个对等点
触摸RecyclerView列表上的对等设备信息,触发响应事件,调用CreateConnect()方法,方法参数为触摸列表上的设备信息。在该方法中,需要先创建一个新的WifiP2pConfig对象,记录传进来的设备参数,然后调用connect()方法。 如果设备会回调onSuccess()方法,这说明对等点连接成功。
private void CreateConnect(String address, final String name) {
WifiP2pDevice device;
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = address;
config.wps.setup = WpsInfo.PBC;
mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {
// Register for WIFI_P2P_CONNECTION_CHANGED_ACTION
@Override
public void onSuccess() {
}
@Override
public void onFailure(int reason) {
}
});
}
判断一方为组拥有者(GroupOwner),提交接收图片的异步任务
对等点连接成功后,会触发WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION
这个广播,在onReceive()中调用requestConnectionInfo()
方法,然后在MainActivity的ConnectionInfoListerner接口中,调用onConnectionInfoAvailable()回调方法,获取到组连接信息,根据获取到的信息,判断是否为组拥有者。若是,作为服务端,向其提交图片下载、读取的异步任务。
WifiP2pManager.ConnectionInfoListener mInfoListener = new WifiP2pManager.ConnectionInfoListener() {
@Override
public void onConnectionInfoAvailable(final WifiP2pInfo minfo) {
info = minfo;
view = (TextView) findViewById(R.id.tv_main);
if (info.groupFormed && info.isGroupOwner) {
//确定为组拥有者,创建线程用于接收连接请求
//提交图片下载、读取的异步任务
} else if (info.groupFormed) {
//作为客户端,创建一个线程用于连接组拥有者
}
}
};
public class FileServerAsyncTask extends
AsyncTask {
private Context context;
private TextView statusText;
/**
* @param context
* @param 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();
final File f = new File(
Environment.getExternalStorageDirectory() + "/" + "RecvPicture"+
"/wifip2pshared-" + System.currentTimeMillis() + ".jpg");
File dirs = new File(f.getParent());
if (!dirs.exists())
dirs.mkdirs();
f.createNewFile();
/*Returns an input stream to read data from this socket*/
InputStream inputstream = client.getInputStream();
copyFile(inputstream, new FileOutputStream(f));
serverSocket.close();
return f.getAbsolutePath();
} catch (IOException e) {
Log.e("xyz", e.toString());
return null;
}
}
/*
* (non-Javadoc)
*
* @see android.os.AsyncTask#onPostExecute(java.lang.Object)
*/
@Override
protected void onPostExecute(String result) {if(result != null){
Toast.makeText(context, "RecvPic:"+result, Toast.LENGTH_SHORT).show();
}
if (result != null) {
statusText.setText("RecvPic: " + result);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" + result), "image/*");
context.startActivity(intent);
}
}public static boolean copyFile(InputStream inputStream, OutputStream out) {
byte buf[] = new byte[1024];
int len;
try {
while ((len = inputStream.read(buf)) != -1) {
out.write(buf, 0, len);
}
out.close();
inputStream.close();
} catch (IOException e) {
return false;
}
return true;
}
}
调用系统图库选择图片,开启后台服务将其发送
点击发送图片按钮,调用系统图库。选取图片后,开启图片发送服务,并将图片路径,发送的ip地址和接收的端口号传入。
public class FileTransferService extends IntentService {
private static final int SOCKET_TIMEOUT = 5000;
public static final String ACTION_SEND_FILE = "com.example.android.wifidirect.SEND_FILE";
public static final String EXTRAS_FILE_PATH = "sf_file_url";
public static final String EXTRAS_GROUP_OWNER_ADDRESS = "sf_go_host";
public static final String EXTRAS_GROUP_OWNER_PORT = "sf_go_port";
public FileTransferService(String name) {
super(name);
}
public FileTransferService() {
super("FileTransferService");
}
/*
* (non-Javadoc)
*
* @see android.app.IntentService#onHandleIntent(android.content.Intent)
*/
@Override
protected void onHandleIntent(Intent intent) {
Context context = getApplicationContext();
if (intent.getAction().equals(ACTION_SEND_FILE)) {
String fileUri = intent.getExtras().getString(EXTRAS_FILE_PATH);
String host = intent.getExtras().getString(
EXTRAS_GROUP_OWNER_ADDRESS);
Socket socket = new Socket();
int port = intent.getExtras().getInt(EXTRAS_GROUP_OWNER_PORT);
try {
Log.d("xyz", "Opening client socket - ");
socket.bind(null);
socket.connect((new InetSocketAddress(host, port)),
SOCKET_TIMEOUT);
Log.d("xyz",
"Client socket - " + socket.isConnected());
/*returns an output stream to write data into this socket*/
OutputStream stream = socket.getOutputStream();
ContentResolver cr = context.getContentResolver();
InputStream is = null;
try {
is = cr.openInputStream(Uri.parse(fileUri));
} catch (FileNotFoundException e) {
}
FileServerAsyncTask.copyFile(is, stream);
} catch (IOException e) {
} finally {
if (socket != null) {
if (socket.isConnected()) {
try {
socket.close();
} catch (IOException e) {
// Give up
e.printStackTrace();
}
}
}
}
}
}
}
可分别设作为组拥有者,完成图片的互发
调用createGroup()方法,分别设定本设备为组拥有者,实现设备图片的互发。
mManager.createGroup(mChannel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
Log.i("tag","BeGroupOwner success");
}
@Override
public void onFailure(int reason) {
Log.i("tag","BeGroupOwner failed");
}
});
声明相关权限