11、WiFi热点创建(传统AP模式):WifiManager.startLocalOnlyHotspot()、OnStartLocalOnlyHotspotCallback、获取热点SSID与密码

好,咱们今天聊聊WiFi热点创建。说白了,就是把手机变成一个“小路由器”,让其他设备连上来。

Android里创建热点,传统方式有两种:一种是系统API,另一种是反射调用隐藏API。我个人更推荐用系统API,也就是startLocalOnlyHotspot()。为什么?因为它安全、稳定,而且Google官方一直在维护。

11.1 传统AP模式 vs 其他模式

先理清概念。传统AP模式,就是手机作为接入点,其他设备作为客户端。跟WiFi Direct、WiFi Aware不一样,AP模式是一对多的中心化结构。

我记得刚做WiFi开发时,总把AP模式和热点模式搞混。其实它们是一回事——都是让设备充当无线接入点。

模式 角色 典型场景
传统AP模式 手机作为热点 共享网络、临时组网
WiFi Direct 点对点连接 文件传输、投屏
WiFi Aware 邻居发现 附近设备服务发现

11.2 startLocalOnlyHotspot() 核心API

这个方法是Android 8.0(API 26)引入的。它最大的好处是——不需要系统权限。你想想看,以前创建热点得申请SYSTEM_ALERT_WINDOW或者root权限,现在直接调就行。

但有个坑:热点名称和密码由系统自动生成,你不能自己指定。嗯,这里要注意,很多新手上来就想设个“MyHotspot”之类的名字,结果发现改不了。

核心方法签名:

public void startLocalOnlyHotspot(
    @NonNull OnStartLocalOnlyHotspotCallback callback,
    @Nullable Handler handler
)

11.3 OnStartLocalOnlyHotspotCallback 回调

这个回调接口有三个方法,我一个个说。

private final WifiManager.OnStartLocalOnlyHotspotCallback callback =
    new WifiManager.OnStartLocalOnlyHotspotCallback() {
        @Override
        public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) {
            // 热点创建成功
            WifiConfiguration config = reservation.getWifiConfiguration();
            String ssid = config.SSID;
            String password = config.preSharedKey;
            Log.d("Hotspot", "SSID: " + ssid + ", Password: " + password);
        }

        @Override
        public void onStopped() {
            // 热点已关闭
            Log.d("Hotspot", "热点已停止");
        }

        @Override
        public void onFailed(int reason) {
            // 创建失败
            Log.e("Hotspot", "热点创建失败,原因码: " + reason);
        }
    };

我在项目中遇到过一个问题:onFailed返回的原因码,文档里只说了几个常量。比如WifiManager.START_HOTSPOT_FAILED_GENERIC(通用错误)、WifiManager.START_HOTSPOT_FAILED_NO_CHANNEL(无可用信道)。但实际测试时,有些厂商会返回自定义的错误码,这时候就得靠经验去排查了。

11.4 获取热点SSID与密码

热点创建成功后,系统会返回一个LocalOnlyHotspotReservation对象。这个对象里藏着热点的配置信息。

获取方式:

WifiConfiguration config = reservation.getWifiConfiguration();
String ssid = config.SSID;          // 注意:SSID带双引号
String password = config.preSharedKey; // 密码也带双引号

记得把双引号去掉,不然其他设备连的时候会报错。

我曾经踩过这个坑——直接把config.SSID显示到界面上,结果用户说“怎么我的热点名字带引号?”后来才意识到,WifiConfiguration里的字符串是带引号的,需要自己处理一下。

// 去掉双引号的工具方法
private String stripQuotes(String str) {
    if (str != null && str.startsWith("\"") && str.endsWith("\"")) {
        return str.substring(1, str.length() - 1);
    }
    return str;
}

11.5 完整代码示例

下面是一个完整的启动热点示例。我习惯把逻辑封装成一个工具类,方便复用。

public class HotspotManager {
    private WifiManager wifiManager;
    private WifiManager.LocalOnlyHotspotReservation reservation;

    public HotspotManager(Context context) {
        wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
    }

    public void startHotspot() {
        if (wifiManager == null) return;

        wifiManager.startLocalOnlyHotspot(
            new WifiManager.OnStartLocalOnlyHotspotCallback() {
                @Override
                public void onStarted(WifiManager.LocalOnlyHotspotReservation res) {
                    reservation = res;
                    WifiConfiguration config = res.getWifiConfiguration();
                    String ssid = stripQuotes(config.SSID);
                    String password = stripQuotes(config.preSharedKey);
                    // 这里可以回调给UI层显示
                }

                @Override
                public void onStopped() {
                    reservation = null;
                }

                @Override
                public void onFailed(int reason) {
                    // 处理失败
                }
            }, null);
    }

    public void stopHotspot() {
        if (reservation != null) {
            reservation.close();
            reservation = null;
        }
    }

    private String stripQuotes(String str) {
        if (str != null && str.startsWith("\"") && str.endsWith("\"")) {
            return str.substring(1, str.length() - 1);
        }
        return str;
    }
}

11.6 避坑指南

做热点开发,有几个坑我不得不提。

  • 热点与WiFi连接冲突:开启热点时,手机会自动断开当前WiFi连接。这是系统行为,你没法控制。
  • Android 10以上限制:从Android 10开始,热点创建需要位置权限。不然startLocalOnlyHotspot()会直接失败。
  • 厂商定制问题:有些国产手机对热点功能做了修改,比如小米、华为。我遇到过华为手机返回的SSID是乱码的情况,后来发现是编码问题。

重要提醒:

热点创建成功后,一定要在合适的时机调用reservation.close()关闭热点。否则热点会一直开着,耗电又占资源。我建议在Activity的onDestroy()或Fragment的onDestroyView()里关闭。

11.7 知识体系图

下面这张图,把热点创建的整个流程串起来了。你看一眼就能明白。

WiFi热点创建流程 startLocalOnlyHotspot() OnStartLocalOnlyHotspotCallback onStarted() 获取SSID和密码 onStopped() 热点已关闭 onFailed() 检查原因码 reservation.close()

11.8 总结

热点创建其实不复杂,核心就三步:调API、等回调、拿配置。但细节决定成败——权限、引号、厂商适配,哪个不注意都会翻车。

我个人建议,在实际项目中,最好封装一个热点管理类,把启动、停止、状态监听都包进去。这样代码干净,也好维护。

好了,这一章就到这儿。记住:热点不是你想开,想开就能开——得看系统给不给你信道,给不给你权限。

公众号:蓝海资料掘金营,微信deep3321