iOS远端推送部署详解

最近几天被iOS的推送部署给搞懵了,现在特地整理下和大家进行分享。
iOS远端推送机制

APNS,全称为Apple Push Notification service,是苹果通知推送服务中最重要的一环。它是苹果通知推送服务器,为所有iOS设备以及OS X设备提供强大并且可靠的推送通知服务。每个注册通知服务的设备都会和该服务器进行长连接,从而实时获取推送通知。即使当前APP不在运行状态,当通知到达的时候也会有提示发生,最常见的就是短信服务。

每一个App必须向APNs注册通知服务,APNs会返回给设备一个DeviceToken,该Token为APNs上针对该设备的唯一标示符。App需要将该DeviceToken返给自身的Server端保存后续使用,如下所示。
DeviceToken的操作流程

DeviceToken的操作流程

当App开发者的server需要向特定设备推送通知时,就使用DeviceToken和固定格式数据(Push payload)发给APNs,然后APNs就会向DeviceToken指定的设备推送通知了,具体流程如下所示,单一推送
通知方推送一条远端通知给客户端代码的整个流程

通知方推送一条远端通知给客户端代码的整个流程

或者多方通知,APNs都能一一对应,靠的就是之前我们提供给它的DeviceToken。
多个通知方向向不同的客户端推送通知的流程示意
多个通知方向向不同的客户端推送通知的流程示意

本地推送证书配置

打开你mac的钥匙串访问,然后点击钥匙串访问
打开钥匙串

打开钥匙串

随后它会弹出一个窗口 用户电子邮件信息
生成CSR文件
生成CSR文件

就填写你苹果开发者账号的名称即可(应该是一个邮件名称),点击保存到磁盘的选项,点击继续,点击存储,文件名为:CertificateSigningRequest.certSigningRequest。
保存生成的CSR文件
保存生成的CSR文件

然后我们打开苹果开发者中心 进入Member Center
苹果开发者中心

苹果开发者中心

然后点击左侧列表中任意一项进入详情页面,
开发者个人首页选项
开发者个人首页选项

选择IOS Apps中列表项
选择IOS Apps中列表项
APP ID

首先我们需要为我们要开发的APP建立身份信息,就是AppID,如图所示,点击左侧
添加AppID

添加AppID

点击添加按钮进入注册页面,我们需要输入App Id的名字以及BundleID,其中BundleID不能有通配符,否则无法具备推送功能,然后在下面的APP Service中勾选Push Notification一项
填写BundleID以及App ID Description
填写BundleID以及App ID Description

选择App Service
选择App Service

点击下一步,然后确认提交即可,大家注意到Push Notification一项为Configurable,这是因为我们还没有为该AppID生成推送证书,等推送证书生成完毕之后可以再回来查看该AppID 的状态。
确认提交App ID
确认提交App ID
Certificates

其次,我们需要生成开发者证书和推送证书,如下图所示,点击左侧Cerifications列表,选择添加进入下一页面,
添加证书

添加证书

如果您的页面如图所示为灰色不可选,说明您已经拥有了开发者证书。就不需要再次生成了,如果可选就选择该选项,
选择证书类型

选择证书类型

接下来进入以下界面,选择你之前添加的AppID,之后点击Continue即可,
选择需要绑定证书的App ID

选择需要绑定证书的App ID

然后选择之前我们保存在本地的CSR文件CertificateSigningRequest.certSigningRequest,点击Generate就生成了开发者的证书。
上传本机CSR文件

上传本机CSR文件

同理我们需要生成推送测试证书,生成流程和开发者证书类似,只是在证书类型页面,选择的证书类型换成了Apple Push Notification service SSL。
选择生成证书类型

选择生成证书类型

当我们生成好推送证书之后再回头看我们之前创建的AppId,能够看Push Notifications一项已经为Enabled了。当然发布推送证书配置完毕之后,Distribution一项也显示为Enable。
再次查看APPID 状态

再次查看APPID 状态
Provisioning Profiles

第三步,需要生成Provisioning Profiles,该文件其实就是以上的证书、AppId以及设备信息的打包集合,我们只要在不同的场景下生成不同类型Provisioning Profiles即可,它会在后续打包ipa文件的时候被嵌入安装包内。
首先我们选择左侧列表中的Provisioning Profiles中的All选项,选择添加
添加Provisioning Profiles文件

添加Provisioning Profiles文件

之后选择生成类型,我们这里以开发类型为例,下面还有发布的两种类型,
选择生成PP文件类型

选择生成PP文件类型

之后点击Continue,进入下一页面,同样选择我们之前创建的具有Push服务的AppId,
选择绑定的App ID

选择绑定的App ID

接下来,选择上面生成的开发证书(一一对应的,如果你选择生成的是发布Provisioning Profiles,则会出现发布证书),
选择之前生成的对应证书

选择之前生成的对应证书

紧接着,我们选择授权设备,即你需要进行开发的设备,该设备可以在左侧Devices列表中添加,需要提供设备的UUID,这里我们选择所有设备,点击Continue,
选择授权设备

选择授权设备

最后一步,我们给Provisioning Profiles添加名称,
添加Provisioning Profiles名称

添加Provisioning Profiles名称

点击Generate即生成我们所需要的Provisioning Profile。

其实同理,我们可以生成发布版的开发者证书,推送证书以及对应的Provisioning Profiles。最后的文件我们都放到同一个文件夹里,如图所示,其中我把发布的两种(Ad Hoc 和 Distribution)都一起搞出来。
生成的各个证书以及Provisioning Profile文件

生成的各个证书以及Provisioning Profile文件

其中Push.p12文件后续会提及~


开发环境配置

我们将上一步生成的开发者证书ios_development.cer以及推送证书aps_development.cer在最初生成CSR文件的MAC机上安装,双击即可安装,同时会打开钥匙串页面,安装之后我们找到之前生成CSR文件时生成的专用密钥,名称就是我们之前生成CSR文件时填写的,选择该专用密钥,同时选中刚刚安装成功的推送证书,(必须注意,同时选择,我们需要将专用密钥以及安装成功的推送证书同时导出成一个文件)右键菜单导出,
导出专用密钥和本地安装的推送证书

导出专用密钥和本地安装的推送证书

如图我们命名Push,点击存储,
保存到本地
保存到本地

接下来需要为证书添加密码,这个密码是需要提供给服务器的。
添加证书密码
添加证书密码

最后我们需要配置我们本地的开发环境,也就是XCode,第一步我们点击XCode的Preference打开XCode的首选项菜单,
打开Preference选项

打开Preference选项

在Account选项中添加我们的开发者账户,如果之前已经登录就会看到该账户信息,然后点击下方的View Details,
查看账户信息

查看账户信息

之后会显示该开发者账户的证书和Provisioning Profiles等信息,该信息会和你开发者账号里面显示的一致,如果不一致就点击刷新,
刷新账户信息

刷新账户信息

不久就会出现我们之前创建的Provisioning Profiles,接下来,我们在XCode中Build Settings -> Code Signing中选择我们需要的Provisioning Profiles文件即可
XCode选择对应的Provisioning Profile文件

XCode选择对应的Provisioning Profile文件

此时本地开发环境已经配置完毕。接下来就开始Coding,Coding,Coding。。。。

远端推送通知的代码实现

首先我们需要注册推送通知服务并获取DeviceToken;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-(void)initPushNotificationWithApp: (UIApplication*)application
{
    // 注册通知服务
    if([UIDevice currentDevice].systemVersion.floatValue < 8.0)
    {
        [application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge
                                                       | UIRemoteNotificationTypeSound
                                                       | UIRemoteNotificationTypeAlert)];
    }
    // IOS8.0以上版本的注册推送方式和以往不同
    else
    {
        UIUserNotificationSettings* settings = [UIUserNotificationSettings settingsForTypes:(UIRemoteNotificationTypeBadge
                                                                                           | UIRemoteNotificationTypeSound
                                                                                           | UIRemoteNotificationTypeAlert)
                                                                                 categories:nil];
        [application registerUserNotificationSettings:settings];
        [application registerForRemoteNotifications];
    }
}

如果注册成功,APNs会返回给你设备的token,iOS系统会把它传递给app delegate代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 如果注册成功,则会收到DeviceToken,我们需要将该Token发给服务器保存
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    PRINT_FUNC
    
    NSString* tokenStr = [NSString stringWithFormat:@"%@", deviceToken];
    NSLog(@"deviceToken: %@", tokenStr);
    if(tokenStr.length == 0)
    {
        NSLog(@"Device Token Invalid!");
    }
	
	// 然后我们需要将该DeviceToken发给我们自己的服务器进行保存;
    [self sendDeviceToken:deviceToken]; 
}

// 如果注册失败,会收到错误信息,包含错误原因
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    PRINT_FUNC
    
    NSLog(@"***************************************\n");
    NSLog(@"Failed to Register The Notification!!!!\n");
    NSLog(@"error = %@", error);
    NSLog(@"***************************************\n");
}

之后我们就可以在AppDelegate中添加处理代码,当用户点击通知栏的通知或者处于运行状态时,App代码会执行- (void)application:(UIApplication )application didReceiveRemoteNotification:(NSDictionary )userInfo代理方法,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    PRINT_FUNC
    
    NSLog(@"收到推送通知: %@", userInfo);
    
    // userInfo是一个字典数据类型,具体Key Value由客户端和服务器进行协商确定
    NSString* orderId = [userInfo objectForKey:@"carryOrderId"];
    NSLog(@"收到订单消息通知,订单号:%@",orderId);
	
	// .... 其余逻辑,拿到具体关键信息之后进行下一步处理
}

还有一个方法

1
2
3
4
5
/*! This delegate method offers an opportunity for applications with the "remote-notification" background mode to fetch appropriate new data in response to an incoming remote notification. You should call the fetchCompletionHandler as soon as you're finished performing that operation, so the system can accurately estimate its power and data cost.
 
 This method will be invoked even if the application was launched or resumed because of the remote notification. The respective delegate methods will be invoked first. Note that this behavior is in contrast to application:didReceiveRemoteNotification:, which is not called in those cases, and which will not be invoked if this method is implemented. !*/
 
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler;

我发现这两个方法在APP处于后台或者前台展示,也就是App存活期会同样调用,但当APP并未启动或者被后台销毁之后,用户点击通知象虽然都会调起App,但是前者这个方法就不会被触发,而后者依然会被触发。注释中也说明了该方法即使在其中或者休眠状态下都会由于远端通知而被调用而且会优先于上一个方法。而且后者可以让你和服务器进行一定的数据交互,比如订单状态变化了,我们在该方法中向服务器请求最新的订单信息等等。

官方文档是这样描述这两个方法的,一目了然:

1
2
3
4
5
// Tells the delegate that the running app received a remote notification.
	- application:didReceiveRemoteNotification:

// Tells the app that a remote notification arrived that indicates there is data to be fetched.
	- application:didReceiveRemoteNotification:fetchCompletionHandler:

服务器端代码实现

Apple官方APNs地址:

  1. 测试地址 gateway.sandbox.push.apple.com:2195
  2. 正式发布地址 gateway.push.apple.com:2195

简单的通知数据格式,以二进制形式发送,网络字节序。
简单的推送通知格式

简单的推送通知格式

服务器端代码也可以自己使用原生的Socket写,这里我使用Javapns这个开源代码实现,比较简单,Payload数据格式也已经被封装,你只需要add,add,add。代码如下:
其中就需要用到我们之前生成的Push.p12

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package testApplePush;

import java.util.List;
import javapns.Push;
import javapns.notification.PushNotificationPayload;
import javapns.notification.PushedNotifications;

public class testApplePush 
{
    public static void main(String[] args)
    {
       try 
       {
          PushNotificationPayload payload = new PushNotificationPayload();

          payload.addAlert("这是一条推送通知!");	// 通知主体内容
          payload.addBadge(1);					// 角标数字
          payload.addSound("default");			// 通知铃音
          // 加入自定义信息
          payload.addCustomDictionary("carryOrderId", "121212121212121212121212");
 
		  // 服务器端记录的DeviceToken
          String deviceToken = "************************************************";
          PushedNotifications notifications = Push.payload(payload, 	// 自定义payload
												          "Push.p12",	// 前面生成的证书
												          "111111111", 	// 证书导出时的密码
												          false,		// 是否发送到发布地址
												          deviceToken); // 客户端DeviceToken

            int numOfFailedNotifications = notifications.getFailedNotifications()
                    .size();
            int numOfSuccessfulNotificatios = notifications
                    .getSuccessfulNotifications().size();

            System.out.println(String.format(
                    "Successful Send: %d, Failed Send: %d",
                    numOfSuccessfulNotificatios, numOfFailedNotifications));

        } 
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

当前这里只是发送一条,如果需要批量发送,貌似Javapns支持的不是太好。这里使用的证书就是上面我们导出的.p12格式证书(Windows平台使用没问题。有些教程说是Win系统不识别是不正确的),接下来还有一份PHP写的代码,供大家查阅。但是这其中需要将我们的p12格式证书转换成pem格式证书。具体教程如下:

  • 将我们之前生成的推送证书aps_developement.cer文件以及Push.p12文件放在同一文件夹下;
  • 在Terminal中切换到该目录下,然后执行命令将aps_developement.cer文件转换成pem格式文件,之后会在本目录下生成PushCert.pem文件

    1
    
    openssl x509 -in aps_development.cer -inform der -out PushCert.pem
    
  • 紧接着执行命令将Push.p12文件转换成pem格式文件,之后在本目录下生成PushKey.pem文件,其中会提示你先输入之前生成Push.p12文件的时候的密码,然后需要为新生成的证书文件添加密码,这个密码是要提供给服务端使用的;

    1
    
    openssl pkcs12 -nocerts -out Pushkey.pem -in Push.p12
    
  • 然后将这两个pem文件合成成一个pem文件,Push.pem

    1
    
    cat PushCert.pem PushKey.pem > Push.pem
    

整个过程如下图所示:
pem文件生成过程

pem文件生成过程

证书生成完毕之后,我们就可以在代码中使用了。
如下为PHP写的推送通知服务。代码很简单,主要是注意其中证书为上一步生成的Push.pem,密码就是生成时输入的密码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php
// DeviceToken 不包含空格
$deviceToken = '********************************************************';
// 证书密码
$passphrase = '1111111111';
// 通知主体内容
$alert = '这是一条推送通知!';


$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'Push.pem');		// ck.pem证书上有提及
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);

// Open a connection to the APNS server
$fp = stream_socket_client(
		'ssl://gateway.sandbox.push.apple.com:2195', 			// 远端测试地址
		$err,													
		$errstr, 
		60, 													// 超时时间
		STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT,
		$ctx);
if (!$fp)
	exit("Failed to connect: $err $errstr" . PHP_EOL);

echo 'Connected to APNS' . PHP_EOL;

// 建立字典数据
$body['aps'] = array(
	'alert' => $alert,
	'sound' => 'default',
	'badge' => 66
	);

// 将字典数据转换成JSON
$payload = json_encode($body);

// 组织二进制数据格式,具体格式参照apple官方文档,本文中也有提及。
// Command + Token length + deviceToken + Payload length + payload
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;

// 发送组成的数据给APNs
$result = fwrite($fp, $msg, strlen($msg));
if (!$result)
	echo 'Fail to delivery Notification' . PHP_EOL;
else
	echo 'Delivery Notification successfully' . PHP_EOL;

fclose($fp);
?>

运行下下测试:
最终测试效果

最终测试效果

参考:
[1]. https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction.html#//apple_ref/doc/uid/TP40008194-CH1-SW1
[2]. https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/
[3]. http://blog.csdn.net/shenjie12345678/article/details/41120637

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值