玩位置服务getLastLocation返回null

4
我正在尝试监听位置变化,但有时候 onLocationChanged 回调函数没有被调用并且 getLastLocation 返回的是 null,而 Google 地图一直完美地工作。 注意 如果我重新启动设备,位置服务将仅在约 2 天内工作;在此之后,我的应用程序和SDK 示例都将无法工作,而 Google 地图仍然可以正常工作。
这是我的服务代码:
public class VisitService extends Service implements
        GooglePlayServicesClient.ConnectionCallbacks,
        GooglePlayServicesClient.OnConnectionFailedListener, 
        LocationListener {

    private final IBinder mBinder = new VisitBinder();


    // A request to connect to Location Services
    private LocationRequest mLocationRequest;

    // Stores the current instantiation of the location client in this object
    private LocationClient mLocationClient;


    private boolean mLocationServiceConnected = false;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        initLocation();
        return super.onStartCommand(intent, flags, startId);
    }

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

    public void startVisit() {
        if (!servicesConnected()) {
            listener.onVisitStartError();
            return;
        }

        if (mLocationServiceConnected) {
            if (isAcceptableLocation(mLocationClient.getLastLocation())) {
                Toast.makeText(this, "You have arrived!", Toast.LENGTH_LONG);
            } else {
                mVisitListener.onVisitStartError();
            }
        }    
    }

    private boolean isAcceptableLocation(Location location) {
        if (location == null) {
            Toast.makeText(this, "Location is null", Toast.LENGTH_LONG).show();
            return false;
        }

        return true;
    }


    private void initLocation() {

        if (mLocationRequest == null) {
            // Create a new global location parameters object
            mLocationRequest = LocationRequest.create();
            /*
             * Set the update interval
             */
            mLocationRequest
                    .setInterval(LocationUtils.UPDATE_INTERVAL_IN_MILLISECONDS);

            // Use high accuracy
            mLocationRequest
                    .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

            // Set the interval ceiling to one minute
            mLocationRequest
                    .setFastestInterval(LocationUtils.FAST_INTERVAL_CEILING_IN_MILLISECONDS);

        }

        if (mLocationClient == null) {
            mLocationClient = new LocationClient(this, this, this);
            mLocationClient.connect();
        }
    }

    /**
     * Verify that Google Play services is available before making a request.
     * 
     * @return true if Google Play services is available, otherwise false
     */
    private boolean servicesConnected() {

        // Check that Google Play services is available
        int resultCode = GooglePlayServicesUtil
                .isGooglePlayServicesAvailable(this);

        // If Google Play services is available
        if (ConnectionResult.SUCCESS == resultCode) {
            // In debug mode, log the status
            Log.d(LocationUtils.APPTAG,
                    getString(R.string.play_services_available));

            return true;
        } else {

            return false;
        }
    }

    public class VisitBinder extends Binder {
        public VisitService getService() {
            return VisitService.this;
        }
    }


    /**
     * In response to a request to start updates, send a request to Location
     * Services
     */
    private void startPeriodicUpdates() {

        mLocationClient.requestLocationUpdates(mLocationRequest, this);
        Toast.makeText(this, "Location Update Requested", Toast.LENGTH_LONG).show();
    }

    /**
     * In response to a request to stop updates, send a request to Location
     * Services
     */
    private void stopPeriodicUpdates() {
        mLocationClient.removeLocationUpdates(this);

    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.d(LocationUtils.APPTAG, "Location Services Connection faild");


    }

    @Override
    public void onConnected(Bundle bundle) {
        Log.d(LocationUtils.APPTAG, "Location Services Connected");
        mLocationServiceConnected = true;
        startPeriodicUpdates();
    }

    @Override
    public void onDisconnected() {
        mLocationServiceConnected = false;
    }

    @Override
    public void onLocationChanged(Location location) {
        Toast.makeText(this, "Location has been changed", Toast.LENGTH_LONG).show();
    }
}

Android清单文件

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

从文档中得知:“如果位置不可用,这应该是非常罕见的情况,将返回null”。看起来你遇到了“罕见”的时刻,可能是由于设备原因。你尝试过从LocationManager获取最后已知的位置吗? - Nikola Despotoski
如果精确位置不可用,我还会请求“ACCESS_COARSE_LOCATION”。 - Robert
你可以在调用startVisit()方法之前等待onLocationChanged回调被调用。例如,你可以这样做,不是吗?你所说的“我无法接收onLocationChanged”是什么意思? - MagicMicky
@MagicMicky 意思是 play location services 从未调用 onLocationChanged - Walid Ammar
@Robert,我已经尝试添加ACCESS_COARSE_LOCATION权限,但是没有任何改变。 - Walid Ammar
8个回答

9

首先,这是一个bug。我也遇到了这个问题。

你可以尝试一个简单的解决方法。

在尝试获取最后一次位置之前,进行位置更新请求,即在使用getLastLocation()查找最后位置之前请求位置更新;

就像这样:

LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
Criteria crta = new Criteria();
crta.setAccuracy(Criteria.ACCURACY_FINE);
crta.setAltitudeRequired(true);
crta.setBearingRequired(true);
crta.setCostAllowed(true);
crta.setPowerRequirement(Criteria.POWER_LOW); 
String provider = locationManager.getBestProvider(crta, true);
Log.d("","provider : "+provider);
// String provider = LocationManager.GPS_PROVIDER; 
locationManager.requestLocationUpdates(provider, 1000, 0, locationListener);
Location location = locationManager.getLastKnownLocation(provider); 

这种实现基于LocationManager。请使用Location Client来完成同样的工作。
希望对您有所帮助!
更新:
可以使用onLocationChanged()方法进行更新,因为它会在每次位置更改时更新您,而getLastLocation()方法返回最好已知的可能为空的位置。
根据文档,GetLastLocation()方法在非常罕见的情况下将返回NULL。但是这在我看来经常发生。
最新更新:
关于GPS的唯一更新:
首先,我们需要使用GoogleApiClient而不是LocationClient类。
第二,我们需要实现GoogleApiClient.ConnectionCallbacks、 GoogleApiClient.OnConnectionFailedListener。
第三,我们需要重写其onConnected()、onConnectionSuspended()和onConnectionFailed()方法。
剩下的就可以去做了。
干杯!

在罕见情况下,我认为谷歌所指的是像三星GT-S7562、小米红米2和Galaxy Core Prime这样的设备。我带着谷歌位置示例应用程序在Github上去了一家展厅,在几台设备上进行了测试。它似乎只能在较新的设备上运行,如Galaxy Note 2、Note 4、OnePlus2、小米红米Note 4G和OPX等。 - Saifur Rahman Mohsin

1
我已经根据以下代码开发了我的应用程序,如果需要,您可以使用它。我的应用程序名称是 "https://play.google.com/store/apps/details?id=com.deep.profilemaper"。
以下是我的代码:BackgroundLocationService.java
public class BackgroundLocationService extends Service implements
    GooglePlayServicesClient.ConnectionCallbacks,GooglePlayServicesClient.OnConnectionFailedListener,LocationListener  {

    IBinder mBinder = new LocalBinder();

    private LocationClient mLocationClient;
    private LocationRequest mLocationRequest;
   // Flag that indicates if a request is underway.
   private boolean mInProgress;

   private Boolean servicesAvailable = false;

public class LocalBinder extends Binder {
    public BackgroundLocationService getServerInstance() {
        return BackgroundLocationService.this;
    }
}

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


    mInProgress = false;
    // Create the LocationRequest object
    mLocationRequest = LocationRequest.create();
    // Use high accuracy
    mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
    // Set the update interval to 5 seconds
    mLocationRequest.setInterval(Constants.UPDATE_INTERVAL);
    // Set the fastest update interval to 1 second
    mLocationRequest.setFastestInterval(Constants.FASTEST_INTERVAL);

    servicesAvailable = servicesConnected();

    /*
     * Create a new location client, using the enclosing class to
     * handle callbacks.
     */
    mLocationClient = new LocationClient(this, this, this);


}

private boolean servicesConnected() {

    // Check that Google Play services is available
    int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

    // If Google Play services is available
    if (ConnectionResult.SUCCESS == resultCode) {

        return true;
    } else {

        return false;
    }
}

public int onStartCommand (Intent intent, int flags, int startId)
{
    super.onStartCommand(intent, flags, startId);

    if(!servicesAvailable || mLocationClient.isConnected() || mInProgress)
        return START_STICKY;

    setUpLocationClientIfNeeded();
    if(!mLocationClient.isConnected() || !mLocationClient.isConnecting() && !mInProgress)
    {
        appendLog(DateFormat.getDateTimeInstance().format(new Date()) + ": Started", Constants.LOG_FILE);
        mInProgress = true;
        mLocationClient.connect();
    }

    return START_STICKY;
}

/*
 * Create a new location client, using the enclosing class to
 * handle callbacks.
 */
private void setUpLocationClientIfNeeded()
{
    if(mLocationClient == null) 
        mLocationClient = new LocationClient(this, this, this);
}

// Define the callback method that receives location updates
@Override
public void onLocationChanged(Location location) {
    // Report to the UI that the location was updated
    String msg = Double.toString(location.getLatitude()) + "," +
            Double.toString(location.getLongitude());
    Log.d("debug", msg);
    // Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    appendLog(msg, Constants.LOCATION_FILE);
}

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

public String getTime() {
    SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return mDateFormat.format(new Date());
}

public void appendLog(String text, String filename)
{       
   File logFile = new File(filename);
   if (!logFile.exists())
   {
      try
      {
         logFile.createNewFile();
      } 
      catch (IOException e)
      {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }
   }
   try
   {
      //BufferedWriter for performance, true to set append to file flag
      BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true)); 
      buf.append(text);
      buf.newLine();
      buf.close();
   }
   catch (IOException e)
   {
      // TODO Auto-generated catch block
      e.printStackTrace();
   }
}

@Override
public void onDestroy(){
    // Turn off the request flag
    mInProgress = false;
    if(servicesAvailable && mLocationClient != null) {
        mLocationClient.removeLocationUpdates(this);
        // Destroy the current location client
        mLocationClient = null;
    }
    // Display the connection status
    // Toast.makeText(this, DateFormat.getDateTimeInstance().format(new Date()) + ": Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show();
    appendLog(DateFormat.getDateTimeInstance().format(new Date()) + ": Stopped", Constants.LOG_FILE);
    super.onDestroy();  
}

/*
 * Called by Location Services when the request to connect the
 * client finishes successfully. At this point, you can
 * request the current location or start periodic updates
 */
@Override
public void onConnected(Bundle bundle) {

    // Request location updates using static settings
    mLocationClient.requestLocationUpdates(mLocationRequest, this);
    appendLog(DateFormat.getDateTimeInstance().format(new Date()) + ": Connected", Constants.LOG_FILE);

}

/*
 * Called by Location Services if the connection to the
 * location client drops because of an error.
 */
@Override
public void onDisconnected() {
    // Turn off the request flag
    mInProgress = false;
    // Destroy the current location client
    mLocationClient = null;
    // Display the connection status
    // Toast.makeText(this, DateFormat.getDateTimeInstance().format(new Date()) + ": Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show();
    appendLog(DateFormat.getDateTimeInstance().format(new Date()) + ": Disconnected", Constants.LOG_FILE);
}

/*
 * Called by Location Services if the attempt to
 * Location Services fails.
 */
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
    mInProgress = false;

    /*
     * Google Play services can resolve some errors it detects.
     * If the error has a resolution, try sending an Intent to
     * start a Google Play services activity that can resolve
     * error.
     */
    if (connectionResult.hasResolution()) {

    // If no resolution is available, display an error dialog
    } else {

      }
    }

}

***Constants.java***

public final class Constants {

// Milliseconds per second
private static final int MILLISECONDS_PER_SECOND = 1000;
// Update frequency in seconds
private static final int UPDATE_INTERVAL_IN_SECONDS = 30;
// Update frequency in milliseconds
public static final long UPDATE_INTERVAL = MILLISECONDS_PER_SECOND * UPDATE_INTERVAL_IN_SECONDS;
// The fastest update frequency, in seconds
private static final int FASTEST_INTERVAL_IN_SECONDS = 30;
// A fast frequency ceiling in milliseconds
public static final long FASTEST_INTERVAL = MILLISECONDS_PER_SECOND * FASTEST_INTERVAL_IN_SECONDS;
// Stores the lat / long pairs in a text file
public static final String LOCATION_FILE = "sdcard/location.txt";
// Stores the connect / disconnect data in a text file
public static final String LOG_FILE = "sdcard/log.txt";


/**
 * Suppress default constructor for noninstantiability
 */
private Constants() {
    throw new AssertionError();
    }
}

这段代码包含一些变量,如当前日期和时间,以及一些仅用于记录目的的常量,如LOG_File。因此,我已经排除了那部分代码。

这段代码已经经过完美测试并且可用。可以试试。

谢谢。


我在这段代码中使用了Fused Location Provider,因此您不必在网络或GPS提供程序之间切换。请参见https://developer.android.com/google/play-services/location.html。 - Deep Shah
谢谢您的建议,我认为这可能是旧安卓设备上的一个小错误,对于漂亮的用户界面点赞:) - Walid Ammar
感谢 @MohammadWalid 的审查。 - Deep Shah

1
如果Google地图尚未确定您的设备位置,则可能会出现此问题。请尝试启动Google地图。如果无法启动,则可以在设备设置中解决此问题(例如通过启用 WLAN + GPS)。

1
您正在使用getLastLocation()来执行与其预期不同的功能。 getLastLocation()用于在第一次启动定位应用程序时使用。两天后,您应该有一个非常准确的位置。为什么您没有遵循定位感知设计指南。
我建议您返回START_STICKY。但是,您已编写了onStartCommand()onBind()。您还需要启动服务,然后绑定到它,以使START_STICKY在客户端取消绑定后起作用。
不要在服务中检查GooglePlay。服务需要轻量级,在启动服务之前在活动中检查GooglePlay。服务越轻,保持在内存中的机会就越大。在单独的进程中运行Service

1

第一部分

这是旧版Android设备上的一个错误。

显然,不同的Android版本上位置API有不同的行为(LocationManager在Android >= 4.1上存在问题,而Play服务在Android 2.3上存在问题),请参见此处

所以我最终得到的结果如下:

尝试使用Play服务检索上次位置,如果失败则使用LocationManager。

第二部分

我想感谢大家提供的宝贵建议,我从AndroidHacker的帖子中获得了使用先前解决方法的提示,因此我认为他应该得到奖励。


0
文档中可以清楚地看到,getLastLocation()有时会返回null。因此,根据您对“我无法接收onLocationChanged回调”的理解,您可以延迟startVisit重要调用(您现在显示的toast),直到您收到此onLocationChanged回调为止。这样,如果您可以直接访问上次已知位置,则这将是直接的,否则您需要等待onLocationChanged回调。
public class VisitService extends Service implements
        GooglePlayServicesClient.ConnectionCallbacks,
        GooglePlayServicesClient.OnConnectionFailedListener, 
        LocationListener {
    //...
    boolean mIsInitialized=false;
    Location mCurrentLocation;
    boolean mStartVisitsCalled=false;
    //...

    public void startVisit() {
        if (!servicesConnected()) {
            listener.onVisitStartError();
            return;
        }

        if (mLocationServiceConnected) {
        if (((mLocationClient.getLastLocation() != null && isAcceptableLocation(mLocationClient.getLastLocation()))
                || (isInitialized && isAcceptableLocation(mCurrentLocation))) {
                //doNiceStuff();
            } else
                mStartVisitsCalled=true;
                //Wait...
            }
        }    
    }
    //...

    @Override
    public void onLocationChanged(Location location) {
        this.mIsInitialized=true;
        this.mCurrrentLocation = location;
        if(mStartVisitsCalled) {
            //delayed doNiceStuff();
        }

    }
}

不幸的是,有时onLocationChange永远不会被调用,当发生这种情况时,SDK示例也无法正常工作。 - Walid Ammar
你尝试过使用requestLocationUpdates(LocationManager.GPS_PROVIDER,0,0,locationListener);requestLocationUpdates(LocationManager.NETWORK_PROVIDER,0,0,locationListener);来更精确地请求位置更新吗?(详见此处https://dev59.com/1eo6XIcBkEYKwwoYQiXQ#9008453) - MagicMicky
你可以尝试在onConnected()中检查getLastLocation的值吗? - MagicMicky
由于您正在使用Google Play服务,您能否启用WiFi并连接到SSID,然后再试一次?我之前遇到过这个问题,因为融合位置提供程序尝试查找位置,在没有WiFi的情况下,有时无法获取位置更改。 - AnkitSomani

0

你的服务可能只是被杀掉了,这种情况是有可能发生的。 尝试将你的 onStartCommand() 返回值进行更改为

return STICKY_START;

一旦资源可用,此操作将重新启动服务。

以下是API参考中的相关部分

public static final int START_STICKY

在API级别5中添加 从onStartCommand(Intent,int,int)返回:如果在启动此服务后(从onStartCommand(Intent,int,int)返回后)杀死了此服务的进程,则将其保留在已启动状态但不保留此传递的意图。稍后,系统将尝试重新创建服务。因为它处于已启动状态,所以它将保证在创建新服务实例后调用onStartCommand(Intent,int,int)。如果没有任何待处理的启动命令要传递到服务,则将使用空意图对象调用它,因此您必须注意检查此内容。

此模式对于明确启动和停止以运行任意时间段的事物很有意义,例如执行背景音乐播放的服务。

常量值:1(0x00000001)


谢谢 @nPn,但我不认为这是问题,因为我可以看到服务创建了一些“Toast”。 - Walid Ammar
看起来你的 Toast 大部分会很早出现,然后你只需要继续定期获取位置更新,所以你是在说你正在从服务中获得持续的 Toast,但却没有看到 onLocationChanged 被调用? - nPn
实际上,我能够看到 Location is nullLocation Services ConnectedLocation Update Requested ... 但是没有 Location has been changed,所以我确定我的服务正在运行,但 onLocationChanged 没有被调用。 - Walid Ammar

0

谷歌地图必须正常工作,因为它必须缓存先前的位置信息或直接使用位置API,而不是Google Play服务位置API。


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