三.能耗

3.1CPU

消耗取决于不同的因素:

  • 对数据的处理(例如,对文本进行格式化)
  • 待处理的数据大小
  • 处理数据的算法和数据结构 执行更新的次数,尤其是在数据更新后,触发应用的状态或UI进行更新。

最佳实践:

  • 针对不同的情况选择优化的算法:(实例小于43时)插入排序,归并排序,(实例大于286时)双枢轴快速排序,单枢轴快速排序。
  • 如果应用从服务器接受数据,尽量减少需要在客户端进行的处理。
  • 优化静态编译(ahead-of-time,AOT,预编译)处理:动态编译(just-in-time,JIT)处理的缺点在于它会强制用户等待操作完成。但是激进的AOT处理则会导致计算资源的浪费。
  • 分析电量消耗:测量目标用户的所有设备上的电量消耗。

3.2网络

智能的网络访问管理可以让应用响应的更快,并有助于延长电池的寿命。应避免在没有连接WiFi的情况下进行高带宽消耗的操作,比如视频流。蜂窝无线系统(LTE,4G,3G等)对电量的消耗都远大于WiFi信号。根源在于LTE设备基于多输入,多输出技术,使用多个并发信号以维护两端的LTE链接。类似的,所有的蜂窝数据链接都会定期扫描以寻找更强的信号。

  • 在进行任何网络操作之前,先检查合适的网络连接是否可用;
  • 持续监视网络的可用性,并在连接状态发生变化时给予适当的反馈。

官方Reachability示例代码:https://developer.apple.com/library/content/samplecode/Reachability/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007324-Intro-DontLinkElementID_2 Reachabilitypod:https://github.com/tonymillion/Reachability

检查网络状态:

-(BOOL)isAPIServerReachable{
	Reachability *r = [Reachability reachabilityWithHostName:@"api.yourdomain.com"];//1.检查服务器域名是否可达
	
	return r.isReachable;//2.对网络状态NetworkStatus进行优化
}

-(void)performNetWorkOperation:(NSDictionary *) params completion:(void(^)(NSError *,id)) completion{//3.提供id类型的结果或NSError类型错误
	if (![self isAPIServerReachable]){
//		[self enqueueRequest:params completion:completion];4.对操作进行排队
		NSError *err = [[NSError alloc]initWithDomain:@"network" code:1 userInfo:nil];//5.code需自定义常量
	}else{
//		[self doNetworkOperation:params completion:completion];//6.网络可用触发请求
	}
}

监控网络并执行队列:

[@interface](https://my.oschina.net/u/996807) HPNewworkOps ()
[@property](https://my.oschina.net/property) (nonatomic , readonly) BOOL isAPISeverReachable;//检测网络是否可用标识
[@property](https://my.oschina.net/property) (nonatomic , strong) Reachability *reachability;//监听状态 本例暂监视WiFi网络的变化
@property (nonatomic , strong) NSOperationQueue *networkOperationQueue;//保留队列的操作,该队列一次只允许执行一个操作
@property (nonatomic , strong) NSBlockOperation *operation;
@end

@implementation HPNewworkOps
-(id)init{
	if (self == [super init]){
		self.reachability = [Reachability reachabilityWithHostName:@"www.baidu.com"];
		self.reachability.reachableOnWWAN = NO;
		
		self.networkOperationQueue = [[NSOperationQueue alloc]init];
		self.networkOperationQueue.maxConcurrentOperationCount = 1;
		
		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkStatusChanged:) name:kReachabilityChangedNotification object:nil];
	}
	return self;
}

-(void)networkStatusChanged:(Reachability *)reachability{//根据网络的可用情况,通知的接受者挂起或恢复队列
	if (reachability.isReachable != ReachableViaWiFi){
		self.networkOperationQueue.suspended = YES;
	}else{
		self.networkOperationQueue.suspended = NO;
	}
}
-(BOOL)isAPISeverReachable{
	return self.reachability.isReachable;
}

-(void)performNetWorkOperation:(NSDictionary *)param completion:(void(^)(NSError *,id)) completion{//总是将网络操作送入队列中
	
	self.operation = [NSBlockOperation blockOperationWithBlock:^{
		[self enqueueRequest:param completion:completion];//对网络操作进行排队
	}];
	[self.networkOperationQueue addOperation:self.operation];
}
-(void)enqueueRequest:(NSDictionary *)params completion:(void(^)(NSError *,id)) completion{

	AFHTTPSessionManager *op = [AFHTTPSessionManager manager];
	op.requestSerializer.timeoutInterval = 10;
	[op POST:@"www.baidu.com" parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
		
	} success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
		completion(nil,responseObject);
	} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
		completion(error,nil);
	}];
}
@end

一个挂起的队列仅仅意味着后续操作在其恢复之前不会被执行,操作只有完成后才会从队列中移除。然而,为了完成执行,必须先启动操作。因为挂起的队列不会启动任何新的操作,所以它也不会移除任何正在排队且未被执行的操作

3.3定位管理器和GPS

使用GPS计算坐标需要确定两点信息:

  • 时间锁:每个GPS卫星每毫秒广播唯一一个1023位随机数。因为数据传播速率是1.024Mbit/s。GPS的接受芯片必须正确地与卫星的时间锁槽对齐。
  • 频率锁:GPS接收器必须计算由接收器与卫星的相对运动导致的多普勒偏移带来的信号误差。
- (void)viewDidLoad {
	[super viewDidLoad];
	self.manager = [[CLLocationManager alloc]init];
	self.manager.delegate = self;
	self.manager.distanceFilter = kCLDistanceFilterNone;//观察所有距离的变化
	self.manager.desiredAccuracy = kCLLocationAccuracyBest;//按照最大精度初始化管理器
	if (IOS8_OR_LATER_NEW){
		[self.manager requestAlwaysAuthorization];	//iOS8 特定API用于应用活动时申请使用定位服务
	}
	[self.manager stopUpdatingLocation];
}

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations{
	CLLocation *loc = [locations lastObject];
	//使用定位信息
}

3.3.1最佳的初始化

  • distanceFilter:只要设备的移动超过了最小距离,距离过滤器就会导致管理器对委托对象的locationManager: didUpdateLocations:事件通知发生变化。该距离使用公制单位(米)。
  • desiredAccuracy:精度参数的使用直接影响了使用天线的个数,进而影响了对电池的消耗。
    ◆ kCLLocationAccuracyBestForNavigation:用于导航的最佳精度级别
    ◆ kCLLocationAccuracyBest:设备可能达到的最佳精度级别
    ◆ kCLLocationAccuracyNearestTenMeters :精度接近10米
    ◆ kCLLocationAccuracyHundredMeters:精度接近100米
    ◆ kCLLocationAccuracyKilometer:精度在千米范围
    ◆ kCLLocationAccuracyThreeKilometers:精度在三千米范围
    距离过滤器只是软件层面的过滤器,而精度级别会影响物理天线的使用

3.3.2关闭无关紧要的特性

无需跟踪位置变化时调用stopUpdatingLocation,向终端用户提供关闭非必要功能的选项是个不错的设计。

3.3.3只在必要时使用网络

定期集中短暂的使用网络,而不是持续的保持活动的数据流,才能更好的节省消耗。

3.3.4后台定位服务

应用进入后台调用监听(startUpdatingLocation也会唤起回调,startMonitoringSignificantLocationChanges在程序被杀掉时调用更好)

- (void)applicationDidEnterBackground:(UIApplication *)application {
	[self.manager startMonitoringSignificantLocationChanges];
//	[self.manager startUpdatingLocation];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
	[self.manager stopMonitoringSignificantLocationChanges];
}

后台定位:http://adad184.com/2015/07/22/how-to-deal-with-background-location-update/

3.3.5NSTimer,NSThread和定位服务

当应用位于后台时,任何定时器或线程都会挂起。但如果你在应用位于后台状态时申请了定位,那么应用会在每次收到更新后被短暂唤醒。在此期间,线程和定时器都会被唤醒。

3.3.6在应用关闭后重启

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
	if(launchOptions[UIApplicationLaunchOptionsLocationKey]){//被杀掉的APP 在后台被系统唤醒时 launchOptions会包含UIApplicationLaunchOptionsLocationKey字段来进行标识
		[self.manager startMonitoringSignificantLocationChanges];//重新监听
	}
}

3.4屏幕

3.4.1动画

通过监听UIApplicationWillResignActiveNotification或UIApplicationDidEnterBackgroundNotification的通知事件来暂停或停止动画,也可以通过监听UIApplicationDidBecomeActiveNotification的通知事件来恢复动画。

3.4.2视频播放

[[UIApplication sharedApplication]setIdleTimerDisabled:YES];//保持屏幕常量

3.4.3多屏幕

官方使用外部屏幕demo:https://apple.co/1jauUnu

@interface HPMultiScreenViewController ()
@property (nonatomic , strong) UIWindow *secondWindow;
@end

@implementation HPMultiScreenViewController

- (void)viewDidLoad {
    [super viewDidLoad];
	[self registerNotifications];
}

-(void)viewDidAppear:(BOOL)animated{
	[super viewDidAppear:animated];
	[self updateScreens];
}

-(void)viewDidDisappear:(BOOL)animated{
	[super viewDidDisappear:animated];
	[self disconnectFromScreen];
}

-(void)disconnectFromScreen{
	if (self.secondWindow != nil){
		//断开链接并准备释放内存
		self.secondWindow.rootViewController = nil;
		self.secondWindow.hidden = YES;
		self.secondWindow = nil;
	}
}

-(void)updateScreens{
	NSArray *screens = [UIScreen screens];
	if (screens.count > 1){
		UIScreen *secondScreen = (UIScreen *)[screens objectAtIndex:1];
		CGRect rect = secondScreen.bounds;
		if (self.secondWindow == nil){
			self.secondWindow = [[UIWindow alloc]initWithFrame:rect];
			self.secondWindow.screen = secondScreen;
			
			HPScreen2ViewController *svc = [[HPScreen2ViewController alloc]init];
			//设置svc的其他属性以完整的对它初始化
//			svc.parent = self;
			
			self.secondWindow.rootViewController = svc;
		}
		self.secondWindow.hidden = NO;
	}else{
		[self disconnectFromScreen];
	}
	
}
-(void)dealloc{
	[self unregisterNotifications];
}

-(void)registerNotifications{
	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
	[nc addObserver:self selector:@selector(screenChanged:) name:UIScreenDidConnectNotification object:nil];
	[nc addObserver:self selector:@selector(screenChanged:) name:UIScreenDidDisconnectNotification object:nil];
}
-(void)screenChanged:(NSNotification *) notification{
	[self updateScreens];
}
-(void)unregisterNotifications{
	[[NSNotificationCenter defaultCenter] removeObserver:self];
}

@end
  • viewDidLoad注册UIScreenDidConnectNotification(屏幕连接)和UIScreenDidDisconnectNotification(屏幕断开)通知
  • 每当有新的屏幕加入或有屏幕移除时,都会调用screenChanged:方法,在这里更新UI
  • viewDidAppear在用户进入视图或离开视图时,更新UI
  • viewDidDisappear当用离开时所在视图控制器时,可能想在另一个屏幕上更新UI。
  • 调用disconnectFromScreen方法将secondWindow从屏幕上移除
  • updateScreens检查屏幕数量,如果大于1,将新的窗口与第二屏连接。

在屏幕之间交换UI

-(void)swapScreens:(UIWindow *)currentWindow newWindow:(UIWindow *)newWindow{
	NSArray *screens = [UIScreen screens];
	
	UIScreen *deviceScreen = [screens objectAtIndex:0];
	UIScreen *extScreen = [screens objectAtIndex:1];
	
	currentWindow.screen = extScreen;
	newWindow.screen = deviceScreen;
}

3.5电池电量与代码感知

一个智能的应用会考虑电池的电量和自身的状态,从而决定是否要真正的执行资源密集消耗性操作。

//使用电量级别和充电状态进行条件处理
-(BOOL)shouldProceedWithMinLevel:(NSInteger)minLevel{
	UIDevice *device = [UIDevice currentDevice];
	device.batteryMonitoringEnabled = YES;
	
	UIDeviceBatteryState state = device.batteryState;
	if (state == UIDeviceBatteryStateCharging || state == UIDeviceBatteryStateFull){//在充电或电池已经充满的状态可以进行操作
		return YES;
	}
	
	NSUInteger batteryLevel = (NSUInteger) (device.batteryLevel * 100);//按自己设定的电池范围操作
	if (batteryLevel >= minLevel){
		return YES;
	}
	return NO;
}

对CPU的利用率:

-(float)appCPUUsage{

	kern_return_t kr;
	task_info_data_t info;
	mach_msg_type_number_t infoCount = TASK_INFO_MAX;
	
	kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)info, &infoCount);
	
	if (kr != KERN_SUCCESS){
		return -1;
	}
	
	thread_array_t thread_list;
	mach_msg_type_number_t thread_count;
	thread_info_data_t thinfo;
	mach_msg_type_number_t thread_info_count;
	thread_basic_info_t basic_info_th;
	
	kr = task_threads(mach_task_self(), &thread_list, &thread_count);
	if (kr != KERN_SUCCESS){
		return -1;
	}
	
	float tot_cpu = 0;
	int j;
	
	for (j = 0 ;j< thread_count ; j++){
		thread_info_count = THREAD_INFO_MAX;
		kr = thread_info(thread_list[j], THREAD_BASIC_INFO, (thread_info_t)thinfo,&thread_info_count);
		if (kr != KERN_SUCCESS){
			return -1;
		}
	
		basic_info_th =(thread_basic_info_t)thinfo;
		if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
			tot_cpu += basic_info_th -> cpu_usage / (float)TH_USAGE_SCALE * 100.0;
		}
	}
	
	vm_deallocate(mach_thread_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
	return tot_cpu;
}

3.6最佳实践

确保对电量的谨慎使用:

  • 最小化硬件使用。
  • 在进行密集型任务前,检查电池电量和充电状态。
  • 在电量低时,提示用户是否确定要执行任务。
  • 或提供设置的选项,允许用户定义电量的阈值,以便在执行密集型操作前提示用户。
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
	BOOL prompt = [defaults boolForKey:@"promptForBattery"];
	NSInteger minLevel = [defaults integerForKey:@"minBatterKevel"];
	
	BOOL canAutoProceed = [self shouldProceedWithMinLevel:minLevel];
	if (canAutoProceed){
		//执行密集操作
	}else{
		if (prompt){//低电量进行提示
		
		}else{
			
		}
	}

猜你喜欢

转载自my.oschina.net/u/2319073/blog/1551467
今日推荐