我一直在努力,一旦设备在接入点范围内而不打开屏幕,就会自动打开Wi-Fi。测试和找出解决方案非常令人沮丧,特别是因为不同的设备具有完全不同的结果。
基础测试
在此测试期间保持屏幕关闭。应用程序应该持有无线网络锁。
- 走出WiFi覆盖,在那里呆一分钟。
- 走回覆盖范围。
结果: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 吗?
- 这完全是错误的方法吗?
感谢您阅读所有这些:)
附加说明
- 代码在从警报管理器启动的 10+ 秒间隔内运行。唤醒锁仅在此调用期间保持。
- 在这个最终看起来很可怕的解决方案/黑客之前,"Wifi睡眠政策"影响了结果。这让我感到困惑,因为我一直拿着WifiLock,我认为这相当于"从不"。
- 以编程方式更改"Wifi睡眠策略"不适用于S4,其他人可以确认吗?
- 是的,我们有这样做的特定需求,并且知道电池的影响。
我的场景略有不同 - 我一开始就没有 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(不要拍摄,我已经给了它测试(。
会及时通知您
Alex和Mr_and_Mrs_D的方法很接近,但在Android 4.4 KitKat(Nexus 4(下并不完全一致。这可能与谷歌从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.
// ...
}
注册无线广播
//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的一个实例;等等...
在该地区转了一圈。虽然上述解决方案确实适用于我们所有的合格设备,但有太多不必要的调用。另外,我们得到了解决方案不起作用的新设备。这是一个更好的解决方案:
每隔一段时间都会调用此代码
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只是一个部分的、非引用计数的唤醒锁,在 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();
}
- Wifi配置的BSSID
- 始终为空,不能用于与扫描结果的BSSID进行唯一比较
- 这是核心代码,显然您将不得不担心两个相同的SSID和其他优化的情况