不會吧,這也行?iOS後臺鎖屏監聽搖一搖

背景介紹

通常狀況下,出於省電、權限、合理性等因素考慮,給人的感受是不少奇怪的需求安卓能夠實現,可是iOS就沒法實現!今天要介紹的需求也有這種感受,就是「當 APP 處於後臺或鎖屏狀態時,依舊能夠監聽到搖一搖,進而觸發某些功能,好比:語音播報」。git

在產品經理提出此需求的一瞬間,彷彿周邊的空氣都凝固了,我也猶如五雷轟頂,愣在原地沒法動彈。不禁心想:「蘋果爸爸怎麼可能容許開發者實現這種功能!這得多費電啊!要是全部 APP 都這麼作了,那還了得!」 與此同時,以前網上瘋傳、遠近聞名的的需求--「作一個會根據手機殼顏色而改變主題顏色的APP」,清晰地浮如今腦海中,頓時一萬隻xx🐎從心中奔騰而過。此時,產品經理解釋到,這是我們好多視力障礙用戶提的需求,他們常常鎖屏或把 APP 退到後臺,且由於視力不佳緣由,致使從新找到 APP 並切到前臺的操做非常麻煩,因此十分但願咱們能實現這個功能。程序員

在短暫的心理活動後,秉着「客戶第一,產品🐂🍺」的原則,因而回復說:「這功能太少見了,我先在網上看看吧,要是有其餘 APP 有相似的功能,麻煩跟我說我參考一下。」而後,就祭出了程序員利器--Google,輸入「iOS 後臺 搖一搖」,只搜索出來的一個思路:利用 CoreMotion 框架,監聽加速計原始數據,而後在 APP 退到後臺後,能夠實現監聽搖一搖的效果。然而,並無完整的代碼或 demo 。頓時,Talk is cheap, show me the code!這句經典臺詞忽然地出如今腦海中!也看到有人評論說 CoreMotion 的確能夠實現跟系統搖一搖相似的效果,可是退到後臺或鎖屏後,沒辦法監聽到搖一搖事件。github

看到這條評論時,我不由開始懷疑此功能是否真的能夠被實現。swift

玩歸玩,鬧歸鬧,開始 code,不開玩笑。app

接下來,開始本身的探索之旅。框架

本文 demo 連接爲 OCDailyTests/BackgroundShakeTest,可自行下載,方便運行和驗證。工具

探索過程

其餘 APP 有沒有相似功能

通過一番 Google,終於找到一款 APP 有相似功能::酷狗音樂 APP,對,就是那個在 PC 端一打開就會大喊 Hello KuGou!的音樂軟件對應的 APP,萬萬沒想到,手機 APP 也是這樣,一句Hello KuGou!把我嚇一跳。按以下步驟,在設置裏打開此功能後,後臺或鎖屏時,搖一搖手機,可實現切歌的效果。oop

既然的確有 APP 實現了此功能,那就踏踏實實地探索它多是怎麼實現的吧。post

系統提供的搖一搖回調可否知足

系統搖一搖回調方法:測試

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
    NSLog(@"%s", __FUNCTION__);
}

經測試,此方法只有在 APP 處於前臺時,纔會被回調。APP 處於後臺或鎖屏時,此方法不會回調。故初步斷定此方法不能知足需求。

其餘方法可否實現

此時,仍是先根據網上各路大神提供的思路進行嘗試,即利用 CoreMotion 框架,監聽加速計原始數據,而後在 APP 退到後臺後,實現監聽搖一搖的效果。

好,咱們先利用 CoreMotion 框架,監聽加速計原始數據,實現相似系統搖一搖回調的效果。

利用 CoreMotion 框架,監聽加速計原始數據

經過加速計監聽搖一搖

因加速計回調比較頻繁,所以比較佔用資源,故把此功能設計爲單例。

  • 快速實現單例效果

    //具體實現詳見 demo 中文件
    #import "HMSingleton.h"
    
    @interface MYAccelerometerTool : NSObject
    HMSingleton_h(MYAccelerometerTool);
    @end
    
    @implementation MYAccelerometerTool
    HMSingleton_m(MYAccelerometerTool);
    @end
  • 聲明和懶加載運動管理員屬性

    @property(nonatomic, strong) CMMotionManager *gMotionMnger;
    
    - (CMMotionManager *)gMotionMnger{
        if (nil == _gMotionMnger) {
            CMMotionManager *lMnger = [[CMMotionManager alloc] init];
            lMnger.accelerometerUpdateInterval = 0.1;
            [lMnger startAccelerometerUpdates];
            _gMotionMnger = lMnger;
        }
        return _gMotionMnger;
    }
  • 聲明和實現時間戳屬性,用於實現節流效果(爲防止頻繁回調,每次檢測成功後,中止搖動 1s 後才繼續響應下次搖一搖。)

    @property(nonatomic, strong) NSDate *gDateLastShakeSuc;
    - (NSDate *)gDateLastShakeSuc{
        if (nil == _gDateLastShakeSuc) {
            _gDateLastShakeSuc = [NSDate distantPast];
        }
        return _gDateLastShakeSuc;
    }
  • 開始監聽搖一搖動做

    - (BOOL)startMonitorShake{
        if (NO == self.gMotionMnger.isAccelerometerAvailable) {
            return NO;
        }
        
        //監聽中,直接返回YES
        if (self.gMotionMnger.isAccelerometerActive) {
            return YES;
        }
        
        [self.gMotionMnger startAccelerometerUpdatesToQueue:[NSOperationQueue new] withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) {
            
            CMAcceleration acceleration = accelerometerData.acceleration;
            
            //綜合x、y兩個方向的加速度(z方向速度無心義,用的話,走路上下抖手機時會誤觸發,系統搖一搖也不會被z軸加速度觸發)
            //當綜合加速度大於2.3時,就激活效果(數據越小,用戶搖動的動做就越小,越容易激活)
            double accelerameter = sqrt( pow( acceleration.x , 2 ) + pow( acceleration.y , 2 ));
            
            if (accelerameter > 2.3) {
                
                //節流效果:距離上次搖一搖成功事件,間隔時間小於1s時,認爲無效
                NSDate *lCrtDate = [NSDate date];
                if ([lCrtDate timeIntervalSinceDate:self.gDateLastShakeSuc] < 1) {
                    self.gDateLastShakeSuc = lCrtDate;
                    return ;
                }
                
                self.gDateLastShakeSuc = lCrtDate;
                [[NSNotificationCenter defaultCenter] postNotificationName:KNTFY_SHAKE_SUCCESS object:nil];
            }
        }];
        
        return YES;
    }
  • 爲了代碼的對稱美和可能的相關業務,實現中止監聽搖一搖方法

    - (void)stopMonitorShake{
        [self.gMotionMnger stopAccelerometerUpdates];
        self.gMotionMnger = nil;
        self.gDateLastShakeSuc = nil;
    }

控制器相關邏輯和代碼

  • 開始監聽搖一搖

    BOOL lRes = [[MYAccelerometerTool sharedMYAccelerometerTool] startMonitorShake];
     NSLog(@"lRes:%d", lRes);
     NSAssert(lRes, @"開始監聽搖一搖失敗");
  • 監聽搖一搖成功的通知

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nmShakeSuccess:) name:KNTFY_SHAKE_SUCCESS object:nil];
    
    //在搖一搖的同時,經過觀察此方法是否有log,能夠判斷是否有監聽到。
    - (void)nmShakeSuccess:(NSNotification *)ntfy{
        NSLog(@"%s", __FUNCTION__);
    }
  • dealloc方法中取消監聽

    - (void)dealloc{
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }

    運行 demo 工程,測試可知,經過上述方法,的確能夠在 APP 處於前臺時,實現監聽搖一搖動做的效果。但是,當把 APP 退到後臺或鎖屏時,nmShakeSuccess 方法再也不有 log,即:APP 處於後臺時,經過監聽加速計的方法,默認也沒法在 APP 處於後臺或鎖屏時實現監聽效果。這也印證了上文提到的那個評論者的疑問。

    但是 Hello KuGou! 明明實現了後臺或鎖屏時搖一搖的效果啊!難道是須要額外的配置?聯想 iOS 處於後臺時,默認會把 APP 的服務給掛起(suspended),只有當 APP 經過某種方式(後臺定位/播放音樂/藍牙掃描等)具備後臺運行權限時,才能夠一直保活。可猜測,也許賦予 APP 具備後臺運行的權限後,就能夠實現想要的功能了。因而,開始進行驗證以下。

APP 申請後臺運行權限後,可否監聽到搖一搖

由於工做中不少 APP 具備後臺定位權限和相關功能,因此本文經過爲 APP 申請後臺定位權限來驗證。

APP 申請後臺定位權限

  • plist 文件中增長」定位請求描述信息「

    <key>NSLocationAlwaysUsageDescription</key>
    	<string>咱們須要根據您的定位提供周邊搜索和導航服務</string>
    	<key>NSLocationWhenInUseUsageDescription</key>
    	<string>咱們須要根據您的定位提供周邊搜索和導航服務</string>

    增長」後臺定位權限「

    <key>UIBackgroundModes</key>
    	<array>
    		<string>location</string>
    	</array>
  • 聲明定位管理員屬性

    @property(nonatomic, strong) CLLocationManager *gMnger;
  • 懶加載定位管理員,請求定位權限、容許後臺位置更新

    - (CLLocationManager *)gMnger{
        if (nil == _gMnger) {
            _gMnger = [[CLLocationManager alloc] init];
            _gMnger.delegate = self;
            _gMnger.allowsBackgroundLocationUpdates = YES;
            [_gMnger requestWhenInUseAuthorization];
        }
        return _gMnger;
    }
  • 代理 3 步走(用於驗證後臺定位是否生效)

    • 遵照代理協議

      @interface ViewController ()<CLLocationManagerDelegate>

    • 指定代理對象

      _gMnger.delegate = self;

    • 實現代理方法

      #pragma mark -  delegate
      - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations{
          NSLog(@"%s", __FUNCTION__);
      }
      
      - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
          NSLog(@"%s", __FUNCTION__);
      }
  • APP 後臺或鎖屏後,測試可否成功監聽搖一搖

    運行 demo 工程,經測試,把 APP 退到後臺或鎖屏,或即退到後臺又鎖屏,都可以檢測到搖一搖事件。

多 APP 都實現此功能時,搖一搖是何效果

這裏用 demo APP 和酷狗音樂 APP 進行測試。

  • 同時打開這兩個 APP,其中酷狗音樂 APP 打開後臺搖一搖切歌的功能。
  • 酷狗音樂 APP 開始放歌,退到後臺。
  • demo APP 打開後,退到後臺。
  • 搖一搖,查看效果:
    • 當搖動的力度不是很大時,demo APP 回調方法會被觸發;
    • 當搖動的力度很大時,demo APP 回調方法和酷狗 APP 切歌會同時被觸發;
  • 因而可知,若是多個 APP 同時實現了此功能時,那麼後臺或鎖屏搖一搖時,只要知足了某個 APP 實現的加速計相關斷定條件,就能夠同時觸發多個 APP 對應的效果。

後臺定位權限 + 系統搖一搖,是否可行?

經測試,仍是不行。果真,系統搖一搖仍是比較受限的,只能在前臺回調。

文章小結

想要實現」iOS後臺鎖屏監聽搖一搖「功能,

首次,必須知足一個硬性條件:APP 具備某種後臺運行的權限。

其次,技術實現上必須使用CoreMotion框架,經過監聽加速計回調本身實現對搖一搖事件的監聽斷定

最後,可經過增長時間屬性,實現對搖一搖事件監聽時的節流效果,防止持續搖動時,太過頻繁的事件回調。

此外,多 APP 都實現此功能時,搖一搖的效果是:只要搖動力度很大,加速計數據知足 APP 實現的搖一搖斷定條件,就能夠同時觸發多個 APP 各自對應的效果

所以,若是不是 APP 特別須要此功能,儘可能不要這樣實現,畢竟,比較佔用系統資源,並且太多 APP 同時實現時,可能會出現效果上的相互干擾。不過,若是合理利用此功能,卻能夠爲特殊用戶羣體提供極大的便利

經過探索,知足了視力障礙用戶的迫切需求,仍是蠻有成就感的!

偷偷的告訴你們,寫到這裏時,產品經理還沒告訴我他所知道的哪一個 APP 實現了這個功能,可能他太忙,給忘記了吧......

參考文章

iOS應用退出到後臺後怎樣監聽搖晃事件

Demo 連接

OCDailyTests/BackgroundShakeTest

最後,感謝「技術創做101訓練營」!經過參加訓練營,讓我對寫做有了更深刻的認識和更高的內心覺悟。

本文由博客羣發一文多發等運營工具平臺 OpenWrite 發佈

相關文章
相關標籤/搜索