IPhoneでロック/ロック解除イベントを検出するにはどうすればよいですか?ジェイルブレイクされたデバイスでのみ可能であると仮定して、正しいAPIを教えていただけますか?
lock eventsとは、ロック画面の表示または非表示を意味します(ロックを解除するにはパスワードが必要かどうか)。
Darwin notifications を使用して、イベントをリッスンできます。ジェイルブレイクされたiOS 5.0.1 iPhone 4での私のテストから、私はこれらのイベントの1つがあなたが必要とするものかもしれないと思います:
_com.Apple.springboard.lockstate
com.Apple.springboard.lockcomplete
_
注:投稿者の によると、私がここで回答した同様の質問へのコメント 、これは脱獄されていない電話も。
これを使用するには、次のようなイベントに登録します(これは上記の最初のイベントだけに登録しますが、lockcomplete
にもオブザーバーを追加できます)。
_CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
(void*)self, // observer (can be NULL)
lockStateChanged, // callback
CFSTR("com.Apple.springboard.lockstate"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
_
ここで、lockStateChanged
はイベントコールバックです。
_static void lockStateChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
NSLog(@"event received!");
if (observer != NULL) {
MyClass *this = (MyClass*)observer;
}
// you might try inspecting the `userInfo` dictionary, to see
// if it contains any useful info
if (userInfo != nil) {
CFShow(userInfo);
}
}
_
lockstate
イベントは、デバイスがロックされているおよびロックが解除されているときに発生しますが、lockcomplete
イベントは、デバイスがロック。イベントがロックイベントかロック解除イベントかを判断する別の方法は、notify_get_state()
を使用することです。ここで説明するように、ロックとロック解除では異なる値 が表示されます 。
回答についてラウンド:
アプリケーションはすべての種類のシナリオでアクティブな取得を拒否します...そして、私のすべてのテストから、バックグラウンドでアプリケーションが起動している場合でも、画面がロックされていると判断する方法はありません(CPU速度は報告せず、BUS速度)同じまま、mach_time denom/numerは変更されません)...
ただし、Appleは加速度計をオフにします... 画面がロックされているときにiPhoneの加速度計を有効にします (iPhone 4でテスト済みのiOS4.2はこの動作)
したがって...
アプリケーションデリゲートで:
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(@"STATUS - Application will Resign Active");
// Start checking the accelerometer (while we are in the background)
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:1]; // Ping every second
_notActiveTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(deviceDidLock) userInfo:nil repeats:NO]; // 2 seconds for wiggle
}
//Deprecated in iOS5
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
NSLog(@"STATUS - Update from accelerometer");
[_notActiveTimer invalidate];
_notActiveTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(deviceDidLock) userInfo:nil repeats:NO];
}
- (void)deviceDidLock
{
NSLog(@"STATUS - Device locked!");
[[UIAccelerometer sharedAccelerometer] setDelegate:nil];
_notActiveTimer = nil;
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSLog(@"STATUS - Application did become active");
[[UIAccelerometer sharedAccelerometer] setDelegate:nil];
[_notActiveTimer invalidate];
_notActiveTimer = nil;
}
私は知っています...それは一種のハックですが、今のところ私にとっては魅力のように機能しています。これが機能しない問題を見つけた場合は更新してください。
タスクの切り替えと画面のロックに起因するapplicationWillResignActive:
コールバックを区別する、より洗練された方法があります。これには、加速度計の状態などの文書化されていない機能さえ含まれていません。
アプリがバックグラウンドに移動すると、アプリデリゲートには最初にapplicationWillResignActive:
、次にapplicationDidEnterBackground:
が送信されます。ロックボタンを押すか、電話の着信によってアプリが中断された場合、後者のメソッドは呼び出されません。この情報を使用して、2つのシナリオを区別できます。
画面がロックされた場合、screenLockActivated
メソッドでコールバックしたいとします。ここに魔法があります:
- (void)applicationWillResignActive:(UIApplication*)aApplication
{
[self performSelector:@selector(screenLockActivated)
withObject:nil
afterDelay:0];
}
- (void)applicationDidEnterBackground:(UIApplication*)aApplication
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
- (void)screenLockActivated
{
NSLog(@"yaay");
}
説明:
デフォルトでは、applicationWillResignActive:
へのすべての呼び出しはアクティブから非アクティブへの状態遷移(画面のロック時など)が原因であると想定していますが、タイムアウト(この場合は1回のrunloopサイクル)screenLockActivated
への呼び出しを遅らせることによって。画面がロックされた場合、システムは他のデリゲートメソッドに触れずに現在の実行ループサイクルを終了します。ただし、これがアクティブからバックグラウンドへの状態遷移である場合は、サイクルの終了前にapplicationDidEnterBackground:
も呼び出されます。これにより、以前にスケジュールされたリクエストをそこから単にキャンセルできるため、リクエストが呼び出されたときに呼び出されなくなります。想定されていません。
楽しい!
執筆時点では、デバイスのロックを検出するための2つのかなり信頼できる方法があります。
データ保護資格 を有効にすると、アプリは applicationProtectedDataWillBecomeUnavailable:
および applicationProtectedDataDidBecomeAvailable:
パスコード/ TouchID認証を使用するデバイスがロック/ロック解除されたときに高い確率で判断する通知。デバイスがパスコード/ TouchIDを使用しているかどうかを確認するには、 LAContext
を照会できます。
警告:この方法は、電話がロックされているのと同時に、「保護されたデータが利用できなくなる」ことに依存しています。電話機がTouchIDを使用しているときにスリープ/ロックボタンを押すと、電話機がロックされ、保護されたデータが使用できなくなります。再びロックを解除するには、パスコードがすぐに必要になります。つまり、保護されたデータが利用できなくなることは、本質的に電話がロックされていることを示します。 誰かがパスコードのみを使用している場合、「パスコードが必要」の時間をのどこにでも設定できるため、これは必ずしも当てはまりません。すぐから4時間のようなものにこの場合、電話は保護されたデータを処理できることを報告しますが、電話をロックしても、しばらくの間保護されたデータが使用できなくなることはありません。
アプリがフォアグラウンドにある場合、2つのライフサイクルイベント UIApplicationWillResignActiveNotification
と UIApplicationDidEnterBackgroundNotification
に応じて、時間差に顕著な変化がありますそれらをトリガーするもの。
(これはiOS 10でテストされ、将来のリリースで変更される可能性があります)
ホームボタンを押すと、2つの間に大幅な遅延が生じます([低減モーション]設定が有効になっている場合でも)。
15:23:42.517 willResignActive
15:23:43.182 didEnterBackground
15:23:43.184 difference: 0.666346
アプリが開いているときにデバイスをロックすると、2つのイベントの間にわずかな遅延(<〜0.2秒)が生じます。
15:22:59.236 willResignActive
15:22:59.267 didEnterBackground
15:22:59.267 difference: 0.031404
iOS 8では、画面をロックするか、ホームボタンを押します。これらはすべて、アプリをバックグラウンドでプッシュしますが、どのオペレーターがこれを引き起こしたかはわかりません。 Nits007akと同じ私のソリューション、notify_register_dispatchを使用して状態を取得します。
#import <notify.h>
int notify_token
notify_register_dispatch("com.Apple.springboard.lockstate",
¬ify_token,
dispatch_get_main_queue(),
^(int token)
{
uint64_t state = UINT64_MAX;
notify_get_state(token, &state);
if(state == 0) {
NSLog(@"unlock device");
} else {
NSLog(@"lock device");
}
}
);
アプリがフォアグラウンドまたはバックグラウンドで実行されている限り。一時停止しないと、このイベントを取得できます。
また、notify_tokenをnotify_get_stateのパラメーターとして使用して、現在の状態をどこでも取得できます。これは、状態と画面の状態が変化しないことを知りたい場合に役立ちます。
このコードを使用する前に、#import notify.hをインポートしてください。楽しい!!
-(void)registerAppforDetectLockState {
int notify_token;
notify_register_dispatch("com.Apple.springboard.lockstate", ¬ify_token,dispatch_get_main_queue(), ^(int token) {
uint64_t state = UINT64_MAX;
notify_get_state(token, &state);
if(state == 0) {
NSLog(@"unlock device");
} else {
NSLog(@"lock device");
}
NSLog(@"com.Apple.springboard.lockstate = %llu", state);
UILocalNotification *notification = [[UILocalNotification alloc]init];
notification.repeatInterval = NSDayCalendarUnit;
[notification setAlertBody:@"Hello world!! I come becoz you lock/unlock your device :)"];
notification.alertAction = @"View";
notification.alertAction = @"Yes";
[notification setFireDate:[NSDate dateWithTimeIntervalSinceNow:1]];
notification.soundName = UILocalNotificationDefaultSoundName;
[notification setTimeZone:[NSTimeZone defaultTimeZone]];
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
});
}
パスコードが設定されている場合、これらのイベントをAppDelegateで使用できます
-(void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application
{
}
- (void)applicationProtectedDataDidBecomeAvailable:(UIApplication *)application
{
}
多くの試行錯誤の結果、空白の画面、ロック完了、およびロック状態イベントの監視が発見され、一貫したロック画面インジケーターが提供されます。状態遷移を監視する必要があります。
// call back
void displayStatusChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
// notification comes in order of
// "com.Apple.springboard.hasBlankedScreen" notification
// "com.Apple.springboard.lockcomplete" notification only if locked
// "com.Apple.springboard.lockstate" notification
AppDelegate *appDelegate = CFBridgingRelease(observer);
NSString *eventName = (__bridge NSString*)name;
NSLog(@"Darwin notification NAME = %@",name);
if([eventName isEqualToString:@"com.Apple.springboard.hasBlankedScreen"])
{
NSLog(@"SCREEN BLANK");
appDelegate.bDeviceLocked = false; // clear
}
else if([eventName isEqualToString:@"com.Apple.springboard.lockcomplete"])
{
NSLog(@"DEVICE LOCK");
appDelegate.bDeviceLocked = true; // set
}
else if([eventName isEqualToString:@"com.Apple.springboard.lockstate"])
{
NSLog(@"LOCK STATUS CHANGE");
if(appDelegate.bDeviceLocked) // if a lock, is set
{
NSLog(@"DEVICE IS LOCKED");
}
else
{
NSLog(@"DEVICE IS UNLOCKED");
}
}
}
-(void)registerforDeviceLockNotif
{
// screen and lock notifications
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
CFBridgingRetain(self), // observer
displayStatusChanged, // callback
CFSTR("com.Apple.springboard.hasBlankedScreen"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
CFBridgingRetain(self), // observer
displayStatusChanged, // callback
CFSTR("com.Apple.springboard.lockcomplete"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
CFBridgingRetain(self), // observer
displayStatusChanged, // callback
CFSTR("com.Apple.springboard.lockstate"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
}
画面ロックインジケーターをバックグラウンドで実行するには、アプリの起動時に以下を呼び出すバックグラウンド処理を実装する必要があります。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.backgroundTaskIdentifier =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
}];
[self registerforDeviceLockNotif];
}
アプリが実行中で、ユーザーがデバイスをロックすると、アプリの代理人は「application Will Resign Active:」の呼び出しを受け取ります。ロックされているときにアプリが実行されていた場合、デバイスがロック解除されると、「application Did Become Active:」の呼び出しが届きます。ただし、ユーザーが電話を受けた後、それを無視することを選択した場合は、同じアプリが呼び出されます。私の知る限り、その違いはわかりません。
また、これらの時間帯にアプリが実行されていなかった場合、アプリが実行されていないため、通知を受ける方法はありません。