Apple Push Notification service (APNs)是苹果的推送服务,本身使用挺简单,但是由于步骤比较繁琐,下面纪录一下的具体步骤。

第一步,新建App IDs/获取推送证书

首先登陆https://developer.apple.com/account/ios/identifiers/bundle/bundleCreate.action注册一个App ID,带push功能的app,必须是Explicit App ID,填入你的项目Bundle ID,App Services的地方一定要勾选Push Notifications。Xcode的项目Bundler Identifier一定要和这里填入的Bundle ID相同,对于已经建好的项目,如果想新加入push功能,需要在Xcode的设置中(快捷键Command+.)的Accounts-View Details点击左下角刷新按钮。重新build项目就可以了。

转到https://developer.apple.com/account/ios/certificate/certificateCreate.action,新建一个推送证书。需要用到keychain工具

请求证书

然后把申请的证书下载下来,双击打开,在keychain中,右键导出为p12格式,一定要记住密码(可以留空)。假设我们到处的文件为:/ramdisk/server_certificates_bundle_sandbox.p12。留到第三步用。

第二步,获取设备token

需要修改AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    //-- Set Notification
    if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)])
    {
        // iOS 8 Notifications
        [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
        
        [application registerForRemoteNotifications];
    }
    else
    {
        // iOS < 8 Notifications
        [application registerForRemoteNotificationTypes:
         (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)];
    }
    
    return YES;
}



- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
    
    NSString *deviceTokenStr = [[[[deviceToken description]
                                  stringByReplacingOccurrencesOfString: @"<" withString: @""]
                                 stringByReplacingOccurrencesOfString: @">" withString: @""]
                                stringByReplacingOccurrencesOfString: @" " withString: @""];
    
    NSLog(@"%@\nDevice Token: %@",[UIDevice currentDevice].name, deviceTokenStr);
}

- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
    NSLog(@"Failed to get token, error: %@", error);
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    NSLog(@"\napns -> didReceiveRemoteNotification,Receive Data:\n%@", userInfo);
    //把icon上的标记数字设置为0,
    application.applicationIconBadgeNumber = 0;
    if ([[userInfo objectForKey:@"aps"] objectForKey:@"alert"]!=NULL) {
        UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"**推送消息**"
                                                        message:[[userInfo objectForKey:@"aps"] objectForKey:@"alert"]
                                                       delegate:self
                                              cancelButtonTitle:@"关闭"
                                              otherButtonTitles:@"处理推送内容",nil];
//        alert.tag = alert_tag_push;
        [alert show];
    }
}

第三步,向Apple的服务器发送消息,以便Apple转发给设备

APNs格式在apple官网:https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html 其实就是和apple的服务器建立一个socket连接,然后发送数据。关键是,不能一个设备/一条消息建立一次连接,这样成本太高,而且太慢,还会让apple认为是DDOS攻击,所以推送消息的时候,最好一次推送完,再close socket。java版的示例代码:

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

/**
 * 测试apple推送服务。千万不要一个用户建立一次socket连接,这会被apple认为是ddos攻击。<br>
 * apple 文档:https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW9
 */
/**
 * @author loganliu
 *
 */
public class APNs {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String keyPath = "/ramdisk/server_certificates_bundle_sandbox.p12";
		String ksType = "PKCS12";
		String ksPassword = "";
		String ksAlgorithm = "SunX509";
		String deviceToken = "f3d7e7e9edb36d22a78e78449e7a1faa724d8fe08cd1183d2e2822f8e0d2bd72";
		String serverHost = "gateway.sandbox.push.apple.com";
		int serverPort = 2195;
		// System.setProperty("socksProxyHost", "127.0.0.1");
		// System.setProperty("socksProxyPort", "8889");
		try {
			InputStream certInput = new FileInputStream(keyPath);
			KeyStore keyStore = KeyStore.getInstance(ksType);
			keyStore.load(certInput, ksPassword.toCharArray());
			KeyManagerFactory kmf = KeyManagerFactory.getInstance(ksAlgorithm);
			kmf.init(keyStore, ksPassword.toCharArray());
			SSLContext sslContext = SSLContext.getInstance("TLS");
			sslContext.init(kmf.getKeyManagers(), null, null);
			SSLSocketFactory socketFactory = sslContext.getSocketFactory();
			Socket socket = socketFactory.createSocket(serverHost, serverPort);
			StringBuilder content = new StringBuilder();
			String text = "你好世界";
			content.append("{\"aps\":");
			content.append("{\"alert\":\"").append(text)
					.append("\",\"badge\":1,\"sound\":\"").append("ping1")
					.append("\"}");
			content.append(",\"cpn\":{\"t0\":")
					.append(System.currentTimeMillis()).append("}");
			content.append("}");
			byte[] msgByte = makebyte((byte) 1, deviceToken,
					content.toString(), 10000001);
			System.out.println(msgByte);
			socket.getOutputStream().write(msgByte);
			socket.getOutputStream().flush();
			socket.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 组装apns规定的字节数组 使用增强型
	 * 
	 * @param command
	 * @param deviceToken
	 * @param payload
	 * @return
	 * @throws IOException
	 */
	private static byte[] makebyte(byte command, String deviceToken,
			String payload, int identifer) {
		try {
			byte[] deviceTokenb = decodeHex(deviceToken);
			ByteArrayOutputStream boas = new ByteArrayOutputStream();
			DataOutputStream dos = new DataOutputStream(boas);
			byte[] payloadBytes = payload.getBytes("UTF-8");
			dos.writeByte(command);
			dos.writeInt(identifer);// identifer
			dos.writeInt(Integer.MAX_VALUE);
			dos.writeShort(deviceTokenb.length);
			dos.write(deviceTokenb);
			dos.writeShort(payloadBytes.length);
			dos.write(payloadBytes);
			return boas.toByteArray();
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * hex representation string to bytes
	 * 
	 * @param s
	 *            input hex string
	 * @return byte array
	 */
	public static byte[] decodeHex(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
					.digit(s.charAt(i + 1), 16));
		}
		return data;
	}
}

这样我们的app就能收到推送消息了。