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 {
@Override
public 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 IP
mManager.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());
}
@Override
public void onStart() {
Log.i(TAG, "Registering " + Context.WIFI_P2P_SERVICE);
publishBinderService(Context.WIFI_P2P_SERVICE, mImpl);
}
@Override
public 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
@Override
public void onPeersAvailable(WifiP2pDeviceList peers) {
Log.d("wny", "onPeersAvailable: peers" + peers.getDeviceList().toString());
}
5.搜索P2p设备
这个方法的调用是异步的,如果你设置了一个WifiP2pManager.ActionListener监听器,那么这个方法的调用结果会通过ActionListener回调返回。onSuccess()方法只告诉你该发现节点的任务执行成功,但不会提供所发现节点的任何信息。
mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
Log.d("wny ", "onSuccess");
Toast.makeText(WifiP2PActivity.this, "检索设备", Toast.LENGTH_SHORT).show();
}//检索成功将执行onSuccess
@Override
public 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() {
@Override
public void onSuccess() {
//success logic
Log.d(TAG, "onSuccess: connect success");
Toast.makeText(WifiP2PActivity.this, "连接设备成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(int reason) {
//failure logic
Log.d(TAG, "onFailure: connect Failure");
Toast.makeText(WifiP2PActivity.this, "连接设备失败", Toast.LENGTH_SHORT).show();
}
});
此方法中重写的方法只能反应连接过程是否成功,但不能确保连接是否可用,在这判断连接结果不是一个可靠的方式。
7.接收连接信息
@Override
public 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() {
@Override
public void run() {
while (true) {
if(isConnect){
break;//当有设备连接上后结束循环,子线程自然死亡,达到停止扫描的效果
}
if (mScanState) {
mManager.discoverPeers(mChannel,new WifiP2pManager.ActionListener()
{
@Override
public void onSuccess() {
Log.d("wny ", "onSuccess");
}
@Override
public 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;
@Override
public 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 stub
byte[] 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 {
// 使用ServerSocket
ServerSocket 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方法开始执行。
- 看下对生命周期方法的描述,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注销广播。