2017-08-04 179 views
17

我在Android應用程序中實現了Geofence。我跟着this鏈接在應用程序中實施「Geofence」。我正在使用'Retrofit'庫來調用'HTTP'請求。Geofencing:通過後臺服務發送HTTP請求失敗。給UnknownHostException


App有以下權限:

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

我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(),並通過調用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()); 

        } 

       }); 

     } 
} 

這裏再一次調用相同。它可以很好地工作,並在應用程序處於後臺或前臺時(輸入/停留/離開),或者即使用戶將應用程序從最近的任務中刪除,也能提供適當的觸發通知。它也是安排適當的工作。並呼籲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是否仍然有效? FCM會喚醒網絡從客戶端向服務器發送消息嗎?

+0

請提供設備和操作系統版本的信息。 – User10001

+0

我在一加二(OS-version 6.0),Xiomi-Mi4i(OS-version 5.0.2)設備上試過。 –

+0

你在5.0.2小米設備中遇到同樣的問題嗎?如果是這樣,問題不是因爲打盹模式(至少在此設備上),因爲它是在6.0中添加的「打盹和應用待機管理所有在Android 6.0或更高版本上運行的應用程序的行爲」https://developer.android .com/training/monitoring-device-state/doze-standby.html無論如何,如果可能的話,我以前的批量上傳事件的建議仍然有效。 –

回答

5

也許您的應用被Androids打盹模式和/或應用待機屏蔽以使用網絡。檢查Optimizing for Doze and App Standby

可能的解決方案是使用AlarmManager設置警報。 Android會在允許您使用網絡的維護窗口中安排警報處理。

+0

我試着用報警管理器,但仍面臨同樣的問題。它給了我同樣的錯誤。 –

4

@Stevensen關於打瞌睡模式是失敗原因的解釋更可能是原因。在documentation你可以閱讀:

以下限制適用於您的應用程序,而在打盹:網絡訪問被暫停...

我會建議儲存在一個數據庫中的事件,和通過使用JobScheduler(API 21+,請參閱tutorial here),或者如果您需要支持較舊的設備,請使用此替換firebase-jobdispatcher(它通過封裝GCM網絡管理器提供與JobScheduler兼容的API)來安排將作業上載到服務器的作業, 。

我會建議設置網絡的條件是需要:.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY),還可能需要.setPeriodic(long intervalMillis)限制它發生的次數(例如,上傳最多每小時一次)。

只要不需要實時性,這對用戶體驗來說是一個更好的方法來節省電池:打盹模式將有助於設備節省電池使用時間,並且JobScheduler將允許批量上傳,並從時間喚醒收音機時間,節省電池壽命。理由參見this quick video

+0

感謝您的回覆。我嘗試了Firebase JobScheduler。它可以在前臺應用程序和後臺應用程序中正常工作。但是當應用程序從最近的任務中消失並得到一些觸發器時,仍然在發送請求時遇到同樣的問它給
onFailure():: t:
java.net.UnknownHostException:無法解析主機 「host_name」:沒有與主機名關聯的地址 –

+0

當你編寫''host_name''時,它是字面的嗎?或者是'「whatever.myhost.com」',你用佔位符取代它以避免提供私人信息? –

+0

是的。我不想分享我的私人域名。它就像'「whatever.myhost.com」' –

2

最後我的問題解決了。感謝@Stevensen,@ Xavier和我的朋友之一,他幫助我識別問題。它與打盹模式有關。


一些手機制造商(秀美,華爲等)採用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版及以上的是,應用程序必須要問權限:

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

之後,用戶可以做你的應用白名單。

+0

感謝您分享最終方案。 –