-我正在开发一个应用程序,该应用程序将用户位置记录在一个.csv文件中,并将其上传到Firebase存储中,并且我编写了一项服务来实现此目的。该服务需要无限期运行,直到用户停止它为止(通过取消选中启动该服务的活动的开关)。该服务在前台运行,并在运行15-20分钟后,电话管理器应用会发出“发现资源密集型应用”通知并终止该服务。 onDestroy()永远不会被调用!我返回应该重新创建服务的START_STICKY,然后再次调用onStartCommand(),但是该服务再也无法创建!
我已经看到一些人针对此问题编写了解决方法,但他们针对的是特定的android版本。我的应用程序以android Pie为目标,并且还没有看到针对此问题的任何“通用”解决方案。所有解决方案都是特定于设备/品牌或特定于API级别的。现在我正在华为Y7 Prime 2018(Android O)和我的设备上测试该应用程序,通过手动禁止电话管理器应用程序管理我的应用程序来解决此问题,这种方式可以无限期运行直到用户将其杀死从应用程序中。该应用程序的性质是,我需要最小的用户参与度,并且我不希望用户经历从电池优化器/电话管理器应用程序手动禁用我的应用程序的麻烦。我的LocationService.java类已附加,如果有人可以提供此服务的更好实现或某种通用方法以使其保持活动状态,那就太好了。甚至有一些黑客可以确保在系统杀死应用程序之前调用onDestroy()可以正常工作!
package com.example.bumps;
import android.Manifest;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.IBinder;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference;
import com.google.firebase.storage.UploadTask;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class LocationService extends Service implements LocationListener {
private static final String CHANNEL_ID = "LocationServiceChannel";
private static final int ONGOING_NOTIFICATION_ID = 1;
public static final String PREFS_TAG = "PendingUploadPrefs";
private LocationManager locationManager;
DecimalFormat numberFormat = new DecimalFormat("#.000");
DecimalFormat coordinatesFormat = new DecimalFormat("#.00000");
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
SimpleDateFormat sdfForDir = new SimpleDateFormat("dd-MM-yyyy_hh:mm:ss");
File locationDirectory;
String currentDataDirectory;
FirebaseStorage filesStorage;
String userUUID;
List<ResumeFileUpload> pendingFileUploads;
private SharedPreferences sharedPreferences;
private SharedPreferences.Editor sharedPrefEditor;
private SOTWFormatter sotwFormatter;
@Override
public void onCreate() {
super.onCreate();
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
filesStorage = FirebaseStorage.getInstance();
pendingFileUploads = new ArrayList<>();
sharedPreferences = this.getSharedPreferences(PREFS_TAG, Context.MODE_PRIVATE);
sharedPrefEditor = sharedPreferences.edit();
sotwFormatter = new SOTWFormatter(this);
String timeStamp = sdfForDir.format(new Date());
currentDataDirectory = "RBD_" + timeStamp;
locationDirectory = new File(getExternalFilesDir(null), currentDataDirectory);
if (!locationDirectory.exists())
locationDirectory.mkdir();
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//userUUID = intent.getStringExtra("userUUID");
userUUID = sharedPreferences.getString("userUUID", "UNKNOWN");
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
0, 0, this);
}
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
createNotificationChannel();
Notification notification =
new Notification.Builder(this, CHANNEL_ID)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.bump)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build();
startForeground(ONGOING_NOTIFICATION_ID, notification);
return START_STICKY;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Location Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
try {
manager.createNotificationChannel(serviceChannel);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
locationManager.removeUpdates(this);
}
File newFileLoc = writeLocationDataToFile(locationStringBuilder);
pendingFileUploads.add(new ResumeFileUpload(currentDataDirectory,
newFileLoc.getPath()));
uploadToStorage(newFileLoc);
/*if there are no pending files from some previous session, just save the ones
* that just got created*/
if (getPendingFileUploads() == null){
setList("PendingUploads", pendingFileUploads);
}else{
/*if there are pending uploads from some previous session, fetch them,
* update the list of pending uploads with and save for later upload*/
List<ResumeFileUpload> pUploads = getPendingFileUploads();
for (ResumeFileUpload resumeFileUpload: pendingFileUploads){
pUploads.add(resumeFileUpload);
}
setList("PendingUploads", pUploads);
}
}
private List<ResumeFileUpload> getPendingFileUploads(){
List<ResumeFileUpload> pendingUploads = null;
String serializedObject = sharedPreferences.getString("PendingUploads", null);
if (serializedObject != null){
Gson gson = new Gson();
Type type = new TypeToken<List<ResumeFileUpload>>(){}.getType();
pendingUploads = gson.fromJson(serializedObject, type);
}
return pendingUploads;
}
private boolean deleteFile(File file){
try {
Uri uri = Uri.fromFile(file);
File fdelete = new File(uri.getPath());
if (fdelete.exists()) {
if (fdelete.delete()) {
System.out.println("file Deleted :" + uri.getPath());
return true;
} else {
System.out.println("file not Deleted :" + uri.getPath());
return false;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private File writeLocationDataToFile(StringBuilder data) {
String fileName = "Location.csv";
String colNamesLoc = "Timestamp,Latitude,Longitude,Accuracy,DirectionOfMotion,Speed\n";
File newFile = new File(locationDirectory, fileName);
try {
FileWriter fileWriter = new FileWriter(newFile);
fileWriter.append(colNamesLoc);
fileWriter.append(data.toString());
fileWriter.flush();
fileWriter.close();
Toast.makeText(this, "Saved To " + getExternalFilesDir(null)
+ "/" + fileName, Toast.LENGTH_LONG).show();
} catch (IOException e) {
e.printStackTrace();
}
return newFile;
}
private void uploadToStorage(final File newFile){
Uri uri = Uri.fromFile(new File(newFile.getPath()));
StorageReference storageReference = filesStorage.getReference()
.child(userUUID + "/" + currentDataDirectory + "/" +
uri.getLastPathSegment());
UploadTask uploadTask = storageReference.putFile(uri);
uploadTask.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception exception) {
Toast.makeText(LocationService.this,
newFile.getName() + " Upload Failed!", Toast.LENGTH_SHORT).show();
}
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
@Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
deleteFile(newFile);
Toast.makeText(LocationService.this,
newFile.getName() + " Upload Successful!", Toast.LENGTH_SHORT).show();
}
});
}
/*Location Listener*/
@Override
public void onLocationChanged(Location location) {
if (location != null) {
String lat = coordinatesFormat.format(location.getLatitude());
String lng = coordinatesFormat.format(location.getLongitude());
String bearing = "0.0";
if (location.getBearing() != 0.0)
bearing = String.valueOf(sotwFormatter.format(location.getBearing()));
int speed = (int) location.getSpeed();
String currentSpeed = String.valueOf(speed * 3.6f) + " KM/h";
String accuracy = "0.0";
if (location.getAccuracy() != 0.0)
accuracy = coordinatesFormat.format(location.getAccuracy());
String timeStamp = simpleDateFormat.format(new Date());
String locationData = timeStamp + ", " + lat +
", " + lng + ", " + accuracy + ", " + bearing + ", "
+ currentSpeed;
locationStringBuilder.append(locationData);
locationStringBuilder.append("\n");
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
/*SharedPreferences code*/
public <T> void setList(String key, List<T> list) {
Gson gson = new Gson();
String json = gson.toJson(list);
set(key, json);
}
public void set(String key, String value) {
sharedPrefEditor.putString(key, value);
sharedPrefEditor.commit();
}
}