省电+电话 – 意图=>没有互联网?

注:事实certificate,原来的问题在假设上是错误的。 在底部查看更多细节。

现在是关于电池节电器,而不是节电器和打盹模式。 这也不是关于服务和广播接收机,而只是广播接收机。

背景

从Android Lollipop开始,Google推出了新的手动和自动方式来帮助节省电量:

“打盹”模式和“省电模式”。

在某些情况下,由于这些技术,应用程序可能无法访问互联网。

问题

我使用一个后台服务来访问互联网,这个服务在特定的情况下触发,如果有什么重要的东西被收到,它会显示一些用户界面。

我注意到,作为一个用户,在某些情况下,它无法访问互联网。

检查应用程序是否可以访问Internet是这样的:

public static boolean isInternetOn(Context context) { final NetworkInfo info = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo(); return !(info == null || !info.isConnectedOrConnecting()); } 

问题是,我需要检查为什么有时返回false,所以如果失败,我们应该告诉用户(通过通知,可能),数据不能访问,因为设备限制了应用程序,并提供用户从电池优化白名单应用程序。

我不确定哪些影响:瞌睡,电池节电,或者两者兼而有之,如果总是这样的话,对于所有的设备,无论如何。

我试过了

我所find的是如何查询打盹模式和省电模式(省电模式):

 public class PowerSaverHelper { public enum PowerSaveState { ON, OFF, ERROR_GETTING_STATE, IRRELEVANT_OLD_ANDROID_API } public enum WhiteListedInBatteryOptimizations { WHITE_LISTED, NOT_WHITE_LISTED, ERROR_GETTING_STATE, IRRELEVANT_OLD_ANDROID_API } public enum DozeState { NORMAL_INTERACTIVE, DOZE_TURNED_ON_IDLE, NORMAL_NON_INTERACTIVE, ERROR_GETTING_STATE, IRRELEVANT_OLD_ANDROID_API } @NonNull public static DozeState getDozeState(@NonNull Context context) { if (VERSION.SDK_INT < VERSION_CODES.M) return DozeState.IRRELEVANT_OLD_ANDROID_API; final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); if (pm == null) return DozeState.ERROR_GETTING_STATE; return pm.isDeviceIdleMode() ? DozeState.DOZE_TURNED_ON_IDLE : pm.isInteractive() ? DozeState.NORMAL_INTERACTIVE : DozeState.NORMAL_NON_INTERACTIVE; } @NonNull public static PowerSaveState getPowerSaveState(@NonNull Context context) { if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) return PowerSaveState.IRRELEVANT_OLD_ANDROID_API; final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); if (pm == null) return PowerSaveState.ERROR_GETTING_STATE; return pm.isPowerSaveMode() ? PowerSaveState.ON : PowerSaveState.OFF; } @NonNull public static WhiteListedInBatteryOptimizations getIfAppIsWhiteListedFromBatteryOptimizations(@NonNull Context context, @NonNull String packageName) { if (VERSION.SDK_INT < VERSION_CODES.M) return WhiteListedInBatteryOptimizations.IRRELEVANT_OLD_ANDROID_API; final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); if (pm == null) return WhiteListedInBatteryOptimizations.ERROR_GETTING_STATE; return pm.isIgnoringBatteryOptimizations(packageName) ? WhiteListedInBatteryOptimizations.WHITE_LISTED : WhiteListedInBatteryOptimizations.NOT_WHITE_LISTED; } //@TargetApi(VERSION_CODES.M) @SuppressLint("BatteryLife") @RequiresPermission(permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) @Nullable public static Intent prepareIntentForWhiteListingOfBatteryOptimization(@NonNull Context context, @NonNull String packageName, boolean alsoWhenWhiteListed) { if (VERSION.SDK_INT < VERSION_CODES.M) return null; if (ContextCompat.checkSelfPermission(context, permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) == PackageManager.PERMISSION_DENIED) return null; final WhiteListedInBatteryOptimizations appIsWhiteListedFromPowerSave = getIfAppIsWhiteListedFromBatteryOptimizations(context, packageName); Intent intent = null; switch (appIsWhiteListedFromPowerSave) { case WHITE_LISTED: if (alsoWhenWhiteListed) intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS); break; case NOT_WHITE_LISTED: intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).setData(Uri.parse("package:" + packageName)); break; case ERROR_GETTING_STATE: case IRRELEVANT_OLD_ANDROID_API: default: break; } return intent; } /** * registers a receiver to listen to power-save events. returns true iff succeeded to register the broadcastReceiver. */ @TargetApi(VERSION_CODES.M) public static boolean registerPowerSaveReceiver(@NonNull Context context, @NonNull BroadcastReceiver receiver) { if (VERSION.SDK_INT < VERSION_CODES.M) return false; IntentFilter filter = new IntentFilter(); filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); context.registerReceiver(receiver, filter); return true; } } 

我想我也find了一种方法来连接到设备时检查出来:

省电:

 ./adb shell settings put global low_power [1|0] 

打盹状态:

 ./adb shell dumpsys deviceidle step [light|deep] 

和:

 ./adb shell dumpsys deviceidle force-idle 

问题

简而言之,我只是想知道,如果不能访问互联网的原因,确实是因为没有互联网连接,或者应用程序由于某些电池优化而受到限制。

只有在被限制的情况下,我可以提醒用户,如果他可以,应用程序将被列入白名单,以便它仍然可以工作。

这是我的问题:

  1. 以上哪项阻止了应用程序访问互联网的后台服务? 所有这些都造成了吗? 它是设备特定的吗? “互动”是否会影响它?

  2. 如果已经有办法去“轻”和“深”打瞌睡状态,什么是“力量闲置”? 还有一种方法可以将睡眠模式重置为正常吗? 我尝试了多个命令,但只有重新启动的设备才真正恢复到正常状态。

  3. 我创build的BroadcastReceiver是否允许正确检查它? 在所有情况下,是否会触发互联网因所有特殊情况而被拒绝? 我是否真的无法在清单上注册?

  4. 是否有可能检查是否无法访问Internet的原因确实是因为没有Internet连接,或者应用程序由于某些电池优化而受到限制?

  5. 在Android O上更改特殊情况下,是否有后台服务的Internet连接限制? 也许更多的情况下,我应该检查?

  6. 假设我改变服务在前台运行(带有通知),这是否覆盖了所有的情况,并且无论设备处于什么特殊状态,都可以访问互联网?


编辑:它似乎根本不是服务的错误,而且它也发生在节电模式,没有打盹模式。

该服务的触发器是一个侦听电话事件的BroadcastReceiver,即使我在onReceive函数上检查Internet连接,我也发现它返回false。 即使是前台服务,也是从它开始的服务。 查看NetworkInfo结果,它被“阻塞”,其状态确实是“断开”。

现在的问题是,为什么发生这种情况

这是一个新的示例POC来检查这一点。 要重现,您需要打开节电模式(使用./adb shell settings put global low_power 1命令,或作为用户),然后启动它,接受权限,closures活动,并从另一个电话呼叫到这个。 你会注意到在这个活动中,它显示有互联网连接,在BroadcastReceiver上,它说没有。

请注意,连接到USB电缆时,电池省电模式可能会自动closures,所以您可能需要在设备未连接时尝试。 使用adb命令阻止它,而不是启用它的用户方法。

示例项目也可以在这里find,即使它最初是为了打盹模式。 只需使用电池省电模式,就可以看到问题出现。

PhoneBroadcastReceiver

 public class PhoneBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(final Context context, final Intent intent) { Log.d("AppLog", "PhoneBroadcastReceiver:isInternetOn:" + isInternetOn(context)); } public static boolean isInternetOn(Context context) { final NetworkInfo info = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo(); return !(info == null || !info.isConnectedOrConnecting()); } } 

performance

 <manifest package="com.example.user.myapplication" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <receiver android:name=".PhoneBroadcastReceiver"> <intent-filter > <action android:name="android.intent.action.PHONE_STATE"/> </intent-filter> <intent-filter> <action android:name="android.intent.action.NEW_OUTGOING_CALL"/> </intent-filter> </receiver> </application> </manifest> 

MainActivity.java

 public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("AppLog", "MainActivity: isInternetOn:" + PhoneBroadcastReceiver.isInternetOn(this)); if (VERSION.SDK_INT >= VERSION_CODES.M) { requestPermissions(new String[]{permission.READ_PHONE_STATE, permission.PROCESS_OUTGOING_CALLS}, 1); } } } 

Solutions Collecting From Web of "省电+电话 – 意图=>没有互联网?"

所以我从问题跟踪器下载了您的示例应用程序,按照您所描述的方式对其进行testing,并在运行Android 6.0.1的Nexus 5上find这些结果:


testing条件1:

  • 应用没有列入白名单
  • 使用adb shell settings put global low_power 1电池保护模式设置为adb shell settings put global low_power 1
  • 使用adb tcpip <port>adb connect <ip>:<port>通过无线连接设备
  • BroadcastReceiver只,没有服务

在这个testing中,app的function就像你刚刚提到的那样:

App在背景中 –

 D/AppLog: PhoneBroadcastReceiver:isInternetOn:false D/AppLog: PhoneBroadcastReceiver:isInternetOn:false 

testing条件2:

  • 与testing1相同,更改如下
  • BroadcastReceiver启动服务(下面的例子)

     public class PhoneService extends Service { public void onCreate() { super.onCreate(); startForeground(1, new Notification.Builder(this) .setSmallIcon(R.mipmap.ic_launcher_foreground) .setContentTitle("Test title") .setContentText("Test text") .getNotification()); } public int onStartCommand(Intent intent, int flags, int startId) { final String msg = "PhoneService:isInternetOn:" + isInternetOn(this); Log.d("AppLog", msg); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); return START_STICKY; } public static boolean isInternetOn(Context context) { final NetworkInfo info = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo(); return !(info == null || !info.isConnectedOrConnecting()); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } } 

这个testing给了我和上面相同的结果。


testing条件3:

  • 与testing2相同,但下面有更改
  • 应用程序已通过电池优化列入白名单

App在背景中 –

 D/AppLog: PhoneService:isInternetOn:false D/AppLog: PhoneService:isInternetOn:true 

这个testing很有意思,第一个日志没有给我连接互联网,但是第二个日志是在第一个日志之后大约4秒钟,并且在它build立了前台服务之后。 当我第二次运行testing时,两个日志都是真实的。 这似乎表示被调用的startForeground函数和将应用程序放到前台的系统之间的延迟。


我甚至通过使用adb shell dumpsys deviceidle force-idle运行testing2和3,并且得到与testing3类似的结果,其中第一个日志未连接,但所有后续日志显示互联网连接。

我相信这一切都是按照预期的function,因为设备上的电池保护程序指出:

为帮助延长电池续航时间,电池保护程序会降低设备性能并限制振动,位置服务和大多数背景数据。 电子邮件,消息传递和其他依赖于同步的应用程序可能不会更新,除非您打开它们。

因此,除非您正在使用应用程序,或者您将应用程序列入白名单运行前台服务,否则如果处于电池保护程序或打盹模式,则应用程序无法使用互联网连接。


编辑#1

这可能会作为一个不同的解决方法与使用一些计时器来重新检查Internet连接:

MyService.java

 @Override public void onCreate() { super.onCreate(); Log.d("AppLog", "MyService:onCreate isInternetOn:" + PhoneBroadcastReceiver.isInternetOn(this)); if (!PhoneBroadcastReceiver.isInternetOn(this)) { if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { final ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(CONNECTIVITY_SERVICE); connectivityManager.registerNetworkCallback(new NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build(), new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { //Use this network object to perform network operations connectivityManager.unregisterNetworkCallback(this); } }); } } } 

如果有可用的networking连接,这将立即返回,或者等到有连接。 如果在一段时间之后你还没有收到任何结果,那么从这里你可能只需要使用一个Handler来取消注册,尽pipe我可能只是将它保持活动状态。


编辑#2

所以这就是我所推荐的。 这是基于你在早些时候的评论( Android检查互联网连接 )的答案:

 @Override public void onCreate() { super.onCreate(); Log.d("AppLog", "MyService:onCreate isInternetOn:" + PhoneBroadcastReceiver.isInternetOn(this)); if (!PhoneBroadcastReceiver.isInternetOn(this)) { if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { final ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(CONNECTIVITY_SERVICE); connectivityManager.registerNetworkCallback(new NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build(), new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { isConnected(network); //Probably add this to a log output to verify this actually works for you connectivityManager.unregisterNetworkCallback(this); } }); } } } public static boolean isConnected(Network network) { if (network != null) { try { URL url = new URL("http://www.google.com/"); HttpURLConnection urlc = (HttpURLConnection)network.openConnection(url); urlc.setRequestProperty("User-Agent", "test"); urlc.setRequestProperty("Connection", "close"); urlc.setConnectTimeout(1000); // mTimeout is in seconds urlc.connect(); if (urlc.getResponseCode() == 200) { return true; } else { return false; } } catch (IOException e) { Log.i("warning", "Error checking internet connection", e); return false; } } return false; } 

Google推荐的处理方法是使用JobScheduler或类似的库(例如Firebase JobDispatcher )在“维护时段”之一有networking时安排作业。 有关更多详细信息,请参阅Optimize for Doze和App Standby 。 如果您确实需要从维护窗口中执行服务,则应将信息存储在磁盘(数据库,文件…)中并定期进行同步,或者在极端情况下将请求列入白名单。

有了这个,让我们来回答你的问题。

以上哪项阻止了应用程序访问互联网的后台服务? 所有这些都造成了吗? 它是设备特定的吗? “互动”是否会影响它?

打盹模式肯定。 省电模式,它表示“限制…大部分背景数据”,但我认为这只是应用程序的一个build议。 看到这里 。

具体来说,注意:

RESTRICT_BACKGROUND_STATUS_ENABLED用户已启用此应用程序的数据保护程序。 应用程序应尽力限制前台的数据使用情况,并妥善处理对后台数据使用的限制。

所以,它看起来像一个build议,而不是强制执行的东西。

此外,请注意,一些手机制造商有节电应用程序,可能会施加额外的限制,并杀死应用程序,以节省电池。 请参阅以下答案和讨论 。

如果已经有办法去“轻”和“深”打瞌睡状态,什么是“力量闲置”? 还有一种方法可以将睡眠模式重置为正常吗? 我尝试了多个命令,但只有重新启动的设备才真正恢复到正常状态。

除了您可能已阅读本文和本文 “testing”部分的文档之外,我没有其他经验可供分享。

我创build的BroadcastReceiver是否允许正确检查它? 在所有情况下,是否会触发互联网因所有特殊情况而被拒绝? 我是否真的无法在清单上注册?

不能完全确定你是否能够检查所有的情况,而应该尽可能地遵循“networking可用时同步”策略。

关于在清单中注册,如果您的应用程序的目标是Android Oreo, 那么您必须以编程方式注册大部分接收者 。

是否有可能检查是否无法访问Internet的原因确实是因为没有Internet连接,或者应用程序由于某些电池优化而受到限制?

你分享的代码看起来不错,但我不期望是100%确定的,因为有时多个条件可能同时发生。

在Android O上更改特殊情况下,是否有后台服务的Internet连接限制? 也许更多的情况下,我应该检查?

支票应该是一样的。 打盹模式会在比棉花糖更多的情况下触发,但对应用程序的影响应该是完全一样的。

假设我改变服务在前台运行(带有通知),这是否覆盖了所有的情况,并且无论设备处于什么特殊状态,都可以访问互联网?

正如我之前所说,在某些设备(电池保护程序应用程序)应用程序将被杀害,所以它可能无法正常工作。 在Android的股票,可能会boost一些限制,但我不能确认,因为我没有testing自己。