Miko Android自学之路 WifiDirect中文最强详解,如何传输数据,如何设置GroupOwener,如何设置客户端以及服务器端
来源:互联网 发布:数据库功能分类 编辑:程序博客网 时间:2024/05/16 06:06
大家好我是Miko,最近有参加一个比赛,用到了WifiDirect技术,于是翻看官方文档之后,想写一个Demo,Google API Sample已经很老了,还是用的Eclipse,宝宝心好累=_=+///,在CSDN上找了几篇文章,竟然都是些API的国语翻译,程序猿节操何在?这里我将会用自己根据官方Demo重写的Demo来详解WifiDirect的使用。
Android4.0之后开始支持WifiDirect技术,即Wifi直连,做为一种通讯方式,它的优势在于传输速度快传输距离远。
ok
首先上我的DemoGithub地址https://github.com/MikoGodZd/WifiDerect
在上官方文档http://developer.android.com/intl/zh-tw/guide/topics/connectivity/wifip2p.html
官方Demo http://download.csdn.net/detail/yichigo/5516627
通过Wi-Fi Direct查找附近的设备,并与之连接一般包括如下几个骤:
一 设置应用程序权限
二 创建一个广播接收器和对等网络管理器
三 初始化对等点的搜索
四 获取对等点列表
五 连接一个对等点
一、设置权限
我们mainifest中添加如下权限
<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" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
注:
android.permission.ACCESS_WIFI_STATE 允许程序访问Wi-Fi网络状态信息(Allows applications to access information about Wi-Fi networks)
android.permission.CHANGE_WIFI_STATE 允许程序改变Wi-Fi连接状态(Allows applications to change Wi-Fi connectivity state)
android.permission.INTERNET 当需要访问网络的时候,需要在AndroidManifest.xml里面添加访问网络的权限
二、创建广播接收器
首先在MainActivity中初始化IntentFilter并让它监听以下动作:
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 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_DISCOVERY_CHANGED_ACTION); mFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); }
然后我们创建一个新的广播类
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); if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { activity.setIsWifiP2pEnabled(true); } else { activity.setIsWifiP2pEnabled(false); } } /*get the list*/ else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { mManager.requestPeers(mChannel, mPeerListListener); } /*查看当前是否处于查找状态 * get the state of discover*/ else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) { int State = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, -1); if (State == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED) Toast.makeText(mActivity, "搜索开启", Toast.LENGTH_SHORT).show(); else if (State == WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED) Toast.makeText(mActivity, "搜索已关闭", Toast.LENGTH_SHORT).show(); } /*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()) { Log.i("xyz", "已连接"); mManager.requestConnectionInfo(mChannel, mInfoListener); } else { Log.i("xyz", "断开连接"); return; } } /*Respond to this device's wifi state changing*/ else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { } }}
构造函数中的manager以及channel比较好理解,剩下的主要是在mainActivity中的方法的实现
第一个action用于检测当前设备的wifi是否打开很好理解
第二个action当你开始搜索之后,当设备列表发生变化的时候即触发通过mManager.requestPeers(mChannel, mPeerListListener);方法可以的到列表,这些我们下文详讲
第三个action用于检测当前是否处于搜索状态,
第四个action用于检测两个设备连接状态是否改变
第五个action适用于设备名称发生改变这里我们不讲
三、创建搜索,获得列表
ok 广播搭建好之后,我们就要初始化一个p2p了
首先在MainActivity中创建一个Manager以及一个Channel
private WifiP2pManager mManager;private WifiP2pManager.Channel mChannel;mManager = (WifiP2pManager) getSystemService(WIFI_P2P_SERVICE);mChannel = mManager.initialize(this, Looper.myLooper(), null);
这样我们就开启了direct服务
下面我们定义搜索函数
private void DiscoverPeers() { mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { } @Override public void onFailure(int reason) { } }); }
这里面的success以及failure没有任何实质的信息,只是提醒你调用这个方法是否成功,而不代表开启搜索是否成功,要监听搜索的状态,如上文所述我们要在广播中实现
else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) { int State = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, -1); if (State == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED) Toast.makeText(mActivity, "搜索开启", Toast.LENGTH_SHORT).show(); else if (State == WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED) Toast.makeText(mActivity, "搜索已关闭", Toast.LENGTH_SHORT).show(); }
ok开启搜索服务之后,当找到一个设备后,设备列表就会发生改变,这个时候就会触发广播中的第二个action
else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { mManager.requestPeers(mChannel, mPeerListListener); }
通过requestPeers(mChannel, mPeerListListener)再通过监听器中的onPeersAvailable方法就可以得到设备列表,当让我们想在主界面中显示列表,因此监听器我们在activity中实现然后通过传参的方式传入到广播之中,
WifiP2pManager.PeerListListener mPeerListListerner = new WifiP2pManager.PeerListListener() { @Override public void onPeersAvailable(WifiP2pDeviceList peersList) { peers.clear(); peersshow.clear(); Collection<WifiP2pDevice> aList = peersList.getDeviceList(); peers.addAll(aList); for (int i = 0; i < aList.size(); i++) { WifiP2pDevice a = (WifiP2pDevice) peers.get(i); HashMap<String, String> map = new HashMap<String, String>(); 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)); mAdapter.SetOnItemClickListener(new MyAdapter.OnItemClickListener() { @Override public void OnItemClick(View view, int position) { CreateConnect(peersshow.get(position).get("address"), peersshow.get(position).get("name")); } @Override public void OnItemLongClick(View view, int position) { } }); } };
这是在Activity中的实现,得到List的方法有很多,我使用了RecyclerView感觉游侠笨重,一定有其他好的方法=_=///
单击每一个设备我所写的Adapter就会返回出来它的MAC地址,通过地址我们就可以来连接设备了
三、创建连接
创建连接我们调用manager中的connect方法,如果连接之前没有创建一个组,系统会自动创建一个组,并且随机分配谁是GroupOwner即谁是组长,这也关系到谁是客户端谁是服务器,
connect方法官方文档解释
If the current device is part of an existing p2p group or has created
* a p2p group with {@link #createGroup}, an invitation to join the group is sent to
* the peer device.
我们写一个方法来创建Group,在两个设备中谁调用这个方法,谁就是组长这样就实现了设定谁是服务器与客户端
private void BeGroupOwener() { mManager.createGroup(mChannel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { } @Override public void onFailure(int reason) { } }); }
private void CreateConnect(String address, final String name) { WifiP2pDevice device; WifiP2pConfig config = new WifiP2pConfig(); Log.i("xyz", address); config.deviceAddress = address; /*mac地址*/ config.wps.setup = WpsInfo.PBC; Log.i("address", "MAC IS " + address); if (address.equals("9a:ff:d0:23:85:97")) { config.groupOwnerIntent = 0; Log.i("address", "lingyige shisun"); } if (address.equals("36:80:b3:e8:69:a6")) { config.groupOwnerIntent = 15; Log.i("address", "lingyigeshiwo"); } Log.i("address", "lingyige youxianji" + String.valueOf(config.groupOwnerIntent)); mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { } @Override public void onFailure(int reason) { } }); }
Wifip2pconfi这个类官方解释是
A class representing a Wi-Fi P2p configuration for setting up a connection
有道翻译 一个类代表一个wi - fi P2p配置为建立一个连接=_=+///
其实是一个类用来储存p2p设备的信息,我们把其中的Address值改为想要链接设备的Mac地址就可以得到设备的实例,
mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { } @Override public void onFailure(int reason) { } });
调用connect方法,实现连接,如上文所述,这里的sucess,以及failure都是表示函数的成功与否,要看是否连接还要到广播之中
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()) { Log.i("xyz", "已连接"); mManager.requestConnectionInfo(mChannel, mInfoListener); } else { Log.i("xyz", "断开连接"); return; } }
NetWorkInfo方法Describes the status of a network interface. 用来描述网络接口的状态
当网络连接时我们调用此方法mManager.requestConnectionInfo(mChannel, mInfoListener);就可让服务器端开始接受数据了,这里在下文中也会详讲。
四、传输数据前的准备
上文讲到mManager.requestConnectionInfo(mChannel, mInfoListener);方法
第二个参数是ConnectionInfoListener类
Interface for callback invocation when connection info is available用来当connect成功后回掉,跟上一个得List的监听器一样,我们也在MainActivity中实现,然后通过传参到广播之中
WifiP2pManager.ConnectionInfoListener mInfoListener = new WifiP2pManager.ConnectionInfoListener() { @Override public void onConnectionInfoAvailable(final WifiP2pInfo minfo) { Log.i("xyz", "InfoAvailable is on"); info = minfo; TextView view = (TextView) findViewById(R.id.tv_main); if (info.groupFormed && info.isGroupOwner) { Log.i("xyz", "owmer start"); mServerTask = new FileServerAsyncTask(MainActivity.this, view); mServerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); mDataTask = new DataServerAsyncTask(MainActivity.this, view); mDataTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else if (info.groupFormed) { SetButtonVisible(); } } };
我们看一下下面这核心代码
if (info.groupFormed && info.isGroupOwner) { Log.i("xyz", "owmer start"); mServerTask = new FileServerAsyncTask(MainActivity.this, view); mServerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); mDataTask = new DataServerAsyncTask(MainActivity.this, view); mDataTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else if (info.groupFormed) { SetButtonVisible(); }
我们使用的传输方式是UDP模型在这个Demo中只能从客户端向服务器端发送消息
ok知道这个前提之后我们看
第一个判断:如果组已经建立,并且是组长,也就是说当前设备是服务器,我们开启两个AsyncTask分别用来接收图片以及字符串,当然看完这篇博客之后你可以传输各种类型的数据,这里仅以这两个举例
AsyncTask怎么写我们后面再说,
看第二个判断:如果组建立了,但不是组长,也就是说当前设备是客户端,这个时候我们让两个发送Button可见,也就是说一开始所有的设备界面都是一样的没有发送数据的按钮,但判断出谁是客户端之后,我们就将发送按钮展现出来。
上截图
ok搜索连接都讲完了,下面就是大头传送数据了
五、传输数据
1.客户端 发送服务的编写
Activity中设置发送图片按钮的监听器
sendpicture.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); startActivityForResult(intent, 20); } });
Intent intent = new Intent(Intent.ACTION_GET_CONTENT)
官方文档Allow the user to select a particular kind of data and return it.
这个Intent会打开文件管理器
intent.setType(“image/*”);这个语句决定着以什么方式打开,我们设以图片方式打开,启动Activity后我们选择好图片,之后Activity关闭自动调用onActivityResult
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 20) { super.onActivityResult(requestCode, resultCode, data); Uri uri = data.getData(); Intent serviceIntent = new Intent(MainActivity.this, FileTransferService.class); serviceIntent.setAction(FileTransferService.ACTION_SEND_FILE); serviceIntent.putExtra(FileTransferService.EXTRAS_FILE_PATH, uri.toString()); serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_ADDRESS, info.groupOwnerAddress.getHostAddress()); serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_PORT, 8988); MainActivity.this.startService(serviceIntent); } }
Uri uri = data.getData();获得图片所在位置
serviceIntent.putExtra(FileTransferService.EXTRAS_FILE_PATH,uri.toString());将位置传入Service
serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_ADDRESS,info.groupOwnerAddress.getHostAddress());传入组长的IP地址,用来创建Socket端口
serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_PORT,8988);传入端口port
ok下面我们看服务究竟怎么写
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) { Log.d("xyz", e.toString()); } FileServerAsyncTask.copyFile(is, stream); Log.d("xyz", "Client: Data written"); } catch (IOException e) { Log.e("xyz", e.getMessage()); } finally { if (socket != null) { if (socket.isConnected()) { try { socket.close(); } catch (IOException e) { // Give up e.printStackTrace(); } } } } } }}
socket.connect((new InetSocketAddress(host, port)),
SOCKET_TIMEOUT);创建Socket连接
/*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) { Log.d("xyz", e.toString()); } FileServerAsyncTask.copyFile(is, stream);
将is copy到stream中ok这样我们的服务就启动成功了,至于怎么接收先不细讲,先看一下AsyncTask
2、服务器端 AsyncTask的编写
public class FileServerAsyncTask extends AsyncTask<Void, Void, String> { 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 { Log.i("xyz", "file doinback"); ServerSocket serverSocket = new ServerSocket(8988); Socket client = serverSocket.accept(); final File f = new File( Environment.getExternalStorageDirectory() + "/" + "com.miko.zd" + "/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) { Log.i("xyz", "file onpost"); Toast.makeText(context, "result"+result, Toast.LENGTH_SHORT).show(); if (result != null) { statusText.setText("File copied - " + result); Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse("file://" + result), "image/*"); context.startActivity(intent); } } /* * (non-Javadoc) * * @see android.os.AsyncTask#onPreExecute() */ @Override protected void onPreExecute() { } 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; }}
AsyncTask原理大家应该很清楚这里不细说,不懂Google=_=+///
ok先看doInbackGround
ServerSocket serverSocket = new ServerSocket(8988);创建Socket
Socket client = serverSocket.accept();这句很关键,
accept方法
Waits for an incoming request and blocks until the connection is opened.
This method returns a socket object representing the just opened connection.
也就说等待连接之前这里是阻塞的,线程停止,当然不会调用下面的onPostExecute,
这一点很关键我们梳理一下,找到设备,点击设备进行连接后ConnectionInfoListener监听器触发,调用方法
onConnectionInfoAvailable{ if (info.groupFormed && info.isGroupOwner) { Log.i("xyz", "owmer start"); mServerTask = new FileServerAsyncTask(MainActivity.this, view); mServerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } }
这个时候Task就启动了,当然它会在 Socket client = serverSocket.accept()这里阻塞
这个时候,我们点击发送数据按钮启动服务,socket.connect((new InetSocketAddress(host, port)),
SOCKET_TIMEOUT);
这时候创建连接,阻塞取消,然后就会尽心之后的copy保存了
ok博主是大二党,计算机科学与技术专业,这搞笑的专业,完全自学,每天学一点,好吧这一点有时候是10个钟头,但自己就能感觉到进步,起码打字快了吧=——=,虽然blog很耗费时间,但我坚信这是一种学习的好方法。
- Miko Android自学之路 WifiDirect中文最强详解,如何传输数据,如何设置GroupOwener,如何设置客户端以及服务器端
- Miko Android自学之路 WifiDirect中文最强详解,如何传输数据,如何设置GroupOwener,如何设置客户端以及服务器端
- Miko Android自学之路 WifiDirect中文最强详解,如何传输数据,如何设置GroupOwener,如何设置客户端以及服务器端
- 如何设置Navicat Premium 传输数据
- 如何设置客户端证书
- 如何设置客户端证书
- 如何设置客户端证书
- 客户端如何设置delegation
- 一个小白从零基础自学Android编程笔记之如何设置EidtText的边框
- 如何做:设置客户端证书
- 如何设置ftp客户端以及配置投票页面
- ios客户端向服务器端发送数据以及接收数据要如何实现?
- ubuntu 如何设置成中文
- Firefox如何设置成中文
- 树莓派如何设置成中文
- Android sdk如何设置?
- exchang2010如何设置允许传输颜色分类!
- Android之如何设置键盘监听
- 南阳题目101-两点距离
- 黑马程序员-C语言回顾-fgets()和puts()函数
- html5 canvas画布居中
- 第4周项目3(1) 猜数字游戏
- 【EJB学习笔记】——建立一个简单的EJB应用
- Miko Android自学之路 WifiDirect中文最强详解,如何传输数据,如何设置GroupOwener,如何设置客户端以及服务器端
- java 生成短网址
- float double 的存储方式
- Linux LVM配置磁盘
- Kotlin环境下使用如何butterKnife
- 南阳题目111-分数加减法
- 服务器请求返回数
- 面试感悟:3年工作经验程序员应有的技能
- Linux 中的零拷贝技术,第 2 部分