iOS 后台稳定定位方案(超级省电)

在开发工程师端的过程中,有一个需求是每隔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.制作证书(我去盗的图 <--臭不要脸!)


第一步.png
第二步.png

后面的步骤,和制作其他证书的步骤都是一样的!

把证书下载下来后,双击安装到电脑,我后面要贴java代码,所以需要导出p12格式的证书,默认导出的就是p12证书。如果要测试,推荐使用 Pusher for Mac

界面样式.png

2.开始代码时间!
(1)导入push kit框架#import <PushKit/PushKit.h>
(2)别忘了导包,之前忘了导,一直报错!


导包.png

(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没写清楚的地方,可以看这个

如果发现错误,请大家留言指出,谢谢!

如果发现不清楚的地方,请留言,我会修改的!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容