Android WifiDirect

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来表示,那么服务端收到的数据可以分为三种,现列举如下:

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

img

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

img

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

img

粘包、拆包发生原因

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

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

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

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

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

等等。

如何处理粘包

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

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

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

2.加分割标识符

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

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

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

img

在开始传输数据时,在包头拼上自定义的一些信息,比如前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方法开始执行。

img

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

img

img

img

img

img

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注销广播。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值