地理围栏:通过后台服务发送时,HTTP请求失败。给出UnknownHostException

时间:2017-08-04 11:52:59

标签: java android http geofencing android-intentservice

我在Android应用程序中实现了Geofence。我按照this链接实施了“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" />

这是我的&#39; IntentService&#39;代码:

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是服务器地址。

我从后台服务ping google或8.8.8.8 ip。仍面临同样的问题。当应用程序处于前台时,这个东西也可以正常工作但是在杀死应用程序之后它不起作用。

那么,为什么会出现这个错误呢?当应用不在最近的任务中时,网络通信是否没有呼叫?



&LT; ------------------------------------------------ -------------------------------------------------- ------------------------&GT;
我尝试过以下事情。从@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'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());

                    }

                });

        }
}

它再次呼唤相同。当app处于后台或前台(进入/停留/离开)或即使用户从最近的任务中杀死应用程序时,它工作正常并提供适当的触发通知。它也正在安排适当的工作。并调用HTTP请求,当应用程序在前台时,它工作正常,它在日志上打印成功。

onResponse()  :: success

但是当应用程序从最近的任务中被杀死并且设备获得任何地理围栏触发器(输入/停留/离开)时,应用程序调度作业并且调用HTTP请求未正确执行。它给出了:

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

所以按照@Xavier&amp; @Stevensen回答我的应用程序,如果它从最近的任务中杀死,则不会唤醒网络。我尝试使用firbase-JobSchedule,但仍面临同样的错误。当应用程序从最近的任务中杀死时,应用程序是否需要任何特殊permission来调用HTTP请求?或FCM是更好的选择。但是,即使app从最近的任务中杀死,FCM是否会起作用仍然存在同样的问题? FCM是否会唤醒网络以从客户端向服务器发送消息?

3 个答案:

答案 0 :(得分:2)

最后我的问题解决了。感谢@Stevensen,@ Xavier和我的一位帮助我确定问题的朋友。它与打盹模式有关。


一些手机制造商(Xiomi,华为等)实施了SmartManager以优化电池消耗。他们有一种杀死应用程序的电池管理器,当应用程序被杀死时,预定的警报被取消,并且他们也没有检测到任何活动网络或阻止来自后台服务的网络呼叫。因为制造商将不受信任的应用归咎于功耗。 Facebook,Whats App等应用程序是值得信赖的,并且被制造商列入白名单。这就是为什么即使应用程序被杀,他们也可以调用网络事件。


我仍然没有找到任何解决方案。所以暂时我克服了Xiomi设备的这个问题。我通过执行以下操作来阻止我的应用程序免受电池节省限制:

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


对于Android M及以上版本,app必须征得许可:

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

之后用户可以将您的应用列入白名单。

答案 1 :(得分:1)

我遇到了同样的问题:“ java.net.UnknownHostException:无法解析主机 “ host_name”:没有与主机名关联的地址。” Internet也可以使用所有授予的Android权限(即使在运行时也对它们进行了测试)。

但是解决方案有所不同。原因是我们的API主机“ host_name”(例如http://xxx.yyyy.zz)位于局域网中,无法从外部网络访问。如果从公司的局域网中调用公司的外部“ host_name”,则可能会遇到相同的问题(对于服务器来说,这似乎是“ DNS重新绑定攻击”)。要对其进行测试,您应该尝试在设备从本地局域网(例如公司的Wi-Fi)连接(以及未连接)到Internet时,在浏览器中打开使用的url,并检查服务器响应是否正确。

@Mangesh Sambare的问题如上所述得到了解决,但也许这种经历将对与我处在同一状况的人有所帮助。

答案 2 :(得分:0)

当应用程序进入后台模式时,您需要立即唤醒应用程序以嗅探手机的位置。当然,嗅闻的次数越多,它就能越快,越可靠地检测到地理围栏。 https://proximi.io/will-my-geofencing-function-in-the-background

相关问题