java - Geofencing: error en la solicitud HTTP al enviar a través del servicio en segundo plano. Da UnknownException
android android-intentservice (5)
Cuando la aplicación entra en modo de fondo, debe activar la aplicación de vez en cuando para oler la posición del teléfono móvil. Naturalmente, cuanto más a menudo olfatearía, más rápido y confiable sería capaz de detectar el geofence. https://proximi.io/will-my-geofencing-function-in-the-background
Implementé Geofence en la aplicación de Android. Seguí this enlace para implementar ''Geofence'' en la aplicación. Estoy utilizando la biblioteca ''Retrofit'' para llamar a la solicitud ''HTTP''.
La aplicación tiene los siguientes permisos:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Aquí está mi código ''IntentService'':
public class GeofenceService extends IntentService
{
private static final String TAG = GeofenceService.class.getName();
public static final int GEOFENCE_NOTIFICATION_ID = 0;
public GeofenceService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
// Retrieve the Geofencing intent
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
createLoggerFile();
// Handling errors
if ( geofencingEvent.hasError() ) {
String errorMsg = getErrorString(geofencingEvent.getErrorCode() );
Logger.Important(true, TAG, "onHandleIntent() :: errorMessage : "+errorMsg );
return;
}
// Retrieve GeofenceTrasition
int geoFenceTransition = geofencingEvent.getGeofenceTransition();
// Check if the transition type
if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT ||
geoFenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL)
{
Log.d(TAG, "onHandleIntent() :: geoFenceTransition : " + geoFenceTransition);
// Get the geofence that were triggered
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// Create a detail message with Geofences received
String geofenceTransitionDetails = getGeofenceTrasitionDetails(geoFenceTransition, triggeringGeofences );
// Send notification details as a String
sendNotification( geofenceTransitionDetails );
}
}
// Create a detail message with Geofences received
private String getGeofenceTrasitionDetails(int geoFenceTransition, List<Geofence> triggeringGeofences) {
// get the ID of each geofence triggered
ArrayList<String> triggeringGeofencesList = new ArrayList<>();
for ( Geofence geofence : triggeringGeofences ) {
triggeringGeofencesList.add( geofence.getRequestId() );
pingGoogle(); // here is I am pinging google
callingHttpRequest(); // calling Http request. Also I called this request through application class, but still it is not worked in background.
}
String status = null;
if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER )
status = "Entering ";
else if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT )
status = "Exiting ";
else if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL )
status = "Staying ";
return status + TextUtils.join( ", ", triggeringGeofencesList);
}
// Send a notification
private void sendNotification( String msg ) {
Log.d( TAG, "sendNotification: " + msg );
// Intent to start the main Activity
Intent notificationIntent = new Intent(getApplicationContext(), DrawerActivity.class);;
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(DrawerActivity.class);
stackBuilder.addNextIntent(notificationIntent);
PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Creating and sending Notification
NotificationManager notificatioMng =
(NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
notificatioMng.notify(
GEOFENCE_NOTIFICATION_ID,
createNotification(msg, notificationPendingIntent));
}
// Create a notification
private Notification createNotification(String msg, PendingIntent notificationPendingIntent) {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this);
notificationBuilder
.setSmallIcon(R.drawable.ic_phi_notification_logo)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.geo))
.setColor(Converter.getColor(getApplicationContext(), R.color.default_pure_cyan))
.setContentTitle(JsonKey.TRIGGER)
.setContentText(msg)
.setContentIntent(notificationPendingIntent)
.setDefaults(Notification.DEFAULT_LIGHTS | Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND)
.setAutoCancel(true);
return notificationBuilder.build();
}
// Handle errors
private static String getErrorString(int errorCode) {
switch (errorCode) {
case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE:
return "GeoFence not available";
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES:
return "Too many GeoFences";
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS:
return "Too many pending intents";
default:
return "Unknown error.";
}
}
private void callingHttpRequest() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10 / 2, TimeUnit.SECONDS)
.sslSocketFactory(sslSocketFactory().getSocketFactory())
.build();
Gson gson = new GsonBuilder()
.setLenient()
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
API api = retrofit.create(***.class);
Call<ResponseBody> req = api.callGeofencingTrigger(***);
req.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
try {
String string = response.body().string();
Log.d (TAG, "onResponse() :: success");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
Log.d (TAG, "onFailure() :: t : "t.getMessage());
}
});
}
}
Cada vez que el dispositivo tiene un activador de geofence, funciona bien y proporciona las notificaciones de activación adecuadas mientras la aplicación está en segundo plano o en primer plano (ingresar / detener / dejar) o incluso si el usuario mata la aplicación en tareas recientes. Cuando llamo a la solicitud HTTP, cuando la aplicación está en primer plano, funciona bien y se imprime correctamente en el registro.
onResponse() :: success
Pero cuando la aplicación es eliminada por tareas recientes y el dispositivo tiene algún activador de geofence (ingresar / detener / dejar), entonces la solicitud HTTP no se ejecuta correctamente. Da :
onFailure() :: t :
</br>java.net.UnknownHostException: Unable to resolve host
"host_name": No address associated with hostname
donde nombre_host es la dirección del servidor.
Hago ping a google o 8.8.8.8 ip desde el servicio en segundo plano. Sigo enfrentando los mismos problemas. Esto también funciona bien cuando la aplicación está en primer plano, pero después de eliminarla no funciona.
Entonces, ¿por qué este error? ¿La comunicación de red no está llamando cuando la aplicación no está en tareas recientes?
<------------------------------------------------- -------------------------------------------------- ----------------------->
Traté de seguir las cosas. Después de obtener respuestas de @Xavier y @Stevensen
Estoy usando firebase-jobscheduler en mi aplicación para llamar a HTTP
solicitud HTTP
. Aquí está mi código:
En mi manifiesto agregué el siguiente servicio:
<service
android:exported="false"
android:name="com.****.service.TriggerJobService">
<intent-filter>
<action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
</intent-filter>
</service>
Esta es mi clase GeofenceService modificada. Acabo de eliminar callingHttpRequest()
y agregué el trabajo de scheduleJob()
llamando a la función scheduleJob()
en la función getGeofenceTrasitionDetails()
. Y el código es el mismo que es.
public class GeofenceService extends IntentService
{
private static final String TAG = GeofenceService.class.getName();
public static final int GEOFENCE_NOTIFICATION_ID = 0;
public GeofenceService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
// Retrieve the Geofencing intent
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
createLoggerFile();
// Handling errors
if ( geofencingEvent.hasError() ) {
String errorMsg = getErrorString(geofencingEvent.getErrorCode() );
Logger.Important(true, TAG, "onHandleIntent() :: errorMessage : "+errorMsg );
return;
}
// Retrieve GeofenceTrasition
int geoFenceTransition = geofencingEvent.getGeofenceTransition();
// Check if the transition type
if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT ||
geoFenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL)
{
Log.d(TAG, "onHandleIntent() :: geoFenceTransition : " + geoFenceTransition);
// Get the geofence that were triggered
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// Create a detail message with Geofences received
String geofenceTransitionDetails = getGeofenceTrasitionDetails(geoFenceTransition, triggeringGeofences );
// Send notification details as a String
sendNotification( geofenceTransitionDetails );
}
}
// Create a detail message with Geofences received
private String getGeofenceTrasitionDetails(int geoFenceTransition, List<Geofence> triggeringGeofences) {
// get the ID of each geofence triggered
ArrayList<String> triggeringGeofencesList = new ArrayList<>();
for ( Geofence geofence : triggeringGeofences ) {
triggeringGeofencesList.add( geofence.getRequestId() );
scheduleJob(); // <code>**Here I schedule job**</code>
}
String status = null;
if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER )
status = "Entering ";
else if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT )
status = "Exiting ";
else if ( geoFenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL )
status = "Staying ";
return status + TextUtils.join( ", ", triggeringGeofencesList);
}
// Send a notification
private void sendNotification( String msg ) {
Log.d( TAG, "sendNotification: " + msg );
// Intent to start the main Activity
Intent notificationIntent = new Intent(getApplicationContext(), DrawerActivity.class);;
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(DrawerActivity.class);
stackBuilder.addNextIntent(notificationIntent);
PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Creating and sending Notification
NotificationManager notificatioMng =
(NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
notificatioMng.notify(
GEOFENCE_NOTIFICATION_ID,
createNotification(msg, notificationPendingIntent));
}
// Create a notification
private Notification createNotification(String msg, PendingIntent notificationPendingIntent) {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this);
notificationBuilder
.setSmallIcon(R.drawable.ic_phi_notification_logo)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.geo))
.setColor(Converter.getColor(getApplicationContext(), R.color.default_pure_cyan))
.setContentTitle(JsonKey.TRIGGER)
.setContentText(msg)
.setContentIntent(notificationPendingIntent)
.setDefaults(Notification.DEFAULT_LIGHTS | Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND)
.setAutoCancel(true);
return notificationBuilder.build();
}
// Handle errors
private static String getErrorString(int errorCode) {
switch (errorCode) {
case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE:
return "GeoFence not available";
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES:
return "Too many GeoFences";
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS:
return "Too many pending intents";
default:
return "Unknown error.";
}
}
private void scheduleJob()
{
Bundle bundle = new Bundle();
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(getApplicationContext()));
Job.Builder builder = dispatcher.newJobBuilder();
builder.setExtras(bundle);
builder.setTag(requestId);
builder.setService(TriggerJobService.class);
builder.setTrigger(Trigger.executionWindow(10, 30));
builder.setReplaceCurrent(true);
builder.addConstraint(Constraint.DEVICE_CHARGING);
builder.addConstraint(Constraint.ON_ANY_NETWORK);
builder.addConstraint(Constraint.ON_UNMETERED_NETWORK);
dispatcher.mustSchedule(builder.build());
}
}
Este es el código de mi TriggerJobService:
public class TriggerJobService extends JobService
{
private static final String TAG = TriggerJobService.class.getName();
private int count;
@Override
public boolean onStartJob(JobParameters job)
{
Log.d(TAG, "onStartJob() :: " + job.getTag());
// Return true as there''s more work to be done with this job.
//TODO have to send request to cloud
Bundle bundle = job.getExtras();
callingHttpRequest(); // here is I am calling ''HTTP'' request
return true;
}
@Override
public boolean onStopJob(JobParameters job)
{
Log.d(TAG, "onStopJob() :: " + job.getTag());
// Return false to drop the job.
return false;
}
private void callingHttpRequest() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10 / 2, TimeUnit.SECONDS)
.sslSocketFactory(sslSocketFactory().getSocketFactory())
.build();
Gson gson = new GsonBuilder()
.setLenient()
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
API api = retrofit.create(***.class);
Call<ResponseBody> req = api.callGeofencingTrigger(***);
req.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
try {
String string = response.body().string();
Log.d (TAG, "onResponse() :: success");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
Log.d (TAG, "onFailure() :: t : "t.getMessage());
}
});
}
}
Nuevamente está llamando lo mismo. Funciona bien y da las notificaciones de activación adecuadas cuando la aplicación está en segundo plano o en primer plano (ingresar / detener / dejar) o incluso si el usuario mata la aplicación en tareas recientes. También está programando el trabajo adecuado. Y llamando a HTTP
solicitud HTTP
, cuando la aplicación está en primer plano, funciona bien y se imprime correctamente en el registro.
onResponse() :: success
Pero cuando la aplicación es eliminada por tareas recientes y el dispositivo tiene algún activador de geofence (ingresar / detener / dejar), el trabajo de programación de aplicaciones y HTTP
solicitud HTTP
llamada no se ejecutan correctamente. Da :
onFailure() :: t :
</br>java.net.UnknownHostException: Unable to resolve host
"host_name": No address associated with hostname
Así que según @Xavier y @Stevensen responde que mi aplicación no es una red de reactivación si mata de tareas recientes. Intenté con firbase-JobSchedule
pero aún enfrentaba el mismo error anterior. ¿La aplicación necesita algún permission
especial para llamar a HTTP
solicitud HTTP
mientras que la aplicación mata de tareas recientes? O es FCM
hay mejores opciones para eso. ¿Pero aún tiene la misma pregunta de si el FCM
funcionará incluso si la aplicación mata en tareas recientes? ¿ FCM
activará la red para enviar un mensaje al servidor desde el cliente?
Finalmente mi problema se resolvió. Gracias a @Stevensen, @Xavier y uno de mi amigo que me ayuda a identificar el problema. Está relacionado con el modo de dormitar.
Algunos fabricantes de móviles (Xiomi, Huawei, etc.) implementaron SmartManager para optimizar el consumo de la batería. Tienen un tipo de administrador de batería que destruye las aplicaciones, y cuando se elimina una aplicación, se cancelan las alarmas programadas y tampoco detectaron ninguna red activa o bloquea las llamadas de red desde el servicio en segundo plano. Porque el fabricante culpa a las aplicaciones no confiables del consumo de energía. Las aplicaciones de Facebook, Whats App, etc. son de confianza y están incluidas en la lista blanca de fabricantes. Es por eso que pueden llamar al evento de la red incluso si la aplicación se cancela.
Todavía no encontré ninguna solución para eso. Por lo tanto, temporalmente, superé este problema para los dispositivos Xiomi. Mantengo mi aplicación fuera de la restricción de ahorro de batería que funciona correctamente haciendo lo siguiente:
settings--> battery -> Power --> App battery saver --> your app
Now select No restrictions( for Background settings) then Allow option for Background location
Para la versión de Android M y superior, la aplicación tiene que pedir permiso:
Intent intent = new Intent();
String packageName = context.getPackageName();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm.isIgnoringBatteryOptimizations(packageName))
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
else {
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
}
context.startActivity(intent);
y en manifiesto:
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
Después de que el usuario puede hacer una lista blanca de su aplicación.
La explicación de @Stevensen acerca de que el modo Doze es la causa del fallo es más probable que sea la causa. En la documentation puedes leer:
Las siguientes restricciones se aplican a sus aplicaciones mientras está en Doze: el acceso a la red está suspendido ...
Le sugeriré que almacene los eventos en una base de datos y programe un trabajo para cargarlos en el servidor mediante el uso de JobScheduler (API 21+, consulte un tutorial aquí ) o, si necesita admitir dispositivos más antiguos, mediante el uso de esta base de firebase-jobscheduler reemplazo firebase-jobscheduler (que proporciona una API compatible con JobScheduler envolviendo GCM Network Manager).
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
que se establezca una condición de red: .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
y, probablemente, un .setPeriodic(long intervalMillis)
para limitar el número de veces que ocurre (por ejemplo, cargarlo como máximo una vez por hora).
Mientras no se requiera tiempo real, la experiencia del usuario es un mejor enfoque para ahorrar batería: el modo Doze ayudará al dispositivo a ahorrar batería, y JobScheduler
permitirá cargar por lotes y simplemente reactivar la radio de vez en cuando, ahorrando duración de la batería. Vea este video rápido para la razón de ser.
Tal vez su aplicación se bloquee para usar la red mediante el modo de dormido de Android y / o la aplicación en espera. Compruebe la developer.android.com/training/monitoring-device-state/… .
Una posible solución es configurar una alarma con el AlarmManager . Android programará el procesamiento de las alarmas en una ventana de mantenimiento en la que se le permite usar la red.
Tuve el mismo problema: "java.net.UnknownHostException: no se puede resolver el host" nombre_host ": no hay una dirección asociada con el nombre de host". Además, Internet estaba disponible con todos los permisos de Android otorgados (probados todos incluso en tiempo de ejecución).
Pero la solución era diferente. El motivo fue que nuestro API-host "nombre_host" (por ejemplo, http: //xxx.yyyy.zz ) estaba en LAN y no podía hacerlo desde la red externa. Se puede detectar el mismo problema si llama al "nombre_host" externo de su compañía desde la LAN de la compañía (parece ser un "ataque de Rebindido de DNS" para el servidor). Para probarlo, debe intentar abrir la URL utilizada en un navegador cuando el dispositivo está (y cuando no lo está) conectado a Internet desde una LAN local (por ejemplo, la conexión Wi-Fi de la empresa) y verificar si la respuesta del servidor es correcta.
El problema de @Mangesh Sambare se resolvió como dijo anteriormente, pero tal vez esta experiencia sea útil para alguien que se encuentre en la misma situación que yo.