不在前台时位置服务被杀死

Posted

技术标签:

【中文标题】不在前台时位置服务被杀死【英文标题】:Location service is being killed when not in foreground 【发布时间】:2021-11-16 21:52:29 【问题描述】:

我正在为出租车司机开发一个应用程序,该应用程序将车辆的位置报告给调度 web 应用程序。我已经设法使用 FusedLocationProviderClient 可靠地获取设备位置,并通过前台服务发送位置更新。不幸的是,我的平板电脑(Huawei MediaPad T5 - android Oreo 8.0)在应用程序不在前台时主动终止服务。根据 Android 文档,我这样做的方式是正确的(如果不是,请随时纠正我)。

我在下面附上服务源代码:


import android.Manifest;
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.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Build;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;

import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;

import driver.taxis.dcsoft.cz.R;
import driver.taxis.dcsoft.cz.communication.CommunicatorManager;
import driver.taxis.dcsoft.cz.gui.activities.MainActivity;
import driver.taxis.dcsoft.cz.preferences.PreferencesConstants;
import driver.taxis.dcsoft.cz.preferences.PreferencesManager;

public class UpdateLocationService extends Service 
    private static final String TAG = "LocationService";

    private FusedLocationProviderClient mFusedLocationClient;
    private final static long UPDATE_INTERVAL = 15 * 1000;  /* 4 secs */
    private final static long FASTEST_INTERVAL = 7 * 1000; /* 2 sec */

    private LocationCallback locationCallback;

    @Override
    public void onCreate() 
        super.onCreate();

        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);

        if (Build.VERSION.SDK_INT >= 26) 
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "My Channel",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Intent notificationIntent = new Intent(this, MainActivity.class);

            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setOngoing(true)
                    .setContentTitle("Aplikace " + MainActivity.getContext().getString(R.string.app_name) + " odesílá polohu na pozadí.")
                    .setContentIntent(pendingIntent)
                    .build();

            startForeground(1, notification);
        
    

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) 
        Log.d(TAG, "onStartCommand: called.");
        getLocation();
        return START_NOT_STICKY;
    

    @Override
    public void onDestroy() 
        super.onDestroy();
        if(locationCallback != null) 
            mFusedLocationClient.removeLocationUpdates(locationCallback);
        
    

    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        return null;
    

    private void getLocation() 
        // Create the location request to start receiving updates
        LocationRequest mLocationRequestHighAccuracy = new LocationRequest();
        mLocationRequestHighAccuracy.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        mLocationRequestHighAccuracy.setInterval(UPDATE_INTERVAL);
        mLocationRequestHighAccuracy.setFastestInterval(FASTEST_INTERVAL);


        // new Google API SDK v11 uses getFusedLocationProviderClient(this)
        if (ActivityCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) 
            Log.d(TAG, "getLocation: stopping the location service.");
            stopSelf();
            return;
        
        Log.d(TAG, "getLocation: getting location information.");

        locationCallback = new LocationCallback() 
            @Override
            public void onLocationResult(LocationResult locationResult) 

                Log.d(TAG, "onLocationResult: got location result.");
                Location location = locationResult.getLastLocation();

                if (location != null) 
                    String vehicleId = PreferencesManager.loadStringPreference(PreferencesConstants.SPZ_STRING, "");
                    if(vehicleId.isEmpty() || vehicleId == null) 
                        return;
                    

                    CommunicatorManager.INSTANCE.sendPositionRequest(location);
                
            
        ;

        mFusedLocationClient.requestLocationUpdates(mLocationRequestHighAccuracy, locationCallback,
                Looper.myLooper()); // Looper.myLooper tells this to repeat forever until thread is destroyed
    

【问题讨论】:

尝试改用 ContextCompat.startForeground 【参考方案1】:

很遗憾,问题不在您的代码中。

在 Android (6+) 中,没有任何服务可以在所有设备品牌/类型上无限期地以安全的方式运行,而无需用户采取任何预防措施,因为任何设备品牌都以不同的方式实施限制、终止、或确保后台服务/应用程序的安全(是的,这很混乱)。

作为第一步,用户应转到 Android 设置并关闭您应用的所有电池监控和优化。在 Android 9+ 上,他们还应检查应用程序是否不受后台限制,并验证是否允许后台活动。

在某些品牌上,他们必须将后台应用程序列入白名单,而在另一些品牌上,您必须设置“高性能”省电模式。

举个具体的例子,在 Android 11 上,三星将默认阻止应用在后台运行,除非用户将应用排除在电池优化之外:他应该继续 Android 设置 -> 应用 -> YOUR_APP -> 电池 - > 电池优化 -> 所有应用 -> YOUR_APP -> 不优化。

此外,其他电池优化器(例如 Digibites 的 AccuBattery)可能会限制后台使用,而且一些防病毒软件对长时间运行的应用程序非常具有攻击性,因此必须正确设置。

也许在您的情况下,更新时间并不重要,您可以使用普通的粘性服务 (START_STICKY);

作为替代方案,您应该检查前台服务是否具备在后台安全运行的所有条件(其中一些是可检查的,有些则不是)。

您可以查看 GPS 前台服务的完整示例here。 它可以在后台可靠地工作(它还包括一个唤醒锁以防止手机休眠)。 它运行良好,但在某些设备上,用户必须禁用上述优化。

app 负责检查是否允许后台活动:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) 
    ActivityManager activityManager = (ActivityManager)getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
    if ((activityManager != null) && (activityManager.isBackgroundRestricted())) 
        isBackgroundActivityRestricted = true;
     else 
        isBackgroundActivityRestricted = false;
    
 else 
    isBackgroundActivityRestricted = false;

并且 GPS 已开启且可访问。

【讨论】:

以上是关于不在前台时位置服务被杀死的主要内容,如果未能解决你的问题,请参考以下文章

即使应用程序被杀死,位置服务也不会停止

当应用程序切换到后台时,我的 android 位置服务被终止

应用程序被杀死时,如何每 10 秒将用户位置上传到服务器? [关闭]

iOS为啥系统在后台使用位置杀死应用程序

前台服务在执行互联网相关操作时被杀死

监视区域但应用程序被杀死时位置图标消失