从Android源代码来看WiFi直连

什么是WiFi直连通俗点说,它可以不通过网络,也不通过蓝牙,只要两台设备都支持WiFi直连,打开WiFi,不用连接任何WiFi,就可以进行信息的传输(请忽略下面两张图中的WiFi连接标志,因为其与WiFi的连接与否无关,打开就可以)。在Android的设置->网络与互联网->WLAN-&

什么是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();

Read more

Volcano 与 Kubernetes GPU 调度学习笔记

本笔记系统整理 Volcano 调度器、Kubernetes 调度框架、GPU Device Plugin、HAMi 等云原生 AI 调度领域的核心知识,适合用于学习、复习和工程实践参考。 目录 * 第一部分:Volcano 入门 * 1. Volcano 是什么 * 2. 安装与快速使用 * 3. 核心特性一览 * 第二部分:Volcano 整体架构 * 4. Volcano 解决的核心问题 * 5. 整体架构与数据流 * 6. 三层抽象模型 * 第三部分:Volcano 核心实现原理 * 7. Session 机制 * 8. Gang Scheduling 实现 * 9. Queue 与 DRF 公平调度

容器镜像(4):镜像的常用工具箱

容器镜像(4):镜像的常用工具箱

前几篇在讲多架构镜像时已经用过 skopeo 和 crane 做镜像复制,这篇系统整理这两个工具的完整能力,同时介绍几个日常操作镜像时同样好用的工具。 一、skopeo:不依赖 Daemon 的镜像瑞士军刀 skopeo 的核心价值是绕过 Docker daemon,直接与 Registry API 交互。上一篇用它做镜像复制和离线传输,但它的能力远不止于此。 1.1 安装 # Ubuntu / Debian sudo apt install -y skopeo skopeo --version # skopeo version 1.15.1 1.2 inspect:免拉取检查镜像元数据 docker inspect 需要先把镜像拉到本地,skopeo inspect 直接向 Registry

容器镜像(3):多架构镜像构建

容器镜像(3):多架构镜像构建

一、什么是多架构镜像 1.1 OCI Image Index 上一篇介绍了单平台镜像的结构:一个 Manifest 指向 Config 和若干 Layer blob。多架构镜像在此之上多了一层——OCI Image Index(也叫 Manifest List),是一个轻量的索引文件,把多个单平台 Manifest 组织在一起: $ docker manifest inspect golang:1.22-alpine { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests&

容器镜像(2):containerd 视角下的镜像

容器镜像(2):containerd 视角下的镜像

一、为什么需要了解 containerd 如果你只用 docker run 跑容器,从来不关心底层,那可以不了解 containerd。但如果你在用 Kubernetes,或者想真正理解"容器运行时"是什么,containerd 是绕不开的。 事实上,当你执行 docker run 的时候,containerd 早就在后台悄悄工作了——Docker 从 1.11 版本开始,就把核心运行时剥离出来交给 containerd 负责。 1.1 Docker 的架构演变 早期的 Docker(1.10 及之前)是一个"大一统"的单体程序:一个 dockerd