Java 地理围栏:通过后台服务发送时HTTP请求失败。给出未知的后异常

Java 地理围栏:通过后台服务发送时HTTP请求失败。给出未知的后异常,java,android,http,geofencing,android-intentservice,Java,Android,Http,Geofencing,Android Intentservice,我在android应用程序中实现了Geofence。我按照链接在应用程序中实现“地理围栏”。我正在使用“改造”库调用“HTTP”请求 应用程序具有以下权限: <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission a

我在android应用程序中实现了Geofence。我按照链接在应用程序中实现“地理围栏”。我正在使用“改造”库调用“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" />
但当应用程序因最近的任务而被终止,并且设备得到任何地理围栏触发器(enter/dwell/leave)时,HTTP请求就不能正确执行。它给出:

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

这是我修改过的GeofenceService类。我刚刚删除了调用HttpRequest(),并通过调用
getGeofenceTrasitionDetails()
函数中的
scheduleJob()
函数添加了调度作业。代码和它是一样的

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

                    }

                });

        }
}
但当应用程序被最近的任务杀死,并且设备得到任何地理围栏触发(enter/dwell/leave)时,应用程序调度作业,调用
HTTP
请求不会正确执行。它给出:

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

java.net.UnknownHostException:无法解析主机 “主机名”:没有与主机名关联的地址

因此,根据@Xavier&@Stevensen的回答,如果我的应用程序因最近的任务而死亡,它就不是唤醒网络。我试过使用
firbase JobSchedule
,但仍然面临上述错误。当应用程序从最近的任务中终止时,应用程序是否需要任何特殊的
权限来调用
HTTP
请求?或者is
FCM
是更好的选择。但是仍然有同样的问题,
FCM
是否会在应用程序因最近的任务而死亡的情况下工作?
FCM
是否会唤醒网络以从客户端向服务器发送消息?

可能您的应用程序被Androids睡眠模式和/或应用程序待机阻止使用网络。检查


一种可能的解决方案是使用设置报警。Android将在允许您使用网络的维护窗口中安排报警处理。

史蒂文森关于瞌睡模式是故障原因的解释更有可能是故障原因。在中,您可以阅读:

打瞌睡时,以下限制适用于你的应用程序:网络访问被挂起

我建议将事件存储在数据库中,并通过使用(API 21+,请参见a)或(如果需要支持较旧的设备,请使用此替换项)(通过包装GCM Network Manager提供与JobScheduler兼容的API)安排一个作业以将其上载到服务器

我建议设置需要网络的条件:
.setRequiredNetworkType(JobInfo.network\u TYPE\u ANY)
和可能的
.setPeriodic(long intervalMillis)
来限制它发生的次数(例如,最多每小时上传一次)


只要不需要实时性,用户体验就可以更好地节省电池:打瞌睡模式将帮助设备节省电池寿命,
JobScheduler
将允许批量上传并不时唤醒收音机,从而节省电池寿命。有关基本原理,请参阅。

当应用程序进入后台模式时,您需要不时唤醒应用程序以嗅探手机的位置。当然,它嗅探的频率越高,探测地球围栏的速度就越快,可靠性也就越高

我的问题终于解决了。感谢@Stevensen、@Xavier和我的一位朋友帮助我发现了问题。这与打瞌睡模式有关


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


我仍然没有找到任何解决方案。所以我暂时解决了Xiomi设备的这个问题。我通过执行以下操作使我的应用程序不受电池节省限制,而无法正常工作:

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

对于android M及以上版本,应用程序必须获得许可:

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

之后,用户可以做你的应用白名单

我也遇到了同样的问题:“java.net.UnknownHostException:无法解析主机 “主机名”:没有与主机名关联的地址。此外,互联网还可以使用所有授予的Android权限(甚至在运行时都进行了测试)。

但解决方案不同。原因是我们的API主机“主机名”(例如)位于LAN中,无法从外部网络访问。如果您从公司的局域网中调用公司的外部“主机名”(这看起来像是服务器的“DNS重新绑定攻击”),则可能会发现相同的问题。要测试它,当设备从本地LAN(如公司的Wi-Fi)连接到Internet(或未连接到Internet)时,应尝试在浏览器中打开使用过的url,并检查服务器响应是否正确。


正如他所说,@Mangesh Sambare的问题已经解决了,但也许这段经历会对与我处境相同的人有所帮助。

请提供设备和操作系统版本的信息。我在一加二(操作系统版本6.0)和Xiomi-Mi4i(操作系统版本5.0.2)设备上试用过。您在5.0.2小米设备上也有同样的问题吗?如果是这样,问题不是因为打瞌睡模式(至少在这个设备上),因为这是在6.0“打瞌睡和应用程序待机管理所有在Android 6.0或更高版本上运行的应用程序的行为”中添加的。无论如何,我之前关于在可能的情况下批量上传事件的建议仍然有效。最终我的问题解决了。请注意。这将有助于发现问题。谢谢回复。我试过firebase作业调度器。它在前台应用程序和后台应用程序中运行良好。但当应用程序从最近的任务中终止时,在发送请求时仍然面临同样的问题&触发了一些事件。它给
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"/>