7

为了在后台检索融合位置,我创建了一个与Commonsguy 创建的cwac-locpoll库非常相似的库。

在 PollerThread 内部,我正在尝试使用LocationClient.

我可以通过在onConnected方法上接收回调来建立连接,但我无法在方法上获得回调。所以onLocationChanged我的onTimeout线程按照确定的间隔执行。

注意: 仅当屏幕灯熄灭时才会出现此问题。否则它会完全正常工作。

我怀疑新的 Location Api 中可能存在错误。

这是我的实现PollerThread

          private class PollerThread extends WakefulThread  implements GooglePlayServicesClient.ConnectionCallbacks,
      GooglePlayServicesClient.OnConnectionFailedListener,LocationListener{

            private static final String TAG = "PollerThread";

            //context
            private Context mContext=null;
            private LocationClient mLocationClient=null;
            private LocationRequest mLocationRequest=null;
            private LocationManager locMgr=null;
            private Intent intentTemplate=null;
            private Handler handler=new Handler();
            private Runnable onTimeout = new Runnable() {

            @Override
            public void run() {

            Log.e(TAG, "onTimeout");

            //prepare broadcast intent
              Intent toBroadcast=new Intent(intentTemplate);
              toBroadcast.putExtra(FusedPoller.EXTRA_ERROR, "Timeout!");
              toBroadcast.putExtra(
                      FusedPoller.EXTRA_ERROR_PROVIDER_DISABLED, false);
              toBroadcast.putExtra(FusedPoller.EXTRA_LASTKNOWN,
                      mLocationClient.getLastLocation());
              sendBroadcast(toBroadcast);

            //stop the thread
              quit();
            }
          };

        PollerThread(Context mContext,LocationRequest mLocationRequest,PowerManager.WakeLock lock, LocationManager locMgr,
                     Intent intentTemplate) {
          super(lock, "LocationPoller-PollerThread");

          Log.e(TAG, "PollerThread");

          this.mContext=mContext;
          this.mLocationRequest=mLocationRequest;
          this.locMgr=locMgr; 
          this.intentTemplate=intentTemplate;
        }


        @Override
        protected void onPreExecute() {
            super.onPreExecute();
              Log.e(TAG, "onPreExecute");

            //setup timeout
            setTimeoutAlarm();

            //initiate connection
            initiateConnection();
        }

        @Override
        protected void onPostExecute() {
          super.onPostExecute();
          Log.e(TAG, "onPostExecute");

          //remove timeout
          removeTimeoutAlarm();
          //disconnect
          initiateDisconnection();
        }

        /**
         * Called when the WakeLock is completely unlocked.
         * Stops the service, so everything shuts down.
         */
        @Override
        protected void onUnlocked() {
            Log.e(TAG, "onUnlocked");
            stopSelf();
        }


        private void setTimeoutAlarm() {
            Log.e(TAG, "setTimeoutAlarm");
            handler.postDelayed(onTimeout, FusedLocationUtils.DEFAULT_TIMEOUT);
        }

        private void removeTimeoutAlarm()
        {
            Log.e(TAG, "removeTimeoutAlarm");
            handler.removeCallbacks(onTimeout);
        }

        private void initiateConnection()
        {
            Log.e(TAG, "initiateConnection");
            mLocationClient = new LocationClient(this.mContext, this, this);
            mLocationClient.connect();
        }

        private void initiateDisconnection()
        {
            Log.e(TAG, "initiateDisconnection");
            if(mLocationClient.isConnected())
            {
                mLocationClient.disconnect();
            }
        }


        @Override
        public void onConnected(Bundle arg0) {
            Log.e(TAG, "onConnected");


            Log.e(TAG, "provider: GPS-"+locMgr.isProviderEnabled(LocationManager.GPS_PROVIDER)+" NETWORK-"+locMgr.isProviderEnabled(LocationManager.NETWORK_PROVIDER));

                if (!(locMgr.isProviderEnabled(LocationManager.GPS_PROVIDER)) && !(locMgr.isProviderEnabled(LocationManager.NETWORK_PROVIDER))) {
                    Log.e(TAG, "both disabled");

                    //get last location and broadcast it
                    getLastLocationAndBroadcast();

                    //stop the thread
                    quit();
                }
                else
                {
                    Log.e(TAG, "provider enabled");

                    //get latest location and broadcast it
                    getLatestLocationAndBroadcast();
                    //don't quit from here,quit from onLocationChanged
                }

        }


        @Override
        public void onDisconnected() {
            Log.e(TAG, "onDisconnected");
            // TODO Auto-generated method stub

        }

        @Override
        public void onConnectionFailed(ConnectionResult arg0) {
            Log.e(TAG, "onConnectionFailed");
            // TODO Auto-generated method stub

        }
        @Override
        public void onLocationChanged(Location location) {


            Log.e(TAG, "onLocationChanged");

            //prepare broadcast intent
            Intent toBroadcast=new Intent(intentTemplate);
            toBroadcast.putExtra(FusedPoller.EXTRA_LOCATION, location);
            sendBroadcast(toBroadcast);

            //stop further updates
            stopUpdates();
            //stop the thread
            quit();

        }

        private void getLatestLocationAndBroadcast() {
            Log.e(TAG, "getLatestLocationAndBroadcast");
            if(mLocationClient.isConnected() && servicesConnected())
            {
                Log.e(TAG, "going to request updates");
                Log.e(TAG, "lockStatic.isHeld(): "+lockStatic.isHeld());
                mLocationClient.requestLocationUpdates(mLocationRequest, this);
            }
            else
            {
                Log.e(TAG, "not going to request updates");
            }
        }


        private void stopUpdates() {
            Log.e(TAG, "stopUpdates");
            if(servicesConnected())
            {
                Log.e(TAG,getString(R.string.location_updates_stopped));
                mLocationClient.removeLocationUpdates(this);
            }
            else
            {
                Log.e(TAG,"can't do:"+getString(R.string.location_updates_stopped));
            }
        }



        private void getLastLocationAndBroadcast() {
            Log.e(TAG, "getLastLocationAndBroadcast");
            if(mLocationClient.isConnected() && servicesConnected())
            {
                Log.e(TAG, "going to get last location: "+mLocationClient.getLastLocation());

                Intent toBroadcast = new Intent(intentTemplate);
                toBroadcast.putExtra(FusedPoller.EXTRA_ERROR,
                        "Location Provider disabled!");
                toBroadcast.putExtra(
                        FusedPoller.EXTRA_ERROR_PROVIDER_DISABLED, true);
                toBroadcast.putExtra(FusedPoller.EXTRA_LASTKNOWN,
                        mLocationClient.getLastLocation());
                sendBroadcast(toBroadcast);
            }
            else
            {
                Log.e(TAG, "not going to get last location");
            }
        }
      }

servicesConnected方法实现,

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

              Log.e(TAG, "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(FusedLocationUtils.APPTAG, getString(R.string.play_services_available));

                // Continue
                return true;
            // Google Play services was not available for some reason
            } else {
                // Display an error dialog
                Log.d(FusedLocationUtils.APPTAG, getString(R.string.play_services_unavailable));
                Toast.makeText(this, getString(R.string.play_services_unavailable), Toast.LENGTH_SHORT).show();

                return false;
            }
        }
4

2 回答 2

19

如果您想在后台收听频繁的位置更新(例如,每秒),您应该在 a 中运行您的代码Service

http://developer.android.com/reference/android/app/Service.html

Android 平台可以在它们不在前台的任何时间点结束活动。

使用服务时,我建议让服务直接实现 LocationListener,而不是服务内部的线程。例如,使用:

public class LocListener extends Service implements com.google.android.gms.location.LocationListener, ...{

我已经使用了这种直接在服务上实现 LocationListener 的设计,并在我的GPS Benchmark 应用LocationClient程序中融合了位置提供程序,我可以确认即使在屏幕关闭且应用程序在后台运行时它也能正常工作。

如果您想使用融合位置提供程序在后台(例如,每分钟)收听偶尔的位置更新,更好的设计是使用 PendingIntents,使用LocationClient.requestLocationUpdates(Location Request, PendingIntent callbackIntent)方法:

https://developer.android.com/reference/com/google/android/gms/location/LocationClient.html#requestLocationUpdates(com.google.android.gms.location.LocationRequest,%20android.app.PendingIntent)

从上面的 Android 文档:

此方法适用于后台用例,更具体地说,适用于接收位置更新,即使应用程序已被系统杀死。为此,请将 PendingIntent 用于已启动的服务。对于前台用例,建议使用 LocationListener 版本的方法,请参阅 requestLocationUpdates(LocationRequest, LocationListener)。

在此 PendingIntent 上注册的任何以前的 LocationRequest 都将被替换。

位置更新使用 KEY_LOCATION_CHANGED 键和意图上的位置值发送。

PendingIntents有关在后台运行时获取更新的更详细说明,请参阅活动识别示例:

https://developer.android.com/training/location/activity-recognition.html

以下是本文档的修改摘录,由我更改为特定于位置更新。

首先声明意图:

public class MainActivity extends FragmentActivity implements
        ConnectionCallbacks, OnConnectionFailedListener {
    ...
    ...
    /*
     * Store the PendingIntent used to send location updates
     * back to the app
     */
    private PendingIntent mLocationPendingIntent;
    // Store the current location client
    private LocationClient mLocationClient;
    ...
}

像现在一样请求更新,但这次传入待处理的意图:

/*
 * Create the PendingIntent that Location Services uses
 * to send location updates back to this app.
 */
Intent intent = new Intent(
        mContext, LocationIntentService.class);

...
//Set up LocationRequest with desired parameter here
...
/*
 * Request a PendingIntent that starts the IntentService.
 */
mLocationPendingIntent =
        PendingIntent.getService(mContext, 0, intent,
        PendingIntent.FLAG_UPDATE_CURRENT);
/*
 * Request location updates
 */
mLocationClient.requestLocationUpdates(mLocationRequest, callbackIntent);

处理位置更新

要处理位置服务为每个更新间隔发送的 Intent,请定义一个 IntentService 及其所需的方法 onHandleIntent()。位置服务使用您在调用 requestLocationUpdates() 时提供的 PendingIntent 将更新作为 Intent 对象发送出去。由于您为 PendingIntent 提供了明确的意图,因此接收该意图的唯一组件是您定义的 IntentService。

定义类和所需的方法 onHandleIntent():

/**
 * Service that receives Location updates. It receives
 * updates in the background, even if the main Activity is not visible.
 */
public class LocationIntentService extends IntentService {
    ...
    /**
     * Called when a new location update is available.
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle b = intent.getExtras();
        Location loc = (Location) b.get(LocationClient.KEY_LOCATION_CHANGED);
        Log.d(TAG, "Updated location: " + loc.toString());


    }
    ...
}

重要 - 为了尽可能高效,您的代码onHandleIntent()应尽快返回以允许 IntentService 关闭。来自 IntentService 文档:

http://developer.android.com/reference/android/app/IntentService.html#onHandleIntent(android.content.Intent)

在工作线程上调用此方法并请求处理。一次只处理一个 Intent,但处理发生在独立于其他应用程序逻辑运行的工作线程上。因此,如果这段代码需要很长时间,它会阻止对同一个 IntentService 的其他请求,但不会阻止其他任何请求。处理完所有请求后,IntentService 会自行停止,因此您不应调用 stopSelf()。

我对IntentService设计的理解是,您可以在内部生成线程onHandleIntent()以避免通过平台调用阻塞其他位置更新onHandleIntent(),只需注意服务将继续运行,直到所有正在运行的线程终止。

于 2013-07-25T14:42:03.620 回答
0

我花了几天时间尝试在 Nexus 6 上使用 Android 6.0 锁定屏幕获取 WiFi 和基于单元的位置。看起来原生 android 定位服务简单不允许这样做。一旦设备被锁定,它仍然会收集 10-15 分钟的位置更新事件,然后停止提供任何位置更新。

就我而言,解决方案是从原生 Android 定位服务切换到名为 com.google.android.gms.location 的 Google Play 服务包装器:https ://developers.google.com/android/reference/com/google/android/gms /位置/包摘要

是的,我知道一些 Android 设备缺少 GMS,但对于我的应用程序来说,这是唯一可以执行的解决方案。

即使在后台和设备屏幕被锁定时,它也不会停止发送位置更新。

我个人更喜欢 RxJava 库将此服务包装成流(包括示例):https ://github.com/mcharmas/Android-ReactiveLocation

于 2016-08-23T10:36:35.940 回答