我在android应用程序中实现了Geofence。我跟随this链接在app中实现了'Geofence‘。我正在使用'Retrofit‘库来调用'HTTP’请求。
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());
}
});
}
}
只要设备获得了geofence触发器,它就能正常工作,并在应用程序处于后台或前台(进入/停留/离开)时发出适当的触发通知,甚至在用户从最近的任务中杀死应用程序时也是如此。当我调用HTTP请求时,当应用程序在前台时,它工作正常,并在日志中打印成功。
onResponse() :: success
但是,当应用程序从最近的任务中被杀死,并且设备获得任何geofence触发器(enter/dwell/leave)时,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。仍然面临着同样的问题。当应用程序在前台时,这个东西也能正常工作,但在杀死应用程序后,它就不能工作了。
那么,为什么会出现这个错误呢?当app不在最近的任务中时,网络通信是否没有呼叫?
<-------------------------------------------------------------------------------------------------------------------------->
我试着做了以下事情。在从@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
是否会唤醒网络以从客户端向服务器发送消息?
发布于 2017-08-11 23:11:34
也许你的应用程序被Android的打瞌睡模式和/或应用程序待机阻止了使用网络。检查Optimizing for Doze and App Standby。
一种可能的解决方案是使用AlarmManager设置警报。Android将在允许您使用网络的维护窗口中安排警报处理。
发布于 2017-08-12 22:55:26
@Stevensen关于Doze模式是失败原因的解释更有可能是原因。在documentation中,您可以阅读:
以下限制适用于处于睡眠状态的应用程序:网络访问被挂起...
我建议将事件存储在一个DB中,并安排一个作业,通过使用JobScheduler (API,请参阅tutorial here)将它们上传到服务器,或者,如果您需要支持较旧的设备,可以使用这个替代的firebase-jobdispatcher (它通过包装GCM Network Manager提供与JobScheduler兼容的API )。
我建议设置一个网络需要的条件:.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
,可能还有一个.setPeriodic(long intervalMillis)
来限制它发生的次数(例如,每小时最多上传一次)。
只要不需要实时,这是一个更好的用户体验的方法来节省电池:打瞌睡模式将帮助设备节省电池寿命,JobScheduler
将允许批量上传并不时唤醒收音机,节省电池寿命。有关基本原理,请参阅this quick video。
发布于 2017-08-18 14:11:53
我的问题终于解决了。感谢@Stevensen,@Xavier和我的一位朋友,他们帮助我发现了这个问题。这与打瞌睡模式有关。
一些手机制造商(小米、华为等)实施了SmartManager来优化电池消耗。它们有一种电池管理器,可以杀死应用程序,当应用程序被杀死时,预定的警报会被取消,而且它们也没有检测到任何活动的网络或阻止来自后台服务的网络呼叫。因为制造商将功耗归咎于不受信任的应用程序。Facebook,Whats App等应用程序是值得信任的,它们被制造商列入了白名单。这就是为什么他们能够调用网络事件,即使应用程序被杀死。
尽管如此,我还是没有为that.So找到任何临时的解决方案,我为小米设备克服了这个问题。我通过做以下事情使我的应用程序避免了电池节省的限制,而不是它的正常工作:
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"/>
在此之后,用户可以将您的应用程序列入白名单。
https://stackoverflow.com/questions/45506162
复制相似问题