《高性能iOS 應用開發》之下降你 APP 的電量消耗

高性能iOS 應用開發

在編寫高性能 代碼時, 電量消耗是一個須要重點處理的重要因素, 就執行時間和 CPU 資源的利用而言, 咱們不只要實現高效的數據結構和算法, 還須要考慮其餘的因素,若是某個應用是個電池黑洞,那麼必定不會有人喜歡他 電量消耗除了 CPU 外,還有一些硬件模塊:網絡硬件, 藍牙,GPS, 麥克風,加速計,攝像頭,揚聲器,和屏幕. 咱們能夠帶着如下問題來看這篇文章:算法

  • 消耗電量的關鍵領域有哪些
  • 如何下降電量的消耗
  • 如何在 IOS 應用中分析電源, CPU 和資源的使用

一 CPU

不論用戶是否正在直接使用, CPU 都是應用所使用的主要硬件, 在後臺操做和處理推送通知時, 應用仍然會消耗 CPU 資源 小程序

iOS 設備與處理器

應用計算的越多,消耗的電量越多.在完成相同的基本操做時, 老一代的設備會消耗更多的電量(換電池呀 哈哈哈 開個玩笑),計算量的消耗取決於不一樣的因素bash

  • 對數據的處理
  • 待處理的數據大小---更大的顯示屏容許軟件在單個視圖中展現更多的信息,但這也意味着要處理更多的數據
  • 處理數據的算法和數據結構
  • 執行更新的次數,尤爲是在數據更新後,觸發應用的狀態或 UI 進行更新(應用收到的推送通知也會致使數據更新,若是此用戶正在使用應用,你還須要更新 UI)

沒有單一原則能夠減小設備中的執行次數,不少規則都取決於操做的本質, 如下是一些能夠在應用中投入使用的最佳實踐服務器

  • 針對不一樣的狀況選擇優化的算法 例如,當你在排序時,若是列表少於43個實例, 則插入排序優於歸併排序, 但實例對於286時, 應當使用快速排序,要優先使用雙樞軸快速排序而不是傳統的單樞軸快速排序
  • 若是應用從服務器接受數據,儘可能減小須要在客戶端進行的處理 例如若是一段文字須要在客戶端進行渲染,儘量在服務器將數據清理乾淨 我曾經作個一個項目, 由於服務器的實現主要用於服務桌面用戶,因此返回的文本中包含 HTML 標籤, 清理 HTML 標籤的工做並無放在客戶端進行, 而是放在了服務端實現,從而減小了設備上的計算過程, 下降了處理時間
  • 優化靜態編譯(ahead-of-time,AOT)處理 動態編譯處理的缺點在於他會強制用戶等待操做完成, 可是激進的 AOT 處理則會致使計算資源的浪費, 須要根據應用和設備選擇精肯定量的 AOT 處理. 例如,在 UITableView 中渲染一組記錄時,在載入列表是處理所有的記錄並非明智之舉,基於單元格的高度,若是設備能夠渲染 N 條記錄, 那麼3N 或4N 則是一個理想的數據載入規模, 相似的,用戶快速滑動,則不該當即載入記錄,而應推遲帶滾動速度降低到某一閾值.精確的閾值應該由每一個單元格的處理時間和單元格的 UI 的複雜性來決定

二 網絡

智能的網絡訪問管理可讓應用響應的更快,並有助於延長電池壽命.在沒法訪問網絡時,應該推遲後續的網絡請求, 直到網絡鏈接恢復爲止. 此外,應避免在沒有鏈接 WiFi 的狀況下進行高寬帶消耗的操做.好比視頻流, 衆所周知, 蜂窩無線系統(LTE,4G,3G等)對電量的消耗遠遠大於 WiFi信號, 根源在於 LTE 設備基於多輸入,多輸出技術,使用多個併發信號以維護兩端的 LTE 連接,相似的,全部的蜂窩數據連接都會按期掃描以尋找更強的信號. 所以:咱們須要網絡

  • 在進行任何網絡操做以前,先檢查合適的網絡鏈接是否可用
  • 持續監視網絡的可用性,並在連接狀態發生變化時給與適當的反饋

三 定位管理器和 GPS

這個知識點我項目中並無用到定位相關的功能 ,不過也總結一下書中所講的知識點 有用的定位功能的朋友能夠參考此知識點來優化本身的 app數據結構

咱們都知道定位服務是很耗電的,使用 GPS 計算座標須要肯定兩點信息:併發

  • 時間鎖 每一個 GPS 衛星每毫秒廣播惟一一個1023位隨機數, 於是數據傳播速率是1.024Mbit/s GPS 的接收芯片必須正確的與衛星的時間鎖槽對齊
  • 頻率鎖 GPS 接收器必須計算由接收器與衛星的相對運動致使的多普勒偏移帶來的信號偏差

計算座標會不斷的使用 CPU 和 GPS 的硬件資源,所以他們會迅速的消耗電池電量 先來看一下初始化CLLocationManager並高效接受地理位置更新的典型代碼app

#import "LLLocationViewController.h"
#import <CoreLocation/CoreLocation.h>

@interface LLLocationViewController ()<CLLocationManagerDelegate>
@property (nonatomic, strong)CLLocationManager *manager;
@end

@implementation LLLocationViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.manager = [[CLLocationManager alloc]init];
    self.manager.delegate = self;    
}

- (void)enableLocationButtonClick:(UIButton *)sender{
    
    self.manager.distanceFilter = kCLDistanceFilterNone;
    // 按照最大精度初始化管理器
    self.manager.desiredAccuracy = kCLLocationAccuracyBest;
    
    if (IS_IOS8) {
        [self.manager requestWhenInUseAuthorization];
    }
    [self.manager startUpdatingLocation];
}

- (void)locationManager:(CLLocationManager *)manager
     didUpdateLocations:(NSArray<CLLocation *> *)locations{
    
    CLLocation *loc = [locations lastObject];
    // 使用位置信息
}
複製代碼
3.1 最佳的初始化
  • distanceFilter 只要設備的移動超過了最小的距離, 距離過濾器就會致使管理器對委託對象的 LocationManager:didUpdateLocations:事件通知發生變化,該距離單位是 M
  • desiredAccuracy 精度參數的使用直接影響了使用天線的個數, 進而影響了對電池的消耗.精度級別的選取取決於應用的具體用途,精度是一個枚舉 咱們應該依照不一樣的需求去恰當的選取精度級別

距離過濾器只是軟件層面的過濾器,而精度級別會影響物理天線的使用.當委託方法 LocationManager:didUpdateLocations:被調用時,使用距離範圍更普遍的過渡器只會影響間隔.另外一方面,更高的精度級別意味着更多的活動天線,這會消耗更多的能量數據結構和算法

3.2 關閉可有可無的特性

判斷什麼時候須要跟蹤位置的變化, 在須要跟蹤的時候調用 startUpdatingLocation方法, 無須跟蹤時調用stopUpdatingLocation方法.性能

當應用在後臺運行或用戶沒有與別人聊天時,也應該關閉位置跟蹤,也就說說,瀏覽媒體庫,查看朋友列表或調整應用設置時, 都應該關閉位置跟蹤

3.3 只在必要時使用網絡

爲了提升電量的使用效率, IOS 老是儘量地保持無線網絡關閉.當應用須要創建網絡鏈接時, IOS 會利用這個機會向後臺應用分享網絡會話, 以便一些低優先級可以被處理, 如推送通知, 收取電子郵件等 關鍵在於每當用戶創建網絡鏈接時,網絡硬件都會在鏈接完成後多維持幾秒的活動時間.每次集中的網絡通訊都會消耗大量的電量 要想減輕這個問題帶來的危害,你的軟件須要有所保留的的使用網絡.應該按期集中短暫的使用網絡,而不是持續的保持着活動的數據流.只有這樣,網絡硬件纔有機會關閉

3.4 後臺定位服務

CLLocationManager提供了一個替代的方法來監聽位置的更新. [self.manager startMonitoringSignificantLocationChanges]能夠幫助你在更遠的距離跟蹤運動.精確的值由內部決定,且與distanceFilter無關 使用這一模式能夠在應用進入後臺後繼續跟蹤運動,典型的作法是在應用進入後臺時執行startMonitoringSignificantLocationChanges方法,而當應用回到前臺時執行startUpdatingLocation 以下代碼

- (void)applicationDidEnterBackground:(UIApplication *)application {
    [self.manager stopUpdatingLocation];
    [self.manager startMonitoringSignificantLocationChanges];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {    
    [self.manager stopMonitoringSignificantLocationChanges];
    [self.manager startUpdatingLocation];    
}
複製代碼
3.5 在應用關閉後重啓

在其餘應用須要更多資源時, 後臺的應用可能會被關閉.在這種狀況下, 一旦發生位置變化,應用會被重啓,於是須要從新初始化監聽過程,若出現這種狀況,application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法會受到鍵值爲UIApplicationLaunchOptionsLocationKey的條目 以下代碼: 在應用關閉後從新初始化監聽

- (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 因缺少資源而關閉應用後, 監測應用是否由於位置變化而被重啓
    if (launchOptions[UIApplicationLaunchOptionsLocationKey]) {
// 開啓監測位置的變化
        [self.manager startMonitoringSignificantLocationChanges];
    }
}
複製代碼

###四 屏幕 屏幕很是耗電, 屏幕越大就越耗電.固然,若是你的應用在前臺運行且與用戶進行交互,則勢必會使用屏幕並消耗電量 這裏仍然有一些方案能夠優化屏幕的使用

4.1 動畫

當應用在前臺時, 使用動畫, 一旦應用進入了後臺,則當即暫停動畫.一般來講,你能夠經過監聽 UIApplicationWillResignActiveNotificationUIApplicationDIdEnterBackgroundNotification的通知事件來暫停或中止動畫,也能夠經過監聽UIApplicationDidBecomeActiveNotification的通知事件來恢復動畫

4.2 視頻播放

我在上家公司就是作視頻類App的,當時就採用了這個技術 保持屏幕常亮

在視頻播放期間,最好保持屏幕常量.可使用UIApplication對象的 idleTimerDisabled屬性來實現這個目的.一旦設置了 YES, 他會阻止屏幕休眠,從而實現常亮. 與動畫相似,你能夠經過相應應用的通知來釋放和獲取鎖

4.3 多屏幕

使用屏幕比休眠鎖或暫停/恢復動畫要複雜得多

若是正在播放電影或運行動畫, 你能夠將它們從設備的屏幕挪到外部屏幕,而只在設備的屏幕上保留最基本的設置,這樣能夠減小設備上的屏幕更新,進而延長電池壽命

處理這一場景的典型代碼會涉及一下步驟

  • 1 在啓動期間監測屏幕的數量 若是屏幕數量大於1,則進行切換
  • 2 監聽屏幕在連接和斷開時的通知. 若是有新的屏幕加入, 則進行切換. 若是全部的外部屏幕都被移除,則恢復到默認顯示
@interface LLMultiScreenViewController ()
@property (nonatomic, strong)UIWindow  *secondWindow;
@end

@implementation LLMultiScreenViewController

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

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

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

- (void)registerNotifications{
    
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self selector:@selector(scrensChanged:) name:UIScreenDidConnectNotification object:nil];
}

- (void)scrensChanged:(NSNotification *)nofi{
    [self updateScreens];
}

- (void)updateScreens{
    
    NSArray *screens = [UIScreen screens];
    if (screens.count > 1) {
        UIScreen *secondScreen = [screens objectAtIndex:1];
        CGRect rect =secondScreen.bounds;
        if (self.secondWindow == nil) {
            self.secondWindow = [[UIWindow alloc]initWithFrame:rect];
            self.secondWindow.screen = secondScreen;
            
            LLScreen2ViewController *svc = [[LLScreen2ViewController alloc]init];
            svc.parent = self;
            self.secondWindow.rootViewController = svc;
        }
        self.secondWindow.hidden = NO;
    }else{
        [self disconnectFromScreen];
    }
}

- (void)disconnectFromScreen{
    
    if (self.secondWindow != nil) {
        // 斷開鏈接並釋放內存
        self.secondWindow.rootViewController = nil;
        self.secondWindow.hidden = YES;
        self.secondWindow = nil;
    }
}

- (void)dealloc{
    
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
}
複製代碼

五 其餘硬件

當你的應用進入後臺是, 應該釋放對這些硬件的鎖定:

  • 藍牙
  • 相機
  • 揚聲器,除非應用是音樂類的
  • 麥克風

基本規則: 只有當應用處於前臺時才與這些硬件進行交互, 應用處於後臺時應中止交互

不過揚聲器和無線藍牙可能例外, 若是你正在開發音樂,收音機或其餘的音頻類應用,則須要在應用進入後臺後繼續使用揚聲器.不要讓屏幕僅僅爲音頻播放的目的而保持常量.相似的, 若應用還有未完成的數據傳輸, 則須要在應用進入後臺後持續使用無線藍牙,例如,與其餘設備傳輸文件

六 電池電量與代碼感知

這一條我發現 摩拜單車小程序 作的挺好的,若是晚上騎車掃描二維碼的話是須要開閃光燈達到照亮二維碼的效果, 可是若是你的手機處於低電量的話 ,你的閃光燈是打不開的, 這一個細節就說明了用戶體驗很重要,他首先會保證不讓你的手機由於閃光燈而直接關機

一個智能的應用會考慮到電池的電量和自身的狀態, 從而決定是否執行資源密集消耗性的操做.另一個有價值的點是對充電的判斷,肯定設備是否處於充電狀態

來看一下此處的代碼實施

- (BOOL)shouldProceedWithMinLevel:(NSUInteger)minLevel{
    
    UIDevice *device = [UIDevice currentDevice];
    // 打開電池監控
    device.batteryMonitoringEnabled = YES;
    
    UIDeviceBatteryState state = device.batteryState;
    // 在充電或電池已經充滿的狀況下,任何操做均可以執行
    if (state == UIDeviceBatteryStateCharging ||
        state == UIDeviceBatteryStateFull) {
        return YES;
    }    
    // UIdevice 返回的 batteryLevel 的範圍在0.00 ~ 1.00
    NSUInteger batteryLevel = (NSUInteger)(device.batteryLevel * 100);
    if (batteryLevel >= minLevel) {
        return YES;
    }
    return NO;
}
複製代碼

咱們也能夠獲得應用對 CPU 的利用率

// 須要導入這兩個頭文件
#import <mach/mach.h>
#import <assert.h>

- (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, 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, 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 / TH_USAGE_SCALE * 100.0;
        }
    }
    vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
    return tot_cpu;
    
}

複製代碼

當剩餘電量較低時,提醒用戶,並請求用戶受權執行電源密集型的操做,---固然,只在 用戶贊成的前提下執行 老是用一個指示符(也就是進度條百分比)顯示長時間任務的進度, 包括設備上即將完成的計算或者只是下載一些內容.向用戶提供完成進度的估算, 以幫助他們決定是否須要爲設備充電

七 最佳實踐

如下的最佳實踐能夠確保對電量的謹慎使用, 遵循如下要點,應用能夠實現對電量的高效使用.

  • 最小化硬件使用. 換句話說,儘量晚的與硬件打交道, 而且一旦完成任務當即結束使用
  • 在進行密集型任務前, 檢查電池電量和充電狀態
  • 在電量低時, 提示用戶是否肯定要執行任務,並在用戶贊成後再執行
  • 或提供設置的選項,容許用戶定義電量的閾值,以便在執行祕籍型操做前提示用戶

下邊代碼展現了設置電量的閾值以提示用戶.

- (IBAction)onIntensiveOperationButtonClick:(id)sender {
    
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    
    BOOL prompt = [defaults boolForKey:@"promptForBattery"];
    int minLevel = [defaults integerForKey:@"minBatteryLevel"];
    
    BOOL canAutoProceed = [self shouldProceeWithMinLevel:minLevel];
    
    if (canAutoProceed) {
        [self executeIntensiveOperation];
    }else{
        
        if (prompt) {
            UIAlertView *view = [[UIAlertView alloc]initWithTitle:@"提示" message:@"電量低於最小值,是否繼續執行" delegate: self cancelButtonTitle:@"取消" otherButtonTitles:@"肯定"];
            [view show];
        }else{
            
            [self queueIntensiveOperation];
        }
    }
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    
    if (buttonIndex == 0) {
        [self queueIntensiveOperation];
    }else{
        [self executeIntensiveOperation];
    }
}
複製代碼

代碼對應的配圖以下

電量水平的閾值和提示選項在應用中的設置

  • 設置由兩個條目組成:promptForBattery(應用設置中的撥動開關,代表是否要在低電量時給予提示)和miniBatteryLevel(區間爲0~100的一個滑塊,代表了最低電量------在此示例中,用戶能夠自行調整),在實際項目中應用的開發人員一般根據操做的複雜性和密集性對閾值進行預設.不一樣的密集型操做可能會有不一樣的最低電量需求
  • 在實際執行密集操做以前,檢查當前電量是否足夠, 或者手機是否正在充電.這就是咱們判斷是否能夠進行後續處理的邏輯,圖中你能夠有本身的定製---最低電量和充電狀態

用戶老是隨身攜帶者手機,因此編寫省電的代碼就格外重要, 畢竟手機的移動電源並非隨處可見,不過如今北京的街電共享充電寶好像很不錯 本人逛街會常用街電充電寶,但仍是要儘量的爲用戶省電 在沒法下降任務複雜性時, 提供一個對電池電量保持敏感的方案並在適當的時機提示用戶, 會讓用戶感受很良好, 而且所以會成爲你 APP 的永久用戶

相關文章
相關標籤/搜索