从Android源代码来看WiFi直连

什么是WiFi直连

通俗点说,它可以不通过网络,也不通过蓝牙,只要两台设备都支持WiFi直连,打开WiFi不用连接任何WiFi,就可以进行信息的传输(请忽略下面两张图中的WiFi连接标志,因为其与WiFi的连接与否无关,打开就可以)。
在Android的设置->网络与互联网->WLAN->WLAN偏好设置->高级->WLAN直连中可以找到关于Wi-Fi直连的设置,如下:
这里写图片描述
在参考其它博客时,写出来的代码并不能搜索到Wi-Fi中的其他设备,但是在这设置里面却可以。因此,找来其Android8.0的源代码作为参考,并成功解决问题。
这里写图片描述
源代码位置:

我们可以通过系统的源代码来了解其相应的API的使用。下面是对应的系统源代码的分析:


注册权限

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>

广播的接收与处理

onResume()中注册广播接收,在onPause()中取消广播接收。

/**
* Broadcast intent action to indicate whether Wi-Fi p2p is enabled or disabled. An
* extra {@link #EXTRA_WIFI_STATE} provides the state information as int.
*
* @see #EXTRA_WIFI_STATE
*/
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);

/**
* Broadcast intent action indicating that the available peer list has changed. This
* can be sent as a result of peers being found, lost or updated.
*
* <p> An extra {@link #EXTRA_P2P_DEVICE_LIST} provides the full list of
* current peers. The full list of peers can also be obtained any time with
* {@link #requestPeers}.
*
* @see #EXTRA_P2P_DEVICE_LIST
*/
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);

/**
* Broadcast intent action indicating that the state of Wi-Fi p2p connectivity
* has changed. One extra {@link #EXTRA_WIFI_P2P_INFO} provides the p2p connection info in
* the form of a {@link WifiP2pInfo} object. Another extra {@link #EXTRA_NETWORK_INFO} provides
* the network info in the form of a {@link android.net.NetworkInfo}. A third extra provides
* the details of the group.
*
* @see #EXTRA_WIFI_P2P_INFO
* @see #EXTRA_NETWORK_INFO
* @see #EXTRA_WIFI_P2P_GROUP
*/
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);

/**
* Broadcast intent action indicating that this device details have changed.
*/
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);

/**
* Broadcast intent action indicating that peer discovery has either started or stopped.
* One extra {@link #EXTRA_DISCOVERY_STATE} indicates whether discovery has started
* or stopped.
*
* <p>Note that discovery will be stopped during a connection setup. If the application tries
* to re-initiate discovery during this time, it can fail.
*/
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);

/**
* Broadcast intent action indicating that remembered persistent groups have changed.
* @hide
*/
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION);

对上面广播的处理(需要留意的是,这些数据都是从Intent中取出来的):

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
            mWifiP2pEnabled = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
                WifiP2pManager.WIFI_P2P_STATE_DISABLED) == WifiP2pManager.WIFI_P2P_STATE_ENABLED;
            handleP2pStateChanged();
        } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
            // 这里是直接从Intent中取出了列表数据
            mPeers = (WifiP2pDeviceList) intent.getParcelableExtra(
                    WifiP2pManager.EXTRA_P2P_DEVICE_LIST);
            handlePeersChanged();
        } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
            if (mWifiP2pManager == null) return;
            NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(
                    WifiP2pManager.EXTRA_NETWORK_INFO);
            // 此处的WifiP2pInfo可以让我们获取到GO(Group Owner)的IP
            // 这是最神奇的地方,没有网络的连接,却得到了IP
            // 然后我们可以通过这个IP,与GO进行socket通信
            WifiP2pInfo wifip2pinfo = (WifiP2pInfo) intent.getParcelableExtra(
                    WifiP2pManager.EXTRA_WIFI_P2P_INFO);
            if (networkInfo.isConnected()) {
                if (DBG) Log.d(TAG, "Connected");
            } else if (mLastGroupFormed != true) {
                //start a search when we are disconnected
                //but not on group removed broadcast event
                startSearch();
            }
            mLastGroupFormed = wifip2pinfo.groupFormed;
        } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
            mThisDevice = (WifiP2pDevice) intent.getParcelableExtra(
                    WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
            if (DBG) Log.d(TAG, "Update device info: " + mThisDevice);
            updateDevicePref();
        } else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) {
            int discoveryState = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE,
                WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
            if (DBG) Log.d(TAG, "Discovery state changed: " + discoveryState);
            if (discoveryState == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED) {
                updateSearchMenu(true);
            } else {
                updateSearchMenu(false);
            }
        } else if (WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION.equals(action)) {
            if (mWifiP2pManager != null) {
                mWifiP2pManager.requestPersistentGroupInfo(mChannel, WifiP2pSettings.this);
            }
        }
    }
};

点击搜索

点击菜单栏上的搜索后,会进行如下操作。然后会接收到相应的广播,刷新是在对相应广播的处理中进行的。

private void startSearch() {
    if (mWifiP2pManager != null && !mWifiP2pSearching) {
        mWifiP2pManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
            public void onSuccess() {
            }
            public void onFailure(int reason) {
                if (DBG) Log.d(TAG, " discover fail " + reason);
            }
        });
    }
}

在对广播的处理中,设备变化的处理主要是靠handlePeersChanged()

private void handlePeersChanged() {
    mPeersGroup.removeAll();

    mConnectedDevices = 0;
    if (DBG) Log.d(TAG, "List of available peers");
    for (WifiP2pDevice peer: mPeers.getDeviceList()) {
        if (DBG) Log.d(TAG, "-> " + peer);
        mPeersGroup.addPreference(new WifiP2pPeer(getActivity(), peer));
        if (peer.status == WifiP2pDevice.CONNECTED) mConnectedDevices++;
    }
    if (DBG) Log.d(TAG, " mConnectedDevices " + mConnectedDevices);
}

连接设备或断开连接

  • 连接设备
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = mSelectedWifiPeer.device.deviceAddress;

int forceWps = SystemProperties.getInt("wifidirect.wps", -1);

if (forceWps != -1) {
    config.wps.setup = forceWps;
} else {
    if (mSelectedWifiPeer.device.wpsPbcSupported()) {
        config.wps.setup = WpsInfo.PBC;
    } else if (mSelectedWifiPeer.device.wpsKeypadSupported()) {
        config.wps.setup = WpsInfo.KEYPAD;
    } else {
        config.wps.setup = WpsInfo.DISPLAY;
    }
}

mWifiP2pManager.connect(mChannel, config,
        new WifiP2pManager.ActionListener() {
            public void onSuccess() {
                if (DBG) Log.d(TAG, " connect success");
            }
            public void onFailure(int reason) {
                Log.e(TAG, " connect fail " + reason);
                Toast.makeText(getActivity(),
                        R.string.wifi_p2p_failed_connect_message,
                        Toast.LENGTH_SHORT).show();
            }
    });

对这段代码中,有一个使用了android.os.SystemProperties这个{@hide}修饰的类。我们可以考虑通过反射的方式来近行调用,如下:

private int getSystemProp(){
    try {
        Class<?> cls = Class.forName("android.os.SystemProperties");
        Method m = cls.getDeclaredMethod("get", String.class, String.class);
        return Integer.parseInt((String)m.invoke(null,"wifidirect.wps","-1"));
    } catch (Exception e) {
        Log.i(TAG, "E = " + e.getMessage());
        e.printStackTrace();
    }
    return -1;
}
  • 断开连接
//disconnect dialog listener
mDisconnectListener = new OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        if (which == DialogInterface.BUTTON_POSITIVE) {
            if (mWifiP2pManager != null) {
                mWifiP2pManager.removeGroup(mChannel, new WifiP2pManager.ActionListener() {
                    public void onSuccess() {
                        if (DBG) Log.d(TAG, " remove group success");
                    }
                    public void onFailure(int reason) {
                        if (DBG) Log.d(TAG, " remove group fail " + reason);
                    }
                });
            }
        }
    }
};
  • 取消已发送的邀请
//cancel connect dialog listener
mCancelConnectListener = new OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        if (which == DialogInterface.BUTTON_POSITIVE) {
            if (mWifiP2pManager != null) {
                mWifiP2pManager.cancelConnect(mChannel,
                        new WifiP2pManager.ActionListener() {
                    public void onSuccess() {
                        if (DBG) Log.d(TAG, " cancel connect success");
                    }
                    public void onFailure(int reason) {
                        if (DBG) Log.d(TAG, " cancel connect fail " + reason);
                    }
                });
            }
        }
    }
};

重命名设备名称

mRenameListener = new OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        if (which == DialogInterface.BUTTON_POSITIVE) {
            if (mWifiP2pManager != null) {
                String name = mDeviceNameText.getText().toString();
                if (name != null) {
                    for (int i = 0; i < name.length(); i++) {
                        char cur = name.charAt(i);
                        if(!Character.isDigit(cur) && !Character.isLetter(cur)
                                && cur != '-' && cur != '_' && cur != ' ') {
                            Toast.makeText(getActivity(),
                                    R.string.wifi_p2p_failed_rename_message,
                                    Toast.LENGTH_LONG).show();
                            return;
                        }
                    }
                }
                mWifiP2pManager.setDeviceName(mChannel,
                        mDeviceNameText.getText().toString(),
                        new WifiP2pManager.ActionListener() {
                    public void onSuccess() {
                        if (DBG) Log.d(TAG, " device rename success");
                    }
                    public void onFailure(int reason) {
                        Toast.makeText(getActivity(),
                                R.string.wifi_p2p_failed_rename_message,
                                Toast.LENGTH_LONG).show();
                    }
                });
            }
        }
    }
};

如何进行信息的传输?

对于GO来说,当与GC(Group Client)连接完成后,也就是接收到WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION广播后,需要充当的角色是服务器,所以可以利用Java中socket通信中的SocketServer来阻塞当前的线程(子),监听所受到的socket。如下:

if (wifip2pinfoG.isGroupOwner) { // 充当服务器
    new Thread(new Runnable() {// 不能阻塞主线程
        @Override              // 同时也不允许在主线程中进行网络通信
        public void run() {    // 所以开启子线程
            try {
                ServerSocket server = new ServerSocket(6666, 100, wifip2pinfoG.groupOwnerAddress);
                Socket socket;
                while((socket = server.accept()) != null){
                    InputStream bis = socket.getInputStream();
                    BufferedReader br = new BufferedReader(new InputStreamReader(bis));
                    String info = br.readLine();
                    final String log = info;
                    // 显示出所接收的信息
                    Log.e("TAG", log);
                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(context, log, Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
    Toast.makeText(context, "服务器已启动", Toast.LENGTH_SHORT).show();
} else { // 充当客户端
    Toast.makeText(context, "可发送消息", Toast.LENGTH_SHORT).show();
}

对GC来说,当与GO连接好了之后,即可发送给GO发送消息。如下:

new Thread(new Runnable() {// 不能在主线程中进行网络通信,需要子线程
    @Override
    public void run() {
        try {
            sendSocket = new Socket(wifip2pinfoG.groupOwnerAddress, 6666);
            OutputStreamWriter osw = new OutputStreamWriter(sendSocket.getOutputStream());
            // getInfo()是一个输入框,没有输入时默认返回hello
            osw.write(getInfo());
            osw.flush();
            Log.e("TAG", "info sended");
            // 注意这里,需要及时关闭socket
            sendSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}).start();

猜你喜欢

转载自blog.csdn.net/asahinokawa/article/details/80826937