【Android Developers Training】 104. 接受地

系统 1781 0

注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。

原文链接: http://developer.android.com/training/location/receive-location-updates.html


如果你的应用有导航的功能,你可能会希望可以定期获取用户的地理位置。虽然你可以通过 LocationClient.getLastLocation() 做到这一点,但是一个更加直接的方法是向定位服务申请定期更新。作为响应,定位服务会自动用最佳的地理位置信息(基于当前激活的可以提供位置信息的传感器,如WiFi或者GPS)更新到你的应用。

要从定位服务定期获取地理位置更新,你使用定位客户端发送一个请求。根据请求的形式,定位服务或是激活一个回调函数,并把一个 Location 对象传递给该函数,或是发送一个 Intent ,在其数据部分包含了地理位置信息。有两方面因素会影响精度和频率,一个是你的应用申请的定位权限,一个是你在请求中传递给定位服务的参数。


一). 指定应用权限

使用位置服务的应用必须请求定位权限。Android有两个定位权限: ACCESS_COARSE_LOCATION (粗定位)和 ACCESS_FINE_LOCATION (精定位)。你所选择的权限决定了定位的精度。如果你只请求粗定位,位置服务所范围的地点信息大致会精确到一个城市街区。

如果请求 ACCESS_FINE_LOCATION ,它也暗含了 ACCESS_COARSE_LOCATION 的权限。

例如,要添加 ACCESS_COARSE_LOCATION ,将下面的代码作为 <manifest> 元素的子元素:

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

二). 检查Google Play服务

位置服务是Google Play服务APK的其中一部分。由于用户设备的状态时难以预料的,你应该一直在你尝试连接定位服务之前,检查APK是否已经安装。要检查APK是否安装,可以调用 GooglePlayServicesUtil.isGooglePlayServicesAvailable() ,它会返回一个整形的结果码,其含义可以参阅: ConnectionResult 。如果你遇到了一个错误,可以调用 GooglePlayServicesUtil.getErrorDialog() ,来获取一个本地的对话框,引导用户执行正确地行为,之后将这一对话框显示在一个 DialogFragment 上。这一对话框可能允许用户解决当前的问题,此时Google Play服务会发回一个结果到你的activity中。要处理这一结果,需要覆写 onActivityResult() 方法。

Note:

要使你的应用可以兼容1.6及以后版本的系统,显示 DialogFragment 的activity必须是 FragmentActivity 的子类,而非 Activity 。使用 FragmentActivity 还可以允许你调用 getSupportFragmentManager() 方法来显示 DialogFragment

由于你一直需要在你的代码多个地方检查Google Play服务,所以应该定义一个方法将检查行为进行封装,之后在每次连接尝试之前进行检查。下面的代码片段包含了检查Google Play服务所需要的代码:

      
        public
      
      
        class
      
       MainActivity 
      
        extends
      
      
         FragmentActivity {

    ...

    
      
      
        //
      
      
         Global constants
      
      
        /*
      
      
        

     * Define a request code to send to Google Play services

     * This code is returned in Activity.onActivityResult

     
      
      
        */
      
      
        private
      
      
        final
      
      
        static
      
      
        int
      
      
        

            CONNECTION_FAILURE_RESOLUTION_REQUEST 
      
      = 9000
      
        ;

    ...

    
      
      
        //
      
      
         Define a DialogFragment that displays the error dialog
      
      
        public
      
      
        static
      
      
        class
      
       ErrorDialogFragment 
      
        extends
      
      
         DialogFragment {

        
      
      
        //
      
      
         Global field to contain the error dialog
      
      
        private
      
      
         Dialog mDialog;

        
      
      
        //
      
      
         Default constructor. Sets the dialog field to null
      
      
        public
      
      
         ErrorDialogFragment() {

            
      
      
        super
      
      
        ();

            mDialog 
      
      = 
      
        null
      
      
        ;

        }

        
      
      
        //
      
      
         Set the dialog to display
      
      
        public
      
      
        void
      
      
         setDialog(Dialog dialog) {

            mDialog 
      
      =
      
         dialog;

        }

        
      
      
        //
      
      
         Return a Dialog to the DialogFragment.
      
      
                @Override

        
      
      
        public
      
      
         Dialog onCreateDialog(Bundle savedInstanceState) {

            
      
      
        return
      
      
         mDialog;

        }

    }

    ...

    
      
      
        /*
      
      
        

     * Handle results returned to the FragmentActivity

     * by Google Play services

     
      
      
        */
      
      
        

    @Override

    
      
      
        protected
      
      
        void
      
      
         onActivityResult(

            
      
      
        int
      
       requestCode, 
      
        int
      
      
         resultCode, Intent data) {

        
      
      
        //
      
      
         Decide what to do based on the original request code
      
      
        switch
      
      
         (requestCode) {

            ...

            
      
      
        case
      
      
         CONNECTION_FAILURE_RESOLUTION_REQUEST :

            
      
      
        /*
      
      
        

             * If the result code is Activity.RESULT_OK, try

             * to connect again

             
      
      
        */
      
      
        switch
      
      
         (resultCode) {

                    
      
      
        case
      
      
         Activity.RESULT_OK :

                    
      
      
        /*
      
      
        

                     * Try the request again

                     
      
      
        */
      
      
        

                    ...

                    
      
      
        break
      
      
        ;

                }

            ...

        }

        ...

    }

    ...

    
      
      
        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("Location Updates"
      
        ,

                    
      
      "Google Play services is available."
      
        );

            
      
      
        //
      
      
         Continue
      
      
        return
      
      
        true
      
      
        ;

        
      
      
        //
      
      
         Google Play services was not available for some reason
      
      

        } 
      
        else
      
      
         {

            
      
      
        //
      
      
         Get the error code
      
      
        int
      
       errorCode =
      
         connectionResult.getErrorCode();

            
      
      
        //
      
      
         Get the error dialog from Google Play services
      
      

            Dialog errorDialog =
      
         GooglePlayServicesUtil.getErrorDialog(

                    errorCode,

                    
      
      
        this
      
      
        ,

                    CONNECTION_FAILURE_RESOLUTION_REQUEST);

            
      
      
        //
      
      
         If Google Play services can provide an error dialog
      
      
        if
      
       (errorDialog != 
      
        null
      
      
        ) {

                
      
      
        //
      
      
         Create a new DialogFragment for the error dialog
      
      

                ErrorDialogFragment errorFragment =

                        
      
        new
      
      
         ErrorDialogFragment();

                
      
      
        //
      
      
         Set the dialog in the DialogFragment
      
      
                        errorFragment.setDialog(errorDialog);

                
      
      
        //
      
      
         Show the error dialog in the DialogFragment
      
      
                        errorFragment.show(

                        getSupportFragmentManager(),

                        
      
      "Location Updates"
      
        );

            }

        }

    }

    ...

}
      
    

在后续章节的代码片段中,都会调用这一方法来验证是否可获取Google Play服务。


三). 定义位置服务回调函数

在你创建定位客户端之前,实现定位服务的接口,以和你的应用进行交互:

ConnectionCallbacks

指定当定位连接上或者没有连接上时,定位服务调用的方法。

OnConnectionFailedListener

指定当尝试连接到定位客户端时,如果出现了错误,定位服务调用的方法。这一方法使用之前定义的 showErrorDialog 方法来显示一个错误对话框,它尝试使用Google Play服务来解决这一问题。

下面的样例代码展示了如何指定接口和定义相关的函数:

      
        public
      
      
        class
      
       MainActivity 
      
        extends
      
       FragmentActivity 
      
        implements
      
      
        

        GooglePlayServicesClient.ConnectionCallbacks,

        GooglePlayServicesClient.OnConnectionFailedListener {

    ...

    
      
      
        /*
      
      
        

     * 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 dataBundle) {

        
      
      
        //
      
      
         Display the connection status
      
      

        Toast.makeText(
      
        this
      
      , "Connected"
      
        , Toast.LENGTH_SHORT).show();

    }

    ...

    
      
      
        /*
      
      
        

     * Called by Location Services if the connection to the

     * location client drops because of an error.

     
      
      
        */
      
      
        

    @Override

    
      
      
        public
      
      
        void
      
      
         onDisconnected() {

        
      
      
        //
      
      
         Display the connection status
      
      

        Toast.makeText(
      
        this
      
      , "Disconnected. Please re-connect."
      
        ,

                Toast.LENGTH_SHORT).show();

    }

    ...

    
      
      
        /*
      
      
        

     * Called by Location Services if the attempt to

     * Location Services fails.

     
      
      
        */
      
      
        

    @Override

    
      
      
        public
      
      
        void
      
      
         onConnectionFailed(ConnectionResult connectionResult) {

        
      
      
        /*
      
      
        

         * 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
      
      
        ,

                        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.

             
      
      
        */
      
      
        

            showErrorDialog(connectionResult.getErrorCode());

        }

    }

    ...

}
      
    

定义地理位置更新回调函数

定位服务或是以一个 Intent 的形式,或者以一个参数的形式将为之更新传递给一个你定义的回调函数。这节课将会讲解如何使用一个回调函数来获取更新,课程中使用的代码基本可以用于任何应用场景。如果你想要以一个 Intent 的形式接收位置更新,可以阅读: Recognizing the User's Current Activity 。它提供了类似的可以参考的模板。

位置服务所调用的将为之更新发送给你的应用的回调函数是在 LocationListener 接口的 onLocationChanged() 方法中指定的。传入的参数是一个 Location 对象,包含了地点的经纬度。下面的代码片段展示了如何指定接口和定义方法:

      
        public
      
      
        class
      
       MainActivity 
      
        extends
      
       FragmentActivity 
      
        implements
      
      
        

        GooglePlayServicesClient.ConnectionCallbacks,

        GooglePlayServicesClient.OnConnectionFailedListener,

        LocationListener {

    ...

    
      
      
        //
      
      
         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 = "Updated Location: " +
      
        

                Double.toString(location.getLatitude()) 
      
      + "," +
      
        

                Double.toString(location.getLongitude());

        Toast.makeText(
      
      
        this
      
      
        , msg, Toast.LENGTH_SHORT).show();

    }

    ...

}
      
    

现在你已经有了回调函数,你可以配置位置更新的请求了。首先第一步是指定控制更新的参数。


四). 指定更新参数

定位服务允许你控制更新之间的时间间隔以及你期望的位置精确度,通过设置 LocationRequest 对象中的值,再将这一对象作为你的请求的一部分发出以开始更新。

首先,设置下列间隔参数:

更新间隔:

通过 LocationRequest.setInterval() 设置。这一方法以毫秒为单位设置你的应用接收更新的事件间隔。如果没有其它应用从定位服务接收更新,那么你的应用将会以这一频率接收更新。

最快更新间隔:

通过 LocationRequest.setFastestInterval() 设置。这一方法设置的是你的应用能处理更新的最快间隔时间,以毫秒为单位。你需要设置这个频率是因为其它应用也会影响位置更新非频率。定位服务会以所有应用通过 LocationRequest.setInterval() 设置的最快的间隔时间来发送更新。如果这一频率比你的应用能够处理的频率要快,那么你可能会遇到UI闪烁或数据溢出等问题。为了避免这一情况发生,应该调用 LocationRequest.setFastestInterval() 这一方法设置更新频率的最高限额。

调用 LocationRequest.setFastestInterval() 方法还可以节省电量。当你通过 LocationRequest.setInterval() 请求了一个更新间隔后,又用 LocationRequest.setFastestInterval() 请求了一个最大速率后,你的应用会以正常速率进行更新。如果其它应用使用了一个更快的更新速率,那么你的更新频率也会加快。如果没有其它应用申请了更快的更新速率,那么你的应用会以 LocationRequest.setInterval() 中所设置的速率进行更新。

接下来,设置精度参数。在一个前台应用程序中,你需要以高频率更新地理位置,所以使用 LocationRequest.PRIORITY_HIGH_ACCURACY 设置精度。

下面的代码片段展示课如何设置更新间隔和精度:

      
        public
      
      
        class
      
       MainActivity 
      
        extends
      
       FragmentActivity 
      
        implements
      
      
        

        GooglePlayServicesClient.ConnectionCallbacks,

        GooglePlayServicesClient.OnConnectionFailedListener,

        LocationListener {

    ...

    
      
      
        //
      
      
         Global constants
      
      
            ...

    
      
      
        //
      
      
         Milliseconds per second
      
      
        private
      
      
        static
      
      
        final
      
      
        int
      
       MILLISECONDS_PER_SECOND = 1000
      
        ;

    
      
      
        //
      
      
         Update frequency in seconds
      
      
        public
      
      
        static
      
      
        final
      
      
        int
      
       UPDATE_INTERVAL_IN_SECONDS = 5
      
        ;

    
      
      
        //
      
      
         Update frequency in milliseconds
      
      
        private
      
      
        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 = 1
      
        ;

    
      
      
        //
      
      
         A fast frequency ceiling in milliseconds
      
      
        private
      
      
        static
      
      
        final
      
      
        long
      
       FASTEST_INTERVAL =
      
        

            MILLISECONDS_PER_SECOND 
      
      *
      
         FASTEST_INTERVAL_IN_SECONDS;

    ...

    
      
      
        //
      
      
         Define an object that holds accuracy and frequency parameters
      
      
            LocationRequest mLocationRequest;

    ...

    @Override

    
      
      
        protected
      
      
        void
      
      
         onCreate(Bundle savedInstanceState) {

        
      
      
        super
      
      
        .onCreate(savedInstanceState);

        
      
      
        //
      
      
         Create the LocationRequest object
      
      

        mLocationRequest =
      
         LocationRequest.create();

        
      
      
        //
      
      
         Use high accuracy
      
      
                mLocationRequest.setPriority(

                LocationRequest.PRIORITY_HIGH_ACCURACY);

        
      
      
        //
      
      
         Set the update interval to 5 seconds
      
      
                mLocationRequest.setInterval(UPDATE_INTERVAL);

        
      
      
        //
      
      
         Set the fastest update interval to 1 second
      
      
                mLocationRequest.setFastestInterval(FASTEST_INTERVAL);

        ...

    }

    ...

}
      
    

Note:

如果你的应用要访问网络或者在接收到更新后需要做其它长期的任务,那么应该调整更新频率到一个比较慢的值。这可以避免你的应用接收到太多它来不及处理的更新数据。一旦长期处理的任务结束了,可以再通过设置最快更新频率到一个较快的值。


五). 开始位置更新

要发送位置更新请求,在 onCreate() 创建一个定位客户端,之后连接它,并通过 requestLocationUpdates() 发起请求。因为你的客户端必须连接以后你的应用才能收到更新,所以你应该在 onStart() 方法中连接到客户端。这能保证当你的应用可见时,你都能获取一个已连接的有效的客户端。因为你需要在发出请求前先进行连接,所以在 ConnectionCallbacks.onConnected() 发出更新请求。

另外要记住用户可能会有各种各样的原因希望关闭位置更新。你应该为用户提供一个这样做的方法,并且你应该保证当更新关闭了之后,你不会在 onStart() 中启动更新。为了记录用户的设置,在 onPause() 方法中保存应用的 SharedPreferences ,并在 onResume() 方法中获取它。

下面的代码片段展示了如何在 onCreate() 方法中设置客户端,以及如何在 onStart() 方法中连接并发出更新请求:

      
        public
      
      
        class
      
       MainActivity 
      
        extends
      
       FragmentActivity 
      
        implements
      
      
        

        GooglePlayServicesClient.ConnectionCallbacks,

        GooglePlayServicesClient.OnConnectionFailedListener,

        LocationListener {

    ...

    
      
      
        //
      
      
         Global variables
      
      
            ...

    LocationClient mLocationClient;

    
      
      
        boolean
      
      
         mUpdatesRequested;

    ...

    @Override

    
      
      
        protected
      
      
        void
      
      
         onCreate(Bundle savedInstanceState) {

        ...

        
      
      
        //
      
      
         Open the shared preferences
      
      

        mPrefs = getSharedPreferences("SharedPreferences"
      
        ,

                Context.MODE_PRIVATE);

        
      
      
        //
      
      
         Get a SharedPreferences editor
      
      

        mEditor =
      
         mPrefs.edit();

        
      
      
        /*
      
      
        

         * Create a new location client, using the enclosing class to

         * handle callbacks.

         
      
      
        */
      
      
        

        mLocationClient 
      
      = 
      
        new
      
       LocationClient(
      
        this
      
      , 
      
        this
      
      , 
      
        this
      
      
        );

        
      
      
        //
      
      
         Start with updates turned off
      
      

        mUpdatesRequested = 
      
        false
      
      
        ;

        ...

    }

    ...

    @Override

    
      
      
        protected
      
      
        void
      
      
         onPause() {

        
      
      
        //
      
      
         Save the current setting for updates
      
      

        mEditor.putBoolean("KEY_UPDATES_ON"
      
        , mUpdatesRequested);

        mEditor.commit();

        
      
      
        super
      
      
        .onPause();

    }

    ...

    @Override

    
      
      
        protected
      
      
        void
      
      
         onStart() {

        ...

        mLocationClient.connect();

    }

    ...

    @Override

    
      
      
        protected
      
      
        void
      
      
         onResume() {

        
      
      
        /*
      
      
        

         * Get any previous setting for location updates

         * Gets "false" if an error occurs

         
      
      
        */
      
      
        if
      
       (mPrefs.contains("KEY_UPDATES_ON"
      
        )) {

            mUpdatesRequested 
      
      =
      
        

                    mPrefs.getBoolean(
      
      "KEY_UPDATES_ON", 
      
        false
      
      
        );



        
      
      
        //
      
      
         Otherwise, turn off location updates
      
      

        } 
      
        else
      
      
         {

            mEditor.putBoolean(
      
      "KEY_UPDATES_ON", 
      
        false
      
      
        );

            mEditor.commit();

        }

    }

    ...

    
      
      
        /*
      
      
        

     * 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 dataBundle) {

        
      
      
        //
      
      
         Display the connection status
      
      

        Toast.makeText(
      
        this
      
      , "Connected"
      
        , Toast.LENGTH_SHORT).show();

        
      
      
        //
      
      
         If already requested, start periodic updates
      
      
        if
      
      
         (mUpdatesRequested) {

            mLocationClient.requestLocationUpdates(mLocationRequest, 
      
      
        this
      
      
        );

        }

    }

    ...

}
      
    

更多关于保存配置信息的知识,可以查看: Saving Key-Value Sets


六). 停止位置更新

要停止位置更新,在 onPause() 方法中保存更新标识的状态,并在 onStop() 方法中通过调用 removeLocationUpdates(LocationListener) 来停止更新,例如:

      
        public
      
      
        class
      
       MainActivity 
      
        extends
      
       FragmentActivity 
      
        implements
      
      
        

        GooglePlayServicesClient.ConnectionCallbacks,

        GooglePlayServicesClient.OnConnectionFailedListener,

        LocationListener {

    ...

    
      
      
        /*
      
      
        

     * Called when the Activity is no longer visible at all.

     * Stop updates and disconnect.

     
      
      
        */
      
      
        

    @Override

    
      
      
        protected
      
      
        void
      
      
         onStop() {

        
      
      
        //
      
      
         If the client is connected
      
      
        if
      
      
         (mLocationClient.isConnected()) {

            
      
      
        /*
      
      
        

             * Remove location updates for a listener.

             * The current Activity is the listener, so

             * the argument is "this".

             
      
      
        */
      
      
        

            removeLocationUpdates(
      
      
        this
      
      
        );

        }

        
      
      
        /*
      
      
        

         * After disconnect() is called, the client is

         * considered "dead".

         
      
      
        */
      
      
        

        mLocationClient.disconnect();

        
      
      
        super
      
      
        .onStop();

    }

    ...

}
      
    

现在你已经有了请求并接收定期位置更新的基本应用框架。你可以将这节课中所讲的东西结合到导航,行为识别,反地址解析等等场景中。

下一节课中,我们将会讲解如何使用当前地点显示现在的街道地址。

【Android Developers Training】 104. 接受地点更新


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论