天天看點

iOS背景運作機制-實踐總結

  從2015年,接觸到的項目裡,就會有這樣的需求:APP需要像Android那樣,在背景狀态下,執行正常的功能。到現在已經一年多了吧,一直在研究這個方面,寫下一些心得,希望與大家共同交流探讨。

  首先,我們要知道,蘋果對APP占用硬體資源管的很嚴,更不要說應用背景時候的資源占用了。正常情況下,使用應用時,APP從硬碟加載到記憶體,開始工作;當使用者按下home鍵,APP便被挂起,依然駐留在記憶體中,這種狀态下,不調用蘋果已開放的幾種背景方法,程式便不會運作;如果在這個時候,使程式繼續運作,則為背景狀态;如果目前記憶體将要不夠用時,系統會自動把之前挂起狀态下的APP請出記憶體。是以我們看到,有些時候打開APP時,還是上次退出時的那個頁面那些資料,有時則是重新從閃屏進入。

  這樣,就知道了背景運作最大的前置條件——APP處于記憶體中的挂起狀态。

  然後,再來看看上面說到的蘋果已開放的背景運作方法。先看這張圖

  

iOS背景運作機制-實踐總結

  很明顯,我們項目裡能用的機制就這麼多,Background Audio,這是背景的音頻,這個很早之前便有,可以實作背景的聲音播放。去年的項目裡用它在背景一直播放沒有聲音的檔案,結果稽核失敗。

  在這裡說一下去年做的那個項目的需求,使用者類型A可以在任何時刻檢視使用者類型B的地理位置。這個功能有點像iPhone上的『查找朋友』,不知道的朋友請自行了解(想知道你的朋友在哪裡嗎,想知道你的另一半在哪裡嗎,對了,就用它);A想看B的時候,B需要上傳自己的目前位置給伺服器;先不考慮APP在挂起狀态怎麼做,先說APP在活動狀态下,伺服器想和用戶端進行通信,告訴用戶端要上傳自己的位置了,這種伺服器主動通信,常用到的就是socket和推送通知。我決定用推送,在APP收到來自APNS的推送時,就進行定位并上傳。

  然而,按下home鍵進入挂起狀态時,程式是不會執行的,是以也擷取不到B的位置。BOSS大為惱火,Android分分鐘幹完的事,你怎麼就搞不定呢(腦補:再搞不出來就滾蛋)。

  當時第一次接觸蘋果這些背景機制,探索之路彎彎曲曲,就不一一表述了。最後用靜默推送解決了這個問題:Remote Notification!原理非常簡單,不過蘋果的初衷不是讓我這樣用的……,說一下這個機制的應用場景:以往聊天類應用接受推送後點進去需要再收一次資訊,這情況在QQ、微信等應用上最為明顯。不過擁有了這個接口後,這情況将不複存在,以後推送将能夠直接啟動背景任務,在背景就已經接收到資訊,點開APP不需要去拉取。

  so,在A檢視B位置的時候,給B一條靜默推送,B在背景定位并上傳資訊。這個喚起時間比較短,在3-5秒左右,有時候B網絡不好,沒有上傳成功就又被挂起了,就需要重複進行。這個機制添加方法和推送一樣,隻有一點差別,就是委托方法不同。普通推送會執行這個回調:

1 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
2 
3     DDLogDebug(@"[普通推送]%@", userInfo);
4 }      

  而勾選住上面那個推送喚醒,就會回調這個方法:

1 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
2     DDLogDebug(@"[背景推送]%@", userInfo);
3     completionHandler(UIBackgroundFetchResultNewData);
4 }      

  可以在這個回調裡,調用定位請求之類的。

  本以為解決了問題,BOSS試了,也覺得可以。就喜滋滋的等着升職加薪走上人生巅峰,咳咳,又做夢了。

  "咋麼回事呃,現在又定不到位了,趕緊搞好"還沒到兩個小時,BOSS氣呼呼的跑過來噴了一頓。說完之後的0.01秒,我就知道是怎麼回事了,"聽我解釋啊,老闆"還沒說出口,他就摔門而出,留下欲哭無淚的我。隻有兩個原因,一、APP被人為上劃kill掉;二、APP被系統回收了,kill 了。

  就醬,明知山有虎,偏向虎山行。為了不讓系統回收APP,我非常強硬的,加上了Background Audio。結果可想而知,被拒絕的同時,收到一封英文郵件,問我為什麼這樣做,如有異議,可提出。哎,總算松了一口氣,可以離職了(個人原因)。

  換了公司,需求也不一樣了,APP需要每五秒和伺服器進行一次資料交換;以下,用到的是VoIP。

  剛開始的時候,推送喚醒機制還可以,不過林子大了什麼鳥都有,一樣的型号一樣的設定,有台iPhone就是喚醒不了,隻能嘗試新方法。想起QQ語音時,切到背景依然可以通話,我想,就是它了。VoIP:背景語音服務,類似Skype通話應用需要調用,可進行背景的語音通話。既然是語音通話,那麼肯定是常連接配接,于是,有了以下代碼。

1 @implementation NSStream(StreamsToHost)
 2 
 3 + (void)getStreamsToHostNamed:(NSString *)hostName
 4                          port:(UInt32)port
 5                   inputStream:(out __strong NSInputStream **)inputStreamPtr
 6                  outputStream:(out __strong NSOutputStream **)outputStreamPtr
 7 {
 8     CFReadStreamRef     readStream;
 9     CFWriteStreamRef    writeStream;
10     
11     assert(hostName != nil);
12     assert( (port > 0) && (port < 65536) );
13     assert( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) );
14     
15     readStream = NULL;
16     writeStream = NULL;
17     
18     CFStreamCreatePairWithSocketToHost(
19                                        NULL,
20                                        (__bridge CFStringRef) hostName,
21                                        port,
22                                        ((inputStreamPtr  != NULL) ? &readStream : NULL),
23                                        ((outputStreamPtr != NULL) ? &writeStream : NULL)
24                                        );
25     
26     if (inputStreamPtr != NULL) {
27         *inputStreamPtr  = CFBridgingRelease(readStream);
28     }
29     
30     if (outputStreamPtr != NULL) {
31         *outputStreamPtr = CFBridgingRelease(writeStream);
32     }
33 }
34 
35 @end      

  給NSStream加了一個類目。然後還需要一個server,我就不寫了;發起連接配接請求讓用戶端與server保持通信,這些代碼也太多了,就不貼了。勾選Voice over IP後,APP挂起狀态時,系統會接管socket會話句柄,當收到從server發來的資料流時,就會喚起APP進入背景執行代碼。這個喚起時間要長一些,可以在十秒多點。已經測試成功,但是還沒有送出稽核,還需要給它一個外套,不然就像上次一樣被拒絕。

  一直在探索,因為以上方法并不完美,而且項目對背景的要求比較苛刻,事實上,使用者在使用APP時,會有很多場景,最常見的就是弱網絡,在這個場景下,不管是推送還是socket都無法收到内容,是以像這種需要依賴外力喚起的方式,弊端還是相當明顯。

  已經感覺到後面寫的比較倉促,VoIP涉及的内容還是比較多,還沒有一一吃透,還是心急了些。個人知識有限,如有錯誤,歡迎指正。

  

轉載于:https://www.cnblogs.com/ChinaLoong/p/5498821.html