今天下了個(gè)軟件,可以記錄手機(jī)解鎖的次數(shù)和使用時(shí)間,當(dāng)然啦,App 必須在后臺(tái)運(yùn)行著。當(dāng)時(shí)比較納悶的是有什么 API 可以接收設(shè)備解鎖事件或通知的,Google 了下,還真有哎——我是鏈接。
設(shè)備鎖定的狀態(tài)
由上面的回答可以知道,設(shè)備在鎖定、解鎖的時(shí)候,SpringBoard 都會(huì)發(fā)出通知,iPhoneDevWiki 這里能找到更多有趣的通知(注意:黃色標(biāo)注的通知是有狀態(tài)變量與之關(guān)聯(lián)的,后面會(huì)用到)。貼訂閱通知的代碼:
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), // 獲取通知中心
NULL, // 設(shè)置觀察者
deviceLockStatusChanged, // 接收到通知時(shí)的回調(diào)函數(shù)
CFSTR("com.apple.springboard.lockstate"), // 通知名
NULL, // 要觀察的對(duì)象
CFNotificationSuspensionBehaviorDeliverImmediately // 決定應(yīng)用在后臺(tái)如何處理通知的標(biāo)記
);
這里所用的通知中心并不是我們常用的 [NSNotificationCenter defalutCenter],而是 CFNotificationCenterRef 對(duì)象。提一下,即便前者的底層確實(shí)是由 CFNotificationCenter 實(shí)現(xiàn)的,但它們兩者不是 Toll-Free Bridged?;氐?CFNotificationCenterRef,有下面三個(gè)函數(shù)獲得不同的通知中心:
- CFNotificationCenterGetLocalCenter(void);
- CFNotificationCenterGetDistributedCenter(void);
- CFNotificationCenterGetDarwinNotifyCenter(void);
第一個(gè)是我們熟悉的 Local Center,可以理解為通知的行為完全由本進(jìn)程維護(hù),作用域也僅在本進(jìn)程;
第二個(gè)是 Distributed Center,如果有看到這個(gè)函數(shù)聲明上的編譯條件,你就會(huì)發(fā)現(xiàn)僅在桌面系統(tǒng)上才有 Distributed Center 可用。它可以實(shí)現(xiàn)兩個(gè)進(jìn)程之間的通信,感興趣可以看看 Communicating With the Target Application ,似乎是通過 BundleID 實(shí)現(xiàn)對(duì)特定應(yīng)用發(fā)送通知;
第三個(gè)是本文的重點(diǎn),Darwin Center 的服務(wù)由系統(tǒng)的一個(gè)守護(hù)進(jìn)程維護(hù),相比 Local Center,通知的作用域擴(kuò)大到了所有進(jìn)程。這就為什么我們的應(yīng)用能夠接收到 SpringBoard.app 發(fā)送的通知。本文用的是用 CoreFoundation 的函數(shù)實(shí)現(xiàn)接收通知,除此之外,文檔里還提到了利用 Mach Port, File Descriptors, Signal 等方法。查看 Darwin Notification Concepts 了解更多。
接下來(lái)要講講回調(diào)函數(shù)了:
static void deviceLockStatusChanged(CFNotificationCenterRef center,
void *observer,
CFStringRef name,
const void *object,
CFDictionaryRef userInfo){
NSString *nameString = (__bridge NSString*)name;
int token;
uint64_t state;
notify_register_check(nameString.UTF8String, &token);
notify_get_state(token, &state);
NSLog(@"%@: token: %d, state: %llu", nameString, token, state);
if (state == 0) {
counter++;
}
notify_cancel(token);
}
函數(shù)的原型是直接抄文檔的,notify_register_check() 可以生成一個(gè) token 值用來(lái)關(guān)聯(lián)某一個(gè)通知,接著用 notify_get_state() 就可以獲得響應(yīng)狀態(tài)值。最后是 notify_cancel(),它用來(lái)取消跟 token 相關(guān)聯(lián)的通知和釋放相應(yīng)的資源,按 manual pages 的描述好像只針對(duì)利用 Mach Port 和 File Descriptors 接收消息時(shí)創(chuàng)建的資源。具體到這個(gè)回調(diào)函數(shù),不太清楚底層做了什么,但我們能看到的是 token 被清零了。
后臺(tái)運(yùn)行
獲得設(shè)備鎖定、解鎖的方法有了,接著是要讓應(yīng)用保存生命力,不能讓它被掛起,否則就統(tǒng)計(jì)不了次數(shù)了。比較常見的方法就是循環(huán)播放一段空白的聲音,然后在 Info.plist 里面添加相應(yīng)的字段(KEY: UIBackgroundModes, VALUE: audio),或者直接在 Capabilities 里面的 Background Modes 中 Audio 的復(fù)選框中打個(gè)勾。
完
??