Примечание: как оказалось, исходный вопрос был неверным в своих предположениях. Подробнее о его изменениях внизу.
Теперь об аккумуляторах, а не об аккумуляторах и режиме доза. Это также не о Service & BroadcastReceiver, а только о BroadcastReceiver.
Фон
Начиная с Android Lollipop, Google представил новые, ручные и автоматические способы экономии заряда батареи:
Режим "Дозировка" и "Аккумулятор".
В некоторых случаях приложения могут не иметь доступа к Интернету из-за этих методов.
Проблема
Я работаю над приложением, которому необходимо получить доступ к Интернету с помощью фоновой службы, которая запускается в определенных случаях, и если что-то важное получает, он показывает некоторый интерфейс.
Я заметил, как пользователь, что в некоторых случаях он не имеет доступа к Интернету.
Проверка того, может ли приложение иметь доступ к Интернету, такова:
public static boolean isInternetOn(Context context) {
final NetworkInfo info = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
return !(info == null || !info.isConnectedOrConnecting());
}
Проблема заключается в том, что мне нужно проверить, почему это иногда возвращает false, поэтому, если это не удается, мы должны сообщить пользователю (через уведомление, возможно), что данные не могут быть доступны, поскольку устройство ограничило приложение и предложило пользователь в белом списке приложения от оптимизации батареи.
Я не уверен, какие из них влияют на это: Doze, battery saver или both, и если это всегда так, для всех устройств, во всех случаях.
Что я пробовал
То, что я нашел, - это запрос режима Doze и Battery-saver (энергосбережение):
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;
}
}
Я думаю, что я также нашел способ проверить их при подключении к устройству:
батарея:
./adb shell settings put global low_power [1|0]
Состояние доз:
./adb shell dumpsys deviceidle step [light|deep]
И:
./adb shell dumpsys deviceidle force-idle
Вопросы
Короче говоря, я просто хочу узнать, не является ли причиной невозможности доступа к Интернету, потому что нет подключения к Интернету или если приложение только что ограничено в настоящее время из-за определенных оптимизаций батареи.
Только в случае ограничения, я мог бы предупредить пользователя, что если он будет в порядке с ним, приложение будет белым, чтобы он мог работать по-прежнему.
Вот мои вопросы по этому поводу:
-
Какое из вышеперечисленных предупреждений позволяет использовать фоновые службы приложений для доступа в Интернет? Все ли они вызывают это? Является ли он специфичным для устройства? "Интерактивный" влияет на него?
-
Что означает "сила-простоя", если есть уже способ перехода в "легкие" и "глубокие" состояния доз? Есть ли способ вернуться в нормальный режим до reset? Я попробовал несколько команд, но только перезапуск устройства действительно получил его до reset обратно к нормальному...
-
Создает ли созданный BroadcastReceiver, чтобы проверить его правильно? Будет ли во всех случаях инициировать отказ в доступе к Интернету из-за всех особых случаев? Верно ли, что я не могу зарегистрироваться в манифесте?
-
Можно ли проверить, не является ли причиной невозможности доступа к Интернету, потому что нет подключения к Интернету или если приложение только что ограничено из-за определенных оптимизаций батареи?
-
Ограничены ли ограничения подключения к Интернету для фоновых служб в особых случаях на Android O? Может быть, еще больше случаев я должен проверить?
-
Предположим, что я изменяю службу для запуска на переднем плане (с уведомлением), будет ли это охватывать все случаи и всегда иметь доступ к Интернету, независимо от того, в каком специальном состоянии находится устройство?
EDIT: кажется, что это не служебная ошибка вообще, и что это происходит и в режиме экономии заряда батареи, без режима Doze.
Триггер службы - это BroadcastReceiver, который слушает события телефонных звонков, и даже если я проверяю подключение к Интернету в его функции onReceive
, я вижу, что он возвращает false. То же самое касается службы, которая запускается с нее, даже если она используется для переднего плана. Посмотрев на результат NetworkInfo, он "BLOCKED", и его состояние действительно "ОТКЛЮЧЕНО".
Вопрос, почему это происходит.
Здесь новый образец POC, чтобы проверить это. Чтобы воспроизвести, вам необходимо включить режим экономии заряда батареи (используя команду ./adb shell settings put global low_power 1
или как пользователь), затем запустите ее, примите разрешения, закрыть активность и позвонить с другого телефона на этот. Вы заметите, что в действии он показывает, что есть подключение к Интернету, и на BroadcastReceiver он говорит, что это не так.
Обратите внимание, что при подключении к USB-кабелю автоматически отключается режим экономии заряда батареи, поэтому вам может потребоваться попробовать, когда устройство не подключено. Использование команды adb предотвращает ее, в отличие от пользовательского метода ее включения.
Образец проекта также можно найти здесь, хотя первоначально он предназначался для режима Doze. Вместо этого используйте режим экономии батареи, чтобы увидеть, что проблема возникает.
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());
}
}
манифеста
<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);
}
}
}