reserva - mejor aplicacion para ahorrar bateria android 2017
Ahorro de batería+intención de llamada telefónica=> ¿no hay Internet? (2)
Nota: como resultado, la pregunta original era incorrecta en sus suposiciones. Ver más detalles sobre sus ediciones en la parte inferior.
Ahora se trata del ahorro de batería, y no del ahorro de batería y el modo de dormitar. Tampoco se trata de Service & BroadcastReceiver, sino solo de BroadcastReceiver.
Fondo
A partir de Android Lollipop, Google introdujo formas nuevas, manuales y automáticas de ayudar con el ahorro de batería:
Modo "Doze" y "Ahorro de batería".
En algunos casos, es posible que las aplicaciones no puedan acceder a Internet debido a esas técnicas.
El problema
Trabajo en una aplicación que necesita acceder a Internet utilizando un servicio en segundo plano que se activa en casos específicos, y si se recibe algo importante, muestra alguna IU.
He notado, como usuario, que en algunos casos no puede acceder a Internet.
La comprobación de si la aplicación puede acceder a Internet es como tal:
public static boolean isInternetOn(Context context) {
final NetworkInfo info = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
return !(info == null || !info.isConnectedOrConnecting());
}
El problema es que debo verificar por qué esto a veces devuelve falso, de modo que si falla, debemos informarle al usuario (probablemente mediante notificación) que no se puede acceder a los datos porque el dispositivo restringió la aplicación y ofrecerle al usuario Lista blanca de la aplicación de optimización de la batería.
No estoy seguro de cuáles afectan esto: dormita, ahorrador de batería, o ambos, y si es así, para todos los dispositivos, en todos los casos.
Lo que he intentado
Lo que encontré es cómo consultar el modo Doze y el modo de ahorro de batería (ahorro de energía):
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;
}
}
Creo que también encontré una forma de revisarlos mientras estoy conectado al dispositivo:
ahorro de batería:
./adb shell settings put global low_power [1|0]
Estado de dormitar
./adb shell dumpsys deviceidle step [light|deep]
Y
./adb shell dumpsys deviceidle force-idle
Las preguntas
En resumen, solo quiero saber si la razón para no poder acceder a Internet se debe a que no hay conexión a Internet o si la aplicación se restringió actualmente debido a ciertas optimizaciones de la batería.
Solo en el caso de estar restringido, podría advertir al usuario que si está de acuerdo con él, la aplicación se incluiría en la lista blanca para que aún pueda funcionar de la misma manera.
Aquí están mis preguntas al respecto:
¿Cuál de los anteriores impide que los servicios en segundo plano de las aplicaciones accedan a Internet? ¿Todos ellos lo causan? ¿Es específico del dispositivo? ¿Lo "interactivo" lo afecta?
¿Para qué sirve la "fuerza inactiva", si ya hay una forma de ir a los estados "livianos" y "profundos"? ¿Hay también una manera de restablecer el modo de reposo a la normalidad? Intenté varios comandos, pero solo reiniciando el dispositivo conseguí que se restableciera a la normalidad ...
¿El BroadcastReceiver que creé permite verificarlo correctamente? ¿Se activará en todos los casos que se niegue el acceso a Internet debido a todos los casos especiales? ¿Es verdad que no puedo registrarme en el manifiesto?
¿Es posible verificar si la razón para no poder acceder a Internet se debe a que no hay conexión a Internet o si la aplicación se restringió actualmente debido a ciertas optimizaciones de la batería?
¿Se han modificado las restricciones de conexión a Internet para servicios en segundo plano en casos especiales en Android O? Tal vez incluso más casos debería comprobar?
Supongamos que cambio el servicio para que se ejecute en primer plano (con una notificación), ¿cubrirá esto todos los casos y siempre tendré acceso a Internet, sin importar en qué estado especial se encuentre el dispositivo?
EDITAR: parece que no es culpa del servicio en absoluto, y que también ocurre en el modo de ahorro de batería, sin el modo Doze.
El desencadenante del servicio es un BroadcastReceiver que escucha eventos de llamadas telefónicas, e incluso si compruebo la conexión a Internet en su función onReceive
, veo que devuelve false. Lo mismo ocurre con el servicio que se inicia a partir de él, incluso si se trata de un servicio en primer plano. Mirando el resultado de NetworkInfo, está "BLOQUEADO", y su estado es de hecho "DESCONECTADO".
Pregunta ahora, es por qué ocurre esto.
Aquí hay una nueva muestra de POC para verificar esto. Para reproducir, debe activar el modo de ahorro de batería (mediante la ./adb shell settings put global low_power 1
, o como usuario), luego iniciarlo, aceptar los permisos, cerrar la actividad y llamar desde otro teléfono a este. Notará que en la actividad, muestra que hay conexión a Internet, y en el BroadcastReceiver, dice que no.
Tenga en cuenta que el modo de ahorro de batería podría desactivarse automáticamente al conectarse al cable USB, por lo que es posible que deba intentarlo cuando el dispositivo no esté conectado. El uso del comando adb lo impide, a diferencia del método de usuario de habilitarlo.
El proyecto de muestra también se puede encontrar here , aunque originalmente estaba destinado a ser sobre el modo Doze. Solo use el modo de ahorro de batería para ver si ocurre el problema.
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());
}
}
manifiesto
<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);
}
}
}
Así que descargué su aplicación de ejemplo del here , la probé de la manera que lo describió y encontré estos resultados en un Nexus 5 con Android 6.0.1:
Condiciones para la prueba 1:
- Aplicación no incluida en la lista blanca
- El modo de ahorro de batería configurado mediante la
adb shell settings put global low_power 1
- Dispositivo conectado vía inalámbrica usando
adb tcpip <port>
yadb connect <ip>:<port>
- BroadcastReceiver solamente, sin servicio
En esta prueba, la aplicación funcionó como usted mencionó:
Aplicación en el fondo -
D/AppLog: PhoneBroadcastReceiver:isInternetOn:false
D/AppLog: PhoneBroadcastReceiver:isInternetOn:false
Condiciones para la prueba 2:
- Igual que la prueba 1 con cambios a continuación
BroadcastReceiver inicia el servicio (ejemplo a continuación)
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; } }
Esta prueba me dio los mismos resultados que arriba.
Condiciones para la prueba 3:
- Igual que la prueba 2 con cambios a continuación
- La aplicación está en la lista blanca de optimizaciones de la batería
Aplicación en el fondo -
D/AppLog: PhoneService:isInternetOn:false
D/AppLog: PhoneService:isInternetOn:true
Esta prueba fue interesante porque el primer registro no me dio conexión a Internet, pero el segundo registro lo hizo, que fue aproximadamente 4 segundos después del primero, y mucho después de que estableciera el servicio en primer plano. Cuando ejecuté la prueba la segunda vez, ambos registros eran verdaderos. Esto parece indicar un retraso entre la función startForeground
que se llama y el sistema que pone la aplicación en primer plano.
Incluso ejecuté las pruebas 2 y 3 utilizando adb shell dumpsys deviceidle force-idle
, y obtuve resultados similares a la prueba 3, donde el primer registro no estaba conectado, pero todos los registros posteriores mostraban conexión a Internet.
Creo que todo esto funciona según lo previsto, ya que el ahorro de batería en el dispositivo dice:
Para ayudar a mejorar la vida útil de la batería, el ahorro de batería reduce el rendimiento de su dispositivo y limita la vibración, los servicios de ubicación y la mayoría de los datos de fondo. Es posible que el correo electrónico, los mensajes y otras aplicaciones que dependen de la sincronización no se actualicen a menos que los abra.
Por lo tanto, a menos que esté usando la aplicación actualmente, o haya incluido la aplicación en la lista blanca y tenga un servicio en primer plano en funcionamiento, puede esperar que no haya conexión a Internet disponible para su uso si está en modo de ahorro de batería o Doze.
Editar # 1
Esto puede funcionar como una solución alternativa diferente al usar algún temporizador para volver a verificar la conexión a 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);
}
});
}
}
}
Esto volverá inmediatamente si hay una conexión de red que puede usar, o esperar hasta que haya una conexión. Desde aquí, es probable que solo puedas usar un Handler
para anular el registro si no recibes ningún resultado después de un tiempo, aunque probablemente lo deje activo.
Editar # 2
Así que esto es lo que estaba recomendando. Esto se basa en la respuesta que dio en los comentarios anteriores ( conexión a Internet con 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;
}
La forma recomendada por Google para manejar esto es usar JobScheduler , o una biblioteca similar (por ejemplo, Firebase JobDispatcher ), para programar el trabajo cuando hay red durante una de las "ventanas de mantenimiento". Consulte Optimización para Doze y App Standby para obtener detalles adicionales. Si realmente necesita ejecutar su servicio desde esta ventana de mantenimiento, debe almacenar la información en el disco (DB, archivos ...) y sincronizarla periódicamente, o en una solicitud de caso extremo para que se incluya en la lista blanca.
Con eso fuera del camino, vamos por tus preguntas.
¿Cuál de los anteriores impide que los servicios en segundo plano de las aplicaciones accedan a Internet? ¿Todos ellos lo causan? ¿Es específico del dispositivo? ¿Lo "interactivo" lo afecta?
El modo de dormitar seguro. Modo de ahorro de batería, indica que "Limita ... la mayoría de los datos de fondo", pero creo que es solo una recomendación para las aplicaciones. Ver here
Específicamente, aviso:
RESTRICT_BACKGROUND_STATUS_ENABLED El usuario ha habilitado Data Saver para esta aplicación. Las aplicaciones deben hacer un esfuerzo para limitar el uso de datos en primer plano y manejar con elegancia las restricciones al uso de datos en segundo plano.
Entonces, parece un consejo, no algo que se hace cumplir.
Además, tenga en cuenta que algunos fabricantes de teléfonos tienen aplicaciones de ahorro de batería que pueden imponer restricciones adicionales y eliminar aplicaciones para ahorrar batería. Vea la siguiente respuesta y discusión .
¿Para qué sirve la "fuerza inactiva", si ya hay una forma de ir a los estados "livianos" y "profundos"? ¿Hay también una manera de restablecer el modo de reposo a la normalidad? Intenté varios comandos, pero solo reiniciando el dispositivo conseguí que se restableciera a la normalidad ...
No tengo experiencia adicional para compartir, aparte de la documentación que probablemente ya haya leído en las secciones "Pruebas" de este y here artículos.
¿El BroadcastReceiver que creé permite verificarlo correctamente? ¿Se activará en todos los casos que se niegue el acceso a Internet debido a todos los casos especiales? ¿Es verdad que no puedo registrarme en el manifiesto?
No estoy completamente seguro de si podrá verificar todos los casos, pero en su lugar debe intentar seguir la estrategia de "sincronizar cuando la red esté disponible" si es posible.
Acerca de registrarse en el manifiesto, si su aplicación está dirigida a Android Oreo, sí, debe registrar la mayoría de los receptores mediante programación .
¿Es posible verificar si la razón para no poder acceder a Internet se debe a que no hay conexión a Internet o si la aplicación se restringió actualmente debido a ciertas optimizaciones de la batería?
El código que compartiste se ve bien, pero no espero estar 100% seguro, ya que a veces pueden ocurrir múltiples condiciones al mismo tiempo.
¿Se han modificado las restricciones de conexión a Internet para servicios en segundo plano en casos especiales en Android O? Tal vez incluso más casos debería comprobar?
Los cheques deben ser los mismos. El modo Doze se activará en más casos que en Marshmallow, pero los efectos en la aplicación deberían ser exactamente los mismos.
Supongamos que cambio el servicio para que se ejecute en primer plano (con una notificación), ¿cubrirá esto todos los casos y siempre tendré acceso a Internet, sin importar en qué estado especial se encuentre el dispositivo?
Como dije antes, en algunos dispositivos (aplicaciones de ahorro de batería) la aplicación se eliminará, por lo que probablemente no funcionará. En stock Android, probablemente aumentarán algunas limitaciones, pero no puedo confirmar, porque no me he probado.