通过Android中的服务获取GPS位置

75

我需要使用后台服务来监控用户位置,并加载并向用户展示路径。

在活动中获取GPS位置相当容易,但是在服务中进行此操作时,我遇到了问题,因为它似乎仅适用于looper线程(或类似的东西)。

在搜索互联网上的解决方案时,我发现许多人都遇到了同样的问题,但我找不到可行的解决方案。有些人说您需要使用 prepareloopquit,还有些人说您必须使用handlerThread,但是,我仍然无法找到如何以正确的方式执行这些操作的方法。


2
循环线程 :D 你的意思是Looper线程。Looper线程是一种带有请求队列机制的特殊线程。主线程或UI线程是一个Looper线程。至于Service的问题,你是在主线程上调用requestLocationUpdates吗?还是在另一个线程中执行?因为活动和服务是通过主线程即Looper线程运行的,所以调用requestLocationUpdates必须相同。你能指出你所谈论的其他帖子中提到这个问题的地方吗? - Vikram Bodicherla
7个回答

60

我不理解在服务中实现位置监听功能的问题具体是什么。这看起来与在活动中所做的非常相似。

只需定义一个位置监听器并注册位置更新即可。您可以参考以下代码示例:

清单文件:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name" >
    <activity android:label="@string/app_name" android:name=".LocationCheckerActivity" >
        <intent-filter >
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <service android:name=".MyService" android:process=":my_service" />
</application>

服务文件:

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {

    private static final String TAG = "BOOMBOOMTESTGPS";
    private LocationManager mLocationManager = null;
    private static final int LOCATION_INTERVAL = 1000;
    private static final float LOCATION_DISTANCE = 10f;

    private class LocationListener implements android.location.LocationListener {
        Location mLastLocation;

        public LocationListener(String provider) {
            Log.e(TAG, "LocationListener " + provider);
            mLastLocation = new Location(provider);
        }

        @Override
        public void onLocationChanged(Location location) {
            Log.e(TAG, "onLocationChanged: " + location);
            mLastLocation.set(location);
        }

        @Override
        public void onProviderDisabled(String provider) {
            Log.e(TAG, "onProviderDisabled: " + provider);
        }

        @Override
        public void onProviderEnabled(String provider) {
            Log.e(TAG, "onProviderEnabled: " + provider);
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
            Log.e(TAG, "onStatusChanged: " + provider);
        }
    }

    LocationListener[] mLocationListeners = new LocationListener[]{
            new LocationListener(LocationManager.GPS_PROVIDER),
            new LocationListener(LocationManager.NETWORK_PROVIDER)
    };

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand");
        super.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        Log.e(TAG, "onCreate");
        initializeLocationManager();
        try {
            mLocationManager.requestLocationUpdates(
                    LocationManager.NETWORK_PROVIDER, LOCATION_INTERVAL, LOCATION_DISTANCE,
                    mLocationListeners[1]);
        } catch (java.lang.SecurityException ex) {
            Log.i(TAG, "fail to request location update, ignore", ex);
        } catch (IllegalArgumentException ex) {
            Log.d(TAG, "network provider does not exist, " + ex.getMessage());
        }
        try {
            mLocationManager.requestLocationUpdates(
                    LocationManager.GPS_PROVIDER, LOCATION_INTERVAL, LOCATION_DISTANCE,
                    mLocationListeners[0]);
        } catch (java.lang.SecurityException ex) {
            Log.i(TAG, "fail to request location update, ignore", ex);
        } catch (IllegalArgumentException ex) {
            Log.d(TAG, "gps provider does not exist " + ex.getMessage());
        }
    }

    @Override
    public void onDestroy() {
        Log.e(TAG, "onDestroy");
        super.onDestroy();
        if (mLocationManager != null) {
            for (int i = 0; i < mLocationListeners.length; i++) {
                try {
                    mLocationManager.removeUpdates(mLocationListeners[i]);
                } catch (Exception ex) {
                    Log.i(TAG, "fail to remove location listners, ignore", ex);
                }
            }
        }
    }

    private void initializeLocationManager() {
        Log.e(TAG, "initializeLocationManager");
        if (mLocationManager == null) {
            mLocationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
        }
    }
}

我知道,在一个小项目中它能正常工作,但是在和一些人一起工作时,我总是会遇到异常:java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()我已经查看了代码,但是没有发现它在特定的线程上运行,就像你的代码一样,服务在指定名称的进程上运行。 - android developer
1
顺便问一下,"onDestroy" 方法是否必须调用 "removeUpdates" 呢? 我认为不调用也不会导致内存泄漏,对吧? - android developer
据我所知,如果您不从locationManager中删除监听器,那么您的监听器对象的引用将继续被LocationManager使用,因此GC将无法释放它们(也就是您的监听器对象)。 - avepr
你怎么从这个GPS服务中获取像纬度和经度这样的数据? - JoaoFilipeClementeMartins
2
@Hissain 正确。我很久以前写的,实际上我不知道当时在想什么。然而,你应该避免在服务/活动中创建匿名处理程序,因为它可能会导致内存泄漏(除非你当然小心)。这也是为什么 Lint 会警告你的原因。 - android developer
显示剩余4条评论

22
public class GPSService extends Service implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, com.google.android.gms.location.LocationListener {
    private LocationRequest mLocationRequest;
    private GoogleApiClient mGoogleApiClient;
    private static final String LOGSERVICE = "#######";

    @Override
    public void onCreate() {
        super.onCreate();
        buildGoogleApiClient();
        Log.i(LOGSERVICE, "onCreate");

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(LOGSERVICE, "onStartCommand");

        if (!mGoogleApiClient.isConnected())
            mGoogleApiClient.connect();
        return START_STICKY;
    }


    @Override
    public void onConnected(Bundle bundle) {
        Log.i(LOGSERVICE, "onConnected" + bundle);

        Location l = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
        if (l != null) {
            Log.i(LOGSERVICE, "lat " + l.getLatitude());
            Log.i(LOGSERVICE, "lng " + l.getLongitude());

        }

        startLocationUpdate();
    }

    @Override
    public void onConnectionSuspended(int i) {
        Log.i(LOGSERVICE, "onConnectionSuspended " + i);

    }

    @Override
    public void onLocationChanged(Location location) {
        Log.i(LOGSERVICE, "lat " + location.getLatitude());
        Log.i(LOGSERVICE, "lng " + location.getLongitude());
        LatLng mLocation = (new LatLng(location.getLatitude(), location.getLongitude()));
        EventBus.getDefault().post(mLocation);

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(LOGSERVICE, "onDestroy - Estou sendo destruido ");

    }

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

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.i(LOGSERVICE, "onConnectionFailed ");

    }

    private void initLocationRequest() {
        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(5000);
        mLocationRequest.setFastestInterval(2000);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

    }

    private void startLocationUpdate() {
        initLocationRequest();

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }
        LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
    }

    private void stopLocationUpdate() {
        LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);

    }

    protected synchronized void buildGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addOnConnectionFailedListener(this)
                .addConnectionCallbacks(this)
                .addApi(LocationServices.API)
                .build();
    }

}

我正在使用lib EventBus将位置发送到视图。--> EventBus.getDefault().post(mLocation) - vrbsm
它没有在后台运行。我正在使用红米Note 3手机。当应用程序处于打开状态时,它可以正常运行,但是当我关闭应用程序并尝试将其作为服务运行时,它无法正常工作。 - Darpan Pathak
1
如何处理权限和位置设置? - humble_wolf
@DarpanPathak 这是小米相关问题。您必须在手机设置中为应用添加权限。 - ViT-Vetal-
1
为什么 onLocationChanged() 方法发送的是 LatLng 而不是直接发送 Location 对象? - pram

14
所有这些答案都不适用于Android 6.0(Marshmallow)-至-Android 8.1(Oreo)-8,由于Doze模式限制服务 - 无论是任何服务还是需要在后台运行离散事物的任何后台操作都将无法继续运行。
因此,方法是通过BroadCastReciever监听系统FusedLocationApiClient,始终监听位置并即使在Doze模式下也能正常工作。
发布链接是没有意义的,请搜索带有Broadcast接收器的FusedLocation。

在后台使用Services或IntentServices请求位置将导致每小时产生新的更新。这适用于Android 8+。 - UserK
前台服务与后台进程不具有相同的问题。我使用一个前台服务,它处理 GPS 更新。 - Luke Dupin
@LukeDupin 即使屏幕锁定,它是否会定期更新? - Alfredo Bazo Lopez
@AlfredoBazoLopez 是的。 - Luke Dupin

2

3
链接被交换了。 - Necrontyr
1
链接已失效。 - Houman
最后两个链接: "页面未找到 · GitHub" - Peter Mortensen

2

补充一下,我是这样实现的,通常在我的Service类中运行良好

在我的Service类中

@Override
public void onCreate()
{
    mHandler = new Handler(Looper.getMainLooper());
    mHandler.post(this);
    super.onCreate();
}

@Override
public void onDestroy() 
{
    mHandler.removeCallbacks(this);     
    super.onDestroy();
}

@Override
public void run()
{
    InciarGPSTracker();
}

0

这是我的解决方案:

步骤1: 在清单文件中注册服务

<receiver
    android:name=".MySMSBroadcastReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED" />
    </intent-filter>
</receiver>

步骤2:服务代码

public class FusedLocationService extends Service {

    private String mLastUpdateTime = null;

    // bunch of location related apis
    private FusedLocationProviderClient mFusedLocationClient;
    private SettingsClient mSettingsClient;
    private LocationRequest mLocationRequest;
    private LocationSettingsRequest mLocationSettingsRequest;
    private LocationCallback mLocationCallback;
    private Location lastLocation;

    // location updates interval - 10sec
    private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 5000;

    // fastest updates interval - 5 sec
    // location updates will be received if another app is requesting the locations
    // than your app can handle
    private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = 500;
    private DatabaseReference locationRef;
    private int notificationBuilder = 0;
    private boolean isInitRef;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.log("LOCATION GET DURATION", "start in service");
        init();
        return START_STICKY;
    }

    /**
     * Initilize Location Apis
     * Create Builder if Share location true
     */
    private void init() {
        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
        mSettingsClient = LocationServices.getSettingsClient(this);
        mLocationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                super.onLocationResult(locationResult);
                receiveLocation(locationResult);
            }
        };

        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
        builder.addLocationRequest(mLocationRequest);
        mLocationSettingsRequest = builder.build();
        startLocationUpdates();
    }

    /**
     * Request Location Update
     */
    @SuppressLint("MissingPermission")
    private void startLocationUpdates() {
        mSettingsClient
                .checkLocationSettings(mLocationSettingsRequest)
                .addOnSuccessListener(locationSettingsResponse -> {
                    Log.log(TAG, "All location settings are satisfied. No MissingPermission");

                    //noinspection MissingPermission
                    mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper());
                })
                .addOnFailureListener(e -> {
                    int statusCode = ((ApiException) e).getStatusCode();
                    switch (statusCode) {
                        case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                            Log.loge("Location settings are not satisfied. Attempting to upgrade " + "location settings ");
                            break;
                        case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                            Log.loge("Location settings are inadequate, and cannot be " + "fixed here. Fix in Settings.");
                    }
                });
    }

    /**
     * onLocationResult
     * on Receive Location  share to other activity and save if save true
     *
     * @param locationResult
     */
    private void receiveLocation(LocationResult locationResult) {
        lastLocation = locationResult.getLastLocation();

        LocationInstance.getInstance().changeState(lastLocation);

        saveLocation();
    }

    private void saveLocation() {
        String saveLocation = getsaveLocationStatus(this);

        if (saveLocation.equalsIgnoreCase("true") && notificationBuilder == 0) {
            notificationBuilder();
            notificationBuilder = 1;
        } else if (saveLocation.equalsIgnoreCase("false") && notificationBuilder == 1) {
            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).cancel(1);
            notificationBuilder = 0;
        }

        Log.logd("receiveLocation : Share :- " + saveLocation + ", [Lat " + lastLocation.getLatitude() + ", Lng" + lastLocation.getLongitude() + "], Time :- " + mLastUpdateTime);

        if (saveLocation.equalsIgnoreCase("true") || getPreviousMin() < getCurrentMin()) {
            setLatLng(this, lastLocation);

            mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());

            if (isOnline(this) && !getUserId(this).equalsIgnoreCase("")) {
                if (!isInitRef) {
                    locationRef = getFirebaseInstance().child(getUserId(this)).child("location");
                    isInitRef = true;
                }
                if (isInitRef) {
                    locationRef.setValue(new LocationModel(lastLocation.getLatitude(), lastLocation.getLongitude(), mLastUpdateTime));
                }
            }
        }
    }

    private int getPreviousMin() {
        int previous_min = 0;
        if (mLastUpdateTime != null) {
            String[] pretime = mLastUpdateTime.split(":");
            previous_min = Integer.parseInt(pretime[1].trim()) + 1;

            if (previous_min > 59) {
                previous_min = 0;
            }
        }
        return previous_min;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopLocationUpdates();
    }

    /**
     * Remove Location Update
     */
    public void stopLocationUpdates() {
        mFusedLocationClient
                .removeLocationUpdates(mLocationCallback)
                .addOnCompleteListener(task -> Log.logd("stopLocationUpdates : "));
    }

    private void notificationBuilder() {
        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

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

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("").build();

            startForeground(1, notification);
        }
    }

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

步骤三:清单

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

步骤4:Gradle

implementation 'com.google.android.gms:play-services-location:16.0.0'

这个怎么使用? - Yaroslav Dukal
按照这里提到的步骤进行操作。 - Rajneesh Shukla

0

使用Hilt的2023解决方案

这里提供了一个优秀的实现,使用Hilt和稳定的原则。如果有人有时间,请编辑并解释如何使其更好。这是基于https://github.com/philipplackner/BackgroundLocationTracking

MainActivity:

ActivityCompat.requestPermissions(
    this,
    arrayOf(
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.ACCESS_FINE_LOCATION,
    ),
    0
)

//To start service
Intent(applicationContext, LocationService::class.java).apply {
    action = LocationService.ACTION_START
    startService(this)
}

//To stop service
Intent(applicationContext, LocationService::class.java).apply {
    action = LocationService.ACTION_STOP
    startService(this)
}

服务:

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import online.slis.core.data.repository.LocationApi
import javax.inject.Inject

@AndroidEntryPoint
class LocationService : Service() {
    private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    @Inject
    lateinit var locationClient: LocationApi

    override fun onBind(p0: Intent?): IBinder? = null

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            ACTION_START -> start()
            ACTION_STOP -> stop()
        }
        return super.onStartCommand(intent, flags, startId)
    }

    private fun start() {
        locationClient
            .getLocationUpdates(5000)//location updates from 5 to 5
            .catch { e -> e.printStackTrace() }
            .onEach { location ->
                val lat = location.latitude.toString()
                val long = location.longitude.toString()

                Log.d("locationupdates", "lat: $lat, long: $long")
            }
            .launchIn(serviceScope)
    }

    private fun stop() {
        stopSelf()
    }

    override fun onDestroy() {
        serviceScope.cancel()
    }

    companion object {
        const val ACTION_START = "ACTION_START"
        const val ACTION_STOP = "ACTION_STOP"
    }
}

模块:

@Module
@InstallIn(SingletonComponent::class)
object DataProviderModule{
    @Provides
    @Singleton
    fun provideFusedLocationProviderClient(
        @ApplicationContext context: Context
    ): FusedLocationProviderClient {
        return LocationServices.getFusedLocationProviderClient(context)
    }
}

@Module
@InstallIn(SingletonComponent::class)
interface DataModule {
    @Binds
    @Singleton
    fun bindLocationRepository(
        locationRepository: LocationRepository
    ): LocationApi
}

位置API:

interface LocationApi {
    fun getLocationUpdates(interval: Long): Flow<Location>
    class LocationException(message: String): Exception()
}

位置仓库:

class LocationRepository @Inject constructor(
    @ApplicationContext private val context: Context,
    private val client: FusedLocationProviderClient
) : LocationApi {

    @SuppressLint("MissingPermission")
    override fun getLocationUpdates(interval: Long): Flow<Location> {
        return callbackFlow {
            if (!context.hasLocationPermission()) {
                throw LocationApi.LocationException("Missing location permission")
            }

            val locationManager =
                context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
            val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
            val isNetworkEnabled =
                locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)

            if (!isGpsEnabled && !isNetworkEnabled) {
                throw LocationApi.LocationException("GPS is disabled")
            }

            val request = LocationRequest.Builder(interval)
                .setMinUpdateIntervalMillis(interval)
                .build()

            val locationCallback = object : LocationCallback() {
                override fun onLocationResult(result: LocationResult) {
                    super.onLocationResult(result)
                    result.locations.lastOrNull()?.let { location ->
                        launch { send(location) }
                    }
                }
            }

            client.requestLocationUpdates(
                request,
                locationCallback,
                Looper.getMainLooper()
            )

            awaitClose {
                client.removeLocationUpdates(locationCallback)
            }
        }
    }
}

fun Context.hasLocationPermission(): Boolean {
    return ContextCompat.checkSelfPermission(
        this,
        Manifest.permission.ACCESS_COARSE_LOCATION
    ) == PackageManager.PERMISSION_GRANTED &&
            ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接