前台服务在 didExitRegion 上重新启动
Foreground Service restarts on didExitRegion
我正在构建一个应用程序来检测 Android 上的 iBeacon。基本功能是通过信标将广告数据存储到设备上并将其上传到服务器。为此,我正在使用 Android Beacon Library.
要运行它在后台就AndroidO 我用的是前台服务。问题是当应用程序在后台超过 30 分钟时,在检测到信标后,当用户退出信标区域并调用 didExitRegion
时,不知何故,服务会自动终止并重新启动,因此没有数据被上传到服务器。同样在重新启动后,在第二次 didExitRegion
调用中,它会在未来的某个时间完全随机地停止,它会重新启动但会再次执行相同的循环。
当应用在大约 30 分钟不活动后进入区域时发生的事件序列
First Restart after didExit(图片)
在这里您可以看到从区域 11 切换到 9。Midway 应用程序立即关闭并重新触发,但是没有发送任何推送
Exit after second didExit(图片)
接下来:现在退出该区域时,应用程序再次停止在后台。但是这次不会立即重新触发。这是始终发生的确切顺序。
代码片段
BeaconScanner.java
@Override
public void didExitRegion(Region region) {
Log.d(TAG, "Exited A Region");
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
notificationHelper.notify(2, notificationHelper.getNotification("BeaconScanner", "Exit Major #"+previousMajor, false));
else
utils.dispatchNotification("Exit Major #"+previousMajor, 1);
Log.e(TAG, "DidSend "+didSend+" has Data "+userData.hasPendingData());
if(didSend && userData.hasPendingData()) {
JSONObject data = userData.getJsonFromUser();
Log.d(TAG, "Timestamp = "+System.currentTimeMillis());
userData.addTimestamp(""+System.currentTimeMillis());
userData.requestDataSync(data);
userData.clearBeaconData();
Log.d(TAG, data.toString());
didSend = !didSend;
}
previousMajor = -1000;
lastBeacon = resetBeacon;
}
User.java
JSONObject getJsonFromUser() {
Log.d(TAG, "Timestamp as in getJsonFromUser "+timestamp);
JSONObject json = new JSONObject();
try {
json.put("email", email);
json.put("name", name);
JSONArray beaconArray = new JSONArray();
for (Beacon beacon : beaconData){
beaconArray.put(new JSONObject()
.put("major", beacon.getId2().toInt())
.put("minor", beacon.getId3().toInt())
.put("uuid", beacon.getId1().toString())
);
}
json.put("data", beaconArray);
Log.d(TAG, timestamp);
json.put("timestamp", ""+System.currentTimeMillis());
return json;
} catch (Exception e){
Log.e(TAG, e.getMessage());
Crashlytics.log(e.getLocalizedMessage());
}
return json;
}
void requestDataSync(final JSONObject json){
User.syncing = true;
Crashlytics.log(1, "User.java", "Requesting Auth Token");
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
user.getIdToken(true).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
@Override
public void onComplete(@NonNull Task<GetTokenResult> task) {
if(task.isSuccessful()){
final Task<GetTokenResult> t = task;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
trustAllHosts();
URL url = new URL("https://indpulse.com/generatetoken");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setHostnameVerifier(DO_NOT_VERIFY);
connection.setRequestProperty("Authorization", ""+t.getResult().getToken());
connection.setRequestMethod("GET");
Log.d(TAG, t.getResult().getToken());
connection.setDoOutput(true);
connection.connect();
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String packet;
while((packet = br.readLine()) != null){
response.append(packet);
}
JSONObject responseObject = new JSONObject(response.toString());
String idToken = responseObject.getString("token");
Crashlytics.log(1, "User.java", "Auth Token Acquired");
sendData(json, idToken);
} catch (MalformedURLException e){
Log.e(TAG, "Malformed URL "+e.getLocalizedMessage());
Crashlytics.log(e.getLocalizedMessage());
} catch (IOException e){
Log.e(TAG, "IOExeption "+e.getLocalizedMessage());
Crashlytics.log(e.getLocalizedMessage());
} catch (JSONException e) {
Log.d(TAG,"Json error");
Crashlytics.log(e.getLocalizedMessage());
}
}
});
thread.start();
}
}
});
void sendData(JSONObject json, final String idToken){
final String sJson = json.toString();
System.out.println(sJson);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL("https://indpulse.com/android");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "Bearer "+idToken);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
DataOutputStream os = new DataOutputStream(conn.getOutputStream());
os.writeBytes(sJson);
os.flush();
os.close();
Log.i(TAG, String.valueOf(conn.getResponseCode()));
Log.i(TAG , conn.getResponseMessage());
conn.disconnect();
} catch (Exception e){
Log.e("BeaconScanner", e.getLocalizedMessage());
Crashlytics.log(e.getLocalizedMessage());
}
User.syncing = false;
}
});
thread.start();
}
编辑 1
One thing to note is that the beacons have an overlapping region, i.e a beacon scanner will detect 2 beacons in the region. So the nearest beacon is decided by the greatest value of the timeAverageRssi
, the bug specifically crops up, after 30 minutes of inactivity there are region switches i.e beacon 1 was the nearest and then beacon 2 becomes the nearest beacon
我怀疑在 Android 8+ 上使用前台服务与 Android Beacon Library 2.15 版存在错误。虽然我自己没有重现这一点,但理论上 Android 8+ 会阻止使用用于从信标扫描服务传送监控回调的 Intent 服务。
我在库版本 2.15.1 beta 1 based on a similar report issue here 中构建了建议的修复程序。请尝试此修复,看看它是否解决了您的问题。
我正在构建一个应用程序来检测 Android 上的 iBeacon。基本功能是通过信标将广告数据存储到设备上并将其上传到服务器。为此,我正在使用 Android Beacon Library.
要运行它在后台就AndroidO 我用的是前台服务。问题是当应用程序在后台超过 30 分钟时,在检测到信标后,当用户退出信标区域并调用 didExitRegion
时,不知何故,服务会自动终止并重新启动,因此没有数据被上传到服务器。同样在重新启动后,在第二次 didExitRegion
调用中,它会在未来的某个时间完全随机地停止,它会重新启动但会再次执行相同的循环。
当应用在大约 30 分钟不活动后进入区域时发生的事件序列
First Restart after didExit(图片)
在这里您可以看到从区域 11 切换到 9。Midway 应用程序立即关闭并重新触发,但是没有发送任何推送
Exit after second didExit(图片)
接下来:现在退出该区域时,应用程序再次停止在后台。但是这次不会立即重新触发。这是始终发生的确切顺序。
代码片段
BeaconScanner.java
@Override
public void didExitRegion(Region region) {
Log.d(TAG, "Exited A Region");
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
notificationHelper.notify(2, notificationHelper.getNotification("BeaconScanner", "Exit Major #"+previousMajor, false));
else
utils.dispatchNotification("Exit Major #"+previousMajor, 1);
Log.e(TAG, "DidSend "+didSend+" has Data "+userData.hasPendingData());
if(didSend && userData.hasPendingData()) {
JSONObject data = userData.getJsonFromUser();
Log.d(TAG, "Timestamp = "+System.currentTimeMillis());
userData.addTimestamp(""+System.currentTimeMillis());
userData.requestDataSync(data);
userData.clearBeaconData();
Log.d(TAG, data.toString());
didSend = !didSend;
}
previousMajor = -1000;
lastBeacon = resetBeacon;
}
User.java
JSONObject getJsonFromUser() {
Log.d(TAG, "Timestamp as in getJsonFromUser "+timestamp);
JSONObject json = new JSONObject();
try {
json.put("email", email);
json.put("name", name);
JSONArray beaconArray = new JSONArray();
for (Beacon beacon : beaconData){
beaconArray.put(new JSONObject()
.put("major", beacon.getId2().toInt())
.put("minor", beacon.getId3().toInt())
.put("uuid", beacon.getId1().toString())
);
}
json.put("data", beaconArray);
Log.d(TAG, timestamp);
json.put("timestamp", ""+System.currentTimeMillis());
return json;
} catch (Exception e){
Log.e(TAG, e.getMessage());
Crashlytics.log(e.getLocalizedMessage());
}
return json;
}
void requestDataSync(final JSONObject json){
User.syncing = true;
Crashlytics.log(1, "User.java", "Requesting Auth Token");
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
user.getIdToken(true).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
@Override
public void onComplete(@NonNull Task<GetTokenResult> task) {
if(task.isSuccessful()){
final Task<GetTokenResult> t = task;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
trustAllHosts();
URL url = new URL("https://indpulse.com/generatetoken");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setHostnameVerifier(DO_NOT_VERIFY);
connection.setRequestProperty("Authorization", ""+t.getResult().getToken());
connection.setRequestMethod("GET");
Log.d(TAG, t.getResult().getToken());
connection.setDoOutput(true);
connection.connect();
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String packet;
while((packet = br.readLine()) != null){
response.append(packet);
}
JSONObject responseObject = new JSONObject(response.toString());
String idToken = responseObject.getString("token");
Crashlytics.log(1, "User.java", "Auth Token Acquired");
sendData(json, idToken);
} catch (MalformedURLException e){
Log.e(TAG, "Malformed URL "+e.getLocalizedMessage());
Crashlytics.log(e.getLocalizedMessage());
} catch (IOException e){
Log.e(TAG, "IOExeption "+e.getLocalizedMessage());
Crashlytics.log(e.getLocalizedMessage());
} catch (JSONException e) {
Log.d(TAG,"Json error");
Crashlytics.log(e.getLocalizedMessage());
}
}
});
thread.start();
}
}
});
void sendData(JSONObject json, final String idToken){
final String sJson = json.toString();
System.out.println(sJson);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL("https://indpulse.com/android");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "Bearer "+idToken);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
DataOutputStream os = new DataOutputStream(conn.getOutputStream());
os.writeBytes(sJson);
os.flush();
os.close();
Log.i(TAG, String.valueOf(conn.getResponseCode()));
Log.i(TAG , conn.getResponseMessage());
conn.disconnect();
} catch (Exception e){
Log.e("BeaconScanner", e.getLocalizedMessage());
Crashlytics.log(e.getLocalizedMessage());
}
User.syncing = false;
}
});
thread.start();
}
编辑 1
One thing to note is that the beacons have an overlapping region, i.e a beacon scanner will detect 2 beacons in the region. So the nearest beacon is decided by the greatest value of the
timeAverageRssi
, the bug specifically crops up, after 30 minutes of inactivity there are region switches i.e beacon 1 was the nearest and then beacon 2 becomes the nearest beacon
我怀疑在 Android 8+ 上使用前台服务与 Android Beacon Library 2.15 版存在错误。虽然我自己没有重现这一点,但理论上 Android 8+ 会阻止使用用于从信标扫描服务传送监控回调的 Intent 服务。
我在库版本 2.15.1 beta 1 based on a similar report issue here 中构建了建议的修复程序。请尝试此修复,看看它是否解决了您的问题。