精通安卓性能优化-第七章(三)

定位

任何房地产经纪人将告诉你3个最重要的事情是:locaiton, location, location。Android理解这件事,并且允许应用知道设备在哪里。(不会告诉你设备正在一个好学区,尽管我很确定有个这样的应用)Listing 7-8给出了如何使用系统的定位服务请求location更新。

Listing 7-8 接收位置更新

private void requestLocationUpdates() {
    LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
    
    List<String> providers = lm.getAllProviders();
    
    if(providers != null && !providers.isEmpty()) {
        LocationListener listener = new LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                Log.i(TAG, location.toString());
            }
            
            @Override
            public void onProviderDisabled(String provider) {
                Log.i(TAG, provider + " location provider disabled");
            }
            
            @Override
            public void onProviderEnabled(String provider) {
                Log.i(TAG, provider + " location provider enabled");
            }
            
            @Override
            public void onStatusChanged(String provider, int status, Bundle extras) {
                Log.i(TAG, provider + " location provider status changed to " + status);
            }
        };
        
        for (String name:providers) {
            Log.i(TAG, "Requesting location updates on " + name);
            lm.requestLocationUpdates(name, 0, 0, listener);
        }
    }
}

NOTE:为了获取位置信息,应用需要ACCESS_COARSE_LOCATION或者ACCESS_FINE_LOCATION权限。GPS定位需要ACCESS_FINE_LOCATION,网络定位需要ACCESS_COARSE_LOCATION或者ACCESS_FINE_LOCATION。

这段代码有严重的缺陷,为了看这段代码对电池的影响,你仍然要运行它,比如在应用的onCreate()方法中。
在一个Galaxy Tab 10.1上,可以观察到如下事情:
(1) 3种定位提供可用(network, gps, passive)
(2) GPS定位更新很频繁(每秒更新一次)
(3) 网络定位更新不是很频繁(45秒更新一次)
让你的应用使用这段代码一段时间,会发现电池电量比平时下降快很多。这段代码3个缺陷是:
(1) 定位的listener没有取消
(2) 定位更新太频繁
(3) 定位更新向多个提供者请求
幸运的是,所有的这些缺陷可以很简单的修复。最终,如何使用定位服务依赖于应用的需要,没有为所有应用的单一解决方案,所以在一个应用中认为是一个缺陷可能在另外一个是设计需求。同样注意通常没有针对所有用户的一个方案:你需要考虑所有用户的需要,在应用中提供不同的设置以迎合你的用户。比如,有些用户希望牺牲电池生命得到更加频繁的定位更新,其他希望限制更新的数量保证他的设备不需要在午休的时候去充电。

注销Listener

为了注销listener,你可以如Listing 7-9所示简单的调用removeUpdates()。通常在onPause()去做比较好,但是应用可能会在其他的地方需要它。长时间的监听地点更新会消耗大量的电能,所以你的应用需要尝试去获得它需要的信息后停止监听更新。有些情况下提供一种方式让用户强制更新定位是好的想法。

Listing 7-9 关闭更新Listener

private void disableLocationListener(LocationListener listener) {
    LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
    lm.removeUpdates(listener);
}

调用requestLocationUpdates()的时候可以调整更新的频率。

更新频率

尽管LocationManager类定义了5个requestLocationUpdates()方法,都有两个相同的参数:minTime和minDistance。第一个,minTime,指定了以毫秒为单位的通知间隔。这只是用来告诉系统要节省电能的暗示,实际时间间隔可能会大于或者小于指定的时间。显然的,更大的值将更多的节省电能。第二个,minDistance,指定了通知的最小距离间隔。更大的值更加节省电能,因为会执行更少的代码,你的应用可能所有的更新都不被通知。Listing 7-10显示的如何注册每隔1小时,或者每隔100米更新的Listener。

Listing 7-10 不要太频繁的接收位置更新

private void requestLocationUpdates() {
    LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
    
    List<String> providers = lm.getAllProviders();
    
    if (providers != null) {
        for (String name : providers) {
            LocationListener listener = new LocationListener() {
                ...
            };
            
            Log.i(TAG, "Requesting location updates on " + name);
            lm.requestLocationUpdates(name, DateUtils.HOUR_IN_MILLIES * 1, 100, listener);
        }
    }
}

选择合适的间隔更多的是艺术而不是技术,并且依赖于你的应用。一个导航应用通常需要频繁的更新,如果同样的应用给徒步者使用,更新可能要求更少。另外,你需要去权衡定位准确性和电能。给用户选择,提供设置交互,允许他们自己选择适合他们的行为。

多个提供者

就像之前提到的,Android提供了多个位置提供者:
1)GPS(全球定位系统)
2)Network (使用Cell-ID和Wifi位置)
3)Passive (从API 8)
GPS定位(LocationManager.GPS_PROVIDER)通常是最精确的,水平精确度大约10米(11 yards)。网络定位没有GPS定位精确,精确度依赖于系统可以使用多少位置去计算设备的位置。比如,我的Log信息显示网络定位大约48米的精确度,一个足球场大小。
尽管更加精确,在时间和电池使用上来说,GPS定位是最昂贵的。获取一个修正的GPS位置,需要锁定多个卫星的信号,在野外需要几秒钟的有限时间,如果在室内,不能锁定信号(就像在停车场,你的车的GPS尝试获取卫星)。比如,在室内GPS开启后需要大约35秒去获取第一个位置,在室外仅需要5秒。网络定位需要5秒去获取第一个位置,不管室内还是室外。辅助定位GPS(AGPS)通常提供更快的修正位置,实际的时间依赖于设备已经缓存的信息和网络访问。

NOTE:用于这些测量的Galaxy Tab 10.1是Wi-Fi only版本。如果使用cell id,将会得到更快的位置修正。

尽管以前从多个提供者接收更新作为一个流程,可能不总是一个。事实上,为了得到一个更加精确的修正位置,你可能希望从几个提供者接收更新。
passive位置提供者是最能节省电能的。当你的应用使用passive位置提供,表示它对位置更新感兴趣,但是不需要位置修正。换句话说,你的应用简单的等待其他的应用、服务、或者系统组件去请求位置更新,和他们的监听器一块接收结果。Listing 7-11给出了如何接受passive位置更新。为了测试是的应用是否接受更新,打开另外一个使用location服务的应用,比如Maps。
Listing 7-11 接收Passive位置更新

private void requestPassiveLocationUpdates() {
    LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
    LocationListener listener = new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            Log.i(TAG, "[PASSIVE] " + location.toString());
            
            // 让我们说你只关注GPS位置更新
            
            if (LocationManager.GPS_PROVIDER.equals(location.getProvider())) {
                // 如果你关心精度,保证你调用了hasAccuracy()
                // (altitude和bearing同样的备注)
                
                if (location.hasAccuracy() && (location.getAccuracy() < 10.0f)) {
                    // 在这里做事情
                }
            }
        }
        
        @Override
        public void onProviderDisabled(String provider) {
            Log.i(TAG, "[PASSIVE] " + provider + " location provider disabled");
        }
        
        @Override
        public void onProviderEnabled(String provider) {
            Log.i(TAG, "[PASSIVE] " + provider + " location provider enabled");
        }
        
        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
            Log.i(TAG, "[PASSIVE] " + provider + " location provider status changed to " + status);
        }
    };
    
    Log.i(TAG, "Requesting passive location updates");
    lm.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, DateUtils.SECOND_IN_MILLIS * 30, 100, listener);
}

如果你使用这段代码开启或者关闭Wi-Fi或者GPS,你会注意到当一个provider屏蔽或者开启的时候passive listener不会被通知,或者说一个提供者状态改变的时候。通常这不重要,可能强制你的应用使用其他的位置提供者,如果真的在意这些改变的通知。
一个好的权衡是注册一个网络位置provider Listener,它比GPS定位使用更少的电能,同时注册一个passive位置provider,为了得到可能更精确的GPS位置信息。

NOTE:即使仅接收网络provider的位置更新,应用需要ACCESS_FINE_LOCATION权限去使用passive位置提供者。这可能成为一个问题,如果你相信这可能提升用户的隐私问题。现在没有方式仅从网络位置提供者和仅使用ACCESS_COARSE_LOCATION权限获取passive更新。

过滤provider

因为我们聚焦于电池使用时间,如果不选择使用passive location provider,你的应用可能希望过滤掉需要高电能的位置提供者。Listing 7-12给出了你如何得到所有位置提供者的电能需求。

Listing 7-12 位置提供者电能需求

private static String powerRequirementCodeToString(int powerRequirement) {
    switch (powerRequirement) {
        case Criteria.POWER_LOW: return "Low";
        case Criteria.POWER_MEDIUM: return "Meidum";
        case Criteria.POWER_HIGH: return "High";
        default: return String.format("Unknown (%d)", powerRequirement);
    }
}

private void showLocationProvidersPowerRequirement() {
    Location lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
    
    List<String> providers = lm.getAllProviders();
    
    if (providers != null) {
        for (String name : providers) {
            LocationProvider provider = lm.getProvider(name);
            
            if (provider != null) {
                int powerRequirement = provider.getPowerRequirement();
                
                Log.i(TAG, name + " location provider power requirement: " + powerRequirementCodeToString(powerRequirement));
            }
        }
    }
}

NOTE:就像期望的,passive location provider的电能需求是不知道的。

然而,因为你的应用可能有非常明确的需求,可能首先尽可能精确的指定你需要什么样的location provider很简单。比如,你的应用可能希望使用除了坐标之外提供速度信息的location provider。Listing 7-13给出了如何创建一个Criteria对象,找出你需要使用哪个location provider。

Listing 7-13 使用Criteria找出Location Provider

private LocationProvider getMyLocationProvider() {
    LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
    Criteria criteria = new Criteria();
    LocationProvider provider = null;
    
    // 定义你自己的criteria
    criteria.setAccuracy(Criteria.ACCURACY_COARSE);
    criteria.setAltitudeRequired(true);
    criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);// API 9
    criteria.setBearingRequired(false);
    criteria.setCostAllowed(true);  // 很可能你希望用户能够设置它
    criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW); // API 9
    criteria.setPowerRequirement(Criteria.POWER_LOW);
    criteria.setSpeedAccuracy(Criteria.ACCURACY_MEDIUM);  // API 9
    criteria.setSpeedRequired(false);
    criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT); // API 9
    
    List<String> names = lm.getProviders(criteria, false);  // 仅完美的匹配
    
    if ((names != null) && !names.isEmpty()) {
        for (String name : names) {
            provider = lm.getProvider(name);
            Log.d(TAG, "[getMyLocationProvider] " + provider.getName() + " " + provider);
        }
        
        provider = lm.getProvider(names.get(0));
    } else {
        Log.d(TAG, "Could not find perfect match for location provider");
        
        String name = lm.getBestProvider(criteria, false);  // 不需要完美的匹配
        
        if (name != null) {
            provider = lm.getProvider(name);
            Log.d(TAG, "[getMyLocationProvider] " + provider.getName() + " " + provider);
        }
    }
    
    return provider;
}

LocationManager.getProviders()和LocationManager.getBestProvider()非常不同。getProviders()仅仅返回完美的匹配,getBestProvider()将首先查找一个完美的匹配,没有的话也将返回一个provider。criteria按如下的顺序放松要求:
(1) 电能需求
(2) 精度
(3) Bearing
(4) Speed
(5) Altitude
因为这个顺序不是你需要采用location provider的必要策略,你可能需要去开发自己的算法去找到合适的provider。同样,算法可能依赖于当前的电池状态:如果电量比较低的话,你的应用可能不会希望去放松电能需求标准。

上次知道的位置

在你决定注册一个location provider的listener之前,你可能首先去检查已经有了一个已知的位置(被系统缓存)。LocationManager类定义了getLastKnownLocation()方法,返回某个指定provider的已知location,没有的话返回null。尽管这个位置可能已经过时了,这通常是一个好的起点,因为这个位置可以被立即获取,调用这个方法不会启动provider。尽管注册了一个location listener,为了更快的响应,应用通常首先获取last known location,因为获取一个位置更新通常需要几秒钟。Listing 7-14给出了怎样获取一个last known location。
Listing 7-14 Last Known Location

private Location getLastKnownLocation() {
    LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
    List<String> names = lm.getAllProviders();
    Location location = null;
    
    if (names != null) {
        for (String name : names) {
            if (! LocationManager.PASSIVE_PROVIDER.equals(name)) {
                Location l = lm.getLastKnownLocation(name);
                
                if ((l != null) && (location == null || l.getTime() > location.getTime())) {
                    location = l;
                    
                    /*
                    * 警告:GPS和network provider的时钟可能会不同步,所以比较时间
                    * 可能不是一个好的想法...我们可能根本没有最近的位置修正
                    */
                }
            }
        }
    }
    
    return location;
}

GPS是卫星敏感的,大多数数的Android设备都有其他类型的传感器,使得Android应用程序相对传统计算机的更加有趣。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值