Подтвердить что ты не робот

Geofencing: HTTP-запрос не удался при отправке через фоновый сервис. Дает UnknownHostException

Я применил Geofence в приложении android. Я выполнил эту ссылку для реализации "Geofence" в приложении. Я использую библиотеку "Retrofit" для вызова запроса "HTTP".


Приложение имеет следующие разрешения:

<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" />


Вот мой код "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());

                }

            });

    }
}

Всякий раз, когда устройство получает триггер геозонности, он отлично работает и дает правильные уведомления о запуске, когда приложение находится в фоновом режиме или на переднем плане (введите/вывести/оставить) или даже если пользователь убил приложение из последних задач. Когда я вызываю HTTP-запрос, когда приложение находится на переднем плане, оно работает отлично, и оно печатает успех в журнале.

onResponse()  :: success

Но когда приложение убито из недавних задач, а устройство получило какой-либо триггер геозонности (введите/выйдут/уйдут), HTTP-запрос не будет выполнен должным образом. Он дает:

onFailure() :: t : 
</br>java.net.UnknownHostException: Unable to resolve host
"host_name": No address associated with hostname

где host_name - адрес сервера.

I ping google или 8.8.8.8 ip из фоновой службы. Все еще сталкиваются с такими же проблемами. Это также отлично работает, когда приложение находится на переднем плане, но после убийства приложение не работает.

Итак, почему эта ошибка? Не связана ли сетевая связь, когда приложение не находится в последних задачах?



< ------------------------------------------------ -------------------------------------------------- ------------------------ >
Я пробовал следующие вещи. Получив ответы от @Xavier и @Stevensen


Я использую firebase-jobscheduler в своем приложении для вызова запроса HTTP. Вот мой код:

В моем манифесте я добавил следующую службу:

 <service
            android:exported="false"
            android:name="com.****.service.TriggerJobService">
            <intent-filter>
                <action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
            </intent-filter>
        </service>


Это мой модифицированный класс GeofenceService. Я просто удалил callingHttpRequest() и добавил задание расписания, вызвав функцию scheduleJob() в функции getGeofenceTrasitionDetails(). И код такой же, как и он.

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());

    }

}


Это код моего 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 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());

                    }

                });

        }
}

Снова он вызывает то же самое. Он отлично работает и дает правильные уведомления о запуске, когда приложение находится в фоновом режиме или на переднем плане (введите/вывести/оставить) или даже если пользователь убил приложение из последних задач. Также это планирование надлежащей работы. И вызывая запрос HTTP, когда приложение находится на переднем плане, оно работает отлично, и оно печатает успех в журнале.

onResponse()  :: success

Но когда приложение убито из недавних задач, а устройство получило какой-либо триггер геофотометрии (введите/задержите/оставьте), то приложение расписания заданий и вызов HTTP не выполняется должным образом. Он дает:

onFailure() :: t : 
</br>java.net.UnknownHostException: Unable to resolve host
"host_name": No address associated with hostname

Итак, согласно @Xavier и @Stevensen, мое приложение не пробуждает сеть, если оно убивает последние задачи. Я попытался с firbase-JobSchedule, но все еще сталкивался с такой же ошибкой выше. Требуется ли приложению специальное permission для вызова запроса HTTP, а приложение убивает из последних заданий? или FCM является лучшим вариантом для этого. Но все-таки есть вопрос, будет ли работать FCM, даже если приложение будет убивать из недавних задач? does FCM пробудит сеть для отправки сообщения серверу с клиента?

4b9b3361

Ответ 1

Может быть, ваше приложение заблокировано для использования сети в режиме доза Androids и/или в режиме ожидания приложения. Проверьте Оптимизация для Doze и App Standby.

Возможное решение - настроить будильник с помощью AlarmManager. Android заплатит обработку сигналов тревоги в окне обслуживания, где вам разрешено использовать сеть.

Ответ 2

Объяснение @Stevensen о режиме Doze, являющемся причиной сбоя, скорее всего является причиной. В документации вы можете прочитать:

Следующие ограничения применяются к вашим приложениям во время Doze: доступ к сети приостановлен...

Я предлагаю хранить события в БД и планировать задание для загрузки их на сервер с помощью JobScheduler (API 21+, см. учебник здесь), или, если вам нужно поддерживать более старые устройства, используя эту замену firebase-jobdispatcher (который предоставляет API, совместимый с JobScheduler, путем объединения GCM Network Manager).

Я предлагаю установить условие сети: .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) и, возможно, .setPeriodic(long intervalMillis), чтобы ограничить количество раз (например, загружать не чаще одного раза в час).

До тех пор, пока не требуется реального времени, это лучший подход для пользователя, чтобы сэкономить аккумулятор: режим Doze поможет устройству экономить время автономной работы, а JobScheduler позволит загружать партии и просто просыпать радио от время от времени, экономия времени автономной работы. См. это быстрое видео для обоснования.

Ответ 3

Наконец, моя проблема решена. Благодаря @Stevensen, @Xavier и одному из моих друзей, который помогает мне идентифицировать проблему. Это связано с дозой.


Некоторые мобильные устройства (Xiomi, Huawei и т.д.) внедрили SmartManager для оптимизации потребления батареи. У них есть своего рода диспетчер батарей, который убивает приложения, а когда приложение убито, запланированные аварийные сигналы отменены, а также они не обнаруживают никакой активной сети или блокируют сетевой вызов из фоновой службы. Потому что производители обвиняют не доверенные приложения в потреблении энергии. Facebook, приложение "Приложения" и т.д. Доверяют, а производители - "белые". Вот почему они могут вызывать сетевое событие, даже если приложение было убито.


Тем не менее я не нашел для этого решения. Временное решение этой проблемы для устройств Xiomi я преодолеваю. Я сохраняю свое приложение с ограничениями экономии аккумулятора, чем его правильное выполнение, выполняя следующие действия:

settings--> battery -> Power --> App battery saver --> your app 
Now select No restrictions( for Background settings) then Allow option for Background location 


Для версии Android и выше, приложение должно спросить разрешения:

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);

и в манифесте:

<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>

После этого пользователь может сделать "белый список" вашего приложения.

Ответ 4

Когда приложение переходит в фоновый режим, вам нужно время от времени пробуждать приложение, чтобы обнюхать позицию мобильных телефонов. Естественно, чем чаще он будет обнюхивать, тем быстрее и надежнее он сможет обнаружить геозонность. https://proximi.io/will-my-geofencing-function-in-the-background