IOS到达率统计

当前IOS到达状态并不准确,调用APNs接口成功,即认为到达成功、展示成功 在IOS 10之后,可以通过扩展统计到真实的到达,此方案提供给需要精确状态的业务方


0.1. 原理

官方文档

IOS10开始,无论客户端是否活着,都可以在push展示前修改push的内容,如标题内容,下载图片等。此接口需要服务端传mutable-content=1,并且客户端实现扩展。

利用此方案,可以在扩展中调用http接口,向服务端上报该push客户端已收到,服务端利用此状态做数据统计

注意:无论app是前台还是后台,都会调用扩展,但是在扩展中无法判断当前的前后台状态,所以此状态为到达,此方案无法统计展示(ios系统,app前台时无法展示push)。

0.2. 如何使用?

1、联系我们对你们的AppId配置使用启用真实到达率的统计,即配置所有的push都下发mutable-content=1和其他安全相关参数 2、客户端push扩展中代码实现,如下:

请关注 (void)request:(NSDictionary *)userInfo方法

#import "NotificationService.h"
#import <CoreServices/CoreServices.h>
#import <CommonCrypto/CommonDigest.h>
#import <UIKit/UIKit.h>
#define kCacheDirectory [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
typedef NS_ENUM(NSInteger,MDPushNotificationFileType)
{
    MDPushNotificationFileTypeNone = 0,
    MDPushNotificationFileTypeImage,
    MDPushNotificationFileTypeVideo,
    MDPushNotificationFileTypeAudio,
};

@interface NotificationService ()

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];

    NSDictionary *dict = self.bestAttemptContent.userInfo;
    // 上传到达统计
    [self request:dict];
    // 下载图片
    if ([dict isKindOfClass:[NSDictionary class]] && dict.count > 0) {
        NSDictionary *apsDict = dict[@"aps"];

        if ([apsDict isKindOfClass:[NSDictionary class]] && apsDict.count > 0) {
            //获取文件类型
            MDPushNotificationFileType fileType = [[apsDict objectForKey:@"file_type"] integerValue];

            //获取文件路径
            NSString *fileUrlStr = [apsDict objectForKey:@"file_path"];
            if (![fileUrlStr isKindOfClass:[NSString class]] || fileUrlStr.length <= 0) {
                self.contentHandler(self.bestAttemptContent);
            }

            //目前仅支持图片的展示
            switch (fileType) {
                case MDPushNotificationFileTypeImage:
                {
                    __weak typeof(self) weakSelf = self;
                    [self loadAttachmentWithUrlString:fileUrlStr completionHandle:^(UNNotificationAttachment *aAttachment) {
                        if (aAttachment) {
                            weakSelf.bestAttemptContent.attachments = @[aAttachment];
                        }
                        weakSelf.contentHandler(self.bestAttemptContent);
                    }];
                    break;
                }
                case MDPushNotificationFileTypeVideo:
                case MDPushNotificationFileTypeAudio:
                default:
                {
                    self.contentHandler(self.bestAttemptContent);
                    break;
                }
            }

        } else {
            self.contentHandler(self.bestAttemptContent);
        }

    } else {
        self.contentHandler(self.bestAttemptContent);
    }
}

#pragma mark 图片下载逻辑
- (void)loadAttachmentWithUrlString:(NSString *)urlStr completionHandle:(void(^)(UNNotificationAttachment *aAttachment))completionHandler
{
    __block UNNotificationAttachment *attachment = nil;

    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    [[session downloadTaskWithURL:[NSURL URLWithString:urlStr] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            attachment = nil;

        } else {
            NSString *extensionType = [self extensionTypeWithStr:[urlStr pathExtension]];

            CFStringRef type;
            if ([extensionType isEqualToString:@"jpg"]) {
                type = kUTTypeJPEG;
            }
            else if ([extensionType isEqualToString:@"png"]) {
                type = kUTTypePNG;
            }
            else {
                type = (__bridge CFStringRef)@"";
            }

            NSError *attachmentError = nil;
            attachment = [UNNotificationAttachment attachmentWithIdentifier:location.path URL:location options:@{UNNotificationAttachmentOptionsTypeHintKey: (__bridge NSString*)type} error:&attachmentError];

            if (attachmentError) {
                attachment = nil;
                NSLog(@"%@",attachmentError.localizedDescription);
            }
        }
        [session invalidateAndCancel];
        completionHandler(attachment);

    }] resume];
}

- (NSString *)extensionTypeWithStr:(NSString *)str
{
    if (!str.length) return nil;

    if ([str containsString:@"?"]) {
        return [str componentsSeparatedByString:@"?"].firstObject;
    }
    return str;
}

- (void)serviceExtensionTimeWillExpire {
    self.contentHandler(self.bestAttemptContent);
}



#pragma mark === 到达统计逻辑 ====
- (void)request:(NSDictionary *)userInfo{
    NSString *exData = userInfo[@"_ext"];
    // 无_ext在不打日志
    if (exData.length > 0) {
        NSMutableDictionary *logDict = [NSMutableDictionary dictionary];
        NSInteger time = [[NSDate date] timeIntervalSince1970] * 1000;
        [logDict setObject:@(time) forKey:@"time"];
        [logDict setObject:@(1) forKey:@"type"];

        NSData *extData = [exData dataUsingEncoding:NSUTF8StringEncoding];
        NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:extData options:kNilOptions error:nil];
        NSString *security = [userInfo valueForKey:@"security"];
        if(security.length == 0){
            return;
        }
        [logDict setValue:dict forKey:@"data"];
        NSMutableDictionary *resultDict = [@{@"log_content":logDict} mutableCopy];
        [resultDict setValue:security forKey:@"security"];
        NSString *content = [self stringWithFromDict:resultDict];

        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://paas-push-api-log.immomo.com/push/log/iosarrive"]];
        NSString *requestForm = [NSString stringWithFormat:@"mzip=%@",[self urlencode:content]];
        [request setHTTPMethod:@"POST"];
        [request setTimeoutInterval:10];
        NSMutableData *postBody = [NSMutableData data];
        [postBody appendData:[requestForm dataUsingEncoding:NSUTF8StringEncoding]];
        [request setHTTPBody:postBody];

        NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];

        [[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
              NSDictionary *dict = nil;
            if (error) {
            }else{
               @try {
                    dict = [NSJSONSerialization JSONObjectWithData:data
                                                           options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves
                                                             error:&error];
                }
                @catch (NSException *e) {
                    dict = [NSDictionary dictionary];
                }
            }
            [session invalidateAndCancel];
        }]  resume];

    }
}

- (NSString *)stringWithFromDict:(NSDictionary *)dict{
    NSError *error = nil;
    NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:kNilOptions error:&error];
    NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    return content;
}

- (NSString *)urlencode: (NSString*) content {
    NSMutableString *output = [NSMutableString string];
    const unsigned char *source = (const unsigned char *)[content UTF8String];
    unsigned long sourceLen = strlen((const char *)source);
    for (int i = 0; i < sourceLen; ++i) {
        const unsigned char thisChar = source[i];
        if (thisChar == ' '){
            [output appendString:@"+"];
        } else if (thisChar == '.' || thisChar == '-' || thisChar == '_' || thisChar == '~' ||
                   (thisChar >= 'a' && thisChar <= 'z') ||
                   (thisChar >= 'A' && thisChar <= 'Z') ||
                   (thisChar >= '0' && thisChar <= '9')) {
            [output appendFormat:@"%c", thisChar];
        } else {
            [output appendFormat:@"%%%02X", thisChar];
        }
    }
    return output;
}
@end

完成后即可统计到真实的到达量

Copyright © momo 2019 all right reserved,powered by Gitbook修订时间: 2020-05-08 15:11:47

results matching ""

    No results matching ""