在开发工程师端的过程中,有一个需求是每隔5分钟就要定位一次工程师的位置,一旦有订单来了,就根据用户的坐标分配最近的工程师。但是这个需求最初的方案是,让APP长驻后台,这个又造成耗电严重的问题。后来发现苹果有个解决长连接的方案,就是PushKit! 。
目前该方法已不可用。
一.定位服务
目前定位服务使用的是百度地图的定位。
1.根据官方的说明集成百度地图
2.除了正常的设置APP的定位权限外,现在有个问题,那就是用户在选择访问位置权限的时候有3个参数,'永不','使用应用期间','始终'。如果用户选中了非始终的其他两个选项,那么后台就无法定位。
解决方案:
-(void) startLocationService{
if ([CLLocationManager locationServicesEnabled] && ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedAlways ||[CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined)) {
}else if ([CLLocationManager locationServicesEnabled] && ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse)){//仅在使用APP时定位
UIAlertController *TipAlert = [UIAlertController alertControllerWithTitle:@"警告" message:@"当前位置权限为\"使用应用期间\" \n请修改为\"始终\"" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *goOnAction = [UIAlertAction actionWithTitle:@"设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSURL * url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
if (iOS10) {
#ifdef NSFoundationVersionNumber_iOS_10
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
#endif
} else {
[[UIApplication sharedApplication] openURL:url];
}
}
}];
[TipAlert addAction:goOnAction];
[self.mainViewController presentViewController:TipAlert animated:YES completion:^{
}];
}else{
//定位不能用
NSLog(@"定位不能用");
UIAlertController *TipAlert = [UIAlertController alertControllerWithTitle:@"警告" message:@"未获取位置权限 \n请设置位置权限为 \"始终\"" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *goOnAction = [UIAlertAction actionWithTitle:@"设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSURL * url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
if (iOS10) {
#ifdef NSFoundationVersionNumber_iOS_10
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
#endif
} else {
[[UIApplication sharedApplication] openURL:url];
}
}
}];
[TipAlert addAction:goOnAction];
[self.mainViewController presentViewController:TipAlert animated:YES completion:^{
}];
}
}
这个方法是检查定位权限的方法,除了在应该检查权限的地方检查,在用户从设置界面回来的时候,也应该再检查一遍!
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
//用户可能去设置定位了,现在回来检查一下
[self startLocationService];
}
这样就能保证至少权限是正常使用的,在info文件设置权限可别忘了-->看这里, 因为已经设置到后台定位了,这个必须要设置!
定位的代码现在这里,下面说一下PushKit!
二.PushKit
PushKit是iOS8之后,苹果引入的新push方式,有别于普通的APNs,它不会弹出通知,而是悄悄的告诉我们的app有推送过来,让app做出相应的处理。我目前的测试环境最低支持到iOS10,不太清楚iOS8和iOS9的稳定性,有心的朋友测试了可以给我留言,哈哈哈。。。
苹果开发PushKit原本是给VoIP应用使用的,所以没有充分理由,非VoIP想要上AppStore,难于上青天!现在更好了,VoIP的体验直逼三大运营商的体验,所以秉承社会主义,PushKit即便在VoIP的应用上使用,同样不能上线到大陆的AppStore!
1.制作证书(我去盗的图 <--臭不要脸!)
后面的步骤,和制作其他证书的步骤都是一样的!
把证书下载下来后,双击安装到电脑,我后面要贴java代码,所以需要导出p12格式的证书,默认导出的就是p12证书。如果要测试,推荐使用 Pusher for Mac。
2.开始代码时间!
(1)导入push kit框架#import <PushKit/PushKit.h>
(2)别忘了导包,之前忘了导,一直报错!
(3)- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions ;
调用下面的方法设置pushKit
#pragma mark - creatVoIP
- (void)creatVoIP{
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound) completionHandler:^(BOOL granted, NSError * _Nullable error) {
}];
[UIApplication.sharedApplication registerForRemoteNotifications];
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// Create a push registry object
PKPushRegistry * voipRegistry = [[PKPushRegistry alloc] initWithQueue: mainQueue];
// Set the registry's delegate to self
voipRegistry.delegate = self;
// Set the push type to VoIP
voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
}
(4)协议方法 PKPushRegistryDelegate
PushKit的推送token与APNs是不一样的!,下面的协议方法可以获取到!
#pragma mark - PKPushRegistryDelegate
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)pushCredentials forType:(PKPushType)type{
NSString *str = [NSString stringWithFormat:@"%@",pushCredentials.token];
NSString *tokenStr = [[[str stringByReplacingOccurrencesOfString:@"<" withString:@""]
stringByReplacingOccurrencesOfString:@">" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""];
NSLog(@"tokenStr获取到的就是PushKit的token");
NSLog(@"实际生产中,把这个token传给后端的同学,我们测试可以拷贝出来,放到pusher里面测试");
}
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void(^)(void))completion{
//收到pushKit通知
[_locationManager requestLocationWithReGeocode:YES withNetworkState:YES completionBlock:^(BMKLocation * _Nullable location, BMKLocationNetworkState state, NSError * _Nullable error) {
if (location) {//得到定位信息,下面的代码纯粹是为了测试的效果,实际应用中,把位置信息传给后台就可以了
if (location.location) {
NSLog(@"LOC = %@",location.location);
}
if (location.rgcData) {
NSLog(@"rgc = %@",[location.rgcData description]);
}
NSString *Str = [NSString stringWithFormat:@"L:%@ R:%@",location.location,[location.rgcData description]];
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = [NSString localizedUserNotificationStringForKey:@"定位信息" arguments:nil];
content.body = [NSString localizedUserNotificationStringForKey:Str arguments:nil];
content.sound = [UNNotificationSound defaultSound];
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"OXNotification" content:content trigger:nil];
[center addNotificationRequest:request withCompletionHandler:^(NSError *_Nullable error) {
NSLog(@"成功添加推送");
}];
}
}];
}
- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type{
NSLog(@"");
}
(5)特别说明!
我在第二个协议方法里面发送了一个自定义通知,为了在杀死APP的情况下进行测试,下面的代码是为了方便大家的测试写的,和本文设计到的长连接没有关系!
1.导入头文件
#ifdef NSFoundationVersionNumber_iOS_9_x_Max
#import <UserNotifications/UserNotifications.h>
#import <UserNotifications/UserNotifications.h>
#import <UserNotificationsUI/UserNotificationsUI.h>
#endif
2.协议方法
UNUserNotificationCenterDelegate
#pragma mark - ios10才支持的通知!
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{
NSLog(@"");
completionHandler(UNNotificationPresentationOptionAlert);
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler{
NSLog(@"");
}
//ios 12才支持
- (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(nullable UNNotification *)notification{
}
java代码:
import javapns.back.PushNotificationManager;
import javapns.back.SSLConnectionHelper;
import javapns.data.Device;
import javapns.data.PayLoad;
public class Main {
public static void main(String[] args) throws Exception{
try {
// token为不包含空格和<>的字母数字组合(字母不区分大小写)
String deviceToken = "XXXXXXXXXX";
PushNotificationManager pushManager = PushNotificationManager.getInstance();
pushManager.addDevice("iphone", deviceToken);
// 苹果推送服务器
/*
开发状态服务器地址 gateway.sandbox.push.apple.com 2195
发布状态服务器地址 gateway.push.apple.com 2195
需要注意:
Xcode打出的Debug安装包只能选择开发服务器,证书可以选择开发推送证书或者发布推送证书;
Xcode打出的AdHoc或Release安装包只能选择发布务器和发布推送证书;
*/
String host= "gateway.push.apple.com";
// 端口号
int port = 2195;
// 在mac系统下导出的p12证书(开发证书对应开发环境,发布证书对应所有环境)
String certificatePath = "J:\\QQdir\\2\\JKX_e.p12";
// p12证书密码
String certificatePassword= "12345678";
// 初始化tcp连接
pushManager.initializeConnection(host, port, certificatePath, certificatePassword, SSLConnectionHelper.KEYSTORE_TYPE_PKCS12);
// Send Push
Device client = pushManager.getDevice("iphone");
// 要推送的消息
PayLoad payLoad = new PayLoad();
payLoad.addAlert("收到消息CALL我");
payLoad.addBadge(1);
payLoad.addSound("default");
// 开始推送
pushManager.sendNotification(client, payLoad);
pushManager.stopConnection();
pushManager.removeDevice("iphone");
System.out.println ("success");
}
catch (Exception e) {
e.printStackTrace();
}
}
}
需要用到的jar包
bcprov-jdk16-145-1.jar
commons-io-2.0.1.jar
commons-lang-2.5.jar
javapns-jdk16-163.jar
log4j-1.2.16.jar
这个代码和后端的同学测试通过的,java没写清楚的地方,可以看这个!