当屏幕指示灯熄灭时,LocationClient不会回调,但我的WakefulThread正如预期的那样完美运行

时间:2013-07-12 11:25:16

标签: android android-location locationlistener

要在后台检索fused location,我创建了一个与Commonsguy创建的cwac-locpoll库非常相似的库。

在PollerThread内部,我正在尝试使用LocationClient连接,请求和检索位置。

我可以通过接收onConnected方法的回调来连接,但我无法在onLocationChanged方法上获得回调。因此我的onTimeout线程按照确定的时间间隔执行。< / p>

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

我怀疑新的位置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;
            }
        }

2 个答案:

答案 0 :(得分:18)

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

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

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

使用服务时,我建议让服务直接实现LocationListener,而不是服务中的Thread。例如,使用:

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

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

如果您想使用融合位置提供程序在后台(例如,每分钟)收听偶尔的位置更新,则更好的设计是使用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上注册的任何先前LocationRequests都将被替换。

     

使用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,请在onHandleIntent()上定义IntentService及其所需方法。位置服务使用您在调用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 docs:

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

  

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

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

答案 1 :(得分: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/location/package-summary

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

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

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