LocalOnlyHotspot

LocalOnlyHotspot也是一种热点,通过startLocalOnlyHotspot接口打开。

打开之后手机的热点图标也会亮起。

官方介绍:

Android O(API 26) 新增应用API以实现本地协同的多个设备进行内容共享。应用程序可使用该API建立本地SoftAP(不可以共享internet上网),该功能可实现多个应用程序共享同一个LOHS。

通过共享同一个LOHS,多个设备之间不仅可以实现协同播放,还可以进行游戏的局域对战,增强了多个设备之间同个应用程序的互动性。

一、创建热点

它的用法一般是在使用startLocalOnlyHotspot方法时,在startLocalOnlyHotspot参数里直接new一个LocalOnlyHotspotCallback 对象,重写他的方法。

1、通过Wifimanager来创建本地热点。不能指定名称和密码。

WifiManager#startLocalOnlyHotspot()

在创建完成的回调接口中,可以通过LocalOnlyHotspotReservation可以拿到热点相关的信息。

二、扫描热点

通过 startScan() 来开启扫描,结果在广播中获取。

WifiManager#startScan()

注意:此方法不同手机实现不同(在OnePlus中,会一直扫描并返回结果,但在小米(Android11)中,只会返回一次结果),因此需要适配。
解决方案:
1、通过线程,持续扫描获取。
2、通过二维码扫描拿到信息。
因为扫描的目的就是拿到wifi的信息,主要是ssid和密码。因此可以通过二维码的方式来拿到。

三、连接热点

有了ssid和密码以后,就可以连接了。

    fun connectToHotspot(context: Context, ssid: String, pwd: String, handler: Handler?) {
        BXWifiManager.WIFI_HOTSPOT_SSID = "\"" + ssid + "\""

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            Toast.makeText(context, "connectToHotpot", Toast.LENGTH_SHORT).show()
            if (handler != null) {
                connectToLocalHotspot(context, ssid, pwd, handler)
            }
        } else {
            println("")
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.Q)
    private fun connectToLocalHotspot(context: Context,ssid: String,pwd: String,handler: Handler) {
        val specifier = WifiNetworkSpecifier.Builder()
            .setSsid(ssid)
            .setWpa2Passphrase(pwd)
            .build()
        val request = NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .setNetworkSpecifier(specifier)
            .build()
        val connectivityManager =
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        // error:java.lang.SecurityException: com.example.huanji was not granted  either of these permissions: android.permission.CHANGE_NETWORK_STATE, android.permission.WRITE_SETTINGS.
        connectivityManager.requestNetwork(request, object : NetworkCallback() {
            override fun onAvailable(network: Network) {
                super.onAvailable(network)

                toastMsg(context, "available")
                try {
                    if (socket == null) {
                        val ip = getServerAddressString(context)
                        Log.i("ip", ip + "")
                        socket = Socket(ip, MainActivity.PORT)
                        Log.i("socket", "creating")
                    }
                    if (!socket!!.isConnected()) {
                        network.bindSocket(socket)
                        Log.i("socket", "binding")
                    }
                    handler.sendEmptyMessage(MainActivity.DEVICE_CONNECTED)
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }

            override fun onLosing(network: Network, maxMsToLive: Int) {
                super.onLosing(network, maxMsToLive)
                toastMsg(context, "onLosing")
            }

            override fun onLost(network: Network) {
                super.onLost(network)
                toastMsg(context, "onLost")
            }

            override fun onUnavailable() {
                super.onUnavailable()
                toastMsg(context, "onUnavailable")
            }
        })
    }

连接成功后,会回调onAvailable(),在这里可以建立socket连接。

3.1、 关闭热点

回调接口中会有这个对象,可以通过它来关闭:

LocalOnlyHotspotReservation#close()

四、建立socket连接

socket连接建立需要ip地址和端口号,IP通过下面的方法获取,端口号要先在两边通信的App中定义好。

            override fun onAvailable(network: Network) {
                super.onAvailable(network)

                toastMsg(context, "available")
                try {
                    if (socket == null) {
                        val ip = getServerAddressString(context)
                        Log.i("ip", ip + "")
                        socket = Socket(ip, MainActivity.PORT)
                        Log.i("socket", "creating")
                    }
                    if (!socket!!.isConnected()) {
                        network.bindSocket(socket)
                        Log.i("socket", "binding")
                    }
                    handler.sendEmptyMessage(MainActivity.DEVICE_CONNECTED)
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }

    /**
     * 获取热点ip地址。
     *
     * @return 热点ip地址,String。
     */
    fun getServerAddressString(context: Context): String? {
        val wifi_service =
            context.getApplicationContext().getSystemService(Context.WIFI_SERVICE) as WifiManager
        val dhcpInfo = wifi_service.dhcpInfo
        Log.i("server ip", "" + dhcpInfo.serverAddress)

        return intToIp(dhcpInfo.serverAddress)
    }

    private fun intToIp(paramInt: Int): String? {
        // kotlin
        // -1195333440
        // 192.0.0.0

// java
// ip:-1195333440
// ip:192.168.192.184
        return ((paramInt and 0xFF).toString() + "." + (0xFF and (paramInt shr 8)) + "."
                + (0xFF and (paramInt shr 16)) + "."
                + (0xFF and (paramInt shr 24)))
    }

五、热点容易断开解决

在热点创建好后,切换应用,或者到后台都会使得热点断开,原因可以看参考中的文章。
我的解决方法是:通过demon线程来一直持有LocalOnlyHotspotReservation对象,使其不会断开。经过测试,目前没有问题。

六、LocalOnlyHotspot与普通hotspot区别

1、LocalOnlyHotspot开启的热点没有网络。
2、LocalOnlyHotspot开启以后,开启它的程序进入后台,热点几秒后就会自动关闭,不管有没有人连。

如何保证热点不断开,有三种方法:

  1. 创建服务
    不稳定。
  2. 创建服务+进程
    相对稳定。
  3. demon线程

七、相关问题

Q1、
W/System.err: java.io.FileNotFoundException: /proc/net/arp: open failed: EACCES (Permission denied)
W/System.err:     at libcore.io.IoBridge.open(IoBridge.java:492)
W/System.err:     at java.io.FileInputStream.<init>(FileInputStream.java:160)
W/System.err:     at java.io.FileInputStream.<init>(FileInputStream.java:115)
W/System.err:     at java.io.FileReader.<init>(FileReader.java:58)
W/System.err:     at com.example.mywifidemo.MainActivity.getConnectedIP(MainActivity.java:231)
W/System.err:     at com.example.mywifidemo.MainActivity.access$600(MainActivity.java:43)
W/System.err:     at com.example.mywifidemo.MainActivity$3.run(MainActivity.java:206)
W/System.err:     at java.lang.Thread.run(Thread.java:923)
W/System.err: Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
W/System.err:     at libcore.io.Linux.open(Native Method)
W/System.err:     at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
W/System.err:     at libcore.io.BlockGuardOs.open(BlockGuardOs.java:254)
W/System.err:     at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
W/System.err:     at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:8127)
W/System.err:     at libcore.io.IoBridge.open(IoBridge.java:478)
W/System.err: 	... 7 more
Q2: Kotlin和JAVA操作int的问题。

Android studio自动JAVA转kotlin时,不会使移位运算先执行,所以需要注意,不然会出现ip地址计算错误的问题。

    /**
     * 获取热点ip地址。
     *
     * @return 热点ip地址,String。
     */
    fun getIpAddressString(context: Context): String? {
        val wifi_service =
            context.getApplicationContext().getSystemService(Context.WIFI_SERVICE) as WifiManager
        val dhcpInfo = wifi_service.dhcpInfo
        return intToIp(dhcpInfo.ipAddress)
    }
I/server ip: -1195333440
I/ip: 192.0.0.0
    private fun intToIp(paramInt: Int): String? {
        return ((paramInt and 0xFF).toString() + "." + (0xFF and paramInt shr 8) + "." + (0xFF and paramInt shr 16) + "."
                + (0xFF and paramInt shr 24))
    }
	
	I/server ip: -1195333440
        I/socket: ip:192.168.192.184
	// java 
    private String intToIp(int paramInt) {
        return (paramInt & 0xFF) + "." + (0xFF & paramInt >> 8) + "." + (0xFF & paramInt >> 16) + "."
                + (0xFF & paramInt >> 24);
    }

原因:kotlin and shr 是从左往右运算,JAVA是移位运算优先,导致计算错误。
return ((paramInt and 0xFF).toString() + “.” + (0xFF and (paramInt shr 8)) + “.”
+ (0xFF and (paramInt shr 16)) + “.”
+ (0xFF and (paramInt shr 24)))

Q3:模拟器联网

输入命令 .\emulator -avd 模拟器名 -dns-server 你自己的DNS地址
~/Android/Sdk/emulator$ .\emulator -avd Nexus_S_API_22 -dns-server 114.114.114.114
DNS查看:systemd-resolve --status
最下面有DNS。

联网是想用模拟器来创建热点的。但并没有成功。

Q4:热点连接上了,不能建立socket通信

ip是serveraddress。gateway。

W/System.err: java.net.ConnectException: failed to connect to /192.168.30.24 (port 54329) from /:: (port 40312): connect failed: ETIMEDOUT (Connection timed out)
W/System.err:     at libcore.io.IoBridge.connect(IoBridge.java:142)
W/System.err:     at java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:142)
W/System.err:     at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:390)
W/System.err:     at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:230)
W/System.err:     at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:212)
W/System.err:     at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:436)
W/System.err:     at java.net.Socket.connect(Socket.java:631)
W/System.err:     at java.net.Socket.connect(Socket.java:571)
W/System.err:     at java.net.Socket.<init>(Socket.java:451)
W/System.err:     at java.net.Socket.<init>(Socket.java:219)
W/System.err:     at com.example.huanji.communication.wifi.WifiConnector$connectToLocalHotspot$1.onAvailable(WifiConnector.kt:109)
W/System.err:     at android.net.ConnectivityManager$NetworkCallback.onAvailable(ConnectivityManager.java:3384)
W/System.err:     at android.net.ConnectivityManager$CallbackHandler.handleMessage(ConnectivityManager.java:3669)
W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:106)
W/System.err:     at android.os.Looper.loop(Looper.java:236)
W/System.err:     at android.os.HandlerThread.run(HandlerThread.java:67)
W/System.err: Caused by: android.system.ErrnoException: connect failed: ETIMEDOUT (Connection timed out)
W/System.err:     at libcore.io.Linux.connect(Native Method)
W/System.err:     at libcore.io.ForwardingOs.connect(ForwardingOs.java:94)
W/System.err:     at libcore.io.BlockGuardOs.connect(BlockGuardOs.java:138)
W/System.err:     at libcore.io.ForwardingOs.connect(ForwardingOs.java:94)
W/System.err:     at libcore.io.IoBridge.connectErrno(IoBridge.java:156)
W/System.err:     at libcore.io.IoBridge.connect(IoBridge.java:134)
W/System.err: 	... 15 more
Q5:热点连接上了,不能建立socket通信

ip是serveraddress。gateway。

W/System.err: java.net.ConnectException: failed to connect to /192.168.30.24 (port 54329) from /:: (port 0): connect failed: ENETUNREACH (Network is unreachable)
W/System.err:     at libcore.io.IoBridge.connect(IoBridge.java:142)
W/System.err:     at java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:142)
W/System.err:     at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:390)
W/System.err:     at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:230)
W/System.err:     at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:212)
W/System.err:     at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:436)
W/System.err:     at java.net.Socket.connect(Socket.java:631)
W/System.err:     at java.net.Socket.connect(Socket.java:571)
W/System.err:     at java.net.Socket.<init>(Socket.java:451)
W/System.err:     at java.net.Socket.<init>(Socket.java:219)
W/System.err:     at com.example.huanji.communication.wifi.WifiConnector$connectToLocalHotspot$1.onAvailable(WifiConnector.kt:109)
W/System.err:     at android.net.ConnectivityManager$NetworkCallback.onAvailable(ConnectivityManager.java:3384)
W/System.err:     at android.net.ConnectivityManager$CallbackHandler.handleMessage(ConnectivityManager.java:3669)
W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:106)
W/System.err:     at android.os.Looper.loop(Looper.java:236)
W/System.err:     at android.os.HandlerThread.run(HandlerThread.java:67)
W/System.err: Caused by: android.system.ErrnoException: connect failed: ENETUNREACH (Network is unreachable)
W/System.err:     at libcore.io.Linux.connect(Native Method)
W/System.err:     at libcore.io.ForwardingOs.connect(ForwardingOs.java:94)
W/System.err:     at libcore.io.BlockGuardOs.connect(BlockGuardOs.java:138)
W/System.err:     at libcore.io.ForwardingOs.connect(ForwardingOs.java:94)
W/System.err:     at libcore.io.IoBridge.connectErrno(IoBridge.java:156)
W/System.err:     at libcore.io.IoBridge.connect(IoBridge.java:134)
W/System.err: 	... 15 more
Q6:热点连接上了,不能建立socket通信

ip是ipaddress。

W/System.err: java.net.ConnectException: failed to connect to /192.168.30.79 (port 54329) from /:: (port 35400): connect failed: ECONNREFUSED (Connection refused)
W/System.err:     at libcore.io.IoBridge.connect(IoBridge.java:142)
W/System.err:     at java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:142)
W/System.err:     at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:390)
W/System.err:     at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:230)
W/System.err:     at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:212)
W/System.err:     at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:436)
W/System.err:     at java.net.Socket.connect(Socket.java:631)
W/System.err:     at java.net.Socket.connect(Socket.java:571)
W/System.err:     at java.net.Socket.<init>(Socket.java:451)
W/System.err:     at java.net.Socket.<init>(Socket.java:219)
W/System.err:     at com.example.huanji.communication.wifi.WifiConnector$connectToLocalHotspot$1.onAvailable(WifiConnector.kt:96)
W/System.err:     at android.net.ConnectivityManager$NetworkCallback.onAvailable(ConnectivityManager.java:3384)
W/System.err:     at android.net.ConnectivityManager$CallbackHandler.handleMessage(ConnectivityManager.java:3669)
W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:106)
W/System.err:     at android.os.Looper.loop(Looper.java:236)
W/System.err:     at android.os.HandlerThread.run(HandlerThread.java:67)
W/System.err: Caused by: android.system.ErrnoException: connect failed: ECONNREFUSED (Connection refused)
W/System.err:     at libcore.io.Linux.connect(Native Method)
W/System.err:     at libcore.io.ForwardingOs.connect(ForwardingOs.java:94)
W/System.err:     at libcore.io.BlockGuardOs.connect(BlockGuardOs.java:138)
W/System.err:     at libcore.io.ForwardingOs.connect(ForwardingOs.java:94)
W/System.err:     at libcore.io.IoBridge.connectErrno(IoBridge.java:156)
W/System.err:     at libcore.io.IoBridge.connect(IoBridge.java:134)

Q7:

serverSocket = ServerSocket(BaseTransfer.PORT)多次调用导致。

W/System.err: java.net.BindException: bind failed: EADDRINUSE (Address already in use)
W/System.err:     at libcore.io.IoBridge.bind(IoBridge.java:99)
W/System.err:     at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:132)
W/System.err:     at java.net.ServerSocket.<init>(ServerSocket.java:105)
W/System.err:     at java.net.ServerSocket.<init>(ServerSocket.java:74)
W/System.err:     at com.example.huanji.other.ServerRunnable.run(ServerRunnable.kt:19)
W/System.err:     at java.lang.Thread.run(Thread.java:818)
W/System.err: Caused by: android.system.ErrnoException: bind failed: EADDRINUSE (Address already in use)
W/System.err:     at libcore.io.Posix.bind(Native Method)
W/System.err:     at libcore.io.ForwardingOs.bind(ForwardingOs.java:56)
W/System.err:     at libcore.io.IoBridge.bind(IoBridge.java:97)
W/System.err: 	... 5 more

参考

1、Android之解决开启热点后跳转页面不稳定问题

通过服务持有LocalOnlyHotspotReservation来保持(进入后台)Local热点不断开。将服务放到一个进程中保持Local热点不断开。 比较麻烦。

2、android热点 8.0 ,7.1 ,6.0一7.0 以及6.0以下热点创建到连接完全适配

3、LocalOnlyHotspot学习总结(二)

通过demon线程持有LocalOnlyHotspotReservation来保持(进入后台)Local热点不断开。

4、Android 通过代码设置、打开wifi热点及热点连接的实现代码

其他

适配后面单独写一篇博客。

开启热点各版本适配
1、6.0以下
setapenable()
2、6.0、7.0
添加修改系统设置的:WRITE_SETTINGS权限。
3、7.1
跳转到热点页面,让用户手动开启。
4、8.0及以上
使用Localhotspot。

可以通过LocalOnlyHotspot来适配8.0的热点开启功能。
通过这个方法开启热点,我们不能设置热点的名称以及密码的只能使用系统生成的随机密码和名称。
可以通过扫描二维码的方式将名称和密码传给另一台设备。

你可能感兴趣的:(其他,android,wifi)