即使移动数据已连接,也可以通过WiFi发送请求(无需连接),方式在Android M上实现

22
我必须发送UDP数据包到一个带有自己AP的WiFi模块上,但当我连接手机到AP时,Android会将我的数据包重定向到移动数据接口,因为它有互联网连接。我已经使用下面的代码完成了我的工作,但在Android M上似乎不起作用。
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setWifiInterfaceAsDefault() {
    ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

    NetworkRequest.Builder builder = new NetworkRequest.Builder();
    NetworkRequest networkRequest= builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .build();

    connectivityManager.requestNetwork(networkRequest, new ConnectivityManager.NetworkCallback());
}

我还添加了

<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />

在我的AndroidManifest.xml中,我确保Settings.System.canWrite(this)返回true,但仍然没有任何作用。

提前致谢。


朋友,你解决了这个问题吗?我有完全相同的要求。 - AppleGrew
如果你找到了真正的解决方案,请告诉我们 ✌ - Alessandro Muzzi
@AleMuzzi 我有一个类似的问题,您能否提供更多关于您的generate_204解决方法的信息,因为我可以对设备进行更改 https://dev59.com/gFgQ5IYBdhLWcg3wnFPJ - Lonergan6275
@Lonergan6275 generate_204 必须放置在 IoT 设备的 http 服务器根目录中,并且您必须在设备上拥有一个提供这些 URL 的 dos 服务器,例如,如果您正在使用 openwrt 作为 IoT 操作系统,则会在 /etc 下有一个名为“hosts”的文件,请添加以下行:10.1.1.1 connectivitycheck.gstatic.com 10.1.1.1 clients3.google.com。 其中,10.1.1.1 是您的“127.0.0.1”IoT 地址。 - Alessandro Muzzi
@Lonergan6275 抱歉,我认为我没有理解你的问题,但如果我已经理解了,我可以告诉你,OpenWRT 提供了一个 DNS 服务器,这就是为什么你有一个包含众所周知主机(具有静态 IP)的 hosts 文件,所以,希望谷歌不会更改互联网检查系统,添加这些地址就足够了。 - Alessandro Muzzi
显示剩余6条评论
2个回答

21

Stanislav的答案是正确的,但不完整,因为它只在棒棒糖中有效。

我已经为您编写了一个完整的解决方案,在棒棒糖和棉花糖及其以上版本中,可通过您选择的特定网络将所有网络请求路由通过WiFi。


Kotlin

在您的Activity中,

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class RoutingActivity : Activity() {

    private var mConnectivityManager: ConnectivityManager? = null
    private var mNetworkCallback: ConnectivityManager.NetworkCallback? = null
    //...

    override fun onCreate(savedInstanceState: Bundle?) {
        //...
        routeNetworkRequestsThroughWifi("Access-Point-SSID-You-Want-To-Route-Your-Requests")
    }

将未来的网络请求从应用程序路由通过WiFi(即使所提供的WiFi网络没有互联网连接,移动数据有互联网连接)

/**
 * This method sets a network callback that is listening for network changes and once is
 * connected to the desired WiFi network with the given SSID it will bind to that network.
 *
 * Note: requires android.permission.INTERNET and android.permission.CHANGE_NETWORK_STATE in
 * the manifest.
 *
 * @param ssid The name of the WiFi network you want to route your requests
 */
private fun routeNetworkRequestsThroughWifi(ssid: String) {
    mConnectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    // ensure prior network callback is invalidated
    unregisterNetworkCallback(mNetworkCallback)

    // new NetworkRequest with WiFi transport type
    val request = NetworkRequest.Builder()
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .build()

    // network callback to listen for network changes
    mNetworkCallback = object : ConnectivityManager.NetworkCallback() {

        // on new network ready to use
        override fun onAvailable(network: Network) {

            if (getNetworkSsid(this@RoutingActivity).equals(ssid, ignoreCase = false)) {
                releaseNetworkRoute()
                createNetworkRoute(network)

            } else {
                releaseNetworkRoute()
            }
        }
    }
    mConnectivityManager?.requestNetwork(request, mNetworkCallback)
}

注销网络回调

private fun unregisterNetworkCallback(networkCallback: ConnectivityManager.NetworkCallback?) {
    if (networkCallback != null) {
        try {
            mConnectivityManager?.unregisterNetworkCallback(networkCallback)

        } catch (ignore: Exception) {
        } finally {
            mNetworkCallback = null
        }
    }
}

创建网络路由

private fun createNetworkRoute(network: Network): Boolean? {
    var processBoundToNetwork: Boolean? = false
    when {
    // 23 = Marshmallow
        Build.VERSION.SDK_INT >= 23 -> {
            processBoundToNetwork = mConnectivityManager?.bindProcessToNetwork(network)
        }

    // 21..22 = Lollipop
        Build.VERSION.SDK_INT in 21..22 -> {
            processBoundToNetwork = ConnectivityManager.setProcessDefaultNetwork(network)
        }
    }
    return processBoundToNetwork
}

释放网络路由

private fun releaseNetworkRoute(): Boolean? {
    var processBoundToNetwork: Boolean? = false
    when {
    // 23 = Marshmallow
        Build.VERSION.SDK_INT >= 23 -> {
            processBoundToNetwork = mConnectivityManager?.bindProcessToNetwork(null)
        }

    // 21..22 = Lollipop
        Build.VERSION.SDK_INT in 21..22 -> {
            processBoundToNetwork = ConnectivityManager.setProcessDefaultNetwork(null)
        }
    }
    return processBoundToNetwork
}

助手

private fun getNetworkSsid(context: Context?): String {
    // WiFiManager must use application context (not activity context) otherwise a memory leak can occur
    val mWifiManager = context?.applicationContext?.getSystemService(Context.WIFI_SERVICE) as WifiManager
    val wifiInfo: WifiInfo? = mWifiManager.connectionInfo
    if (wifiInfo?.supplicantState == SupplicantState.COMPLETED) {
        return wifiInfo.ssid.removeSurrounding("\"")
    }
    return ""
}

1
更多关于此的信息可以在这里找到:https://android-developers.googleblog.com/2016/07/connecting-your-app-to-wi-fi-device.html - Ryan Amaral
3
你应该考虑写一篇关于这个话题的博客文章;这是一个文献资料不足但 Stack Overflow 上有大量相关问题的话题,而你在这里和 https://dev59.com/oZ_ha4cB1Zd3GeqPwUto#51470122 的撰写提供了比 Google 博客更完整和有用的信息。 - mph
1
非常感谢您的积极反馈,@mikeh!如果您想自己写一篇文章并包含这些内容,我很乐意在您发布前进行审查。 - Ryan Amaral
1
谢谢 @ryan-amaral,这真的非常友善。让我看看我能做什么! - mph
1
工作正常,谢谢你救了我的一天。 - Bruno Horta
显示剩余6条评论

13
使用ConnectivityManager.setProcessDefaultNetwork()绑定网络可以防止漫游,并允许完全的TCP访问。因此,在onAvailable()回调中,您可以将应用程序进程绑定到该网络,而不是打开到特定URL的连接。
ConnectivityManager connection_manager = 
(ConnectivityManager) activity.getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);

NetworkRequest.Builder request = new NetworkRequest.Builder();
request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);

connection_manager.registerNetworkCallback(request.build(), new NetworkCallback() {

    @Override
    public void onAvailable(Network network) {
        ConnectivityManager.setProcessDefaultNetwork(network);
    }
}

Original answer


感谢@Stanislav,提供了很好的解决方案。它适用于Marshmallow和Naugat。 - Indra KP
你真的救了我的一天。非常感谢。这也适用于Android P。如果需要,我稍后将尝试实现Ryan的代码。 - Pawan Pillai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接