4

一旦设备在接入点范围内而不打开屏幕,我一直在努力自动打开 Wi-Fi。测试和找出解决方案非常令人沮丧,特别是因为不同的设备有完全不同的结果。

基本测试
在此测试期间保持屏幕关闭。应用程序应该持有一个 WifiLock。

  1. 走出 WiFi 覆盖范围并在那里停留一分钟。
  2. 回到报道中。

结果:Wifi 没有立即重新连接,因此应用程序没有重新连接到服务器。根据设备和设置,有时在屏幕打开之前它根本不会重新连接。

强制 Wi-Fi 重新连接
好的,这次我的应用程序会在 Wifi 断开连接时每隔一段时间调用 WifiManager.Reconnect()。

重复测试。结果:适用于 S3,其他设备失败。

尝试添加一些其他调用
尝试了 WifiManager.Scan()、WifiManager.Reassociate()、...等的不同组合。最终它适用于除 S4 之外的大多数设备(HTC、S3)。

似乎适用于所有设备的代码

NetworkInfo wifiInfo = _androidConnectivityMgr.GetNetworkInfo(ConnectivityType.Wifi);
if (!_wifiManager.IsWifiEnabled || _wifiManager.WifiState == WifiState.Disabled || _wifiManager.WifiState == WifiState.Disabling)
{
    // Make sure the Wi-Fi is enabled, required for some devices when enable WiFi does not occur immediately
    _wifiManager.SetWifiEnabled(true);
}

if (!wifiInfo.IsConnectedOrConnecting)
{
    // Do not wait for the OS to initiate a reconnect to a Wi-Fi router
    _wifiManager.PingSupplicant();
    if (_wifiManager.WifiState == WifiState.Enabled)
    {
        try
        {
            // Brute force methods required for some devices
            _wifiManager.SetWifiEnabled(false);
            _wifiManager.SetWifiEnabled(true);
        }
        catch (Java.Lang.SecurityException)
        {
            // Catching exception which should not occur on most devices. OS bug details at :
            // https://code.google.com/p/android/issues/detail?id=22036
        }
    }
    _wifiManager.Disconnect();
    _wifiManager.StartScan();
    _wifiManager.Reassociate();
    _wifiManager.Reconnect();
}

我什至不确定所有这些代码是否必要,因为我无法在网上找到太多信息。WifiFixer确实帮助了一些人。但这似乎适用于我测试过的设备。

问题

  • 有没有更好的方法来做到这一点?
  • 制造商真的修改了我可以看到这么多差异的基本 Android 吗?
  • 这是完全错误的方法吗?

感谢您阅读所有这些:)

补充说明

  1. 代码在从 AlarmManager 启动的 10+ 秒间隔内运行。WakeLock 仅在此呼叫期间保持。
  2. 在这个看起来很吓人的解决方案/破解之前,“Wifi 睡眠策略”影响了结果。这让我很困惑,因为我一直拿着 WifiLock,我认为这相当于“从不”。
  3. 以编程方式更改“Wifi 睡眠策略”不适用于 S4,其他人可以确认吗?
  4. 是的,我们有特定的需要这样做,并且知道电池的影响。
4

3 回答 3

2

我的情况略有不同 -我一开始没有 wifi 锁(而且我在普通的 android 上,所以我不得不翻译你的方法)。

屏幕关闭,CPU 关闭,收音机死机。闹钟唤醒我的(清醒)服务 - 我持有(部分)唤醒锁。

我想要的是 - 如果 wifi 能够连接到它在收音机死机之前连接的接入点 - 我获得了一个 wifi 锁,我调用了你的函数 - wakeWifiUp()。当收音机死机时(!wifiInfo.IsConnectedOrConnecting是真的),当我尝试连接时,我的网络无法访问。我解决方法如下:

public final class NetworkService extends WakefulIntentService {

    // this is an intent service - runs on its own thread - otherwise it would
    // deadlock as I am using it. Moreover it holds a wakelock and woken up by
    // an AlarmManager's Receiver - works reliably
    private BroadcastReceiver mConnectionReceiver;
    private volatile static CountDownLatch latch;

    @Override
    protected void doWakefulWork(Intent intent) {
        WifiLock _wifiLock = null;
        WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        boolean failedToConnect = true;
        if (wm != null && wm.isWifiEnabled()) {// Don't want to enable it myself
            _wifiLock = wm.createWifiLock(
            /* WifiManager.WIFI_MODE_FULL_HIGH_PERF */0x3, this.getClass()
                .getName() + ".WIFI_LOCK");
            _wifiLock.acquire();
            failedToConnect = !wakeWifiUp();
        }
        if (failedToConnect) {
            if (_wifiLock != null) _wifiLock.release();
            w("No connection !");
            return;
        }
        HttpURLConnection connection = null;
        try {
            connection = connection(); 
        } catch (IOException e) {/* won't throw - it doesn't do much*/}
        OutputStream serverOutputStream = null;
        try {
            serverOutputStream = connection.getOutputStream(); // now
            // this is really where the connection might seriously throw
            // .... Work ....
        } catch (IOException e) {
            w("IOException sending data " + e.getMessage());
            // I get here : Network unreachable when radio dies
        } finally {
            if (_wifiLock != null) _wifiLock.release();
            if (connection != null) connection.disconnect();
        }
    }

    private HttpURLConnection connection() throws MalformedURLException,
            IOException {
        HttpURLConnection connection = (HttpURLConnection) new URL("localhost")
            .openConnection();
        connection.setDoOutput(true); // triggers POST
        connection.setRequestProperty("Connection", "Keep-Alive");
        connection.setRequestProperty("User-Agent",
            "Android Multipart HTTP Client 1.1");
        return connection;
    }

    private boolean wakeWifiUp() {
        ConnectivityManager _androidConnectivityMgr = (ConnectivityManager)
                getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo wifiInfo = _androidConnectivityMgr
            .getNetworkInfo(ConnectivityManager.TYPE_WIFI);
        WifiManager _wifiManager = (WifiManager)
                getSystemService(Context.WIFI_SERVICE);
        final int wifiState = _wifiManager.getWifiState();
        if (!_wifiManager.isWifiEnabled()
            || wifiState == WifiManager.WIFI_STATE_DISABLED
            || wifiState == WifiManager.WIFI_STATE_DISABLING) {
            // Make sure the Wi-Fi is enabled, required for some devices when
            // enable WiFi does not occur immediately
            d("!_wifiManager.isWifiEnabled()");
            _wifiManager.setWifiEnabled(true);
            // do not enable if not enabled ! FIXME
            return false;
        }
        if (!wifiInfo.isConnectedOrConnecting()) {
            d("Wifi is NOT Connected Or Connecting - "
                + "wake it up and wait till is up");
            // Do not wait for the OS to initiate a reconnect to a Wi-Fi router
            _wifiManager.pingSupplicant();
            if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
                try {
                    // Brute force methods required for some devices
                    _wifiManager.setWifiEnabled(false);
                    _wifiManager.setWifiEnabled(true);
                } catch (SecurityException e) {
                    // Catching exception which should not occur on most
                    // devices. OS bug details at :
                    // https://code.google.com/p/android/issues/detail?id=22036
                }
            }
            _wifiManager.disconnect();
            _wifiManager.startScan();
            _wifiManager.reassociate();
            _wifiManager.reconnect();
            // THIS IS WHAT I DO TO WAIT FOR A CONNECTION
            try {
                mConnectionReceiver = new WifiConnectionMonitor();
                startMonitoringConnection();
                latch = new CountDownLatch(1);
                w("I wait");
                latch.await();
                w("Woke up");
                return true; // made it
            } catch (InterruptedException e) {
                w("Interrupted while waiting for connection", e);
                return false;
            } finally {
                stopMonitoringConnection();
            }
        }
        return true;
    }

    static void downTheLatch() {
        latch.countDown();
    }

    private synchronized void startMonitoringConnection() {
        IntentFilter aFilter = new IntentFilter(
            ConnectivityManager.CONNECTIVITY_ACTION);
        aFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        registerReceiver(mConnectionReceiver, aFilter);
    }

    private synchronized void stopMonitoringConnection() {
        unregisterReceiver(mConnectionReceiver);
    }

    private final class WifiConnectionMonitor extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent in) {
            String action = in.getAction();
            if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
                NetworkInfo networkInfo = in
                    .getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
                d(networkInfo + "");
                if (networkInfo.isConnected()) {
                    d("Wifi is connected!");
                    NetworkService.downTheLatch(); // HERE THE SERVICE IS WOKEN!
                }
            }
        }
    }
}

顺便说一句,并不是所有的技巧wakeWifiUp()都需要(在我的情况下)并且所有的都!_wifiManager.isWifiEnabled()可以被省略 - 因为我只在用户启用的情况下使用网络。为了完整起见,我将其保留。

回顾:在我的场景中,你的方法是不够的(如果我正确地翻译成 java 并且没有犯一些愚蠢的错误,这总是适用的 - 另见我的connection())。我需要等待建立连接 - 但一切都很好。仍然不确定你是如何使用它的——如果像我一样,那么不同之处可能是你一直拿着一个 wifi 锁

HTC Nexus 1、2.3.7、Cyanogen mod(别拍我已经给它测试了)。

会及时向大家发布

于 2013-11-14T03:01:46.933 回答
1

Alex 和 Mr_and_Mrs_D 的方法很接近,但在 Android 4.4 KitKat (Nexus 4) 下并不完全一致。这可能与 Google 从 KitKat 开始的更积极的节能政策有关。我结合了他们的方法和修改。

一般的想法是,在定期 WiFi 检查期间,显式启动扫描,然后在扫描结果处理程序中调用 reassociate() 和 reconnect()。另外在 NETWORK_STATE_CHANGED_ACTION 回调中,在释放唤醒锁之前检查连接是否建立。关键是保持唤醒锁足够长的时间,以便 WiFi 连接正确建立(并且显然不会超过必要的时间)。

定期进行 WiFi 检查以启动:

public static void CheckWiFi() {  
    mWakeLock.acquire();
    if (!WiFi_Mgr.isWifiEnabled()) {
        WiFi_Mgr.setWifiEnabled(true);
    }
    WiFi_Mgr.startScan();

    //Set an alarm to fire after N seconds to release wake lock & shut off WiFi if no connection is available.
    // ... 
}

注册 WiFi 广播

//Setup WiFi connection status receiver
IntentFilter WiFiFilters = new IntentFilter();
WiFiFilters.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
WiFiFilters.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
this.registerReceiver(WiFiReceiver, WiFiFilters);

和 WiFi 事件处理程序

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

        if(WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
            NetworkInfo netInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
            // This is the magic bullet. The connection usually establishes after 
            // the scan results callback so release the wake lock here.
            if(netInfo.isConnected()) {
                mWakeLock.release();
            }
        }
        //Search the scan results for saved WiFi APs.
        else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action))
        {
            boolean foundMatch = false;
            if (!IsWiFiConnected()) {
                Map<String, Integer> savedNetworks = new HashMap<String, Integer>();
                for (WifiConfiguration config : WiFi_Mgr.getConfiguredNetworks()) {
                    String escapedSSID = config.SSID.replaceAll("\"", "");
                    savedNetworks.put(escapedSSID, config.networkId);
                }
                List<ScanResult> scanResults = WiFi_Mgr.getScanResults();
                for (ScanResult ap : scanResults) {
                    Integer networkId = savedNetworks.get(ap.SSID);
                    if (networkId != null) {
                        savedNetworks.remove(ap.SSID);
                        WiFi_Mgr.enableNetwork(networkId, false);
                        foundMatch = true;
                    }
                }
            }
            if(foundMatch) {
                WiFi_Mgr.reassociate();
                WiFi_Mgr.reconnect();
            }
            if (IsWiFiConnected())
                mWakeLock.release();
        }
    }
};

您需要声明必要的变量(例如,mWakeLock 是一个部分的、非引用计数的唤醒锁;WiFi_Mgr 是 WifiManager 的一个实例;等等...)。

于 2014-04-25T20:41:29.527 回答
0

在该地区进行了第二次转机。虽然上述解决方案确实适用于我们所有合格的设备,但可能有太多不必要的调用。另外,我们得到了解决方案不起作用的新设备。这是一个更好的解决方案:

在每个间隔调用此代码

NetworkInfo wifiInfo = _androidConnectivityMgr.GetNetworkInfo(ConnectivityType.Wifi);
if (!wifiInfo.IsConnectedOrConnecting)
{
    // Need to make sure the CPU does not go to sleep before the following async calls are finished
    _wifiScanWakeLock.Acquire();

    // Do not wait for the OS to initiate a reconnect to a Wi-Fi router
    _wifiManager.StartScan();
}
  • _wifiScanWakeLock 只是一个部分的、非引用计数的 WakeLock,在 OnDestroy 中处理

当 Wi-Fi 扫描完成时

private void OnWifiScanResultsReceived(Intent result)
{
    NetworkInfo wifiInfo = _androidConnectivityMgr.GetNetworkInfo(ConnectivityType.Wifi);
    if (!wifiInfo.IsConnectedOrConnecting)
    {
        Dictionary<string, int> savedNetworks = new Dictionary<string, int>();
        foreach (WifiConfiguration config in _wifiManager.ConfiguredNetworks)
        {
            string escapedSsid = Regex.Replace(config.Ssid, "^\"|\"$", String.Empty);
            savedNetworks[escapedSsid] = config.NetworkId;
        }

        foreach (ScanResult ap in _wifiManager.ScanResults)
        {
            int networkId;
            if (savedNetworks.TryGetValue(ap.Ssid, out networkId))
            {
                savedNetworks.Remove(ap.Ssid);
                _wifiManager.EnableNetwork(networkId, false);
            }
        }
    }
    _wifiScanWakeLock.Release();
}
  • WifiConfiguration 的 BSSID 始终为空,不能用于唯一地与 ScanResult 的 BSSID 进行比较
  • 这是核心代码,显然您将不得不担心两个相同 SSID 和其他优化的情况
于 2014-03-18T15:52:12.800 回答