FusedLocationApi.getLastLocation 始终为 null

83

我希望在我的Android项目中简单地获取设备位置,为此我使用play-services的方法:

    protected synchronized void buildGoogleApiClient() {

    mGoogleApiClient = new GoogleApiClient.Builder( MainSearchActivity.this )
        .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
            @Override
            public void onConnected( Bundle bundle ){
                Location location = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
                if( location == null ){
                    LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, new LocationListener() {
                        @Override
                        public void onLocationChanged(Location location) {
                            lastLocation = location;
                        }
                    });
                }
            }
            @Override
            public void onConnectionSuspended( int i ){

            }

        })
        .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
            @Override
            public void onConnectionFailed( ConnectionResult connectionResult ){
                if( connectionResult.hasResolution() ){
                    try {
                        // Start an Activity that tries to resolve the error
                        connectionResult.startResolutionForResult(MainSearchActivity.this, CONNECTION_FAILURE_RESOLUTION_REQUEST);
                    }catch( IntentSender.SendIntentException e ){
                        e.printStackTrace();
                    }
                }else{
                    Utils.logger("Location services connection failed with code " + connectionResult.getErrorCode(), Utils.LOG_DEBUG );
                }
            }
        })
        .addApi(LocationServices.API)
        .build();

    mGoogleApiClient.connect();
}

public Location retrieveLastLocation(){
    Location loc = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
    if( loc == null)
    {

    }
    return loc; //TODO: What if loc is null?
}

loc变量始终为null。在不同的手机上,每次都是这样。同时,我试图在onLocationChanged中赋值的lastLocation也从未改变过。始终为null。

这些是我为应用设置的权限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="com.vogella.android.locationapi.maps.permission.MAPS_RECEIVE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />

我就是不明白:为什么LocationServices不能获取位置?我已经在我测试的三个设备上启用了所有的地理位置设置。


2
我请求您请查看此答案https://dev59.com/tlsV5IYBdhLWcg3wwhHw#35833552 - Subho
2
com.google.android.gms:play-services-location:17.1.0 开始,现在有一个 getCurrentLocation() 方法,它会主动尝试获取用户的当前位置。https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderClient#getCurrentLocation(int,%20com.google.android.gms.tasks.CancellationToken) - Andre Romano
13个回答

72

如果没有至少一个客户端连接到融合的位置提供程序(Location Provider),它将只会在后台维护位置信息。现在仅仅打开位置服务并不能保证存储上一次已知的位置。

一旦第一个客户端连接,它将立即尝试获取位置。如果您的活动是第一个连接的客户端,并且在onConnected()中立即调用了getLastLocation(),那么这可能不足以等待第一个位置信息到达。

我建议您先启动地图应用程序,以便至少有一些确认的位置信息,然后再测试您的应用程序。


74
您的意思是,当用户在我们的应用程序中需要其位置信息时,我们需要打开Google Maps应用程序,然后希望用户会返回到我们的应用程序? - Kaloyan Roussev
27
那么,我该如何在我的应用程序中强制执行某些位置获取事件? - Kaloyan Roussev
6
在这种情况下,您需要经过常规的位置检索流程。毕竟,融合定位提供程序是执行“安全而不太重要”任务的一种方式。 - Amit K. Saha
1
谢谢 - 我没有设置第一个位置,模拟器上的地图帮助我设置了它。但是,您能否澄清一下位置检索的常规过程,以便如果用户尚未收到他们的第一个位置,应用程序不会崩溃?Google没有关于此主题的任何说明:https://developer.android.com/training/location/retrieve-current.html - Simon
9
我认为这篇博客实际上解决了这个问题:http://blog.teamtreehouse.com/beginners-guide-location-android - Simon
显示剩余2条评论

50

就像这篇文章提到的那样,只有至少一个客户端连接到融合位置提供者时,它才会维护后台位置。

但是我们可以通过以下方式跳过启动Google Maps应用程序以获取上次位置。

我们需要做的是:

  1. 我们必须从FusedLocationProviderClient请求位置更新。
  2. 然后,我们可以从FusedLocationProviderClient中获取上次位置,它不会为空。

请求位置

LocationRequest mLocationRequest = LocationRequest.create();
mLocationRequest.setInterval(60000);
mLocationRequest.setFastestInterval(5000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationCallback mLocationCallback = new LocationCallback() {
    @Override
    public void onLocationResult(LocationResult locationResult) {
        if (locationResult == null) {
            return;
        }
        for (Location location : locationResult.getLocations()) {
            if (location != null) {
                //TODO: UI updates.
            }
        }
    }
};
LocationServices.getFusedLocationProviderClient(context).requestLocationUpdates(mLocationRequest, mLocationCallback, null);

获取最后位置

LocationServices.getFusedLocationProviderClient(context).getLastLocation().addOnSuccessListener(new OnSuccessListener<Location>() {
        @Override
        public void onSuccess(Location location) {
            //TODO: UI updates.
        }
    });

为了获得最佳结果,请在Activity的onStart()方法中请求位置更新,然后您可以获取最后位置。


4
这是为我工作的唯一解决方案。我必须按照步骤进行操作,包括使用“PRIORITY_HIGH_ACCURACY”和设置时间间隔。任何间隔似乎都可以起作用。我还添加了“setNumUpdates(1)”。如果有人想知道如何重现上次已知位置为空的问题,只需在模拟器上进行冷启动并立即调用“getLastKnownLocation()”。 - Big McLargeHuge
在这里调用getLastLocation()的目的是什么?是为了在位置回调中第一次更新之前尽早获取位置吗?此外,您可以在调用requestLocationUpdates后立即调用getLastLocation()吗? - Adam Johns
@AdamJohns的getLastLocation()方法只是从FusedLocationProviderClient中返回上次保存的位置,而requestLocationUpdates方法只是用于触发FusedLocationProviderClient请求位置,因此FusedLocationProviderClient将尝试获取最新的位置。基本上,当getLastLocation()方法返回null时,我们将使用此方法。 - Gunaseelan
@Gunaseelan为什么不一开始就使用requestLocationUpdates,而不是只有在getLastLocation返回null时才使用呢? - Adam Johns
您可以使用FusedLocationProviderClient的实际目的是为了节省电池电量。大多数情况下,getLastLocation仅返回您当前的位置,因此我们可以跳过requestLocationUpdates。更直接的答案是,requestLocationUpdates会消耗更多的电池电量。 - Gunaseelan

16

我认为代码中存在一个小错误,但我看不到。

mGoogleApiClient已经构建,但似乎没有连接。您可以通过调用mGoogleApiClient.isConnected()来验证此情况。

您可以简单地重写onStart方法并在其中调用connect。或者,如果您希望每当您的活动可见时访问位置,则可以覆盖onResume()

  @Override
protected void onStart() {
    super.onStart();
    if (mGoogleApiClient != null) {
        mGoogleApiClient.connect();
    }
}

1
哇,这对我有用,但是在onConnected()中使用它:) 谢谢您 - CommonSenseCode

13

首先,创建一个 LocationRequest 对象:

 // Create the LocationRequest object
 mLocationRequest = LocationRequest.create()
    .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
    .setInterval(10 * 1000)        // 10 seconds, in milliseconds
    .setFastestInterval(1 * 1000); // 1 second, in milliseconds

接下来,请确保用户已经授权使用位置信息。如果已经授权,可以按照以下方式从requestLocationUpdates中获取位置:

void getLocation() {
    Location location = null;
    if (ContextCompat.checkSelfPermission(activity, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1);

        /*TODO!! INSERT CODE TO PROMPT USER TO GIVE PERMISSION*/

    } else {
        LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
    }

    mLastLocation = location;
    LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient,this);
}

如果您只需要一个位置而不是持续监视,请务必删除更新。这样,您就永远不会获得空的Location

有关更多信息,请访问http://blog.teamtreehouse.com/beginners-guide-location-android


2
“requestLocationUpdates” 回调仍可能返回空的 “Location”。 - manfcas

3

Android - Kotlin

如果您需要在安卓中获取用户位置,我使用以下的函数并建议使用:

// Get user location after getting permission.
private fun findUserLocation() {

    if (ActivityCompat.checkSelfPermission(requireContext(),
            Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
        && ActivityCompat.checkSelfPermission(requireContext(),
            Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
    ) {
        // TODO: Check location permission. Some one is missing.
    }
    else {
        val locationRequest = LocationRequest.create() // Create location request.
        locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY // Set priority.

        val locationCallback: LocationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                for (location in locationResult.locations) {
                    if (location != null) {

                        // TODO: Show your code here. 
                        // Such as:
                        val lat = location.latitude  
                        val lon = location.longitude
                    }
                }
            }
        }

        // Create a location provider client and send request for getting location.
        val client = LocationServices.getFusedLocationProviderClient(requireContext())
        client.requestLocationUpdates(locationRequest, locationCallback, null)
    }
}

3
我正在使用以下代码,根据Android的最新文档获取位置信息: https://developer.android.com/training/location/retrieve-current https://developer.android.com/training/location/receive-location-updates

MainActivity.java

public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CHECK_SETTINGS = 1;
private static final int REQUEST_GRANT_PERMISSION = 2;
private FusedLocationProviderClient fusedLocationClient;
LocationRequest locationRequest;
private Location currentLocation;
private LocationCallback locationCallback;
Button getUpdates,removeUpdates;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
    createLocationRequest();
    settingsCheck();
    getUpdates = findViewById(R.id.button);
    removeUpdates = findViewById(R.id.button2);

    getUpdates.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ) {
                ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.ACCESS_FINE_LOCATION},REQUEST_GRANT_PERMISSION);
                return;
            }
            if(locationCallback==null)
                buildLocationCallback();
            if(currentLocation==null)
                fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
        }
    });
    removeUpdates.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ) {
                ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.ACCESS_FINE_LOCATION},REQUEST_GRANT_PERMISSION);
                return;
            }
            if(locationCallback!=null)
                fusedLocationClient.removeLocationUpdates(locationCallback);
        }
    });
}

protected void createLocationRequest() {
    locationRequest = LocationRequest.create();
    locationRequest.setInterval(10000);
    locationRequest.setFastestInterval(5000);
    locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}

// Check for location settings
public void settingsCheck() {
    LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
            .addLocationRequest(locationRequest);

    SettingsClient client = LocationServices.getSettingsClient(this);
    Task<LocationSettingsResponse> task = client.checkLocationSettings(builder.build());
    task.addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
        @Override
        public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
            // All location settings are satisfied. The client can initialize
            // location requests here.
            Log.d("TAG", "onSuccess: settingsCheck");
            getCurrentLocation();
        }
    });

    task.addOnFailureListener(this, new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            if (e instanceof ResolvableApiException) {
                // Location settings are not satisfied, but this can be fixed
                // by showing the user a dialog.
                Log.d("TAG", "onFailure: settingsCheck");
                try {
                    // Show the dialog by calling startResolutionForResult(),
                    // and check the result in onActivityResult().
                    ResolvableApiException resolvable = (ResolvableApiException) e;
                    resolvable.startResolutionForResult(MainActivity.this,
                            REQUEST_CHECK_SETTINGS);
                } catch (IntentSender.SendIntentException sendEx) {
                    // Ignore the error.
                }
            }
        }
    });
}

public void getCurrentLocation(){
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.ACCESS_FINE_LOCATION},1);
        return;
    }
    fusedLocationClient.getLastLocation()
            .addOnSuccessListener(this, new OnSuccessListener<Location>() {
                @Override
                public void onSuccess(Location location) {
                    Log.d("TAG", "onSuccess: getLastLocation");
                    // Got last known location. In some rare situations this can be null.
                    if (location != null) {
                        currentLocation=location;
                        Log.d("TAG", "onSuccess:latitude "+location.getLatitude());
                        Log.d("TAG", "onSuccess:longitude "+location.getLongitude());
                    }else{
                        Log.d("TAG", "location is null");
                        buildLocationCallback();
                    }
                }
            });
}

private void buildLocationCallback() {
    locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            if (locationResult == null) {
                return;
            }
            for (Location location : locationResult.getLocations()) {
                // Update UI with location data
                currentLocation=location;
                Log.d("TAG", "onLocationResult: "+currentLocation.getLatitude());
            }
        };
    };
}

//called after user responds to location permission popup
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if(requestCode==REQUEST_GRANT_PERMISSION){
        getCurrentLocation();
    }
}
//called after user responds to location settings popup
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    Log.d("TAG", "onActivityResult: ");
    if(requestCode==REQUEST_CHECK_SETTINGS && resultCode==RESULT_OK)
        getCurrentLocation();
    if(requestCode==REQUEST_CHECK_SETTINGS && resultCode==RESULT_CANCELED)
        Toast.makeText(this, "Please enable Location settings...!!!", Toast.LENGTH_SHORT).show();
}}

XML 文件

<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.locationexample.MainActivity">

<Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginEnd="8dp"
    android:layout_marginStart="8dp"
    android:layout_marginTop="88dp"
    android:text="getLocationUpdates"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.502"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<Button
    android:id="@+id/button2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="8dp"
    android:layout_marginEnd="8dp"
    android:layout_marginStart="8dp"
    android:layout_marginTop="8dp"
    android:text="RemoveLocationUpdates"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/button"
    app:layout_constraintVertical_bias="0.363" /</android.support.constraint.ConstraintLayout>

清单文件

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.locationexample">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>


2
如果getLastLocation()总是返回null,请尝试这个hack。我用它来解决我的问题。在你的活动中实现LocationListener(导入com.google.android.gms.location.LocationListener)。
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);

        Context mContext = this;

        manager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);

        createLocationRequest();

        mGoogleApiClient = new GoogleApiClient.Builder(mContext)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
        mGoogleApiClient.connect();

}

private void createLocationRequest(){
    mLocationRequest = LocationRequest.create();
    mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    mLocationRequest.setInterval(SET_INTERVAL);
    mLocationRequest.setFastestInterval(FASTEST_INTERVAL);
}

在onStart或onResume(我更喜欢onResume)中

@Override
protected void onResume() {
    super.onResume();
    if (manager.isProviderEnabled(LocationManager.GPS_PROVIDER) ) {
        if (mGoogleApiClient != null) {
            mGoogleApiClient.connect();
        }
    }else{
        // Showyourmesg();  
    }
}

@Override
protected void onPause() {
    super.onPause();
    if (mGoogleApiClient != null) {
        mGoogleApiClient.disconnect();
    }
}

protected void startLocationUpdates(){
    LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
            mLocationRequest,this);
}

protected void stopLocationUpdates() {
    LocationServices.FusedLocationApi.removeLocationUpdates(
            mGoogleApiClient, this);
}

现在,在您的onLocationChanged方法中检查googleApiClient是否已连接。如果已连接,请断开连接,然后重新连接。

@Override
public void onLocationChanged(Location location) {
    Log.d("SplashAct", "LocatinChngListner, loc: " + location.getLatitude() + "," + location.getLongitude());

    if (mGoogleApiClient != null)
        if (mGoogleApiClient.isConnected() || mGoogleApiClient.isConnecting()){
            mGoogleApiClient.disconnect();
            mGoogleApiClient.connect();
        } else if (!mGoogleApiClient.isConnected()){
            mGoogleApiClient.connect();
        }
}

最后,在您的onConnected()方法中

@Override
  public void onConnected(Bundle bundle) {

    Log.d("ACTIVITY", "ApiClient: OnConnected");

    mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
            mGoogleApiClient);

    if (mLastLocation == null){
        startLocationUpdates(); // bind interface if your are not getting the lastlocation. or bind as per your requirement.
    }

    if (mLastLocation != null){
        while (latitude == 0 || longitude == 0){
            pDialog.setMessage("Getting Location");
            pDialog.show();

            latitude = mLastLocation.getLatitude();
            longitude = mLastLocation.getLongitude();

            if (latitude != 0 && longitude != 0){
                stopLocationUpdates(); // unbind the locationlistner here or wherever you want as per your requirement.
                pDialog.dismiss(); // location data received, dismiss dialog, call your method or perform your task.
            }
        }
    }
}

我们会不断尝试连接googleApiClient,即使它已经连接,以便我们可以获取lastLocation数据。这将取决于LocationRequest间隔。您也可以在onLocationChanged中获取位置数据,并从那里执行您的任务。

你的 while 循环是否会导致无限循环,还是总是得到一个位置?如果手机没有连接,会发生什么?如果没有连接 GoogleApiClient,如何调用 onLocationChanged()?似乎你想在 onLocationChanged 事件中调用 getLastLocation() 而不是在 onConnected 事件中。 - John Ward
@JohnWard 是的,我总是获取位置信息,我的while循环从未进入无限模式,尽管有时即使在获取位置信息后,我也无法执行进一步的任务,因为我在onconnected中的两种情况下都执行任务,其中lastLocation == null和lastLocation!= null。现在我已经将其更改为if-else条件,并且它完美地工作。连接性?连接到互联网还是GPS?连接到GPS是绝对必要的。我想如果没有连接GoogleApiClient,您无法调用onLocChange。如果它在OnConctd中提供loc,则很好,否则请从OnLocChngd中获取。 - beginner

1

1
如果您使用的是Android Marshmallow(API 23)或更高版本的Android,则可能会遇到相同的问题,因为未授予权限。您应该显式地在运行时请求位置权限,或者如果这是一个测试项目,则可以从手机上的应用程序设置中授予位置权限

1

我之前获取位置信息时得到了null,后来发现模拟器上没有安装地图应用。我安装了地图应用并启用了设备位置,问题就解决了。


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