Android WifiDirect

1. 简介

WiFi直连也就是WiFi设备点对点连接(WiFi P2P),它允许具有适当硬件的Android 4.0(API级别14)或更高版本的设备通过Wi-Fi直接相互连接,而无需中间接入点。使用这些API,您可以发现并连接到其他设备(前提是每个设备支持Wi-Fi P2P),然后通过比蓝牙连接更长的距离快速连接进行通信。这对于在用户之间共享数据的应用程序很有用,例如多人游戏或照片共享应用程序。

Wi-Fi P2P API包含以下主要部分
允许您发现,请求和连接到对等的方法在WifiP2pManager类中定义。
允许您通知WifiP2pManager方法调用成功或失败的监听器。调用WifiP2pManager方法时,每个方法都可以接收作为参数传入的特定侦听器。
通知您Wi-Fi P2P框架检测到的特定事件的意图,例如断开的连接或新发现的对等体。
您经常将API的这三个主要组件一起使用。例如,您可以提供WifiP2pManager.ActionListener呼叫discoverPeers(),以便您可以使用ActionListener.onSuccess()和ActionListener.onFailure() 方法通知您。

2. 使用流程

1.权限获取

在manifest文件当中声明相关权限,同时声明你应用的SDK 最小版本号,因为Android4.0才开始支持WiFi直连,所以版本号是14。

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.INTERNET" />
2.编写广播接收器

接收Wifi状态变化的广播

public class WiFiDirectBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, final Intent intent) {String action = intent.getAction();Log.d("wny", "onReceive: " + action.toString());if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);if (networkInfo.isConnected()) {// we are connected with the other device, request connection// info to find group owner IPmManager.requestConnectionInfo(mChannel, WifiP2PActivity.this);} else {//断开连接置空IP地址mDeviceIp = null;// It's a disconnect}}if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {//检测 WIFI 功能是否被打开int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,-1);if (state == 1) {Toast.makeText(WifiP2PActivity.this, "请打开Wlan",                                                                  Toast.LENGTH_SHORT).show();}} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action))             {boolean flag = false;//获取当前可用连接点的列表WifiP2pDeviceList wifiP2pDeviceList =                                             intent.getParcelableExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST);Log.d(TAG, "onReceive: intent" + intent.toString());List<WifiP2pDevice> p2pDeviceList = new ArrayList<>();p2pDeviceList.addAll(wifiP2pDeviceList.getDeviceList());for (WifiP2pDevice wifiP2pDevice : p2pDeviceList) {Log.d(TAG, "onReceive: "+wifiP2pDevice.deviceName);if (wifiP2pDevice.deviceName.equals("电子相框")) {mDeviceAdress = wifiP2pDevice.deviceAddress;//Toast.makeText(WifiP2PActivity.this, "检索到设备", Toast.LENGTH_SHORT).show();flag = true;break;}}if (!flag){Toast.makeText(WifiP2PActivity.this,"没有检索到可用设                                                             备",Toast.LENGTH_SHORT).show();}if (p2pDeviceList.size() !=0) {Log.d("wny", "" + p2pDeviceList.get(0).toString());}} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {//建立或者断开连接} else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {//当前设备的 WIFI 状态发生变化}}}

Channel(信道)类的作用:

​ 将应用程序连接到Wifi p2p框架的通道,大多数p2p操作都需要一个通道作为参数。得到信道实例。

public static class Channel implements AutoCloseable {/** @hide */public Channel(Context context, Looper looper, ChannelListener l, Binder binder,WifiP2pManager p2pManager) {mAsyncChannel = new AsyncChannel();mHandler = new P2pHandler(looper);mChannelListener = l;mContext = context;mBinder = binder;mP2pManager = p2pManager;mCloseGuard.open("close");}........
}
3.初始化广播过滤器和广播接收器
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);
mManager = (WifiP2pManager) this.getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, this.getMainLooper(), null);//生成Channel实例
mReceiver = new WiFiDirectBroadcastReceiver();
this.registerReceiver(mReceiver, mIntentFilter);//注册广播接收者

在你的Activity的onCreate方法当中,获取WifiP2pManager的实例。然后调用initialize()方法来将你的应用注册到 Wi-Fi P2P framework当中,这个方法将会返回一个WifiP2pManager.Channel对象,它把你的应用与底层的 Wi-Fi P2P framework连接起来

**Manager.initialize **():

使用Wi-Fi框架注册应用程序。这个函数在执行任何p2p操作之前必须首先调用。

@param srcContext是源的上下文

@param srcLooper是接收回调的循环程序

@param侦听器,用于在失去框架通信时回调。可以为空。

@return Channel实例,这是执行任何进一步p2p操作所必需的

 /*** Registers the application with the Wi-Fi framework. This function* must be the first to be called before any p2p operations are performed.** @param srcContext is the context of the source* @param srcLooper is the Looper on which the callbacks are receivied* @param listener for callback at loss of framework communication. Can be null.* @return Channel instance that is necessary for performing any further p2p operations*/public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {Binder binder = new Binder();Channel channel = initalizeChannel(srcContext, srcLooper, listener, getMessenger(binder),binder);//getMessenger(binder)获取对WifiP2pService处理程序的引用。这是用来建立与WifiService的异步信道通信(IWifiP2pManager.getMessenger(binder))return channel;}

getMessenger方法如下:

//获取对WifiP2pService处理程序的引用。这是用来建立
//与WifiService的异步信道通信
//@param binder服务与此客户端关联的绑定器。
//@return Messenger指向WifiP2pService处理程序public Messenger getMessenger(Binder binder) {try {return mService.getMessenger(binder);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}

下边的方法,在调用getSystemService(Context.WIFI_P2P_SERVICE)后会执行,传入IWifiP2pManager实现类赋值给mService,用于后边的调用。

/*** Create a new WifiP2pManager instance. Applications use* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve* the standard {@link android.content.Context#WIFI_P2P_SERVICE Context.WIFI_P2P_SERVICE}.* @param service the Binder interface* @hide - hide this because it takes in a parameter of type IWifiP2pManager, which* is a system private class.*/@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)public WifiP2pManager(IWifiP2pManager service) {mService = service;}
/*** Interface that WifiP2pService implements** {@hide}*/
public interface IWifiP2pManager extends android.os.IInterface

WifiP2pService如下:

public final class WifiP2pService extends SystemService {private static final String TAG = "WifiP2pService";final WifiP2pServiceImpl mImpl;//实现类public WifiP2pService(Context context) {super(context);mImpl = new WifiP2pServiceImpl(context, WifiInjector.getInstance());}@Overridepublic void onStart() {Log.i(TAG, "Registering " + Context.WIFI_P2P_SERVICE);publishBinderService(Context.WIFI_P2P_SERVICE, mImpl);}@Overridepublic void onBootPhase(int phase) {if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {mImpl.connectivityServiceReady();}}
}
public class WifiP2pServiceImpl extends IWifiP2pManager.Stub
public static abstract class Stub extends android.os.Binder implements android.net.wifi.p2p.IWifiP2pManager

由此可得出:在系统开启wifip2p服务时会调用wifip2pManager(IWifiP2pManager service)将实现了IWifiP2pManager接口的WifiP2pServiceImpl类传给WifiP2pManager的mService,以便后续调用

private Channel initalizeChannel(Context srcContext, Looper srcLooper,                               ChannelListener listener,Messenger messenger, Binder binder) {if (messenger == null) return null;//若没有成功获取WifiP2pService的引用,返回空信道构造失败Channel c = new Channel(srcContext, srcLooper, listener, binder, this);if (c.mAsyncChannel.connectSync(srcContext, c.mHandler, messenger)== AsyncChannel.STATUS_SUCCESSFUL)//将处理程序同步连接到Messenger,服务器收到CMD_CHANNEL_FULL_CONNECTION请求并初始化内部实例变量以允许通信.{Bundle bundle = new Bundle();bundle.putString(CALLING_PACKAGE, c.mContext.getOpPackageName());bundle.putBinder(CALLING_BINDER, binder);c.mAsyncChannel.sendMessage(UPDATE_CHANNEL_INFO, 0,c.putListener(null), bundle);return c;} else {c.close();return null;}}
4.请求Peers设备列表并监听
if (mManager != null) {mManager.requestPeers(mChannel, this);}

监听如下:

Activity实现PeerListListener来获取可连接peers的列表,另外一种方式就是通过广播接收者获取,逻辑见上述WiFiDirectBroadcastReceiver

implements WifiP2pManager.PeerListListener
@Overridepublic void onPeersAvailable(WifiP2pDeviceList peers) {Log.d("wny", "onPeersAvailable: peers" + peers.getDeviceList().toString());}
5.搜索P2p设备

这个方法的调用是异步的,如果你设置了一个WifiP2pManager.ActionListener监听器,那么这个方法的调用结果会通过ActionListener回调返回。onSuccess()方法只告诉你该发现节点的任务执行成功,但不会提供所发现节点的任何信息。

mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {@Overridepublic void onSuccess() {Log.d("wny ", "onSuccess");Toast.makeText(WifiP2PActivity.this, "检索设备",                                                                           Toast.LENGTH_SHORT).show();}//检索成功将执行onSuccess@Overridepublic void onFailure(int reasonCode) {Log.d("wny", "onFailure: onFailure" + reasonCode);Toast.makeText(WifiP2PActivity.this, "检索设备失败",                                                                      Toast.LENGTH_SHORT).show();}//执行失败进入onFailure,一般检索失败是由于权限未开启导致});

如果发现节点的任务执行成功,并且检测到了适合节点。系统会发送WIFI_P2P_PEERS_CHANGED_ACTION广播,当你收到这个广播的时候,你就可以调用requestPeers()方法,来获取发现了节点列表

6.连接设备

当你找到自己想要连接的设备时,你可以通过调用connect()方法来连接这个设备,调用这个方法需要一个WifiP2pConfig对象作为参数。WifiP2pConfig对象包含了进行连接的一些配置信息,包括设备地址,认证方式,谁作为GroupOwner等。这个方法调用的结果会通过WifiP2pManager.ActionListener返回

WifiP2pConfig config = new WifiP2pConfig();config.deviceAddress = mDeviceAdress;//设备地址通常通过广播接收者中获取mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {@Overridepublic void onSuccess() {//success logicLog.d(TAG, "onSuccess: connect success");Toast.makeText(WifiP2PActivity.this, "连接设备成功",                                                                    Toast.LENGTH_SHORT).show();}@Overridepublic void onFailure(int reason) {//failure logicLog.d(TAG, "onFailure: connect Failure");Toast.makeText(WifiP2PActivity.this, "连接设备失败",                                                                     Toast.LENGTH_SHORT).show();}});

此方法中重写的方法只能反应连接过程是否成功,但不能确保连接是否可用,在这判断连接结果不是一个可靠的方式。

7.接收连接信息
@Overridepublic void onConnectionInfoAvailable(final WifiP2pInfo info) {Log.d(TAG, "onConnectionInfoAvailable: ");if (info.groupFormed && info.isGroupOwner) {Log.d(TAG, "isGroupOwner: ");Log.d(TAG, info.toString().toString());Log.d(TAG, info.groupOwnerAddress.toString());} else if (info.groupFormed) {// The other device acts as the client. In this case, we enable the// get file button.Log.d(TAG, "isnotGroupOwner: ");Log.d(TAG, info.toString().toString());mDeviceIp = info.groupOwnerAddress.toString().replace("/","");if (mDeviceIp != null) {if (socket.isConnected()) {Toast.makeText(WifiP2PActivity.this,"Socket                                                            connected",Toast.LENGTH_SHORT).show();} else {Toast.makeText(WifiP2PActivity.this,"Socket                                                         Disconnected",Toast.LENGTH_SHORT).show();}} else {Toast.makeText(WifiP2PActivity.this,"无设备                                                                            ip",Toast.LENGTH_SHORT).show();}Log.d(TAG, info.groupOwnerAddress.toString());}}

在此方法中会先判断本机的角色(服务端/客户端),一般情况下发起连接请求的是客户端,即连接上的设备为GroupOwner,判断完后可以获取GroupOwner的ip地址,但需要注意的是直接输出的ip地址首位为“/”,需要toString().replace("/",""),到此步骤后就可以通过Socket连接上服务端,进行通信。

8.建立Socket通讯

一旦设备间的连接成功建立,你就可以通过socket来传输数据,基本的步骤如下。
1、创建ServerSocket,并调用accept()方法在指定端口等待客户端的连接,注意这个过程将会阻塞线程,请在后台线程完成这个它。

2、创建ClientSocket,然后使用服务器IP和端口去连接充当服务器的设备。

3、 客户端往服务器发送数据。当客户端成功连接上服务器端之后,你就可以以字节流的形式向服务器发送数据了。

4、服务器接收数据。当服务器的accept()接受一个客户端连接之后,服务器端口能够收到客户端发来的数据了。

注意在WiFi P2P连接中,Group Owner和Group Client都能够作为Server Socket,并且当一个Socket连接建立后,双方都可以收发数据。

下面是一个通过WiFi P2P从客户端往服务器端发送图片的例子,以下是服务器端部分代码。

客户端连接方式:

socket.connect((new InetSocketAddress(mDeviceIp, 8888)), 1500);
outputStream = socket.getOutputStream();
objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(dataBean);//DataBean用来存储需要传输的数据信息,需要实现Serializable接口

服务端接受方式:

serverSocket = new ServerSocket(8888);
client = serverSocket.accept();
InputStream inputstream = client.getInputStream();
objectInputStream = new ObjectInputStream(inputstream);
dataBean = (DataBean) objectInputStream.readObject()

最重要的是因为服务端也会用到DataBean类,所以在服务端也要编写相同的类,且这个类所在服务端和客服端的包名需要相同

3.Tips

  • 我们在使用广播接收者的时候在暂停情况下我们需要去注销掉它
@Override
protected void onResume() {super.onResume();registerReceiver(mReceiver, mIntentFilter);
}
/* unregister the broadcast receiver */
@Override
protected void onPause() {super.onPause();unregisterReceiver(mReceiver);
}
  • 当客户端和服务端同事进行扫描时才能发现彼此,因此需要在服务端的扫描方法scanPeers()需要这样写:
new Thread(new Runnable() {@Overridepublic void run() {while (true) {if(isConnect){break;//当有设备连接上后结束循环,子线程自然死亡,达到停止扫描的效果}if (mScanState) {mManager.discoverPeers(mChannel,new WifiP2pManager.ActionListener() {@Overridepublic void onSuccess() {Log.d("wny ", "onSuccess");}@Overridepublic void onFailure(int reasonCode) {Log.d("wny", "onFailure: onFailure" + reasonCode);}});}try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}}}).start();

这样写的原因是当无设备连接时就需要进行循环扫描,当连接后停止扫描,当程序结束后子线程需要自然结束。

  • DateBean中需要实现序列化的话需要成员变量都为基本类型,不能存在包装类的或者其他类的引用,所以我们需要将需要传输的文件读成Byte[]存储在DateBean中进行传输。

  • 一旦设备间的连接成功建立,你就可以通过socket来传输数据,创建ServerSocket,并调用accept()方法在指定端口等待客户端的连接,这个过程将会阻塞线程,需要在后台线程完成这个它

public void acceptConn() throws IOException {client = serverSocket.accept();Log.d(TAG, "run: " + client.getInetAddress().getHostAddress() + "客户端已连接!");Message msg = Message.obtain();msg.what = 2;msg.obj = client.getInetAddress().getHostAddress() + "客户端socket已连接!";handler.sendMessage(msg);InputStream inputstream = client.getInputStream();objectInputStream = new ObjectInputStream(inputstream);}Thread acceptThread = new Thread(new Runnable() {boolean isEnd = false;@Overridepublic void run() {try {Log.d(TAG, "run: start serversocket");serverSocket = new ServerSocket(8888);Log.d(TAG, "run: accpeting");} catch (IOException e) {e.printStackTrace();Log.d(TAG, "run: start serversocket error");}try {while (true) {if(isEnd){break;}Log.d(TAG, "run: while");if (client == null) {accptConn();Log.d(TAG, "run: check conn");} else {if ((!client.isConnected())) {accptConn();Message msg = Message.obtain();msg.what = 2;msg.obj = "连接已断开";handler.sendMessage(msg);} else {if (objectInputStream != null) {if (((dataBean = (DataBean) objectInputStream.readObject()) != null)) {Log.d(TAG, "run: come in");if (dataBean.getBytes() != null) {copyFile(mContext, dataBean.getBytes());Log.d(TAG, "run: accept success");}}}else{Message msg = Message.obtain();msg.what = 2;msg.obj = "连接已断开";handler.sendMessage(msg);}}}//Log.d(TAG, "run: serverSocket accpet" + dataBean.getOrder());}} catch (Exception e) {e.printStackTrace();Log.d(TAG, "Exception throws");}}});
  • Socket粘包处理:

什么是粘包

TCP有粘包现象,而UDP不会出现粘包。

**TCP(Transport Control Protocol,传输控制协议)**是面向连接的,面向流的。TCP的收发两端都要有成对的Socket,因此,发送端为了将更多有效的包发送出去,采用了合并优化算法(Nagle算法),将多次、间隔时间短、数据量小的数据合并为一个大的数据块,进行封包处理。这样的包对于接收端来说,就没办法分辨,所以需要一些特殊的拆包机制。

**UDP(User Datagram Protocol,用户数据报协议)**是无连接的,面向消息的提供高效率服务。不会使用合并优化算法。UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。

粘包、拆包表现形式

现在假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下:

第一种情况,接收端正常收到两个数据包,即没有发生拆包和粘包的现象,此种情况不在本文的讨论范围内。

第二种情况,接收端只收到一个数据包,由于TCP是不会出现丢包的,所以这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。

第三种情况,这种情况有两种表现形式,如下图。接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。

粘包、拆包发生原因

发生TCP粘包或拆包有很多原因,现列出常见的几点:

1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。

2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。

3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。

4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

等等。

如何处理粘包

1.提前通知接收端要传送的包的长度

粘包问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

不建议使用,因为程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这样会放大网络延迟带来的性能损耗

2.加分割标识符

{数据段01}+标识符+{数据段02}+标识符
发送端和接收端约定好一个标识符来区分不同的数据包,如果接收到了这么一个分隔符,就表示一个完整的包接收完毕。

也不建议使用,因为要发送的数据很多,数据的内容格式也有很多,可能会出现标识符不唯一的情况

3.自定义包头(建议使用)

在开始传输数据时,在包头拼上自定义的一些信息,比如前4个字节表示包的长度,5-8个字节表示传输的类型(Type:做一些业务区分),后面为实际的数据包。

Demo:

//发送端:package com.tcp;import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;public class TcpSend {public static void main(String[] args) {// TODO Auto-generated method stubbyte[] bt = new byte[29];try {// 创建连接到服务端的Socket对象Socket cs = new Socket("10.168.69.177", 9999);// 获取当前连接的输入流/输出流InputStream din = cs.getInputStream();OutputStream dout = cs.getOutputStream();// 模拟一个请求消息包.byte[] pack = new EncodePackage().gepPackage();//发送请求消息包.dout.write(pack); // 从服务器中读取数据并打印din.read(bt, 0, 29);if (bt[0] == 2) {System.out.println("=======收到响应包=======");System.out.println("包类型:" + bt[0]);System.out.println("包标识:" + new String(bt, 1, 4));System.out.println("包长度:" + new Integer(new String(bt, 5, 4)));System.out.println("MD5校验和:" + new String(bt, 9, 16));System.out.println("版本号:" + bt[25]);System.out.println("协议类型:" + (byte) (bt[26]));System.out.println("命令类型" + new String(bt, 27, 2));}// 关闭流,关闭Socket连接din.close();dout.close();cs.close();} catch (Exception e) {e.printStackTrace();}}
}
//接收端:
package com.tcp;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class TcpServer {public static void main(String[] args) throws IOException {// 声明用来计数的int局部变量int count = 0;byte[] head = new byte[29];byte[] body;try {// 使用ServerSocketServerSocket server = new ServerSocket(9999);// 打印提示信息System.out.println("服务器端正在对端口9999进行监听");// 等待客户端连接while (true) {// 若有连接返回对应的Socket对象。Socket sc = server.accept();// 获取当前链接对象的输入流InputStream din = sc.getInputStream();// 获取当前连接对象的输出流OutputStream dout = sc.getOutputStream();// 读取包头并打印包头所包含的信息din.read(head);System.out.println("==========================" + (++count)+ "========================");System.out.println("客户端IP地址:" + sc.getInetAddress());System.out.println("客户端端口号:" + sc.getPort());System.out.println("本地端口号:" + sc.getLocalPort());System.out.println("接收到的数据包16进制表示为:" + byteToHexStr(head, true));// 判断,如果是请求包则返回一个响应if (head[0] == 1) {//临时代码,接收请求包并原样返回消息包头head[0] = 2;dout.write(head);System.out.println("******收到请求包,已返回响应......******");} else if (head[0] == 2) {System.out.println("************收到响应包****************");} else {System.out.println("*******数据包已损坏,接收失败************");}// 关闭流din.close();dout.close();// 关闭Socket连接sc.close();}} catch (Exception e) {e.printStackTrace();}}//该方法将数据转为16进制显示public static String byteToHexStr(byte[] bArray, boolean format) {StringBuffer strb = new StringBuffer(bArray.length);String str;for (int i = 0; i < bArray.length; i++) {str = Integer.toHexString(0xFF & bArray[i]).trim();if (str.length() < 2){str = "0" + str;}if (format){str += " ";}strb.append(str);}str = strb.toString().toUpperCase().trim();return str;}
}
  • 动态广播在activity 的onResume里注册,onPause里注销。

大家都知道,activity的生命周期方法基本上是成对出现的,例如onCreate对应onDestory,onStart对应onStop,onResume对于onPause。

对于动态广播来说,有注册必然得有注销,这也得成对出现。重复注册注销或者注册忘了注销这都不行,后者会报Are you missing a call to unregisterReceiver()?错误,虽然不至于让应用奔溃,但是会导致内存泄露。

那么为什么不在onCreate和onDestory或onStart和onStop,注册注销呢?那是因为考虑到,当系统因为内存不足要回收activity占用的资源时,有些生命周期方法(onStop,onDestory)可能不会执行。

看下官方对于activity生命周期的解释:

1.先看生命周期图,注意红色矩形框部分可以发现:当其他优先级更高的应用需要内存时,activity在执行完onPause方法以后就会被销毁,那么onStop,onDestory方法就会不执行,当再回到这个activity时,就会从onCreate方法开始执行。

  1. 看下对生命周期方法的描述,onPause被标记为可以killable的在3.0以前,而onStop,onDestory也同样标记为可以killable的。

3.对于killable的解释,意思大概是onPause执行完以后,activity就有可能会被销毁,所以应该利用onPause以及onSaveInstanceState方法来保存必要的数据。

4.看官方的特别声明,在3.0以后,应用不会处于killable状态直到onStop返回,onStop以及onSaveInstanceState会被安全地调用。

3.0以后,综合1、2、3点,onPause方法执行完以后,该activity有可能会被系统回收,所以后续的生命周期方法onStop,onDestory不会被执行;而4证明onStop执行完以后,该activity才有可能会被系统回收,所以后续的生命周期方法onDestory不会被执行。感觉官方对于activity被回收的时机有矛盾的地方。

但是我觉得根据1,2,3点得出的结论(在onResume注册广播,在onPause注销广播),来注册销毁广播比较保险,因为onPause必然会被执行,而且当activity处于onPause时,焦点已经不在了,理论上那就可以不用接收广播了。

结论:对于动态广播,在onResume注册广播,在onPause注销广播。

Android WifiDirect相关推荐

  1. android手机wifi设置ipv6,更改WiFi-Direct IP范围?在Android WiFi-Direct中强制使用IPv6?...

    我有两个 Android KitKat手机,两个都是作为Group Owners运行WiFi-Direct组,我们称之为GO1和GO2 我设法将GO1作为传统客户端连接到GO2而不破坏任何(先前设置的 ...

  2. wifi直连(Android)Wifi-Direct

    wifi直连也叫做wifi设备点对点连接(peer-peer),不需要连接热点或者网络,需要打开wifi,就可以查找到附近的设备.大概可以分为以下步骤:1.设置以下权限,并且注意最小sdk=14 2. ...

  3. android 照片多选,Android: 关于系统相册多选图片的问题

    最近在做毕设,想在调用系统相册的时候直接返回多张图片的地址.我本意是想用尽量简单的方法来解决这个问题,不需要剪裁啊什么的功能,只要可以多选就好.可是百度搜出来的方案基本上全部是自己写一个相册或者调用第 ...

  4. Android四大组件完全解析(一)---Activity

    本文参考\android\android\frameworks\base\core\java\android\app\Activity.java文件中的类注释,以及android/frameworks ...

  5. android官方的wifi direct demo.....,Android WIFI Direct开发实例演示

    实例 WIFIDirectDemo 改编 Android SDK 提供的实例,演示了通过 WIFI 搜寻连接点,建立连接,并进行数据传输的过程. 该实例包含 5 个类,说明说下: WIFIDirect ...

  6. 我想自学php但是网上视频很不连贯,ThinkPHP - 连贯操作

    /** * 连贯操作 * @return 无返回值 */ public function coherentOperation(){ //实例化模型 $user = M('User'); // +--- ...

  7. Android 4.0新增WiFiDirect功能

    Android 4.0引入了一项很重要的技术就是 WiFiDirect (WiFi直连) ,它可以让WiFi设备无需热点即可实现两个WiFi设备的P2P数据交换.使用最新的Android 4.0 SD ...

  8. wifi直连 android,Android 4.0 WiFiDirect (WiFi直连)功能

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 Android 4.0引入了一项很重要的技术就是 WiFiDirect (WiFi直连) ,它可以让WiFi设备无需热点即可实现两个WiFi设备的P2P数 ...

  9. wifi设备名称android,Android重命名设备的名称为wifi-direct

    搜索热词 我正在尝试使用Wifi Direct连接两个设备,但我想以编程方式实现,而不是由用户启动. 为此,我必须更改设备的WifiDirect的名称,如下图所示: 现在使用以下方法发现对等体: wi ...

最新文章

  1. Laravel深入学习5 - 应用架构
  2. python 简单数据库_Python打造一个简单的本地数据库
  3. AJAX (异步 javascript 和 xml)
  4. window系统JAVA开发环境的搭建
  5. java笔记之数组的概念、声明、初始化、访问方式、复制和动态扩展算法以及递归...
  6. boost跨平台 c++_跨平台C++整数类型 之一 固定宽度整数(boost和C++11)
  7. 自动论文生成器 python_Python生成器常见问题及解决方案
  8. linux 无法找到函数定义,找到定义Linux函数的位置
  9. PHP+MySQL 跨服务器跨数据库数据拷贝系统
  10. (53)FPGA条件选择(casez)
  11. 网络编程学习2-套接字编程简介
  12. 苹果首席设计官将离职;华为将从世界范围招揽天才少年;新版 Edge 更新 | 极客头条...
  13. ERP仓库管理系统查询(十)
  14. osr matlab,DPD-Matlab-FPGA 好不容易找到的马岳林的 数字预失真 DPD仿真代码 包括 simulink 和 实现 275万源代码下载- www.pudn.com...
  15. 你知道什么是大数据的核心吗?
  16. 数据模型的概念,数据模型的作用和数据模型的三个要素
  17. 【产品功能】弹性网卡支持私网多IP
  18. Qt报错Parse error at “IID“的解决办法
  19. 店内扫码点餐系统 计算机毕业设计 微信小程序开发
  20. Thonny - 为初学者准备的Python开发工具

热门文章

  1. MySQL必知必会01:一个完整的存储过程
  2. 生物信息/微生物组期刊推荐: Genome Biology
  3. 520快到了,我用代码画了一幅画「可以送给自己喜欢的人」
  4. 通信协议以及protobuf使用、语法指南一
  5. Python开发项目基于改进高斯混合模型的图割算法
  6. Advancing Transformer Transducer for Speech Recognition on Large-Scale Dataset》
  7. java flux api,JAVA Reactor API 简单使用(Flux和Mono)及WebFlux的应用
  8. oracle移动表空间的数据文件,移动Oracle表空间数据文件方案
  9. NXP Support Package S32K1xx 安装
  10. 史上首次科技股票大PK!你为哪家科技公司打Call十年?