zoukankan      html  css  js  c++  java
  • Android Location在GPS中的应用(二)

    这一篇其实跟GPS毫无关系。 继续上一篇的内容,讲GPS以外的东西,比如说Service的使用。比如说gps监控,它并不需要任何UI,在后台默默地运行就行。为什么不做成Service呢?悄悄地向服务器发送用户的位置坐标是一个不错的想法,因为它完全不需要用户的干预。当然为了保留用户权利,我们应当留一个地方让用户把服务关掉。

    继续前一篇的工程,如果你没有保留前面的工作也没有关系,从头来就是了。

    一、AndroidManifext.xml

    新建Google Project ,注意选择Google APIs。

    编辑AndroidManifest.xml,加入相应的权限:

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

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

     

    其次,由于我们使用了服务,需要在<application></application>标签加入一个<service>标签,以注册自己的服务:

    < service android:label = "@string/app_name" android:name = ".GpsService" >

    < intent-filter >

    < action android:name = "start_gps_service" ></ action >

    < category android:name = "android.intent.category.DEFAULT" ></ category >

    </ intent-filter >

    </ service >

    这个.GpsService是我们自定义的service类,它可以相应一个”start_aps_servicee”的Action。

    二、main.java

    我们这个应用就一个Activity。main.xml不用做任何修改。打开main.java,以下代码在菜单中添加了2个菜单项,以便让用户开启和关闭gps:

    // 创建菜单

    public boolean onCreateOptionsMenu(Menu menu) {

    // TODO Auto-generated method stub

    super .onCreateOptionsMenu(menu);

    menu.add(0, Menu. FIRST + 1, 1, " 打开 GPS 监控 " );

    menu.add(0, Menu. FIRST + 2, 2, " 关闭 GPS 监控 " );

    return true ;

    }

     

    // 菜单项监听

    public boolean onOptionsItemSelected(MenuItem item) {

    Intent i;

    // TODO Auto-generated method stub

    super .onOptionsItemSelected(item);

    switch (item.getItemId()) {

    case Menu. FIRST + 1: // 打开 GPS 监控

    this .setTitle( "GPS Service Started" );

    i = new Intent( this , GpsService. class );

    this .bindService(i, connection , Context. BIND_AUTO_CREATE );

    break ;

    case Menu. FIRST + 2: // 关闭 GPS 监控

    this .setTitle( "GPS Service Stopped" );

    // 通过检测 mBinder 是否为空来判断服务是否已绑定,从而避免 service not registered 错误

    if ( mBinder != null ){

    i = new Intent( this , GpsService. class );

    this .unbindService( connection );

    mBinder=null;//释放mBinder,防止重复解绑定

    }

    break ;

    }

    return true ;

    }

    this.bindService 和this. unbindService 和分别启动和关闭服务。这里的服务是GpsService类,呆会再介绍。

    Android的Service有两种使用方式。一种比较简单,使用Activity的startService/stopService方法启动关闭服务。但这种方式不能满足我们的需要,因为无法向Service类传递参数。因此我们决定使用第2种方式,即IBinder的方式。其实第2种方式跟第1种方式的区别只在于,service实现了onBind方法,在onBind方法中返回了IBinder对象,你可以把更多的代码实现在IBinder中。同时service类会调用IBinder中的方法,于是我们可以通过该方法向IBinder传递参数。

    而且在调用方法上,bindService和unbindService都需要一个 ServiceConnection 类的参数。因此我们在main.java中申明了这个 ServiceConnection:

    ServiceConnection connection = new ServiceConnection() {

    @Override

    public void onServiceConnected(ComponentName arg0, IBinder arg1) {

    // TODO Auto-generated method stub

    mBinder = (IGpsBinder) arg1;

    if ( mBinder != null ) {

    mBinder .bindService(main. this );

    }

     

    }

    @Override

    public void onServiceDisconnected(ComponentName name) {

    // TODO Auto-generated method stub

     

    }

    };

    在这个 ServiceConnection 示例中,有两个方法需要实现。我们关注的只有 onServiceConnected方法。

    这个方法在连接服务后调用。我们在这个方法中使用了一个接口对象IGpsBinder:

    IGpsBinder mBinder = null ;

    IGpsBinder是一个接口,它对应的实现是GpsBinder类。这两个东西后面会说。

    先看服务的绑定,代码:

    i = new Intent( this , GpsService. class );

    this .bindService(i, connection , Context. BIND_AUTO_CREATE );

     

    首先,Intent中已经包含了服务的实现类GpsService,这样在bindService时就可以把服务类传递进去。其次,ServiceConnection也被传递进去了。

    这样,当bindService时,会先调用服务类GpsService的onBind方法,这个方法会返回一个IBinder对象(其实是IGpsBinder)。在后面GpsService的代码中可以看到,其实onBinder方法返回了一个IGpsBinder的实现类GpsBinder。

    这样就获得了一个GpsBinder,然后把这个GpsBinder传递给connection的 onServiceConnected 方法。通过 onServiceConnected 代码,最终调用的是GpsBinder的接口方法bindService(这个方法中包含了一个参数,我们通过它把main这个Activity传递进去)。

    对于服务的解绑定,先调用GpsService的unOnbind方法。

    了解了服务绑定的大致工作过程。接下来就是服务类GpsService了。

    三、GpsService.java

    public class GpsService extends Service {

    private GpsBinder binder = new GpsBinder();

    @Override

    public void onCreate() {

    super .onCreate();

    }

     

    @Override

    public void onDestroy() {

    // TODO Auto-generated method stub

    super .onDestroy();

    }

     

    @Override

    public void onStart(Intent intent, int startId) {

    // TODO Auto-generated method stub

    super .onStart(intent, startId);

    }

     

    @Override

    public boolean onUnbind(Intent intent) {

    // TODO Auto-generated method stub

    binder .unbindService();

    return true ;

    }

     

    @Override

    public IBinder onBind(Intent intent) {

    // TODO Auto-generated method stub

    return binder ;

    }

    }

    可以看到,GpsService的代码很简单,继承了Service,覆盖Service的5个生命周期方法。但除了onUnbind和onBinder方法外,我们都使用了默认的实现。在onUnbind方法中,我们调用binder的unbindService方法进行解绑定。其中,binder是一个GpsBinder,后面会介绍。在onBind方法中,我们返回了GpsBinder对象。这个GpsBinder对象能为我们做些什么呢?

    四、GpsBinder.java

    public class GpsBinder extends Binder implements IGpsBinder{

    private LocationManager locationManager ;

    private Location location ;

    private String provider ;

    private Context mContext = null ;

    public GpsBinder(){

     

    }

    @Override

    // 接口暴露方法

    public void bindService(Context ctx){

    mContext =ctx;

    // 获取 LocationManager 服务

    locationManager = (LocationManager) mContext

    .getSystemService(Context. LOCATION_SERVICE );

     

    // 如果未设置位置源,打开 GPS 设置界面

    openGPS();

    // 获取 Location Provider

    getProvider();

    // 获取位置

    location = locationManager .getLastKnownLocation( provider );

    // 显示位置信息到文字标签

    updateWithNewLocation( location );

    // 注册监听器 locationListener ,第 2 3 个参数可以控制接收 gps 消息的频度以节省电力。第 2 个参数为毫秒,

    // 表示调用 listener 的周期,第 3 个参数为米 , 表示位置移动指定距离后就调用 listener

    locationManager .requestLocationUpdates( provider , 2000, 10,

    locationListener );

    }

    public void unbindService(){

    // 注销 location 监听器

    locationManager .removeUpdates( locationListener );

    }

    // 获取 Location Provider

    private void getProvider() {

    // 构建位置查询条件

    Criteria criteria = new Criteria();

    // 查询精度:高

    criteria.setAccuracy(Criteria. ACCURACY_FINE );

    // 是否查询海拨:否

    criteria.setAltitudeRequired( false );

    // 是否查询方位角 :

    criteria.setBearingRequired( false );

    // 是否允许付费:是

    criteria.setCostAllowed( true );

    // 电量要求:低

    criteria.setPowerRequirement(Criteria. POWER_LOW );

    // 返回最合适的符合条件的 provider ,第 2 个参数为 true 说明 , 如果只有一个 provider 是有效的 , 则返回当前 provider

    provider = locationManager .getBestProvider(criteria, true );

    }

     

    // Gps 消息监听器

    private final LocationListener locationListener = new LocationListener() {

    // 位置发生改变后调用

    public void onLocationChanged(Location location) {

    updateWithNewLocation(location);

    }

     

    // provider 被用户关闭后调用

    public void onProviderDisabled(String provider) {

    updateWithNewLocation( null );

    }

     

    // provider 被用户开启后调用

    public void onProviderEnabled(String provider) {

    }

     

    // provider 状态变化时调用

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

    }

    };

     

    // Gps 监听器调用,处理位置信息

    private void updateWithNewLocation(Location location) {

    // 利用反射机制调用 mContext locationChanged 方法

    Class<?>[] types = new Class[] {Location. class }; // 这个方法有 1 个参数

    try {

    Method m = mContext .getClass().getMethod( "locationChanged" , types);

    if (m != null ) m.invoke( mContext , location);

    } catch (Exception e) {

    Log.e (GpsBinder. class .getName(), e.toString());

    }

    Log.i (GpsBinder. class .getName(), "location is changed:" +location);

    }

    // 判断是否开启 GPS ,若未开启,打开 GPS 设置界面

    private void openGPS() {

    boolean bGPS = locationManager

    .isProviderEnabled(android.location.LocationManager. GPS_PROVIDER );

    boolean bNetwork = locationManager

    .isProviderEnabled(android.location.LocationManager. NETWORK_PROVIDER );

    Log.e ( ":::" , "bGPS=" + bGPS + "bNetwork=" + bNetwork);

    if (bGPS || bNetwork) {

    Toast.makeText ( mContext , " 位置源已设置! " , Toast. LENGTH_SHORT ).show();

    return ;

    }

    // Toast.makeText(this, " 位置源未设置! ", Toast.LENGTH_SHORT).show();

    ((Activity) mContext ).showDialog(0);

    }

    }

    这个代码不解释了,完全就是把我们在上一篇中关于Location的代码搬到这里了。需要注意的是 unbindService bindService updateWithNewLocation 方法。

    unbindService方法前面已提过,调用main对象的unbindService方法时被调用,它会把location监听器注销,于是位置变化不再通知监听器。

    bindService方法是IGpsBinder中的接口方法,它注册了location监听器,当Gps芯片检测到位置发生改变时通知locationListener监听器。值得注意的是这个方法中的ctx参数,实际上是把main这个Activity传递进来了,并且保存在mContext变量中,这样在 updateWithNewLocation 方法中可以使用main。

    updateWithNewLocation 方法是监听器里的主要方法,它使用了一个技巧:利用java反射机制调用mContext的 locationChanged 方法。如果main没有实现 locationChanged 方法,则什么也不会做。这个技巧用来更新UI是很实用的。

    当然,我们在main.java中也实现了这个 locationChanged 方法:

     

    public void locationChanged(Location loc) {

    updateWithNewLocation(loc);

    }

    // Gps 监听器调用,处理位置信息

    private void updateWithNewLocation(Location location) {

    String latLongString;

    TextView myLocationText = (TextView) findViewById(R.id. text );

    if (location != null ) {

    double lat = location.getLatitude();

    double lng = location.getLongitude();

    latLongString = " 纬度 :" + lat + "/n 经度 :" + lng;

    } else {

    latLongString = " 无法获取地理信息 " ;

    }

    myLocationText.setText( " 您当前的位置是 :/n" + latLongString + "/n 地址 :"

    + getAddressString(getAddressbyGeoPoint(location)));

    }

    // 获取地址信息

    private List<Address> getAddressbyGeoPoint(Location location) {

    List<Address> result = null ;

    // 先将 Location 转换为 GeoPoint

    // GeoPoint gp =getGeoByLocation(location);

    try {

    if (location != null ) {

    // 获取 Geocoder ,通过 Geocoder 就可以拿到地址信息

    Geocoder gc = new Geocoder( this , Locale.getDefault ());

    // double geoLatitude = (int ) gp.getLatitudeE6() / 1E6;

    // double geoLongitude = (int ) gp.getLongitudeE6() / 1E6;

    result = gc.getFromLocation(location.getLatitude(),

    location.getLongitude(), 1);

    }

    } catch (Exception e) {

    e.printStackTrace();

    }

    return result;

    }

     

    // 把地址信息转换为一定格式的字符串

    private String getAddressString(List<Address> list) {

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

    Address add = list.get(0);

    return String.format ( "%s%s%s%s" , add.getCountryName(),

    add.getAdminArea(), add.getLocality(),

    add.getThoroughfare());

    } else

    return "" ;

    }

    内容很多,但大部分仍然是上一篇中实现过的内容。

    此外,在main.java中还要实现托管对话框:

    protected Dialog onCreateDialog( int id) {

    Log.e ( "::::" , "ddsdg" );

    return new AlertDialog.Builder( this )

    .setMessage( " 位置源未设置!是否现住设置位置源? " )

    .setPositiveButton( " " , new DialogInterface.OnClickListener() {

    public void onClick(DialogInterface dialog, int which) {

    // 转至 GPS 设置界面

    Intent intent = new Intent(

    Settings. ACTION_SECURITY_SETTINGS );

    startActivityForResult(intent, 0);

    }

    })

    .setNegativeButton( " " , new DialogInterface.OnClickListener() {

    public void onClick(DialogInterface dialog, int which) {

    dialog.dismiss(); // removeDialog(0); 移除对话框

    }

    }).create();

    }

    因为在GpsBinder的openGps方法中调用了mContext.showDialog方法。

    五、IGpsBinder.java

    IGpsBinder中只定义了1个接口方法,这也是在ServiceConnection唯一能识别和调用的IBinder方法:

     

    public interface IGpsBinder {

    // 接口暴露方法

    public void bindService(Context ctx);

    }

    方法本身含有一个参数,这个就是把main对象传递给GpsBinder的地方。

    六、测试

    可以在模拟器中进行测试了,别忘记前面说的利用DDMS的Location Controls来修改Gps坐标。点击“打开GPS监控”菜单,可以看到模拟器从Gps获取到当前位置信息,修改坐标经纬度后点”send”,新坐标立即被捕获并显示在窗口里;点“关闭GPS监控”菜单后,再修改坐标就没用了,还是显示原来的坐标。

     

     

     

     

  • 相关阅读:
    教育单元测试mock框架优化之路(下)
    教育单元测试mock框架优化之路(中)
    教育单元测试mock框架优化之路(上)
    spring /spring boot中mock
    Multi-Projector Based Display Code ------- Home
    投影的一些链接
    香茅油:不只是驱虫剂 new
    python特定时间发送消息到微信公众号
    基金定投研究
    PE就是市盈率的缩写 PB是平均市净率的缩写
  • 原文地址:https://www.cnblogs.com/encounter/p/2188507.html
Copyright © 2011-2022 走看看