快速获取Android设备的当前位置

60

我有一个需要设备当前位置(经度和纬度)的android应用程序。我尝试了网上的一些教程,特别是一些来自stackoverflow的解决方案,但它们对我来说效果不好。我的要求很简单:首先,我需要它快速,并且在片段启动时只需获取一次位置信息。其次,我需要尽可能精确,也就是说,如果GPS不可用,则应该首先使用GPS,然后使用网络提供商。

例如,我尝试了这个解决方案,但它在30秒后返回null,但我知道一切都正常,因为google地图和其他应用程序都可以正常工作!!!

几乎所有答案都建议使用getLastKnownLocation(),但我认为它不是当前位置,如果是这样,我不想使用它。

有人能否建议我一种简单快捷的方法,只需获得一次位置信息?!


1
检查这个 https://dev59.com/yanka4cB1Zd3GeqPKzyf#53348535 - Ketan Ramani
在同一个上述线程中,有一个使用LocationProvider类的示例,该类提供了灵活性以获取从一个到无限个位置更新。 - Mabz
请查看以下链接https://dev59.com/yanka4cB1Zd3GeqPKzyf#68096652,希望能对您有所帮助。 - gpuser
8个回答

91

在这里,你可以使用这个...

示例用法:

public void foo(Context context) {
  // when you need location
  // if inside activity context = this;

  SingleShotLocationProvider.requestSingleUpdate(context, 
   new SingleShotLocationProvider.LocationCallback() {
     @Override public void onNewLocationAvailable(GPSCoordinates location) {
       Log.d("Location", "my location is " + location.toString());
     }
   });
}

你可能希望验证纬度/经度是实际值而不是0或其他。如果我记得正确,这不应抛出NPE,但你可能要验证一下。

public class SingleShotLocationProvider {

  public static interface LocationCallback {
      public void onNewLocationAvailable(GPSCoordinates location);
  }

  // calls back to calling thread, note this is for low grain: if you want higher precision, swap the 
  // contents of the else and if. Also be sure to check gps permission/settings are allowed.
  // call usually takes <10ms
  public static void requestSingleUpdate(final Context context, final LocationCallback callback) {
      final LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
      boolean isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
      if (isNetworkEnabled) {
          Criteria criteria = new Criteria();
          criteria.setAccuracy(Criteria.ACCURACY_COARSE);
          locationManager.requestSingleUpdate(criteria, new LocationListener() {
              @Override
              public void onLocationChanged(Location location) {
                  callback.onNewLocationAvailable(new GPSCoordinates(location.getLatitude(), location.getLongitude()));
              }

              @Override public void onStatusChanged(String provider, int status, Bundle extras) { }
              @Override public void onProviderEnabled(String provider) { }
              @Override public void onProviderDisabled(String provider) { }
          }, null);
      } else {
          boolean isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
          if (isGPSEnabled) {
              Criteria criteria = new Criteria();
              criteria.setAccuracy(Criteria.ACCURACY_FINE);
              locationManager.requestSingleUpdate(criteria, new LocationListener() {
                  @Override
                  public void onLocationChanged(Location location) {
                      callback.onNewLocationAvailable(new GPSCoordinates(location.getLatitude(), location.getLongitude()));
                  }

                  @Override public void onStatusChanged(String provider, int status, Bundle extras) { }
                  @Override public void onProviderEnabled(String provider) { }
                  @Override public void onProviderDisabled(String provider) { }
              }, null);
          }
      }
  }


  // consider returning Location instead of this dummy wrapper class
  public static class GPSCoordinates {
      public float longitude = -1;
      public float latitude = -1;

      public GPSCoordinates(float theLatitude, float theLongitude) {
          longitude = theLongitude;
          latitude = theLatitude;
      }

      public GPSCoordinates(double theLatitude, double theLongitude) {
          longitude = (float) theLongitude;
          latitude = (float) theLatitude;
      }
  }  
}

8
为了改善SingleShotLocationProvider,可以在运行时添加权限检查,或者让该方法处理可能出现的SecurityException异常。 - Bart Burg
谢谢,它可以在Android 5及以下版本上运行。但我无法在Android 7上运行它。 - MHSaffari
10
2019年时这是否仍是最佳/推荐的方式? - Abhilash Kishore

8

对于任何想要获取单个位置更新的人,使用最新的API和Kotlin的魔力以最佳、惯用方式进行操作,请看这里:

Gradle依赖:

dependencies {
    ...
    implementation "com.google.android.gms:play-services-location:18.0.0"
    ...
}

清单文件权限:

<manifest>
    ...
    <!-- required only for LocationRequest.PRIORITY_HIGH_ACCURACY -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> 
    <!-- required for all other priorities -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    ...
</manifest>

在你的 Extensions 文件中的某个位置:

// To use PRIORITY_HIGH_ACCURACY, you must have ACCESS_FINE_LOCATION permission.
// Any other priority will require just ACCESS_COARSE_LOCATION,
// but will not guarantee a location update
@SuppressLint("MissingPermission")
suspend fun FusedLocationProviderClient.awaitCurrentLocation(priority: Int): Location? {
    return suspendCancellableCoroutine {
        // to use for request cancellation upon coroutine cancellation
        val cts = CancellationTokenSource()
        getCurrentLocation(priority, cts.token)
            .addOnSuccessListener {location ->
                // remember location is nullable, this happens sometimes
                // when the request expires before an update is acquired
                it.resume(location)
            }.addOnFailureListener {e ->
                it.resumeWithException(e)
            }

        it.invokeOnCancellation {
            cts.cancel()
        }
    }
}

在你的片段中:

// need to register this anywhere before onCreateView, idealy as a field
private val permissionRequester = registerForActivityResult(
    // you can use RequestPermission() contract if you only need 1 permission
    ActivityResultContracts.RequestMultiplePermissions()
) { map ->
    // If you requested 1 permission, change `map` to `isGranted`
    // Keys are permissions Strings, values are isGranted Booleans
    // An easy way to check if "any" permission was granted is map.containsValue(true)
    // You can use your own logic for multiple permissions, 
    // but they have to follow the same checks here:
    val response = map.entries.first()
    val permission = response.key
    val isGranted = response.value
    when {
        isGranted -> onPermissionGranted()
        ActivityCompat.shouldShowRequestPermissionRationale(requireContext(), permission) -> {
            // permission denied but not permanently, tell user why you need it. 
            // Idealy provide a button to request it again and another to dismiss
            AlertDialog.Builder(requireContext())
                .setTitle(R.string.perm_request_rationale_title)
                .setMessage(R.string.perm_request_rationale)
                .setPositiveButton(R.string.request_perm_again) { _, _ -> 
                     requirePermission() 
                }
                .setNegativeButton(R.string.dismiss, null)
                .create()
                .show()
        } 
        else -> {
            // permission permanently denied
            // 1) tell user the app won't work as expected, or
            // 2) take him to your app's info screen to manually change permissions, or
            // 3) silently and gracefully degrade user experience
            // I'll leave the implementation to you
        }
    }
}

onPermissionGranted 函数:

private fun onPermissionGranted() {
    val lm = requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
    if(LocationManagerCompat.isLocationEnabled(lm)) {
        // you can do this your own way, eg. from a viewModel
        // but here is where you wanna start the coroutine.
        // Choose your priority based on the permission you required
        val priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        lifecycleScope.launch {
            val location = LocationServices
                .getFusedLocationProviderClient(requireContext())
                .awaitCurrentLocation(priority)
            // do whatever with this location, notice that it's nullable
        }
    } else {
        // prompt user to enable location or launch location settings check
    }
}

现在你只需要将这个代码添加到MyLocation按钮的点击监听器中:
private fun requirePermission() {
    val permissions = arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        // optional: Manifest.permission.ACCESS_COARSE_LOCATION
    )
    permissionRequester.launch(permissions)
}

请注意,这种方法可以美观地检查权限是否已经隐含地给予,并在这种情况下不显示对话框/请求。 因此,请始终通过启动请求器来开始您的流程,并仅在其回调中进行检查。

5

AndroidManifest.xml

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />

Requesting User Permissions


build.gradle(模块:app)

dependencies {
    ...
    implementation 'com.google.android.gms:play-services-location:15.0.0'
    ...
}

如果您收到错误,请检查您的顶级build.gradle文件是否包含对google()仓库或maven { url "https://maven.google.com" }的引用。Set Up Google Play Services

LocationService.kt

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.net.Uri
import android.os.Looper
import android.provider.Settings
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.location.*
import org.jetbrains.anko.alert
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.okButton

object LocationService {

    @SuppressLint("StaticFieldLeak")
    private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
    private lateinit var locationRequest: LocationRequest
    private val locationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {
            doAsync {
                location = locationResult.lastLocation
                onSuccess(location)
            }
        }
    }
    private lateinit var onSuccess: (location : Location) -> Unit
    private lateinit var onError: () -> Unit
    lateinit var location: Location

    fun init(activity: Activity) {
        fusedLocationProviderClient = FusedLocationProviderClient(activity)
        locationRequest = LocationRequest().setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY).setInterval(1000).setFastestInterval(1000).setNumUpdates(1)
    }

    private fun checkLocationStatusAndGetLocation(activity: Activity) {
        doAsync {
            when {
                ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED -> LocationServices.getSettingsClient(activity).checkLocationSettings(LocationSettingsRequest.Builder().addLocationRequest(locationRequest).setAlwaysShow(true).build()).addOnCompleteListener { task ->
                    doAsync {
                        try {
                            task.getResult(ApiException::class.java)
                            fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
                        } catch (exception: ApiException) {
                            when (exception.statusCode) {
                                LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> {
                                    try {
                                        (exception as ResolvableApiException).startResolutionForResult(activity, 7025)
                                    } catch (ex: Exception) {
                                        promptShowLocation(activity)
                                    }
                                }
                                LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> {
                                    promptShowLocation(activity)
                                }
                            }
                        }
                    }
                }
                ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_FINE_LOCATION) -> activity.runOnUiThread {
                    activity.alert("To continue, allow the device to use location, witch uses Google's Location Service") {
                        okButton {
                            val ite = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", activity.packageName, null))
                            ite.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                            activity.startActivity(ite)
                            onError()
                        }
                        negativeButton("Cancelar", { onError() })
                        onCancelled { onError() }
                    }.show()
                }
                else -> ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 7024)
            }
        }
    }

    private fun promptShowLocation(activity: Activity) {
        activity.runOnUiThread {
            activity.alert("To continue, allow the device to use location, witch uses Google's Location Service") {
                okButton {
                    activity.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
                    onError()
                }
                negativeButton("Cancelar", { onError() })
                onCancelled { onError() }
            }.show()
        }
    }

    fun onRequestPermissionsResult(activity: Activity, requestCode: Int, grantResults: IntArray) {
        if (requestCode == 7024) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                checkLocationStatusAndGetLocation(activity)
            } else {
                onError()
            }
        }
    }

    fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int) {
        if (requestCode == 7025) {
            if (resultCode == Activity.RESULT_OK) {
                checkLocationStatusAndGetLocation(activity)
            } else {
                onError()
            }
        }
    }

    fun getLocation(activity: Activity, onSuccess: () -> Unit, onError: () -> Unit) {
        this.onSuccess = onSuccess
        this.onError = onError
        checkLocationStatusAndGetLocation(activity)
    }

}

你的活动

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    LocationService.init(this)
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    LocationService.onRequestPermissionsResult(this, requestCode, grantResults)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    LocationService.onActivityResult(this, requestCode, resultCode)
}

private fun yourFunction() {
    LocationService.getLocation(this, { location ->
        //TODO: use the location
    }, {
        //TODO: display error message
    })
}

这正是我想要的。它运行得非常好,在进行 API 调用之前,我能够获取纬度和经度。谢谢。 - K P

5

AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />

MainActivity.java:

public class MainActivity extends AppCompatActivity implements LocationListener {

    private LocationManager locationManager;
    private Location onlyOneLocation;
    private final int REQUEST_FINE_LOCATION = 1234;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)
            ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_FINE_LOCATION);
    }

    @Override public void onLocationChanged(Location location) {
        onlyOneLocation = location;
        locationManager.removeUpdates(this);
    }
    @Override public void onStatusChanged(String provider, int status, Bundle extras) { }
    @Override public void onProviderEnabled(String provider) { }
    @Override public void onProviderDisabled(String provider) { }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
        case REQUEST_FINE_LOCATION:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.d("gps", "Location permission granted");
                try {
                    locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
                    locationManager.requestLocationUpdates("gps", 0, 0, this);
                }
                catch (SecurityException ex) {
                    Log.d("gps", "Location permission did not work!");
                }
            }
            break;
    }
}

好的,它编译通过了,但是不确定位置存储在哪里。 - Joe
1
onlyOneLocation.getLatituide(); onlyOneLocation.getLongitude(); - Lou Morda
在 "onlyOneLocation = location;" 中添加一个空值检查,否则位置可能会被设置为空,然后永远不会更新... - Biswas Khayargoli

2
你需要做的是使用LocationManager#requestSingleUpdate来实现。这个方法会在给定的looper中附加一个监听器(如果你需要或者有它),并且只会在收到位置信息时立即通知一次。你提出的方法仅用于在真正的位置信息到达之前获取不精确的位置。

无论如何,它都会比毫秒级别更快(除非你足够幸运,在设备接收到位置信息时开始监听)。将GPS视为一个在等待位置时启用并在移除此监听时禁用的元素。此行为是为了避免耗尽用户的电池。

因此,总结一下:

  • 从开始监听到接收到位置信息的时间取决于设备的GPS(制造商、用户位置、卫星覆盖范围等)
  • Android SDK中有一个方法可以监听单个更新。
  • 通过提供一个标准对象,您可以管理哪些标准对您接收位置信息是可接受的。更强的标准意味着需要更多的时间来获得准确的响应。

1
    // Get LocationManager object
    LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

    // Create a criteria object to retrieve provider
    Criteria criteria = new Criteria();

    // Get the name of the best provider
    String provider = locationManager.getBestProvider(criteria, true);

    // Get Current Location
    Location myLocation = locationManager.getLastKnownLocation(provider);

    //latitude of location
    double myLatitude = myLocation.getLatitude();

    //longitude og location
    double myLongitude = myLocation.getLongitude();

    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;
    }

1
为什么if语句使用'&&'而不是'||'?你只需要其中一个权限来访问位置吗? - DAVIDBALAS1

0

所有上述答案对我都没有用,所以我回答了这个问题。 首先添加依赖项。

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />

在添加MyLocationListiner.java类之后

package com.example.firebase_auth;

/**
 * Created by Chromicle(Ajay Prabhakar).
 */

import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;

import static android.content.Context.LOCATION_SERVICE;

public class MyLocationListener implements LocationListener {

    public static double latitude;
    Context ctx;
    Location location;
    LocationManager locationManager;
    boolean isGPSEnabled = false;
    boolean isNetworkEnabled = false;
    public static double longitude;
    MyLocationListener(Context ctx) {
        this.ctx = ctx;
        try {
            locationManager = (LocationManager) ctx.getSystemService(LOCATION_SERVICE);
            isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
            Toast.makeText(ctx, "GPS Enable " + isGPSEnabled, Toast.LENGTH_LONG).show();
            isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
            Toast.makeText(ctx, "Network Enable " + isNetworkEnabled, Toast.LENGTH_LONG).show();

            if ( Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission
                    ( ctx, android.Manifest.permission.ACCESS_FINE_LOCATION )
                    != PackageManager.PERMISSION_GRANTED &&
                    ContextCompat.checkSelfPermission( ctx,
                            android.Manifest.permission.ACCESS_COARSE_LOCATION) !=
                            PackageManager.PERMISSION_GRANTED) {  }
            if (isGPSEnabled == true) {
                locationManager.requestLocationUpdates(
                        LocationManager.GPS_PROVIDER,     0,       0, this);
                location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
            }
            if (isNetworkEnabled==true) {
                locationManager.requestLocationUpdates(
                        LocationManager.NETWORK_PROVIDER,    0,     0, this);
                location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
            }
            latitude = location.getLatitude();
            longitude = location.getLongitude();
            // Toast.makeText(ctx,"latitude: "+latitude+" longitude: "+longitude,Toast.LENGTH_LONG).show();


        }
        catch(Exception ex)
        {

            Toast.makeText(ctx,"Exception "+ex, Toast.LENGTH_LONG).show();
        }
    }
    @Nullable
    @Override
    public void onLocationChanged(Location loc)
    {
        loc.getLatitude();
        loc.getLongitude();
        latitude=loc.getLatitude();
        longitude=loc.getLongitude();
    }

    @Override
    public void onProviderDisabled(String provider)
    {
        //print "Currently GPS is Disabled";
    }
    @Override
    public void onProviderEnabled(String provider)
    {
        //print "GPS got Enabled";
    }
    @Override
    public void onStatusChanged(String provider, int status, Bundle extras)
    {

    }
}

要使用该类,请添加此方法,位置存储在地址字符串中。
public void getLocation(){
        Double latitude = 0.0, longitude;
        String message = "";
        LocationManager mlocManager = null;
        LocationListener mlocListener;
        mlocManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        mlocListener = new MyLocationListener(this);
        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;
        }
        mlocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mlocListener);
        if (mlocManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {

            latitude = MyLocationListener.latitude;
            longitude = MyLocationListener.longitude;
            message = message +"https://www.google.com/maps/dir/@"+ latitude +","+  longitude;
            address=message;
            Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
            if (latitude == 0.0) {
                Toast.makeText(getApplicationContext(), "Currently gps has not found your location....", Toast.LENGTH_LONG).show();
            }

        } else {
            Toast.makeText(getApplicationContext(), "GPS is currently off...", Toast.LENGTH_LONG).show();
        }
    }

希望它有用


-1

我创建了一些类,使用它们可以轻松获取当前位置。我使用FusedLocationProviderClient来获取当前位置。

首先将以下内容添加到您的清单文件中:

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

然后检查位置权限:

 private fun startCheckingLocation() {
    if (checkLocationPermissions() == true) {
        checkGPSEnabled()
    } else {
        askLocationPermission()
    }
}

checkLocationPermissions方法:

 private fun checkLocationPermissions(): Boolean? {
    return PermissionUtils.hasPermissions(
        requireContext(),
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )
}

checkGPSEnabled方法:

 private fun checkGPSEnabled() {
    GpsUtils(requireContext()) {
        it?.let {
            startCheckingCurrentLocation()
        }
    }.apply {
        turnGPSOn(gpsDialogCallback)
    }
}

由于OnActivityResult已被弃用:

     private val gpsDialogCallback =     registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
        activityResult?.let { result ->
            when (result.resultCode) {
                RESULT_OK -> {
                    startCheckingCurrentLocation()
                }
                RESULT_CANCELED -> {
                }
            }
        }
    }

startCheckingCurrentLocation 方法:

 private fun startCheckingCurrentLocation() {
    LocationUtils(requireContext()) { location ->
        Log.d(TAG, ">>>>>>>>>>>>>>" + location.latitude + " " + location.longitude)
        startIntentService(location)
    }.apply {
        startLocationUpdates()
    }
}

我已经创建了一个GPS类,您可以简单地放置和使用它:
GPSUtils:
class GpsUtils(
private val context: Context,
private val gpsStatus: (isEnable: Boolean?) -> Unit
) {

private val mSettingsClient: SettingsClient = LocationServices.getSettingsClient(context)
private val mLocationSettingsRequest: LocationSettingsRequest
private val locationManager: LocationManager =
    context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val locationRequest: LocationRequest = LocationRequest.create()

init {
    locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
    locationRequest.interval = 10 * 1000.toLong()
    locationRequest.fastestInterval = 2 * 1000.toLong()
    val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
    mLocationSettingsRequest = builder.build()
    builder.setAlwaysShow(true) //this is the key ingredient
}

// method for turn on GPS
fun turnGPSOn(gpsDialogCallback: ActivityResultLauncher<IntentSenderRequest>) {
    if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
        gpsStatus.invoke(true)
    } else {
        mSettingsClient
            .checkLocationSettings(mLocationSettingsRequest)
            .addOnSuccessListener(
                (context as Activity)
            ) {
                //  GPS is already enable, callback GPS status through listener
                gpsStatus.invoke(true)
            }
            .addOnFailureListener(context) { e ->
                when ((e as ApiException).statusCode) {
                    LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try {
                        // Show the dialog by calling startResolutionForResult() and check the result in onActivityResult().

                        if (e is ResolvableApiException) {
                            try {
                                val intentSenderRequest = IntentSenderRequest.Builder(e.resolution).build()
                                gpsDialogCallback.launch(intentSenderRequest)
                            } catch (throwable: Throwable) {
                                // Ignore the error.
                            }
                        }

                    } catch (sie: IntentSender.SendIntentException) {
                        // Ignore the error.
                        Timber.i("PendingIntent unable to execute request.")
                    }
                    LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> {
                        // Location settings are not satisfied. However, we have no way to fix the
                        // settings so we won't show the dialog.
                        val errorMessage =
                            "Location settings are inadequate, and cannot be fixed here. Fix in Settings."
                        Timber.e(errorMessage)
                    }
                    LocationSettingsStatusCodes.CANCELED -> {
                        val errorMessage =
                            "Location settings are inadequate, and cannot be fixed here. Fix in Settings."
                        Timber.e(errorMessage)
                    }
                    LocationSettingsStatusCodes.SUCCESS -> {
                        // All location settings are satisfied. The client can initialize location
                        // requests here.
                        val errorMessage =
                            "Location settings are inadequate, and cannot be fixed here. Fix in Settings."
                        Timber.e(errorMessage)
                    }
                }
            }
    }
}

}

为了检查位置,我创建了另一个类:

class LocationUtils(
context: Context,
private val latLng: (location: Location) -> Unit) {

private var fusedLocationClient: FusedLocationProviderClient? = null

private val locationRequest = LocationRequest.create()?.apply {
    interval = 20 * 1000.toLong()
    fastestInterval = 2 * 1000.toLong()
    priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}


init {
    fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
}

/**
 * call when location permission is allowed and you want to fetch the last location of the user
 */
fun getLastLocation() {
    fusedLocationClient?.lastLocation?.addOnSuccessListener { location ->
        location?.let {
            latLng.invoke(location)
            stopLocationUpdates()
        }
    }
}

/**
 * Requested location callback
 */
private val locationCallback = object : LocationCallback() {
    override fun onLocationResult(locationResult: LocationResult?) {

        locationResult ?: return

        for (location in locationResult.locations) {
            location?.let {
                latLng.invoke(it)
                stopLocationUpdates()
            }
        }
        super.onLocationResult(locationResult)
    }
}

/**
 * call when location permission is already given to user and you want to receive continues location updates
 */
fun startLocationUpdates() {
    fusedLocationClient?.requestLocationUpdates(
        locationRequest,
        locationCallback,
        Looper.getMainLooper()
    )
}

/**
 * call when you want to stop location updates
 */
fun stopLocationUpdates() {
    fusedLocationClient?.removeLocationUpdates(locationCallback)?.addOnCompleteListener { }
}


}

我创建了一个工作类来从latlng获取地址:
class FetchAddressWorker(
context: Context,
workerParameters: WorkerParameters
) :
Worker(context, workerParameters) {

companion object {
    const val TAG = "FetchAddressWorker"
}

override fun doWork(): Result {

    val geoCoder = Geocoder(applicationContext, Locale.getDefault())

    var errorMessage = ""

    val lat: Double = inputData.getDouble(CPKConstants.LATITUDE, 0.0)
    val lng: Double = inputData.getDouble(CPKConstants.LONGITUDE, 0.0)

    var addresses: List<Address> = emptyList()

    try {
        addresses =
            geoCoder.getFromLocation(lat, lng, 1)
    } catch (ioException: IOException) {
        // Catch network or other I/O problems.
        errorMessage = "Service not available"
        LogUtils.e(TAG, errorMessage, ioException)
    } catch (illegalArgumentException: IllegalArgumentException) {
        // Catch invalid latitude or longitude values.
        errorMessage = "Invalid lat lng used"
        LogUtils.e(
            TAG,
            "$errorMessage. Latitude = $lat , Longitude =  $lng",
            illegalArgumentException
        )
    }

    // Handle case where no address was found.
    if (addresses.isEmpty()) {
        if (errorMessage.isEmpty()) {
            errorMessage = "No Address Found"
            LogUtils.e(TAG, errorMessage)
        }

        val data = Data.Builder()
            .putString(
                CPKConstants.FAILURE_RESULT,
                errorMessage
            )
            .build()

        return Result.failure(data)
    } else {
        val address: Address = addresses[0]
        // Fetch the address lines using getAddressLine,
        // join them, and send them to the thread.
        val addressFragments = with(address) {
            (0..maxAddressLineIndex).map { getAddressLine(it) }
        }
        LogUtils.i(TAG, "Address Found " + addressFragments.joinToString(separator = "\n"))
        
       
        val data = Data.Builder()
            .putString(
                CPKConstants.SUCCESS_RESULT,
                addressFragments.joinToString(separator = "\n")
            )
            .build()
        // Indicate whether the work finished successfully with the Result
        return Result.success(data)
    }

}

}

然后在您的片段或活动中使用AddressResultReceiver:

 internal inner class AddressResultReceiver(handler: Handler) : ResultReceiver(handler) {

    override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {

        // Display the address string
        // or an error message sent from the intent service.
        val addressOutput = resultData?.getString(AppConstants.RESULT_DATA_KEY).orEmpty()
        //displayAddressOutput()
        // Show a toast message if an address was found.
        if (resultCode == AppConstants.SUCCESS_RESULT) {
            Boast.showText(requireContext(), "Address found = $addressOutput")
            txtContinueWith.text = addressOutput
        }

    }
}

您需要在片段或活动中初始化此项,以便使用上述接收器获取地址:

  private var resultReceiver = AddressResultReceiver(Handler())

以下是一些常量,您应该直接使用它们。

//Location Constants
const val LOCATION_SERVICE = "LOCATION_SERVICE"
const val SUCCESS_RESULT = 0
const val FAILURE_RESULT = 1
const val PACKAGE_NAME = "com.google.android.gms.location.sample.locationaddress"
const val RECEIVER = "$PACKAGE_NAME.RECEIVER"
const val RESULT_DATA_KEY = "${PACKAGE_NAME}.RESULT_DATA_KEY"
const val LOCATION_DATA_EXTRA = "${PACKAGE_NAME}.LOCATION_DATA_EXTRA"

从视图启动工作程序

  private fun startAddressWorkManager(location: Location) {

    val inputData = Data.Builder()
        .putDouble(CPKConstants.LATITUDE, location.latitude)
        .putDouble(CPKConstants.LONGITUDE, location.longitude)
        .build()

    val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build()

    val fetchAddressWorkRequest: WorkRequest =
        OneTimeWorkRequestBuilder<FetchAddressWorker>()
            .setConstraints(constraints)
            .setInputData(inputData)
            .build()

    WorkManager
        .getInstance(this)
        .enqueue(fetchAddressWorkRequest)

    WorkManager.getInstance(this).getWorkInfoByIdLiveData(fetchAddressWorkRequest.id)
        .observe(this,
            { workInfo ->
                when (workInfo.state) {
                    WorkInfo.State.SUCCEEDED -> {
                        if (workInfo.state.isFinished) {
                            val addressData =
                                workInfo.outputData.getString(CPKConstants.SUCCESS_RESULT)
                            Timber.d("AddressData %s", addressData)
                            _binding?.searchEdt?.setText(addressData)
                        }
                    }
                    else -> {
                        Timber.d("workInfo %s", workInfo)
                    }
                }
            })

}

1
一个简单的位置请求变成了这种丑陋的东西...我真的希望没有人从这个答案中复制任何东西,并延续它所包含的所有暴行。虽然其他答案也不怎么样,但这个是最丑陋的,所以我必须采取行动。 - Ace
2
@Ace,:))) 也许你是对的,但这个答案包含了一些好的方法。我看到了很多解决方案,它们都包含错误。大多数示例甚至不检查 GPS 设置,也不检查 addOnFailureListener,不检查 addOnSuccessListenerlocation == nulllocationAvailability?.isLocationAvailable == false)等等。FusedLocationProviderClient 存在很多漏洞。 - CoolMind

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