iOS后台運行機制-實踐總結


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

  首先,我們要知道,蘋果對APP占用硬件資源管的很嚴,更不要說應用后台時候的資源占用了。正常情況下,使用應用時,APP從硬盤加載到內存,開始工作;當用戶按下home鍵,APP便被掛起,依然駐留在內存中,這種狀態下,不調用蘋果已開放的幾種后台方法,程序便不會運行;如果在這個時候,使程序繼續運行,則為后台狀態;如果當前內存將要不夠用時,系統會自動把之前掛起狀態下的APP請出內存。所以我們看到,有些時候打開APP時,還是上次退出時的那個頁面那些數據,有時則是重新從閃屏進入。

  這樣,就知道了后台運行最大的前置條件——APP處於內存中的掛起狀態。

  然后,再來看看上面說到的蘋果已開放的后台運行方法。先看這張圖

  

  很明顯,我們項目里能用的機制就這么多,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涉及的內容還是比較多,還沒有一一吃透,還是心急了些。個人知識有限,如有錯誤,歡迎指正。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM