融合定位提供程序似乎没有使用GPS接收器

39

Moto G上运行Android 4.3,Nexus 7 2012和Nexus 5上运行Android 4.4.2。使用的是Android Studio 0.4。

我不想接收定期位置更新,我只想在用户按下按钮时获得精确的位置。

我已经按照这个例子进行了操作:https://developer.android.com/training/location/retrieve-current.html

在清单文件中:

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

我使用GooglePlayServicesUtil.isGooglePlayServicesAvailable检查Play服务是否可用。

在主活动中:

//in activity onCreate method
mLocationClient = new LocationClient(this, this, this);

@Override
protected void onStart() {
    mLocationClient.connect();
    super.onStart();
}

@Override
protected void onStop() {
    mLocationClient.disconnect();
    super.onStop();
}

//in button onclick method    
mCurrentLocation = mLocationClient.getLastLocation();

我的手机没有SIM卡。如果我开启WiFi,有时候可以得到准确的位置信息。其他时候,mCurrentLocation为空。

如果我关闭WiFi,则mCurrentLocation始终为空。

我在外面测试了多个有天空视野的位置,每个位置等待了三分钟。

我从未看到GPS图标出现在Android通知栏顶部。

我的定位设置如下: enter image description here

一个GPS测试应用程序成功地在同一设备上禁用WiFi室内使用GPS,因此GPS是正常工作的: enter image description here

注册位置更新,如https://developer.android.com/training/location/receive-location-updates.html,也不起作用。注册方法从未被调用。

我做错了什么?


1
发布一些代码,GPS 获得适当的锁定也需要一些时间。 - tyczj
1
@tyczj 刚刚编辑了问题并添加了代码。我在每个位置等待了三分钟,但GPS图标都没有出现在Android通知栏中。 - cja
1
谢谢!你能否发布一下你正在使用的LocationRequest代码来注册位置更新?另外,我假设当你说你打开WiFi时也有互联网连接?你还可以使用我的GPS基准应用程序在这里测试融合提供者-https://play.google.com/store/apps/details?id=com.gpsbenchmark.android - Sean Barbeau
1
GPS基准测试看起来会非常有用。 - cja
3
你真的需要这两个权限吗?<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> - Marian Paździoch
显示剩余5条评论
12个回答

16

我解决了问题。问题在于“允许谷歌应用访问您的位置”未开启:enter image description here

当我开启后,我就可以获取GPS读数;关闭则无法读取。

我曾经关闭它有两个原因:

  1. 我正在开发一款应用程序,需要在许多公司设备上使用,我想尽量减少手动配置的必要性

  2. 屏幕清晰地显示“此设置仅适用于Google应用程序”,我知道Play Services是谷歌软件,但我不认为谷歌会期望最终用户理解这一点。

然后我升级了Android 4.4.2,位置设置页面发生了变化。现在,即使关闭了Google位置报告,我也可以从融合位置提供程序获取GPS读数:enter image description here

所以可能谷歌意识到这个设置很困惑,并进行了改进。无论如何,如果我几天前就得到了4.4.2,我将节省很多时间。


你们都很友善和乐于助人,但我希望有一种方法可以让我自己获得声望奖励! - cja
1
对于现在找到这个帖子的人 - 坏消息:谷歌使它更难理解。现在没有明确的“谷歌位置设置”。如果您选择“高精度”或“省电模式”定位模式,则表示您接受谷歌位置设置。好消息是:您可以使用SettingsApi,而不必将用户重定向到神秘的设备设置视图https://coderwall.com/p/zvmefa/fusedlocationproviderapi-doesn-t-work-without-network-location-provider-use-settingsapi-for-checking-and-setting-up-user-s-configuration?p=1&q=author%3Adaisy1754 - Kazuki

15
问题出在getLastLocation()方法上,因为它使用了缓存的位置信息。我曾经尝试过这种简单的方法,但也遇到了同样的问题。后来我改用监听位置更新的方式,并在第一次成功更新后自动停止监听。

下面是我的代码,已经能够正常工作:

首先,在Application中检查可用性(非必要,也可以在Activity中进行,并且不需要保存结果):

public class MainApp extends Application {
  public static enum PlayServices {
    NOT_CHECKED, AVAILABLE, UNAVAILABLE
  };
  public static PlayServices mPlayServices = PlayServices.NOT_CHECKED;

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

    if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS) {
      MainApp.mPlayServices = MainApp.PlayServices.AVAILABLE;
    }
  }
}

然后,接下来进入活动:
public class MainActivity extends SherlockFragmentActivity implements
  GooglePlayServicesClient.ConnectionCallbacks,
  GooglePlayServicesClient.OnConnectionFailedListener, LocationListener {

在其onCreate()中:
if (MainApp.mPlayServices != MainApp.PlayServices.UNAVAILABLE) {
  mLocationClient = new LocationClient(this, this, this);

  mLocationRequest = LocationRequest.create();
  mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
  mLocationRequest.setInterval(5000);
  mLocationRequest.setNumUpdates(1);
  mLocationRequest.setFastestInterval(1000);

  mUpdatesRequested = false;
  MainApp.prefs.edit().putBoolean(MainApp.KEY_LOCATION_UPDATES_REQUESTED, mUpdatesRequested)
      .commit();
}

其余的MainActivity类代码:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  Log.d(this.getClass().getSimpleName(), "onActivityResult(" + requestCode + ", " + resultCode
      + ")");
  // Decide what to do based on the original request code
  switch (requestCode) {
    case MainApp.PLAY_CONNECTION_FAILURE_RESOLUTION_REQUEST:
      /*
       * If the result code is Activity.RESULT_OK, try
       * to connect again
       */
      switch (resultCode) {
        case Activity.RESULT_OK:
          // here we want to initiate location requests!
          mLocationClient = new LocationClient(this, this, this);

          break;
      }
      break;
  }
}

@Override
public void onConnected(Bundle dataBundle) {
  Log.d(this.getClass().getSimpleName(), "onConnected()");

  Log.d(this.getClass().getSimpleName(), "Google Play Services are available.");
  MainApp.mPlayServices = MainApp.PlayServices.AVAILABLE;

  if (!mUpdatesRequested) {

    LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

    boolean gps_enabled = false;
    try {
      gps_enabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
    } catch (Exception ex) {
    }

    boolean network_enabled = false;
    try {
      network_enabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
    } catch (Exception ex) {
    }

    // don't start listeners if no provider is enabled
    MainApp.locEnabled = gps_enabled || network_enabled;

    if (!MainApp.locEnabled) {
      // we have access to PlayServices, but user has disabled location visibility --> alert him
      alertLocationOff();
    } else {
      mLocationClient.requestLocationUpdates(mLocationRequest, this);
      mUpdatesRequested = true;
    }
  }
}

@Override
public void onDisconnected() {
  Log.d(this.getClass().getSimpleName(), "onDisconnected()");
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
  Log.d(this.getClass().getSimpleName(), "onConnectionFailed()");

  Log.d(this.getClass().getSimpleName(), "Google Play Services not available.");
  MainApp.mPlayServices = MainApp.PlayServices.UNAVAILABLE;

  /*
   * 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()) {
    try {
      // Start an Activity that tries to resolve the error
      connectionResult.startResolutionForResult(this,
          MainApp.PLAY_CONNECTION_FAILURE_RESOLUTION_REQUEST);
      /*
       * Thrown if Google Play services canceled the original
       * PendingIntent
       */
    } catch (IntentSender.SendIntentException e) {
      // Log the error
      e.printStackTrace();
    }
  } else {
    /*
     * If no resolution is available, display a dialog to the
     * user with the error.
     */
    GooglePlayServicesUtil.getErrorDialog(connectionResult.getErrorCode(), this, 0).show();
  }
}

@SuppressLint("NewApi")
@Override
public void onLocationChanged(Location location) {
  Log.d(this.getClass().getSimpleName(), "onLocationChanged(), location=" + location);

  if (location != null) {
    boolean present = true;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
      present = Geocoder.isPresent();
    }

    if (present) {
      (new ExtractLocationTask(this)).execute(location);
    } else {
      Log.e(this.getClass().getSimpleName(), "Geocoder not present");
      MainApp.mPlayServices = MainApp.PlayServices.UNAVAILABLE;
    }
  }
}


private class ExtractLocationTask extends AsyncTask<Location, Void, Boolean> {
  Context mContext;

  public ExtractLocationTask(Context context) {
    super();
    mContext = context;
  }

  @Override
  protected Boolean doInBackground(Location... params) {
    Log.d(getClass().getSimpleName(), "ExtractLocationTask.onPreExecute()");

    boolean found = false;
    try {
      Geocoder geoCoder_local = new Geocoder(mContext, Locale.getDefault());
      Geocoder geoCoder_en = new Geocoder(mContext, Locale.ENGLISH);

      List<Address> addresses_local = geoCoder_local.getFromLocation(params[0].getLatitude(),
          params[0].getLongitude(), 10);
      List<Address> addresses_en = geoCoder_en.getFromLocation(params[0].getLatitude(),
          params[0].getLongitude(), 10);

      if (addresses_local != null && addresses_local.size() > 0) {

        // do what you want with location info here

        // based on mLocationRequest.setNumUpdates(1), no need to call
        // removeLocationUpdates()

        MainApp.locEnabled = true;

        mUpdatesRequested = false;
        MainApp.prefs.edit()
            .putBoolean(MainApp.KEY_LOCATION_UPDATES_REQUESTED, mUpdatesRequested).commit();

        found = true;
      }
    } catch (IOException e) {
      Log.e(this.getClass().getSimpleName(), "Exception: ", e);
    }

    return found;
  }

  @Override
  protected void onPostExecute(Boolean found) {
    Log.d(getClass().getSimpleName(), "ExtractLocationTask.onPostExecute()");

    if (found) {
      // update UI etc.
    } else if (!mUpdatesReRequested) {
      mLocationClient.requestLocationUpdates(mLocationRequest, (LocationListener) mContext);
      mUpdatesRequested = true;
      mUpdatesReRequested = true;
    }
  }
}

我希望这能帮助你使其正常工作!

3
LocationManager.isProviderEnabled方法适用于融合定位提供程序吗? - cja
我发现它可以正常工作,但你可以轻松地从onConnected()中删除LocationManager检查,然后只使用mLocationClient.requestLocationUpdates(mLocationRequest, this); - Saran
8
顺便提一句,从FusedLocation源代码(https://android.googlesource.com/platform/frameworks/base/+/6fa9ad4afcd762aea519ff61811386c23d18ddb2/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java)中可以看到,他们使用LocationManager以相同的方式(使用“isProviderEnabled()”)来检查GPS和网络的可用性。 - Saran
2
感谢确认LocationManager的使用。 - cja
3
Google Play 定位服务仍存在很多问题。 有时它不能检测到位置或花费太长时间。 然而,旧技术比 Google Play 定位服务 API 更快地提供定位信息。 如果有人想体验这个问题,只需注意当你的地图应用程序显示“等待位置”时,同时运行使用旧定位技术的示例应用程序。旧技术返回的结果比 Play Location Service 更快。 - Sachin Shelke
显示剩余2条评论

5

当一些客户要求高精度位置(如其他用户所描述的示例中所述),位置提供程序才会唤醒GPS。

GPS测试应用程序不使用位置提供程序,而是使用旧的“直接”方式获取位置信息。

此外,还有一种过期机制,如果认为上次位置信息已过期,则会删除该信息。

总结以上所有内容,很有可能位置提供程序(LP)没有任何可以为您提供的信息。


2

我认为你正在室内测试应用程序,但它无法正常工作。

还有代码流程...

public void setUpLocationClientIfNeeded() {
        createLocReq();
        if (mLocationClient == null) {
            mLocationClient = new LocationClient(getApplicationContext(), this, // ConnectionCallbacks
                    this); // OnConnectionFailedListener
        }
    }

    private void createLocReq() {
        if (mLocationRequest == null) {
            // Create a new global location parameters object
            mLocationRequest = LocationRequest.create();
            // Set the update interval
            mLocationRequest.setInterval(LocationServices.UPDATE_INTERVAL_IN_MILLISECONDS);
            // Use high accuracy
            mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
            // Set the interval ceiling to one minute
            mLocationRequest.setFastestInterval(LocationServices.FAST_INTERVAL_CEILING_IN_MILLISECONDS);

            mLocationClient = new LocationClient(getApplicationContext(), this, this);
            mLocationClient.connect();
        }
    }

    public void updateLocation(Location location) {
        if (lastTrackedLat == 0.0) {
            lastTrackedLat = location.getLatitude();
        }
        if (lastTrackedLng == 0.0) {
            lastTrackedLng = location.getLongitude();
        }

        currentLat = location.getLatitude();
        currentLng = location.getLongitude();
    }

    @Override
    public void onLocationChanged(Location location) {
        if (location != null) {
            this.location = location;
            updateLocation(location);
        }
    }

    public Location getLocation() {
        // mLocationClient.getLastLocation();
        return location;
    }

感谢您提供的代码示例。不过,我不需要定期更新,我只想在用户按下按钮时获取精确的位置信息。我正在遵循https://developer.android.com/training/location/retrieve-current.html。 - cja
为什么不使用FusedLocationProvider? - IgorGanapolsky
1
现在你可以使用FusedLocation Provider,它比以前的LocationManager更好。 - vinay Maneti

1

你只需要像这样在请求对象中添加一个优先级属性。

public void onConnected(Bundle arg0)
{
    locationrequest = LocationRequest.create();
    locationrequest.setInterval(1000);
    locationrequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    locationclient.requestLocationUpdates(locationrequest, this);
}

也许可以使用布尔变量让用户有选择强制使用GPS的选项。
boolean forceGPS=false;
.
.
.//make the user choose to change the value of the boolean
.
.
public void onConnected(Bundle arg0)
    {
        locationrequest = LocationRequest.create();
        locationrequest.setInterval(1000);
        if(forceGPS==true)
        {
        locationrequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        }
        locationclient.requestLocationUpdates(locationrequest, this);
    }

1

根据文档

该方法提供了一种简化的获取位置的方式。 它特别适用于不需要准确位置并且不想为位置更新维护额外逻辑的应用程序。

因此,它可能返回高度准确或不准确的位置。

GPS 可能需要一段时间才能锁定,因此调用 getLastLocation 可能会或可能不会返回位置。

最好是请求位置更新,然后在获得位置后停止请求位置更新。

另外,从您提供的代码来看,您是在等待 LocationClient 连接后再尝试获取位置吗?这肯定会给您一个空位置,因为它尚未连接以获取位置。

您应该在 onConnected 中获取上次位置,例如:

public void onConnected(Bundle connectionHint) {
    Location location = mLocationClient.getLastLocation();
}

正如在那个例子中所说的一样,onConnected是当连接客户端请求成功完成时由位置服务调用的。此时,您可以请求当前位置或开始定期更新。


2
如果它可以获取网络位置,那么它可能会在不使用GPS的情况下给我提供该位置,但是如果无法获取网络位置,那么肯定会使用GPS。我无法相信它会被设计成甚至不尝试使用GPS。我确定我做错了什么。 - cja
2
从http://developer.android.com/google/play-services/location.html:「立即可用:使您的应用程序立即访问最佳、最新的位置。」要么这是谎言,要么我做错了什么。 - cja
1
注册位置更新,如https://developer.android.com/training/location/receive-location-updates.html所示,也不起作用。已注册的方法从未被调用。 - cja
@Saran,现在我明白你为什么发表评论了——因为你得到了最多的投票。我可以看出你的答案可能对有各种问题的人有所帮助,但它并没有解决我的问题。 - cja
1
得到最多赞的人肯定值得获得悬赏。@cja - BenjaminPaul
显示剩余4条评论

1
没有SIM卡,粗略位置提供程序无法知道粗略位置,除非它能够找到已被Google映射的WiFi网络。
请求上次已知位置可能会导致过时的位置,因此相当无用。我猜这是记录某个应用程序请求位置更新的最后一次位置。
我使用以下代码来获取最近的位置:
    Criteria criteria = new Criteria();
    criteria.setAccuracy(Criteria.ACCURACY_COARSE);
    criteria.setAltitudeRequired(false);
    criteria.setSpeedRequired(false);
    criteria.setBearingRequired(false);
    criteria.setCostAllowed(false);

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

    ....
    LocationListener listener = new LocationListener() {
        @Override
        public void onLocationChanged(Location lastKnownLocation) {
                     ....
        }
        // rest of interface
     }


     manager.requestSingleUpdate(criteria, listener, null);

最后一个调用确保我们请求的是当前位置,而不是之前某个时间找到的位置。
您可以尝试将其更改为Criteria.ACCURACY_FINE以启动GPS。请注意,如果GPS很长时间没有修复,可能需要几分钟才能实际上获取修复。在此期间,我希望您看到GPS图标指示它正在等待修复。

“最后一次调用” - 您指的是哪一行? - cja
似乎manager.requestSingleUpdate(criteria, listener, null);恰好可以做到这一点。 - meeDamian
“最后一次调用”是 requestSingleUpdate() 调用。这将刷新当前位置,并一旦确定,将传递给监听器。之后将不再有更新。 - M. le Rutte
4
请稍等,这个问题是关于融合位置提供程序的。您的答案似乎使用 android.location.LocationManager,这是不同的。 - cja
@user1993392 为什么要使用 LocationManager 进行演示?现在不是 FusedLocationProvider 更受欢迎吗? - IgorGanapolsky

1

OnConnected 方法中有什么内容?

在这个方法中,你应该使用 PRIORITY_HIGH_ACCURACY 优先级创建 LocationRequest 对象。

@Override
public void onConnected(Bundle dataBundle) {
    mLocationRequest = LocationRequest.create();
    mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    mLocationClient.requestLocationUpdates(mLocationRequest, fusedListener);
}

0

这里有两个问题会导致你有时会得到null,有时会得到位置信息。

首先,在onClick方法中创建了一个新的LocationClient实例,这不是你在onStart()中调用connect()的同一个实例。这将导致不可预测的行为,有时客户端将有足够的时间在从LocationClient.getLastLocation()返回结果之前连接,有时则不会。

其次,你应该使用LocationClient.isConnected()来保护对LocationClient.getLastLocation()的调用。在LocationClient.isConnected()文档中说了以下内容: https://developer.android.com/reference/com/google/android/gms/location/LocationClient.html#isConnected()

检查客户端当前是否连接到服务,以便对其他方法的请求将成功。应用程序应该使用此方法来保护由用户引起的客户端操作。

由于用户通过点击按钮触发onClick()代码,所以在尝试获取最后位置之前,您应该先调用此方法。

因此,您的代码应该如下所示:

LocationClient mLocationClient;
Location mCurrentLocation;

@Override
protected void onCreate() {
    ...
    mLocationClient = new LocationClient(this, this, this);
    ...
}

@Override
protected void onStart() {
    mLocationClient.connect();
    super.onStart();
}

@Override
protected void onStop() {
    mLocationClient.disconnect();
    super.onStop();
}

public void onClick(View v) {
    ...
    if (mLocationClient.isConnected()) {
        // You should get a valid location here
        mCurrentLocation = mLocationClient.getLastLocation();
    }
}

这应该给LocationClient足够长的时间来连接并提供有效的位置信息,希望其中包括GPS数据。


抱歉 - 我问题中的代码略微误导了我的应用程序中的代码。请查看最新编辑。 - cja
将尝试使用isConnected。 - cja

0

试试这个:

public final static int MINACCURACY = 50;

private LocationManager lm;
private LocationListener listener;

private void foundLoc(double x, double y) {
    // do something with x,y
    Log.v("DEBUG", x + "," + y);
}


public void findMe(View v) {
    lm = (LocationManager) getSystemService(LOCATION_SERVICE);
    listener = new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            if(location.getAccuracy() < MINACCURACY) {
                foundLoc(location.getLatitude(), location.getLongitude());
                lm.removeUpdates(listener);
            }
        }

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

        @Override
        public void onProviderEnabled(String provider) {
        }

        @Override
        public void onProviderDisabled(String provider) {
        }
    };
    lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 100, 0, listener);
}

MINACCURACY 的单位为米。这样,在按下按钮(调用 findMe 方法)时,GPS 就会启用,找到您的位置并达到最小精度,foundLoc 方法获取数据并监听器终止(反过来禁用 GPS)。


1
这个问题涉及到融合位置提供程序。您的答案似乎使用了android.location.LocationManager,这是不同的。 - cja

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