-
2017-03-01 09:58:19
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Net;
using System.Timers;
using PushSharp;
using PushSharp.Apple;
namespace ConsoleApplication1
{
//class Program
//{
// static void Main(string[] args)
// {
// X509Certificate2 cert = new X509Certificate2("123.p12", "8dbmds");
// X509CertificateCollection certificate = new X509CertificateCollection();
// certificate.Add(cert);
// //发布模式, 主机地址是 gateway.push.apple.com
// //开发模式, 主机地址是 gateway.sandbox.push.apple.com
// TcpClient client = new TcpClient("gateway.sandbox.push.apple.com", 2195);
// SslStream sslStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ServerCertificateValidationCallback), null);
// //方法AuthenticateAsClient()可能会引起异常,我们需要try..catch..起来
// try
// {
// //SslStream参考
// //https://msdn.microsoft.com/en-us/library/system.net.security.sslstream(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-2
// sslStream.AuthenticateAsClient("gateway.sandbox.push.apple.com", certificate, SslProtocols.Default, false);
// }
// catch (Exception e)
// {
// Console.WriteLine("Exception Message: {0} ", e.Message);
// sslStream.Close();
// }
// //把值赋给payload
// PushNotificationPayload payload = new PushNotificationPayload();
// payload.deviceToken = "b606c95c4b3956de89d67ecfa02811ebc5d1a09c6f832c3be67b1d9554a66683";
// payload.badge = 56789;
// payload.sound = "default";
// payload.message = "This message was pushed by C# platform.";
// Push(payload, sslStream);
// }
// //这是握手后的回调
// private static bool ServerCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
// {
// if (sslPolicyErrors == SslPolicyErrors.None)
// {
// Console.WriteLine("Specified Certificate is accepted.");
// return true;
// }
// Console.WriteLine("Certificate error : {0} ", sslPolicyErrors);
// return false;
// }
// //然后调用Push()方法
// public static void Push(PushNotificationPayload payload, SslStream _sslStream)
// {
// string payloadStr = payload.PushPayload();
// string deviceToken = payload.deviceToken;
// MemoryStream memoryStream = new MemoryStream();
// BinaryWriter writer = new BinaryWriter(memoryStream);
// writer.Write((byte)0); //The command
// writer.Write((byte)0); //deviceId长度的第一个字节,大头字节序第一个字节
// writer.Write((byte)32); //deviceId长度,大头字节序第二个字节
// //方法DataWithDeviceToken() , [具体看源码](https://github.com/Victor-Studio/PushNotification)
// byte[] deviceTokenBytes = DataWithDeviceToken(deviceToken.ToUpper());
// writer.Write(deviceTokenBytes);
// writer.Write((byte)0); //payload的长度的第一个字节,大头字节序的第一个字节
// writer.Write((byte)payloadStr.Length); //payload的长度,大头字节序的第二个字节
// byte[] bytes = Encoding.UTF8.GetBytes(payloadStr);
// writer.Write(bytes);
// writer.Flush();
// _sslStream.Write(memoryStream.ToArray());
// _sslStream.Flush();
// Thread.Sleep(3000);
// //方法ReadMessage() , 具体看[本库的源码](https://github.com/Victor-Studio/PushNotification)
// string result = ReadMessage(_sslStream);
// Console.WriteLine("server said: " + result);
// _sslStream.Close();
// }
// static string ReadMessage(SslStream sslStream)
// {
// // Read the message sent by the client.
// // The client signals the end of the message using the
// // "<EOF>" marker.
// byte[] buffer = new byte[2048];
// StringBuilder messageData = new StringBuilder();
// int bytes = -1;
// do
// {
// // Read the client's test message.
// bytes = sslStream.Read(buffer, 0, buffer.Length);
// // Use Decoder class to convert from bytes to UTF8
// // in case a character spans two buffers.
// Decoder decoder = Encoding.UTF8.GetDecoder();
// char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
// decoder.GetChars(buffer, 0, bytes, chars, 0);
// messageData.Append(chars);
// // Check for EOF or an empty message.
// if (messageData.ToString().IndexOf("<EOF>") != -1)
// {
// break;
// }
// }
// while (bytes != 0);
// return messageData.ToString();
// }
//}
class Program
{
public static DateTime? Expiration { get; set; }
public static readonly DateTime DoNotStore = DateTime.MinValue;
private static readonly DateTime UNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static string DeviceToken = "手机标识码";
public const int DEVICE_TOKEN_BINARY_SIZE = 32;
public const int DEVICE_TOKEN_STRING_SIZE = 64;
public const int MAX_PAYLOAD_SIZE = 256;
private static X509Certificate certificate;
private static X509CertificateCollection certificates;
static void Main(string[] args)
{
Console.WriteLine("1");
// //发布模式, 主机地址是 gateway.push.apple.com
// //开发模式, 主机地址是 gateway.sandbox.push.apple.com
string hostIP = "gateway.sandbox.push.apple.com";//
int port = 2195;
string password = "证书密码";//
string certificatepath = "证书路径.p12";//bin/debug
string p12Filename = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, certificatepath);
byte[] b = System.IO.File.ReadAllBytes(p12Filename);
certificate = new X509Certificate2(System.IO.File.ReadAllBytes(p12Filename), password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
certificates = new X509CertificateCollection();
certificates.Add(certificate);
Console.WriteLine("2");
TcpClient apnsClient = new TcpClient();
apnsClient.Connect(hostIP, port);
Console.WriteLine("3");
SslStream apnsStream = new SslStream(apnsClient.GetStream(), false, new RemoteCertificateValidationCallback(validateServerCertificate), new LocalCertificateSelectionCallback(selectLocalCertificate));
try
{
Console.WriteLine("4");
//APNs已不支持SSL 3.0
apnsStream.AuthenticateAsClient(hostIP, certificates, System.Security.Authentication.SslProtocols.Tls, false);
Console.WriteLine("5");
}
catch (System.Security.Authentication.AuthenticationException ex)
{
Console.WriteLine("error+" + ex.Message);
}
if (!apnsStream.IsMutuallyAuthenticated)
{
Console.WriteLine("error:Ssl Stream Failed to Authenticate!");
}
if (!apnsStream.CanWrite)
{
Console.WriteLine("error:Ssl Stream is not Writable!");
}
Console.WriteLine("6");
//Console.WriteLine(ReadMessage(apnsStream));
Console.WriteLine("7");
//Console.WriteLine(ReadMessage(apnsStream));
Byte[] message = ToBytes();
apnsStream.Write(message);
Console.WriteLine("8");
Console.WriteLine(ReadMessage(apnsStream));
Console.ReadLine();
}
static string ReadMessage(SslStream sslStream)
{
// Read the message sent by the client.
// The client signals the end of the message using the
// "<EOF>" marker.
byte[] buffer = new byte[2048];
StringBuilder messageData = new StringBuilder();
int bytes = -1;
do
{
// Read the client's test message.
bytes = sslStream.Read(buffer, 0, buffer.Length);
// Use Decoder class to convert from bytes to UTF8
// in case a character spans two buffers.
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
decoder.GetChars(buffer, 0, bytes, chars, 0);
messageData.Append(chars);
// Check for EOF or an empty message.
if (messageData.ToString().IndexOf("<EOF>") != -1)
{
break;
}
}
while (bytes != 0);
return messageData.ToString();
}
public static byte[] ToBytes()
{
// Without reading the response which would make any identifier useful, it seems silly to
// expose the value in the object model, although that would be easy enough to do. For
// now we'll just use zero.
int identifier = 0;
byte[] identifierBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(identifier));
// APNS will not store-and-forward a notification with no expiry, so set it one year in the future
// if the client does not provide it.
int expiryTimeStamp = -1;//过期时间戳
if (Expiration != DoNotStore)
{
//DateTime concreteExpireDateUtc = (Expiration ?? DateTime.UtcNow.AddMonths(1)).ToUniversalTime();
DateTime concreteExpireDateUtc = (Expiration ?? DateTime.UtcNow.AddSeconds(20)).ToUniversalTime();
TimeSpan epochTimeSpan = concreteExpireDateUtc - UNIX_EPOCH;
expiryTimeStamp = (int)epochTimeSpan.TotalSeconds;
}
byte[] expiry = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(expiryTimeStamp));
byte[] deviceToken = new byte[DeviceToken.Length / 2];
for (int i = 0; i < deviceToken.Length; i++)
deviceToken[i] = byte.Parse(DeviceToken.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
if (deviceToken.Length != DEVICE_TOKEN_BINARY_SIZE)
{
Console.WriteLine("Device token length error!");
}
byte[] deviceTokenSize = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Convert.ToInt16(deviceToken.Length)));
//string str = "{\"aps\":{\"alert\":\"这是测试消息!!\",\"badge\":1,\"sound\":\"default\"}}";
string str = "{\"aps\" : {\"alert\" : {\"title\" : \"标题\",\"body\" : \"文本\"},\"sound\" : \"default\",\"badge\" : 0}}";
byte[] payload = Encoding.UTF8.GetBytes(str);
byte[] payloadSize = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Convert.ToInt16(payload.Length)));
List<byte[]> notificationParts = new List<byte[]>();
//1 Command
notificationParts.Add(new byte[] { 0x01 });// Enhanced notification format command
notificationParts.Add(identifierBytes);
notificationParts.Add(expiry);
notificationParts.Add(deviceTokenSize);
notificationParts.Add(deviceToken);
notificationParts.Add(payloadSize);
notificationParts.Add(payload);
return BuildBufferFrom(notificationParts);
}
private static byte[] BuildBufferFrom(IList<byte[]> bufferParts)
{
int bufferSize = 0;
for (int i = 0; i < bufferParts.Count; i++)
bufferSize += bufferParts[i].Length;
byte[] buffer = new byte[bufferSize];
int position = 0;
for (int i = 0; i < bufferParts.Count; i++)
{
byte[] part = bufferParts[i];
Buffer.BlockCopy(bufferParts[i], 0, buffer, position, part.Length);
position += part.Length;
}
return buffer;
}
private static bool validateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true; // Dont care about server's cert
}
private static X509Certificate selectLocalCertificate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
{
return certificate;
}
}
}更多相关内容 -
个推使用指南
2018-09-14 17:31:13按照个推文档CocoaPods集成,基本能实现个推的基本推送功能。但是很多细节文档没有说,我就走了很多弯路。 首先登录个推网站。在个推·消息推送登记应用。 要登记测试环境和生产环境两个应用。 点击应用...按照个推文档CocoaPods集成,基本能实现个推的基本推送功能。但是很多细节文档没有说,我就走了很多弯路。
第一步:首先登录个推网站,注册用户并登录。
第二步:在个推·消息推送登记应用。
要登记测试环境和生产环境两个应用。
第三步:点击应用配置,显示上图页面,上传或修改开发环境的p12正书。设置证书密码,这个密码要和p12证书(《制作p12证书》)的密码一致。然后点击测试一下。
注意:证书测试就是apns测试,走的是苹果推送服务器。
没有什么好说的,运行你集成了个推的应用,把打印deviceToken输入对话框就能测试apns推送了。也可以创建透传消息进行测试了。个推网站只支持发送透传消息。证书测试时是测试的apns消息。
遇到的问题1:在苹果的远程通知注册成功委托函数didRegisterForRemoteNotificationsWithDeviceToken中获得token成功,然后修改别名: [GeTuiSdk bindAlias:@“个23推11” andSequenceNum:@“s1eq-1”]; 那两个参数我无论怎么改都是报30002错误,
我可是按照文档来的的啊!
为何文档上把上面的例子的格式推翻了呢?为何不举一个标准的别名例子呢?
- (void)GeTuiSdkDidRegisterClient:(NSString *)clientId { FLDDLogDebug(@"函数 clientId:%@", clientId); if(!isEmptyString(clientId) && (!self.clientId || ![clientId isEqualToString:self.clientId])) { NSLog(@"clientId:%@", clientId); // [GeTuiSdk bindAlias:@"y790715966c94d93e84182fbfce36182123456GS" andSequenceNum:@"1sssas2223446"]; if(self.payloadFlag && !self.noFirstGetClientIdFlag) { [GeTuiSdk resetBadge]; //重置角标计数 [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; // APP 清空角标 } self.noFirstGetClientIdFlag = YES; self.payloadFlag = NO; // [GeTuiSdk resetBadge]; //重置角标计数 // [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; // APP 清空角标 // [[NSNotificationCenter defaultCenter] postNotificationName:@"submitNotification" object:nil userInfo:@{@"clientId":clientId}]; [self submitWithUserInfo:@{@"clientId":clientId}]; } }
通过这个问题,大家知道了吧!cid回调以后,调用设置标签,别名绑定等接口,清除角标。
遇到的问题2:我在个推网站测试时,证书测试正常,但是测试推送消息时,有很多消息我手机收不到。咨询了客服还是找不到,在下班时突然收到一条推送消息。第二天我继续定位。5.2 苹果 APNs 静默推送 如果需要使用 静默推送(Remote Notifications)功能,请在推送时指定content-available:1参数。
根据文档apns推送content-available为1。
可以看到个推测试网站说iOS只支持透传消息,当然apns也可以测试,那就是证书测试。当设置content-available为1时,下面的title和body输入框变灰色,没有办法输入,但是若先输入内容再选择为1,那里面也有内容。结果我推送这样的消息,手机是收不到消息的。文档上不是说content-available:1是静默推送(apns)吗?文档和测试网站的配置为何有看似相互矛盾的说明和配置呢?我本着宁可错过不可放过的原则,把content-available选择为0,title和body输入框可以输入了,输入内容,应用在后台时终于收到消息了。但是在前台时仍旧收不到消息。然后就检查个推的接口函数,发现是拷贝的以前个推的老函数,以前在做曹操专车时用的也个推,那时候个推比较老,现在新的个推函数很多都变了。是应用在前台时,被透传函数收到了,只是我没有把透传函数名(GeTuiSdkDidReceivePayloadData)写正确,写的是老函数名(GexinSdkDidReceivePayload)。后来咨询个推客服,他们说个推现在服务端只支持iOS的透传模版。应用在前台收透传消息,在后台收apns消息。我被这个content-available要搞的脑神经分裂了,切记切记。
遇到的问题3:然后和服务器联调发现,在个推网站测试能收到开发环境证书的应用推送来的消息。收不到服务发送来推送消息,安卓手机测试时可以收到消息。于是就各种的着急。服务开发给的答复,他是按照个推文档第一个点击通知打开应用模板推送的消息。我让他试一试其它推送模版。测试发现只有第4个透传消息模版推送的消息,app能收到。
后来咨询个推客服,他说服务器向iOS普通推送时只能使用第4个透传消息模版推送的消息。
遇到的问题4:我们的领导要求像其它的应用,无论应用在前台和后台都要能收到系统状态栏的消息。
我的思路是:应用在后台消息自动出现在系统消息栏,在前台收到消息自己显示本地通知消息。具体的说是,应用在后台或应用没有启动,很简单走的是apns通道,这个你可以不需要管,若你的手机按装你的应用的情况下,消息会出现在系统消息栏。在你应用在前台时,走的是透传消息,被GeTuiSdkDidReceivePayloadData函数(早期的个推函数是GexinSdkDidReceivePayload。注意早期个推和新的个推函数的变更。这个我走了半天弯路。)接收,不走apns消息通道,走的是个推服务器和app直接的长连接通道,然后自己弹出本地日历通知。UILocalNotification *notification = [[UILocalNotification alloc] init]; if (notification != nil) { // 设置推送时间 notification.fireDate = [NSDate date]; // 设置时区 notification.timeZone = [NSTimeZone defaultTimeZone]; // 设置重复间隔 notification.repeatInterval = 0; // 推送声音 notification.soundName = UILocalNotificationDefaultSoundName; if (@available(iOS 8.2, *)) { notification.alertTitle = title; } else { // Fallback on earlier versions } // 推送内容 notification.alertBody = body; notification.userInfo = jsonObject; notification.category = payloadMsg; //显示在icon上的红色圈中的数子 notification.applicationIconBadgeNumber =0; //添加推送到UIApplication UIApplication *app = [UIApplication sharedApplication]; [app scheduleLocalNotification:notification]; }
注意:这个通知本质是一个日历事件,可以部分模拟系统通知,但不能完全代替。iOS8.2之前只能设置消息内容,不能设置消息标题。iOS8.2及以后只能设置消息内容和标题,不能设置payload等自定义键值对。透传函数收到的是推送的消息内容,title和body都看不到。详细见个推发送消息各字段在系统状态栏显示的信息,点击消息图标激活应用传递的信息,应用在线收到的透传消息信息。若消息内容含有title和body键值 json串,就可以解析出来弹出本地通知。
遇到的问题5:应用在前台,由于弹出的是本地通知(日历事件),而它最多只能携带标题和内容,无法携带自定义payload字段。所以无法实现拉掉应用,点击消息图标打开app并进入对应消息页面。
咨询个推客服,他说应用在线时只能走透传弹出本地通知。看来既然用个推就要遵循个推的规则。这个是框架问题,不是技术问题。安卓手机,无论应用是否在线都是把消息显示在系统消息栏中,点击消息,若应用不在线直接打开应用进入对应页面。看来苹果手机和安卓手机的应用也很难做到这方面的统一,不知道除个推外的第三方推送的逻辑是否和它一样。
遇到的问题6:我想在个推网站发送推送消息时,只给我的测试手机。个推提送了推送给特定用户。
我在文档文件里找cid列表格式,找了很就久,竟然没有找到。于是去骚扰度娘,她也不给里,找了半天也没有找到,接着找,终于在一个度娘的犄角旮旯里找到了。说每个cid占一行,多个cid之间用换行符号分开,文本文件。马上用文本编译器建立一个只含一个cid(从xcode的打印控制台日志里拷贝出来的)的列表,软后测试,还是推送不到。怀疑是文件格式不正确,转换成纯文本文件,再试还是不行。把cid后面加个换行符然后再测试,终于成功了。 个推cid列表文件。
遇到的问题7:iOS10及以后的推送操作新特性集成。iOS10及以后需要按照个推文档,增加一个NotificationService,Bundle Identifier要是主Target的Bundle Identifie(com.yixiang.agent)开头(com.yixiang.agent.NotificationService)。如怕有问题就按照我下图配置吧!然后按照个推文档增加对应的代码就可以。测试确实哪些新功能确实生效了。
遇到的问题8:在测试环境测试完毕,一切OK。再次转战发布环境进行测试。结果落下一地的鸡毛。首先发布环境p12证书测试一直失败。
遇到问题按照我怀疑一切的个性。我怀疑:难道通过配置测试证书的app和正式证书的app,他们的token不同吗?经过打印token,发现在使用正式证书时,产生token确实不同,个推产生的cid也不同。我输入token正确然后测试生产p12证书还是不正确。看来问题和token有关,但是还有其他问题。我怀疑时p12证书有问题,我记得,我配置测试环境p12证书时是测试过了,配置生产环境证书时,我忘记是否测试过。
登录研发者账号,发现我的生产证书处于invalid状态(当时没有截图)。why?我的证书生成还不到半月,怎么是invalid状态呢?仔细想一想,我最近只进行了一次通过xcode同步一个ios8的手机配置(定位app打开外部链接在iOS9前后走的那个- (BOOL)application函数问题,以便于实现模块化开发。苹果的自动把设备加入研发测试手机列表),可能和那次操作有关。重新生成证书,再次测试生产环境的p12证书。这次终于apns证书测试通过了。
遇到的问题9:按照个推文档,生产环境开发和测试环境需要分开测试。那么生产环境需要生用生产证书,当配置生产证书时,无法把应用直接安装到手机上,需要把ipa文件上传苹果商店,授权测试用户,用苹果的test flight软件下载测试,那样太麻烦了。那是否有更直接的方案呢?带有生产环境的证书性质,又可以安装到测试机上面呢?苹果还真有这种证书:
虽然你要生成一个hot证书,并且不能联调,只能安装测试,和正式的生产证书有所不同,但是他们的功能已经很接近了,可以部分代替生产证书进行测试。注意:hot证书,生产证书,测试证书他们对应的token,cid都不相同。
遇到的问题9:生产环境含有NotificationService的证书配置。推送测试环境,只需要把两个tartgets->General->Signing勾选 Automatically manage signing,电脑导入开发环境证书就可以测试了。然而生产环境要配置正确的证书,可没有这种傻瓜模式。只能去掉勾选Automatically manage signing。在Build Settings->Signing配置正确的证书。
如果你对Targets的NotificationService不配置证书或者配置和主Target的ArtEnjoymentWeChatAuction一样的发布证书会编译不过。经过咨询个推技术客户,他们说要对两个Bundle Identifie创建两个不同的证书。我经过紧张的证书制作。终于创建一个生产证书,一个hot证书在加上原来的测试证书,一个app共三个证书。
按照上面配置好证书终于不报错误了。可以愉快的玩耍了。
遇到的问题10:既然主Target和NotificationService都有自己的生产证书,那么我们p12证书是使用的那个呢?
咨询个推客服,他说用com.yixiang.agent.NotificationService产生的证书。
结果报一个新的错误:连接异常。
买嘎,为什么受伤的总是我。反正这个问题是非正既反的问题,反着来试一试,ok,hot证书推送消息通过。呼唤服务器总部推送生产环境消息,测试pass。
综合问题9和问题10,发现由com.yixiang.agent.NotificationService产生的证书表面看只是帮助编译通过,没有其它大用,真正个推应用配置的生产证书还是主Target的com.yixiang.agent产生的p12证书。
一个支持个推推送的应用在研发者中心需要创建的证书有5个必须的证书和两个非必须的hot证书 :生产证书,测试环境证书,NotificationService证书 产生生产环境(包括 hot 证书)的p12证书的证书,产生测试环境的p12证书的证书,hot证书(非必须),NotificationService的hot证书(非必须)。
遇到的问题11:上传苹果商店时报ERROR ITMS-90715错误。ERROR ITMS-90715: "Minimum OS too low. The Payload/ArtEnjoymentWeChatAuction.app/PlugIns/NotificationService.appex extension requires a version of iOS higher than the value specified for the MinimumOSVersion key in Info.plist." WARNING ITMS-90473: "CFBundleVersion Mismatch. The CFBundleVersion value '1.0.0' of extension 'ArtEnjoymentWeChatAuction.app/PlugIns/NotificationService.appex' does not match the CFBundleVersion value '1.0.3' of its containing iOS application 'ArtEnjoymentWeChatAuction.app'." WARNING ITMS-90473: "CFBundleShortVersionString Mismatch. The CFBundleShortVersionString value '1.0.0' of extension 'ArtEnjoymentWeChatAuction.app/PlugIns/NotificationService.appex' does not match the CFBundleShortVersionString value '1.0.3' of its containing iOS application 'ArtEnjoymentWeChatAuction.app'."
修改target的NotificationService版本号和target的ArtEnjoymentWeChatAuction版本号一致,然后打包上传就可以。报错如下:
ERROR ITMS-90715: "Minimum OS too low. The Payload/ArtEnjoymentWeChatAuction.app/PlugIns/NotificationService.appex extension requires a version of iOS higher than the value specified for the MinimumOSVersion key in Info.plist."
原来是Deployment Target版本支持的是8.0引起的。
这个是个推客服的回答。我删除Targets下的NotificationService重新打开工程打包上串苹果商店就可以了。
注意:iOS8是iPhone 6安装的初始版本,现在市场上还有大量的iPhone 6手机,有的用户从来没有升级过苹果系统。升级操作系统是有风险的,我的iPhone 5s手机升级操作系统就遇到过升级成砖头了。我估计是手机空间不足引起的,也可能是一直提示验证系统我等不及了(5分钟左右)把手机重启了。幸亏我是搞app开发的,通过iTunes还原了系统,结果我所有的数据和应用都没有了,安装了两天app。
遇到的问题12:当有应用在后台或没有启动,收到apns消息后,应用角标数字增加。无论是点击应用图标启动应用还是点击系统消息启动应用都不能清除角标。我们的应用还没有做消息列表页面,那如何清除角标呢?只有获取到 clientId(cid)后才能判断设置重置角标。我的做法是,应用启动是根据launchOptions是否为空来判断是否是点击系统消息启动应用,若非空(我们的apns消息的自定义键payload对应的值都可以解析出redirectUrl)就在获取到 clientId(cid)后重置角标。launchOptions的解析见添加链接描述《点击app系统消息打开app并进入指定页面》。- (void)GeTuiSdkDidRegisterClient:(NSString *)clientId { FLDDLogDebug(@"函数 clientId:%@", clientId); if(!isEmptyString(clientId) && (!self.clientId || ![clientId isEqualToString:self.clientId])) { NSLog(@"clientId:%@", clientId); if(self.payloadFlag && !self.noFirstGetClientIdFlag) { [GeTuiSdk resetBadge]; //重置角标计数 [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; // APP 清空角标 } self.noFirstGetClientIdFlag = YES; self.payloadFlag = NO; [self submitWithUserInfo:@{@"clientId":clientId}]; } }
注意:由于应用每次从后台切换到前台都触发GeTuiSdkDidRegisterClient函数,所以要只第一获取到 clientId(cid)后才重置角标。
来点干货,个推主代码如下,由于我们的app采用的是组件化,推送的代码在推送组件里处理,为了便于大家理解我把他移植到AppDelegate里以便于大家理解:
AppDelegate.h#import <Foundation/Foundation.h> #import <GTSDK/GeTuiSdk.h> // GetuiSdk头文件应用 // iOS10 及以上需导入 UserNotifications.framework #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 #import <UserNotifications/UserNotifications.h> #endif @interface AppDelegate : UIResponder<UIApplicationDelegate, GeTuiSdkDelegate, UNUserNotificationCenterDelegate> @end
AppDelegate.m
#import "AppDelegate.h" @interface AppDelegate ()<WXApiDelegate> @property (nonatomic, assign) BOOL isLaunch; @property (nonatomic, strong) NSString *clientId; //上传个推clientId,每次应用启动和从后台切换到前台应用都重新获取到clientId @property (nonatomic, strong) NSString *deviceToken; //上传个推deviceToken @property (nonatomic, assign) BOOL existCookieFlag; //存在cookie标志 @property (nonatomic, assign) BOOL noFirstGetClientIdFlag; //每次应用从前台切换到后台就重新获取到ClientId,处理点击系统中的消息图标启动应用第一次获取到ClientId时重置角标 @property (nonatomic, assign) BOOL payloadFlag; //标记点击系统中的消息图标启动应用,为YES时,第一次获取到ClientId时重置角标 @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { FLDDLogVerbose(@"didFinishLaunchingWithOptions"); // [EXT] 重新上线 [GeTuiSdk startSdkWithAppId:kGeXinAppId appKey:kGeXinAppKey appSecret:kGeXinAppSecret delegate:self]; // 注册 APNs [self registerRemoteNotification]; // [2-EXT]: 获取启动时收到的APN NSDictionary* message = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; FLDDLogVerbose(@"didFinishLaunchingWithOptions message:%@,[message className]:%@", message, [message className]); if (message) { // [AWSingleObject sharedInstance].redirectUrl = @"https://m.1-joy.com/market/cat/list.htm"; NSString *payload = [message objectForKey:@"payload"]; FLDDLogVerbose(@"payload:%@,[payload className]:%@", payload, [payload className]); if(payload) { self.payloadFlag = YES; NSData* jsondata = [payload dataUsingEncoding:NSUTF8StringEncoding]; FLDDLogVerbose(@"jsondata:%@,[jsondata className]:%@", jsondata, [jsondata className]); NSError *error = nil; id jsonObject = [NSJSONSerialization JSONObjectWithData:jsondata options:NSJSONReadingAllowFragments error:&error]; FLDDLogVerbose(@"jsonObject:%@,[jsonObject className]:%@,[jsonObject isKindOfClass:[NSDictionary class]]:%d, error:%@", jsonObject ,[jsonObject className], [jsonObject isKindOfClass:[NSDictionary class]], error); if(!error && jsonObject && [jsonObject isKindOfClass:[NSDictionary class]]) { NSString *redirectUrl = [jsonObject safeObjectForKey:@"redirectUrl"]; FLDDLogVerbose(@"redirectUrl:%@,[redirectUrl className]:%@", redirectUrl, [redirectUrl className]); if(redirectUrl) { [AWSingleObject sharedInstance].redirectUrl = redirectUrl; // [[NSNotificationCenter defaultCenter] postNotificationName:@"redirectLoginNotification" object:nil userInfo:@{@"redirectUrl":@"http://getui.com\\"}]; } } } // NSString *record = [NSString stringWithFormat:@"[APN]%@, %@", [NSDate date], payload]; //如何跳转页面自己添加代码 // // self.window.rootViewController = self.viewController; } self.isLaunch = YES; return YES; } /** 注册 APNs */ - (void)registerRemoteNotification { /* 警告:Xcode8 需要手动开启"TARGETS -> Capabilities -> Push Notifications" */ /* 警告:该方法需要开发者自定义,以下代码根据 APP 支持的 iOS 系统不同,代码可以对应修改。 以下为演示代码,注意根据实际需要修改,注意测试支持的 iOS 系统都能获取到 DeviceToken */ if (@available(iOS 10.0, *)) { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 // Xcode 8编译会调用 UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = self; [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionCarPlay) completionHandler:^(BOOL granted, NSError *_Nullable error) { if (!error) { NSLog(@"request authorization succeeded!"); } }]; [[UIApplication sharedApplication] registerForRemoteNotifications]; #else // Xcode 7编译会调用 UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge); UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; [[UIApplication sharedApplication] registerForRemoteNotifications]; [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; #endif } else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) { UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge); UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; [[UIApplication sharedApplication] registerForRemoteNotifications]; [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; } else { UIRemoteNotificationType apn_type = (UIRemoteNotificationType)(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeBadge); [[UIApplication sharedApplication] registerForRemoteNotificationTypes:apn_type]; } } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { NSLog(@"\nuserInfo:%@\n\n", userInfo); // 将收到的APNs信息传给个推统计 [GeTuiSdk handleRemoteNotification:userInfo]; completionHandler(UIBackgroundFetchResultNewData); } /** 远程通知注册成功委托 */ - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]; token = [token stringByReplacingOccurrencesOfString:@" " withString:@""]; FLDDLogDebug(@"\n>>>[DeviceToken Success]:%@\n\n kGeXinAppId:%@", token, kGeXinAppId); if(isEmptyString(token)) { return; } // [[NSNotificationCenter defaultCenter] postNotificationName:@"submitNotification" object:nil userInfo:@{@"deviceToken":token}]; [self submitWithUserInfo:@{@"deviceToken":token}]; // 向个推服务器注册deviceToken [GeTuiSdk registerDeviceToken:token]; NSString *clientId = [GeTuiSdk clientId]; FLDDLogDebug(@"函数 clientId:%@", clientId); if(!isEmptyString(clientId)) { NSLog(@"clientId:%@", clientId); } } #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 // iOS 10: App在前台获取到通知 - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler API_AVAILABLE(ios(10.0)){ NSLog(@"willPresentNotification:%@", notification.request.content.userInfo); // 根据APP需要,判断是否要提示用户Badge、Sound、Alert if (@available(iOS 10.0, *)) { completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert); } else { // Fallback on earlier versions } } // iOS 10: 点击通知进入App时触发,在该方法内统计有效用户点击数 - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0)){ NSLog(@"didReceiveNotification:%@", response.notification.request.content.userInfo); [GeTuiSdk resetBadge]; //重置角标计数 [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; // APP 清空角标 // [ GTSdk ]:将收到的APNs信息传给个推统计 [GeTuiSdk handleRemoteNotification:response.notification.request.content.userInfo]; completionHandler(); } #endif #pragma mark - GexinSdkDelegate - (void)GexinSdkDidOccurError:(NSError *)error { // [EXT]:个推错误报告,集成步骤发生的任何错误都在这里通知,如果集成后,无法正常收到消息,查看这里的通知。 NSLog(@">>>[GexinSdk error]:%@", [error localizedDescription]); } /** SDK收到透传消息回调 */ - (void)GeTuiSdkDidReceivePayloadData:(NSData *)payloadData andTaskId:(NSString *)taskId andMsgId:(NSString *)msgId andOffLine:(BOOL)offLine fromGtAppId:(NSString *)appId { //收到个推消息 NSString *payloadMsg = nil; if ((payloadData) && ([payloadData isKindOfClass:[NSData class]])) { payloadMsg = [[NSString alloc] initWithBytes:payloadData.bytes length:payloadData.length encoding:NSUTF8StringEncoding]; // NSData* jsondata = [payload dataUsingEncoding:NSUTF8StringEncoding]; FLDDLogVerbose(@"payloadData:%@,[payloadData className]:%@", payloadData, [payloadData className]); NSError *error = nil; id jsonObject = [NSJSONSerialization JSONObjectWithData:payloadData options:NSJSONReadingAllowFragments error:&error]; FLDDLogVerbose(@"jsonObject:%@,[jsonObject className]:%@,[jsonObject isKindOfClass:[NSDictionary class]]:%d, error:%@", jsonObject ,[jsonObject className], [jsonObject isKindOfClass:[NSDictionary class]], error); if(!error && jsonObject && [jsonObject isKindOfClass:[NSDictionary class]]) { NSString *redirectUrl = [jsonObject safeObjectForKey:@"redirectUrl"]; NSString *title = [jsonObject safeObjectForKey:@"title"]; NSString *body = [jsonObject safeObjectForKey:@"body"]; FLDDLogVerbose(@"redirectUrl:%@,[redirectUrl className]:%@", redirectUrl, [redirectUrl className]); if((title && [title isKindOfClass:[NSString class]]) || (body && [body isKindOfClass:[NSString class]])) { redirectUrl = isEmptyString(redirectUrl) ? @"" : redirectUrl; title = isEmptyString(title) ? @"" : title; body = isEmptyString(body) ? @"" : body; UILocalNotification *notification = [[UILocalNotification alloc] init]; if (notification != nil) { // 设置推送时间 notification.fireDate = [NSDate date]; // 设置时区 notification.timeZone = [NSTimeZone defaultTimeZone]; // 设置重复间隔 notification.repeatInterval = 0; // 推送声音 notification.soundName = UILocalNotificationDefaultSoundName; if (@available(iOS 8.2, *)) { notification.alertTitle = title; } else { // Fallback on earlier versions } // 推送内容 notification.alertBody = body; notification.userInfo = jsonObject; notification.category = payloadMsg; //显示在icon上的红色圈中的数子 notification.applicationIconBadgeNumber =0; //添加推送到UIApplication UIApplication *app = [UIApplication sharedApplication]; [app scheduleLocalNotification:notification]; } } } } NSString *msg = [NSString stringWithFormat:@"taskId=%@,messageId:%@,payloadMsg:%@%@",taskId,msgId, payloadMsg,offLine ? @"<离线消息>" : @""]; NSLog(@"\n>>>[GexinSdk ReceivePayload]:%@\n\n", msg); } /** * SDK登入成功返回clientId * * @param clientId 标识用户的clientId * 说明:启动GeTuiSdk后,SDK会自动向个推服务器注册SDK,当成功注册时,SDK通知应用注册成功。 * 注意: 注册成功仅表示推送通道建立,如果appid/appkey/appSecret等验证不通过,依然无法接收到推送消息,请确保验证信息正确。 */ - (void)GeTuiSdkDidRegisterClient:(NSString *)clientId { FLDDLogDebug(@"函数 clientId:%@", clientId); if(!isEmptyString(clientId) && (!self.clientId || ![clientId isEqualToString:self.clientId])) { NSLog(@"clientId:%@", clientId); // [GeTuiSdk bindAlias:@"y790715966c94d93e84182fbfce36182123456GS" andSequenceNum:@"1sssas2223446"]; if(self.payloadFlag && !self.noFirstGetClientIdFlag) { [GeTuiSdk resetBadge]; //重置角标计数 [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; // APP 清空角标 } self.noFirstGetClientIdFlag = YES; self.payloadFlag = NO; // [GeTuiSdk resetBadge]; //重置角标计数 // [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; // APP 清空角标 // [[NSNotificationCenter defaultCenter] postNotificationName:@"submitNotification" object:nil userInfo:@{@"clientId":clientId}]; [self submitWithUserInfo:@{@"clientId":clientId}]; } } /** * SDK运行状态通知 * * @param aStatus 返回SDK运行状态 */ - (void)GeTuiSDkDidNotifySdkState:(SdkStatus)aStatus; { FLDDLogDebug(@"aStatus:%d", aStatus); } /** * SDK设置关闭推送模式回调 * * @param isModeOff 关闭模式,YES.服务器关闭推送功能 NO.服务器开启推送功能 * @param error 错误回调,返回设置时的错误信息 */ - (void)GeTuiSdkDidSetPushMode:(BOOL)isModeOff error:(NSError *)error; { FLDDLogDebug(@"isModeOff:%d, error:%@", isModeOff, error); } /** * SDK绑定、解绑回调 * * @param action 回调动作类型 kGtResponseBindType 或 kGtResponseUnBindType * @param isSuccess 成功返回 YES, 失败返回 NO * @param aSn 返回请求的序列码 * @param aError 成功返回nil, 错误返回相应error信息 */ - (void)GeTuiSdkDidAliasAction:(NSString *)action result:(BOOL)isSuccess sequenceNum:(NSString *)aSn error:(NSError *)aError; { FLDDLogDebug(@"action:%@, isSuccess:%d, aSn:%@, aError:%@", action, isSuccess, aSn, aError); } -(void)submit:(NSNotification *)notification { NSDictionary* userInfo = [notification userInfo]; [self submitWithUserInfo:userInfo]; } -(void)submitWithUserInfo:(NSDictionary *)userInfo { NSLog(@"userInfo:%@", userInfo); NSString *existCookieFlag = [userInfo objectForKey:@"existCookieFlag"]; if(!isEmptyString(existCookieFlag)) { if([existCookieFlag isEqualToString:@"YES"]) { self.existCookieFlag = YES; } else { self.existCookieFlag = NO; } } NSString *deviceToken = [userInfo objectForKey:@"deviceToken"]; if(!isEmptyString(deviceToken)) { self.deviceToken = deviceToken; } NSString *clientId = [userInfo objectForKey:@"clientId"]; if(!isEmptyString(clientId)) { self.clientId = clientId; } if(isEmptyString(self.clientId ) || !(self.existCookieFlag)) { return; } NSString *urlStr = [NSString stringWithFormat:@"%@getui/dd.htm?appId=%@&type=ios", kBaseURL, kGeXinAppId]; if(!isEmptyString(self.clientId)) { urlStr = [NSString stringWithFormat:@"%@&clientId=%@", urlStr, self.clientId]; } if(!isEmptyString(self.deviceToken)) { urlStr = [NSString stringWithFormat:@"%@&deviceToke=%@", urlStr, self.deviceToken]; } urlStr = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];//编码 NSLog(@"urlStr:%@", urlStr); NSURL * url = [NSURL URLWithString:urlStr]; NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url]; NSURLSession * session = [NSURLSession sharedSession]; NSString *cookie = [self readCurrentCookieWithDomain:urlStr]; [request addValue:cookie forHTTPHeaderField:@"Cookie"]; // 发送请求 NSURLSessionTask * sessionTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { return; } NSString *mmmmmmm = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"urlStr:%@ error:%@, request.allHTTPHeaderFields:%@", urlStr, error, request.allHTTPHeaderFields); NSLog(@"mmmmmmm: %@, response:%@", mmmmmmm, response); }]; [sessionTask resume]; } - (NSString *)readCurrentCookieWithDomain:(NSString *)domainStr{ NSHTTPCookieStorage*cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage]; NSMutableString * cookieString = [[NSMutableString alloc]init]; for (NSHTTPCookie*cookie in [cookieJar cookies]) { [cookieString appendFormat:@"%@=%@;",cookie.name,cookie.value]; } //删除最后一个“;” [cookieString deleteCharactersInRange:NSMakeRange(cookieString.length - 1, 1)]; return cookieString; } - (void)applicationDidEnterBackground:(UIApplication *)application { self.isLaunch = NO; } - (void)applicationDidBecomeActive:(UIApplication *)application { if (self.isLaunch) { return; } } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end
-
个推推送步骤
2016-06-24 16:43:28第一步:导入个推SDK 将SDK资料包中“GETUI_ANDROID_SDK\资源文件”目录下的GetuiSdk-xxx.jar、so文件夹子文件复制到app模块目录下的libs文件夹中。 注:导入需要的 cpu 架构的 so 库即可 打开app/build.gradle...方法二:手动导入
第一步:导入个推SDK
将SDK资料包中“GETUI_ANDROID_SDK\资源文件”目录下的GetuiSdk-xxx.jar、so文件夹子文件复制到app模块目录下的libs文件夹中。
注:导入需要的 cpu 架构的 so 库即可
打开app/build.gradle,在dependencies中添加相关jar包的引用:
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile files('libs/GetuiSDK2.9.0.0.jar') compile 'com.android.support:support-v4' }
在app/build.gradle文件中的android{}中添加jnilib引用,代码如下:
sourceSets { main { jniLibs.srcDirs = ['libs'] } }
第二步:导入布局文件
将 “GETUI_ANDROID_SDK\资源文件\layout”下的xml布局文件复制到app模块的layout文件夹中:
注:为了支持最新的展开式样式和浮动通知,必须导入getui_notification.xml布局文件
第三步:添加服务声明
在Application标签内加入如下服务声明:
<!-- 个推SDK配置开始 --> <!-- 配置的第三方参数属性 --> <meta-data android:name="PUSH_APPID" android:value="你的APPID" /> <!-- 替换为第三方应用的APPID --> <meta-data android:name="PUSH_APPKEY" android:value="你的APPKEY" /> <!-- 替换为第三方应用的APPKEY --> <meta-data android:name="PUSH_APPSECRET" android:value="你的APPSECRET" /> <!-- 替换为第三方应用的APPSECRET --> <!-- 配置SDK核心服务 --> <service android:name="com.igexin.sdk.PushService" android:exported="true" android:label="NotificationCenter" android:process=":pushservice" > <intent-filter> <action android:name="com.igexin.sdk.action.service.message"/> </intent-filter> </service> <service android:name="com.igexin.sdk.PushServiceUser" android:exported="true" android:label="NotificationCenterUser" /> <receiver android:name="com.igexin.sdk.PushReceiver" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> <action android:name="android.intent.action.USER_PRESENT" /> <action android:name="com.igexin.sdk.action.refreshls" /> <!-- 以下三项为可选的action声明,可大大提高service存活率和消息到达速度 --> <action android:name="android.intent.action.MEDIA_MOUNTED" /> <action android:name="android.intent.action.ACTION_POWER_CONNECTED" /> <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" /> </intent-filter> </receiver> <receiver android:name="com.igexin.sdk.PushManagerReceiver" android:exported="false" > <intent-filter> <action android:name="com.igexin.sdk.action.pushmanager" /> </intent-filter> </receiver> <activity android:name="com.igexin.sdk.PushActivity" android:excludeFromRecents="true" android:exported="false" android:process=":pushservice" android:taskAffinity="com.igexin.sdk.PushActivityTask" android:theme="@android:style/Theme.Translucent.NoTitleBar" /> <activity android:name="com.igexin.sdk.GActivity" android:excludeFromRecents="true" android:exported="true" android:process=":pushservice" android:taskAffinity="com.igexin.sdk.PushActivityTask" android:theme="@android:style/Theme.Translucent.NoTitleBar" /> <service android:name="com.igexin.download.DownloadService" android:process=":pushservice" /> <receiver android:name="com.igexin.download.DownloadReceiver" > <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> </intent-filter> </receiver> <provider android:name="com.igexin.download.DownloadProvider" <!-- 把"你的包名"替换为第三方应用的包名 --> android:authorities="downloads.你的包名" android:exported="true" android:process=":pushservice" /> <!-- 个推SDK配置结束 -->
注:需要将注释部分参数替换为个推开发者平台上应用所分配到的参数
第四步:添加权限声明
在Application标签外加入个推SDK运行时需要的权限:
<!-- 解决Android L上通知显示异常问题,targetSdkVersion需要设置成22 --> <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="22" /> <!-- 个推SDK权限配置开始 --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.GET_TASKS" /> <!-- iBeancon功能与个推3.0电子围栏功能所需要的权限为非必需的可选择权限,可以选择性配置,以便使用个推3.0电子围栏功能 -->; <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <!-- 个推3.0电子围栏功能所需权限 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 浮动通知权限 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <!-- 自定义权限 --> <uses-permission android:name="getui.permission.GetuiService.你的包名" /> <!--替换为第三方应用的包名--> <permission android:name="getui.permission.GetuiService.你的包名" android:protectionLevel="normal" > </permission><!--替换为第三方应用的包名--> <!-- 个推SDK权限配置结束 -->
自定义权限解释:部分手机型号不能正常运行个推SDK,需添加自定义权限进行配置。
第五步:完整示例
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.getui.demo" android:versionCode="2" android:versionName="2.0.0"> <!-- 解决Android L上通知显示异常问题,targetSdkVersion需要设置成22 --> <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="22"/> <!-- 个推SDK权限配置开始 --> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.GET_TASKS"/> <!-- iBeancon功能所需权限 -->; <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <!-- 个推3.0电子围栏功能所需权限 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <!-- 浮动通知权限 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <!-- 自定义权限 --> <uses-permission android:name="getui.permission.GetuiService.com.getui.demo"/> <permission android:name="getui.permission.GetuiService.com.getui.demo" android:protectionLevel="normal"> </permission> <!-- 个推SDK权限配置结束 --> <application android:icon="@drawable/demo" android:label="@string/app_name" android:persistent="true"> <!-- 第三方应用配置 --> <activity android:name=".GetuiSdkDemoActivity" android:label="@string/app_name" android:launchMode="singleTop"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <!-- 在上面加入你的你的activity配置 --> <!-- 个推SDK配置开始 --> <!-- 配置的第三方参数属性 --> <meta-data android:name="PUSH_APPID" android:value="wT1tGnaFC98Kgpfoi2u7g6"/> <meta-data android:name="PUSH_APPKEY" android:value="wT1tGnaFC98Kgpfoi2u7g6"/> <meta-data android:name="PUSH_APPSECRET" android:value="my9R0U9s2Y8vLSfeToj6N5"/> <!-- 配置第三方Receiver --> <receiver android:name="com.getui.demo.PushDemoReceiver" android:exported="false"> <intent-filter> <action android:name="com.igexin.sdk.action.wT1tGnaFC98Kgpfoi2u7g6"/> </intent-filter> </receiver> <!-- 配置SDK核心服务 --> <service android:name="com.igexin.sdk.PushService" android:exported="true" android:label="NotificationCenter" android:process=":pushservice"> <intent-filter> <action android:name="com.igexin.sdk.action.service.message"/> </intent-filter> </service> <service android:name="com.igexin.sdk.PushServiceUser" android:exported="true" android:label="NotificationCenterUser"/> <receiver android:name="com.igexin.sdk.PushReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> <action android:name="android.intent.action.USER_PRESENT"/> <action android:name="com.igexin.sdk.action.refreshls"/> <!-- 以下三项为可选的action声明,可大大提高service存活率和消息到达速度 --> <action android:name="android.intent.action.MEDIA_MOUNTED"/> <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/> <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/> </intent-filter> </receiver> <receiver android:name="com.igexin.sdk.PushManagerReceiver" android:exported="false"> <intent-filter> <action android:name="com.igexin.sdk.action.pushmanager"/> </intent-filter> </receiver> <activity android:name="com.igexin.sdk.PushActivity" android:excludeFromRecents="true" android:exported="false" android:process=":pushservice" android:taskAffinity="com.igexin.sdk.PushActivityTask" android:theme="@android:style/Theme.Translucent.NoTitleBar"/> <activity android:name="com.igexin.sdk.GActivity" android:excludeFromRecents="true" android:exported="true" android:process=":pushservice" android:taskAffinity="com.igexin.sdk.PushActivityTask" android:theme="@android:style/Theme.Translucent.NoTitleBar"/> <service android:name="com.igexin.download.DownloadService" android:process=":pushservice"/> <receiver android:name="com.igexin.download.DownloadReceiver"> <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> </intent-filter> </receiver> <provider android:name="com.igexin.download.DownloadProvider" android:authorities="downloads.com.getui.demo" android:exported="true" android:process=":pushservice"/> <!-- 个推SDK配置结束 --> </application> </manifest>
第六步:配置透传
根据业务需要,在AndroidManifest.xml添加用于接收透传消息的BroadcastReceiver,第三方开发者需要自行实现该BroadcastReceiver,以便接收CID信息和服务端推送的透传消息。
<!-- 配置第三方Receiver --> <receiver <!-- 此处com.getui.demo.PushDemoReceiver,需要替换成开发者自己的BroadcastReceiver --> android:name="com.getui.demo.PushDemoReceiver" android:exported="false"> <intent-filter> <action android:name="com.igexin.sdk.action.你的APP_ID" /> </intent-filter> </receiver>
注:需要替换APP_ID参数
第七步:配置混淆
在混淆文件中加入如下配置即可:
-dontwarn com.igexin.** -keep class com.igexin.**{*;}
第四步:配置可选权限
该接入方式已包含个推服务所需必备权限,在此之外,您也可以在自己的AndroidManifest.xml中配置以下可选权限,以便使用个推3.0电子围栏功能。
<!-- iBeancon功能所需权限 -->; <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <!-- 个推3.0电子围栏功能所需权限 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
第五步:导入通知栏图标
为了修改通知栏提示图标,请在res/drawable-hdpi/、res/drawable-mdpi/、res/drawable-ldpi/等各分辨率资源目录下,放置相应尺寸的push.png图片。
该图标将会作为通知图标展示在通知栏顶部,如下所示:
第六步:初始化SDK
在您应用程序主Activity里导入PushManager类,如下所示:
import com.igexin.sdk.PushManager;
然后在您应用程序启动初始化阶段,初始化SDK:
PushManager.getInstance().initialize(this.getApplicationContext());
注:该方法必须在Activity或Service类内调用,一般情况下,可以在Activity的onCreate()方法中调用。由于应用每启动一个新的进程,就会调用一次Application的onCreate()方法,而个推SDK是一个独立的进程,因此如果在Application的onCreate()中调用intialize接口,会导致SDK初始化在一个应用中多次调用,所以不建议在Application继承类中调用个推SDK初始化接口。
建议应用程序每次启动时都调用一次该初始化接口。
第七步:资源精简配置
如果您的工程启用了资源精简,即在build.gradle中指定如下参数:
buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro' } }
需要在res/raw中添加keep.xml,明确指定个推SDK所需的layout资源文件不能被精简,keep.xml文件:
keep.xml文件内容如下:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/getui_notification"/>
如此可以完成layout资源保护工作。
第八步:确认gradle配置
确认gradle文件中的applicationId和参数中需要的包名一致,如图:
第九步:测试
在手机或模拟器上运行您的工程,查看Android Monitor信息,如图所示。在搜索框中输入“clientid”可以看到“clientid is xxx”,则意味则初始化SDK成功,并获取到相应的cid信息,恭喜你:-D,可以开始进行推送测试了。
登录 个推开发者平台,点击应用管理,进入待测试应用的推送界面:
依填写相应的通知标题、通知内容,进行通知推送,具体推送方法见:创建推送通知
如果手机或模拟器收到通知,如图所示:恭喜您,SDK接入已经成功!
-
个推使用教程
2019-04-22 16:03:33因为工作需要,甲方单位使用极光推送总是出现用户收取不到推送消息,甲方要求我们使用个推。 因为我们访问网络的时候使用的是代理,所以个推提供的接口发送接口不能使用,所以需要重新写一个支持代理的http链接。...因为工作需要,甲方单位使用极光推送总是出现用户收取不到推送消息,甲方要求我们使用个推。
因为我们访问网络的时候使用的是代理,所以个推提供的接口发送接口不能使用,所以需要重新写一个支持代理的http链接。目前项目中的个推使用流程如下:
目前使用的是群推:pushToList,群推第一步使用taskid ,taskid 就是我们的消息在推送平台对应的一个映射id,通过taskid 来找这个消息。第二部是发送消息。发送消息的流程和获取taskId的流程几乎是一致的,就是在发送的过程中,gt_action 传的参数不一样,获取taskid 时传的是getContentIdAction,而推送消息的时候使用的是pushMessageToListAction。
个推的推送过程是:
http 代理代码如下:
package com.ruim.ifsp.merser.util; import com.alibaba.dubbo.common.utils.StringUtils; import com.ruim.ifsp.log.IfspLoggerFactory; import com.ruim.ifsp.merser.cache.CommonConstants; import com.ruim.ifsp.utils.client.http.IfspHttpClientUtil; import org.slf4j.Logger; import java.io.*; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URL; /** * 个推 * @author lxw * @deprecated 个推消息推送http链接 */ public class IGtpushHttpClient extends IfspHttpClientUtil { private static Logger logger = IfspLoggerFactory.getLogger(JpushHttpClient.class); static String proxyIp = CommonConstants.getProperties("PROXY_IP"); static String proxyProt = CommonConstants.getProperties("PROXY_PORT"); // private static String appId = "TxzlIyCcfS9KuENjjP4ux1"; // private static String appKey = "rAnoicfrNX7915IxPocAL2"; // private static String masterSecret = "KFDNBNKAVj9bgykwvqgeA5"; // static String host = "http://sdk.open.api.igexin.com/apiex.htm"; private static String appId = CommonConstants.getProperties("IGTAPP_ID");//个推appid private static String appKey = CommonConstants.getProperties("IGTAPP_KEY");//应用appKey private static String masterSecret = CommonConstants.getProperties("IGTMASTER_SECRET");//应用证书号 private static String host = CommonConstants.getProperties("IGTPUST_HOST");//个推送地址 public static HttpURLConnection buildConnect(String Action) throws Exception { URL url = new URL(host); logger.info("开始初始化服务器连接..."); logger.info("服务器地址:[ " + url + " ]"); /**启用代理*/ Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyIp, Integer.valueOf(proxyProt))); HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(proxy); httpURLConnection.setConnectTimeout(30000); httpURLConnection.setReadTimeout(30000); httpURLConnection.setDoInput(true); httpURLConnection.setDoOutput(true); httpURLConnection.setUseCaches(false); httpURLConnection.setRequestProperty("Content-Type", "text/html;charset=UTF-8"); httpURLConnection.setRequestProperty("Gt-Action", Action); httpURLConnection.setRequestMethod("POST"); httpURLConnection.connect(); logger.info("服务器连接完成..."); return httpURLConnection; } public static void sendMsg(HttpURLConnection httpURLConnection,String message) throws Exception { logger.info("上送服务报文开始..."); logger.info("上送报文:[" + message + "]"); OutputStream outputStream = null; try { outputStream = httpURLConnection.getOutputStream(); // 注意编码格式,防止中文乱码 if (StringUtils.isNotEmpty(message)) { outputStream.write(message.getBytes("utf-8")); } else { outputStream.write("".getBytes()); } if (null != outputStream) outputStream.close(); logger.info("上送服务报文完成..."); } catch (Exception e) { e.printStackTrace(); throw e; } finally { if (null != outputStream) try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } public static String receiveMsg(HttpURLConnection httpURLConnection) throws IOException { logger.debug("接收服务器响应报文开始..."); InputStream in = null; StringBuilder sb = new StringBuilder(1024); BufferedReader br = null; String temp = null; int code = httpURLConnection.getResponseCode(); if (200 == code) { in = httpURLConnection.getInputStream(); br = new BufferedReader(new InputStreamReader(in, "utf-8")); while (null != (temp = br.readLine())) { sb.append(temp); } } else { in = httpURLConnection.getErrorStream(); br = new BufferedReader(new InputStreamReader(in, "utf-8")); while (null != (temp = br.readLine())) { sb.append(temp); } } String str1 = sb.toString(); logger.info("报文: [ " + str1 + " ] "); logger.info("接收服务器响应报文完成"); return str1; } }
推送消息代码:
package com.ruim.ifsp.merser.util; import com.gexin.rp.sdk.base.IPushResult; import com.gexin.rp.sdk.base.impl.*; import com.gexin.rp.sdk.base.payload.APNPayload; import com.gexin.rp.sdk.base.payload.Payload; import com.gexin.rp.sdk.base.uitls.Base64Util; import com.gexin.rp.sdk.base.uitls.LangUtil; import com.gexin.rp.sdk.base.uitls.SignUtil; import com.gexin.rp.sdk.http.IGtPush; import com.gexin.rp.sdk.http.utils.GTConfig; import com.gexin.rp.sdk.http.utils.ParamUtils; import com.gexin.rp.sdk.template.LinkTemplate; import com.gexin.rp.sdk.template.NotificationTemplate; import com.ruim.ifsp.exception.BizException; import com.ruim.ifsp.log.IfspLoggerFactory; import com.ruim.ifsp.merser.cache.CommonConstants; import com.ruim.ifsp.utils.message.IfspFastJsonUtil; import com.ruim.ifsp.utils.verify.IfspDataVerifyUtil; import org.slf4j.Logger; import java.net.HttpURLConnection; import java.time.format.TextStyle; import java.util.*; public class IGtpushThread extends Thread { private static Logger log = IfspLoggerFactory.getLogger(IGtpushThread.class); private static String appId = CommonConstants.getProperties("IGTAPP_ID");; private static String appKey = CommonConstants.getProperties("IGTAPP_KEY"); private static String masterSecret = CommonConstants.getProperties("IGTMASTER_SECRET"); private static String host = CommonConstants.getProperties("IGTPUST_HOST"); private String alias; private String message; private String msgSendType; public IGtpushThread(String alias,String message,String msgSendType) { // super(); this.alias = alias; this.message = message; this.msgSendType = msgSendType; } /** * If this thread was constructed using a separate * <code>Runnable</code> run object, then that * <code>Runnable</code> object's <code>run</code> method is called; * otherwise, this method does nothing and returns. * <p> * Subclasses of <code>Thread</code> should override this method. * * @see #start() * @see #stop() * @see #(ThreadGroup, Runnable, String) */ @Override public void run() { IGTpushMsg(alias,message,msgSendType); } public void IGTpushMsg(String alias,String message,String msgSendType){ String respMsg = new String(); String[] arr = alias.split(","); // 通知透传模板 消息传递 NotificationTemplate template = notificationTemplateDemo(message); ListMessage messages = new ListMessage(); messages.setData(template); // 设置消息离线,并设置离线时间 messages.setOffline(true); // 离线有效时间,单位为毫秒,可选 messages.setOfflineExpireTime(24 * 1000 * 3600); // 配置推送目标 List targets = new ArrayList(); // taskId用于在推送时去查找对应的message try{ String taskId = getListAppContentId(messages,null); Map<String,Object> postData = null; for (int i = 0; i < arr.length; i++) { Target target = new Target(); target.setAppId(appId); target.setAlias(arr[i]); targets.add(target); //每50个别名(CID)推送一次 if (arr.length>50&&targets.size()==50){ postData =pushMessageToList(taskId,targets); Map<String,Object> response = httpResponse(postData); if (!"ok".equals(response.get("result"))){ response = httpResponse(postData); respMsg = IfspFastJsonUtil.mapTOjson(response); log.info("个推建议值推送结果是:"+respMsg); } targets.clear(); } } if (targets.size()>0){ postData =pushMessageToList(taskId,targets); Map<String,Object> response = httpResponse(postData); if (!"ok".equals(response.get("result"))){ response = httpResponse(postData); respMsg = IfspFastJsonUtil.mapTOjson(response); log.info("个推第二次推送结果是:"+respMsg); } } }catch (Exception e){ log.error("个推推送失败,原因是:"+e.getMessage()); } } public Map<String, Object> pushMessageToList(String contentId, List<Target> targetList) throws Exception{ Map<String, Object> postData = new HashMap(); postData.put("authToken", null); postData.put("action", "pushMessageToListAction"); postData.put("appkey", appKey); postData.put("contentId", contentId); boolean needDetails = GTConfig.isPushListNeedDetails(); postData.put("needDetails", needDetails); boolean async = GTConfig.isPushListAsync(); postData.put("async", async); boolean needAliasDetails = GTConfig.isPushListNeedAliasDetails(); postData.put("needAliasDetails", needAliasDetails); int limit; if (async && !needDetails) { limit = GTConfig.getAsyncListLimit(); } else { limit = GTConfig.getSyncListLimit(); } if (targetList.size() > limit) { throw new BizException("1000","target size:" + targetList.size() + " beyond the limit:" + limit); } else { List<String> clientIdList = new ArrayList(); List<String> aliasList = new ArrayList(); String appId = null; Iterator var12 = targetList.iterator(); while(true) { Target target; do { if (!var12.hasNext()) { postData.put("appId", appId); postData.put("clientIdList", clientIdList); postData.put("aliasList", aliasList); postData.put("type", 2); return postData; } target = (Target)var12.next(); String targetCid = target.getClientId(); String targetAlias = target.getAlias(); if (targetCid != null && !"".equals(targetCid.trim())) { clientIdList.add(targetCid); } else if (targetAlias != null && !"".equals(targetAlias.trim())) { aliasList.add(targetAlias); } } while(appId != null && !"".equals(appId.trim())); appId = target.getAppId(); } } //苹果参数组装 } public NotificationTemplate notificationTemplateDemo(String message) { NotificationTemplate template = new NotificationTemplate(); // 设置APPID与APPKEY template.setAppId(appId); template.setAppkey(appKey); // 设置通知栏标题与内容 template.setTitle(""); template.setText(""); // // 配置通知栏图标 // template.setLogo("icon.png"); // 配置通知栏网络图标 // template.setLogoUrl(""); // 设置通知是否响铃,震动,或者可清除 // template.setIsRing(true); // template.setIsVibrate(true); // template.setIsClearable(true); // 透传消息设置,1为强制启动应用,客户端接收到消息后就会立即启动应用;2为等待应用启动 template.setTransmissionType(2); template.setTransmissionContent(message); //ios 消息推送 // APNPayload apnPayload = new APNPayload(); // APNPayload.SimpleAlertMsg alertMsg = new APNPayload.SimpleAlertMsg(message); // apnPayload.setAlertMsg(alertMsg); // template.setAPNInfo(apnPayload); return template; } public static void main(String[] args) throws Exception { //单推测试 // LinkTemplate template = linkTemplateDemo(); // SingleMessage message = new SingleMessage(); // message.setOffline(true); // // 离线有效时间,单位为毫秒,可选 // message.setOfflineExpireTime(24 * 3600 * 1000); // message.setData(template); // // 可选,1为wifi,0为不限制网络环境。根据手机处于的网络情况,决定是否下发 // message.setPushNetWorkType(0); // Target target = new Target(); // target.setAppId(appId); // target.setAlias("201006700000010_18611111116"); // IPushResult ret = null; // Map<String,Object> reqMap = getSingleMessagePostData(message,target,null); // String requestMsg = IfspFastJsonUtil.mapTOjson(reqMap); // long startTime=System.currentTimeMillis(); HttpURLConnection client = null; // try { // HttpURLConnection client = null; // client = IGtpushHttpClient.buildConnect(reqMap.get("action").toString()); // IGtpushHttpClient.sendMsg(client, requestMsg); // String respMsg =IGtpushHttpClient.receiveMsg(client); // client.disconnect(); // Map<String,Object> responseDate = (Map<String, Object>) IfspFastJsonUtil.jsonTOmap(respMsg); // if ("sign_error".equals(responseDate.get("result"))){ // Map<String,Object> post = new HashMap<>(); // long timeStamp = System.currentTimeMillis(); // String sign = SignUtil.getSign(appKey, masterSecret, timeStamp); // post.put("action", "connect"); // post.put("appkey", appKey); // post.put("timeStamp", timeStamp); // post.put("sign", sign); // post.put("version", GTConfig.getSDKVersion()); // client = IGtpushHttpClient.buildConnect(post.get("action").toString()); // IGtpushHttpClient.sendMsg(client, IfspFastJsonUtil.mapTOjson(post).toString()); // String resp = IGtpushHttpClient.receiveMsg(client); // Map<String,Object> respDate = (Map<String, Object>) IfspFastJsonUtil.jsonTOmap(resp); // reqMap.put("authToken",respDate.get("authtoken")); // client.disconnect(); // } // client = IGtpushHttpClient.buildConnect(reqMap.get("action").toString()); client.setRequestProperty("Gt-Action",reqMap.get("action").toString()); // String reqMsg = IfspFastJsonUtil.mapTOjson(reqMap); // IGtpushHttpClient.sendMsg(client, reqMsg); // String respMseeage =IGtpushHttpClient.receiveMsg(client); // // } catch (Exception e) { // try { client = IGtpushHttpClient.buildConnect(); IGtpushHttpClient.sendMsg(client, requestMsg); IGtpushHttpClient.receiveMsg(client); } catch (Exception e1) { // e.printStackTrace(); } // }finally { if(client != null){ client.disconnect(); } // } // long endTime=System.currentTimeMillis(); // } // 群推测试 // 通知透传模板 // NotificationTemplate template = notificationTemplateDemo(); // ListMessage message = new ListMessage(); // message.setData(template); // // 设置消息离线,并设置离线时间 // message.setOffline(true); // // 离线有效时间,单位为毫秒,可选 // message.setOfflineExpireTime(24 * 1000 * 3600); // // 配置推送目标 // List targets = new ArrayList(); // Target target1 = new Target(); // Target target2 = new Target(); // target1.setAppId(appId); // target1.setAlias("201006700000010_18611111116"); // targets.add(target1); // // taskId用于在推送时去查找对应的message // String taskId = getListAppContentId(message,null); // Map<String, Object> ret = pushMessageToList(taskId, targets); String requestMsg = IfspFastJsonUtil.mapTOjson(ret); // long startTime=System.currentTimeMillis(); // HttpURLConnection client = null; // try { // ret.put("version", GTConfig.getSDKVersion()); // ret.put("authToken",null); // String requestMsg = IfspFastJsonUtil.mapTOjson(ret); // client = IGtpushHttpClient.buildConnect(ret.get("action").toString()); // IGtpushHttpClient.sendMsg(client, requestMsg); // String response = IGtpushHttpClient.receiveMsg(client); // client.disconnect(); // Map<String,Object> responseDate = (Map<String, Object>) IfspFastJsonUtil.jsonTOmap(response); // if ("sign_error".equals(responseDate.get("result"))){ // Map<String,Object> post = new HashMap<>(); // long timeStamp = System.currentTimeMillis(); // String sign = SignUtil.getSign(appKey, masterSecret, timeStamp); // post.put("action", "connect"); // post.put("appkey", appKey); // post.put("timeStamp", timeStamp); // post.put("sign", sign); // post.put("version", GTConfig.getSDKVersion()); // client = IGtpushHttpClient.buildConnect(post.get("action").toString()); // IGtpushHttpClient.sendMsg(client, IfspFastJsonUtil.mapTOjson(post).toString()); // String resp = IGtpushHttpClient.receiveMsg(client); // Map<String,Object> respDate = (Map<String, Object>) IfspFastJsonUtil.jsonTOmap(resp); // ret.put("authToken",respDate.get("authtoken")); // client.disconnect(); // } // client = IGtpushHttpClient.buildConnect(ret.get("action").toString()); // String reqMsg = IfspFastJsonUtil.mapTOjson(ret); // IGtpushHttpClient.sendMsg(client, reqMsg); // String respMseeage =IGtpushHttpClient.receiveMsg(client); // System.out.println(respMseeage); // } catch (Exception e) { // e.printStackTrace(); // }finally { // if(client != null){ // client.disconnect(); // } // } } private String getListAppContentId(Message message, String taskGroupName) { Map<String, Object> postData = new HashMap(); Map<String, Object> response = null; if (taskGroupName != null) { postData.put("taskGroupName", taskGroupName); } postData.put("action", "getContentIdAction"); postData.put("appkey", appKey); postData.put("clientData", Base64Util.getBASE64(message.getData().getTransparent().toByteArray())); postData.put("transmissionContent", message.getData().getTransmissionContent()); postData.put("isOffline", message.isOffline()); postData.put("offlineExpireTime", message.getOfflineExpireTime()); postData.put("pushType", message.getData().getPushType()); postData.put("type", 2); if (message instanceof ListMessage) { postData.put("contentType", 1); } else if (message instanceof AppMessage) { postData.put("contentType", 2); AppMessage appMessage = (AppMessage) message; ParamUtils.checkAppid(message, appMessage.getAppIdList()); postData.put("appIdList", appMessage.getAppIdList()); List<String> phoneTypeList = null; List<String> provinceList = null; List<String> tagList = null; new ArrayList(); if (appMessage.getConditions() == null) { phoneTypeList = appMessage.getPhoneTypeList(); provinceList = appMessage.getProvinceList(); tagList = appMessage.getTagList(); postData.put("phoneTypeList", phoneTypeList); postData.put("provinceList", provinceList); postData.put("tagList", tagList); } else { List<Map<String, Object>> conditions = appMessage.getConditions().getCondition(); } postData.put("speed", appMessage.getSpeed()); String pushTime = appMessage.getPushTime(); if (pushTime != null && !pushTime.isEmpty()) { postData.put("pushTime", pushTime); } } postData.put("pushNetWorkType", message.getPushNetWorkType()); //调用个推 获取绑定的个推平台信息绑定的id response = httpResponse(postData); String result = LangUtil.parseString(response.get("result")); String contentId = LangUtil.parseString(response.get("contentId")); if ("ok".equals(result) && contentId != null) { return contentId; } else { throw new RuntimeException("host:[" + host + "]获取contentId失败:" + result); } } /** * http 调用方法 * @param postData * @return */ public Map<String,Object> httpResponse(Map<String,Object> postData){ HttpURLConnection client = null; String response = null; try { postData.put("version", GTConfig.getSDKVersion()); postData.put("authToken",null); client = IGtpushHttpClient.buildConnect(postData.get("action").toString()); IGtpushHttpClient.sendMsg(client, IfspFastJsonUtil.mapTOjson(postData).toString()); response =IGtpushHttpClient.receiveMsg(client); client.disconnect(); Map<String,Object> respData =(Map<String,Object>)IfspFastJsonUtil.jsonTOmap(response); if ("ok".equals(respData.get("result"))){ return respData; } if("sign_error".equals(respData.get("result"))){ long timeStamp = System.currentTimeMillis(); String sign = SignUtil.getSign(appKey, masterSecret, timeStamp); Map<String, Object> postData1 = new HashMap(); postData1.put("action", "connect"); postData1.put("appkey", appKey); postData1.put("timeStamp", timeStamp); postData1.put("sign", sign); postData1.put("version", GTConfig.getSDKVersion()); client = IGtpushHttpClient.buildConnect(postData1.get("action").toString()); IGtpushHttpClient.sendMsg(client, IfspFastJsonUtil.mapTOjson(postData1).toString()); response =IGtpushHttpClient.receiveMsg(client); client.disconnect(); Map<String,Object> respdata =(Map<String,Object>)IfspFastJsonUtil.jsonTOmap(response); //放入authToken 值 postData.put("authToken",respdata.get("authtoken")); client = IGtpushHttpClient.buildConnect(postData.get("action").toString()); IGtpushHttpClient.sendMsg(client, IfspFastJsonUtil.mapTOjson(postData).toString()); response =IGtpushHttpClient.receiveMsg(client); client.disconnect(); respData =(Map<String,Object>)IfspFastJsonUtil.jsonTOmap(response); if ("ok".equals(respData.get("result"))){ return respData; } } } catch (Exception e) { e.printStackTrace(); } return null; } }
运行时需要引入个推的jar包,maven 引入时需要先将jar包上传到服务器中,然后在maven配置文件中。我是用的版本jra包是:
至于详细的文档可以参考官方提供的文档,地址:http://docs.getui.com/getui/server/java/start/
使用个推的时候如果不使用代理的话可以直接使用下载的文件中test工程中的代码,集成jra包后,可以参考单推,群推,直接使用。如果使用代理的话就需要重新写http链接。
个推的单推接口调用没有频次的限制,群推pushToList 接口调用是有频次限制的,就是每天调用200万次,每次发给用户数量建议50,pushToapp 每天不超过100次,每分钟不能超过5次。
使用官方提供的接口时:
每次初始化IGtPush这个类的时候都会去个推平台查询那些可用的推送地址。
然后推送消息的时候,步骤就如我画的流程图一样了,只不过单推pushTosigle 时没有获取TaskId这一步。
---------------二更---------------------------
由于我是使用的服务端,不区分安卓和ios所以使用的消息模板NotificationTemplate(通知)不能够满足要求(这个模板如果添加了iOS带码的话,就不能推送安卓了)所以要将消息模板换成TransmissionTemplate(透传)这个模板,然后在模板中设置三方通知。具体的代码如下:
package com.ruim.ifsp.merser.util; import com.gexin.rp.sdk.base.impl.*; import com.gexin.rp.sdk.base.notify.Notify; import com.gexin.rp.sdk.base.payload.APNPayload; import com.gexin.rp.sdk.base.uitls.Base64Util; import com.gexin.rp.sdk.base.uitls.LangUtil; import com.gexin.rp.sdk.base.uitls.SignUtil; import com.gexin.rp.sdk.dto.GtReq; import com.gexin.rp.sdk.http.utils.GTConfig; import com.gexin.rp.sdk.http.utils.ParamUtils; import com.gexin.rp.sdk.template.TransmissionTemplate; import com.ruim.ifsp.exception.BizException; import com.ruim.ifsp.log.IfspLoggerFactory; import com.ruim.ifsp.merser.cache.CommonConstants; import com.ruim.ifsp.utils.message.IfspFastJsonUtil; import org.slf4j.Logger; import java.net.HttpURLConnection; import java.util.*; public class IGtpushThread extends Thread { private static Logger log = IfspLoggerFactory.getLogger(IGtpushThread.class); private static String appId = CommonConstants.getProperties("IGTAPP_ID"); private static String appKey = CommonConstants.getProperties("IGTAPP_KEY"); private static String masterSecret = CommonConstants.getProperties("IGTMASTER_SECRET"); private static String host = CommonConstants.getProperties("IGTPUST_HOST"); private String alias; private String message; private String msgSendType; public IGtpushThread(String alias,String message,String msgSendType) { this.alias = alias; this.message = message; this.msgSendType = msgSendType; } /** * If this thread was constructed using a separate * <code>Runnable</code> run object, then that * <code>Runnable</code> object's <code>run</code> method is called; * otherwise, this method does nothing and returns. * <p> * Subclasses of <code>Thread</code> should override this method. * * @see #start() * @see #stop() * @see #(ThreadGroup, Runnable, String) */ @Override public void run() { IGTpushMsg(alias,message,msgSendType); } public void IGTpushMsg(String alias,String message,String msgSendType){ String respMsg = new String(); String[] arr = alias.split(","); // 通知透传模板 消息传递 TransmissionTemplate template = notificationTemplateDemo(message); ListMessage messages = new ListMessage(); messages.setData(template); // 设置消息离线,并设置离线时间 messages.setOffline(true); // 离线有效时间,单位为毫秒,可选 messages.setOfflineExpireTime(24 * 1000 * 3600); // 配置推送目标 List targets = new ArrayList(); // taskId用于在推送时去查找对应的message try{ String taskId = getListAppContentId(messages,null); Map<String,Object> postData = null; for (int i = 0; i < arr.length; i++) { Target target = new Target(); target.setAppId(appId); target.setAlias(arr[i]); targets.add(target); //每50个别名(CID)推送一次 if (arr.length>50&&targets.size()==50){ postData =pushMessageToList(taskId,targets); Map<String,Object> response = httpResponse(postData); if (!"ok".equals(response.get("result"))){ response = httpResponse(postData); respMsg = IfspFastJsonUtil.mapTOjson(response); log.info("个推建议值推送结果是:"+respMsg); } targets.clear(); } } if (targets.size()>0){ postData =pushMessageToList(taskId,targets); Map<String,Object> response = httpResponse(postData); if (!"ok".equals(response.get("result"))){ response = httpResponse(postData); respMsg = IfspFastJsonUtil.mapTOjson(response); log.info("个推第二次推送结果是:"+respMsg); } } }catch (Exception e){ log.error("个推推送失败,原因是:"+e.getMessage()); } } public Map<String, Object> pushMessageToList(String contentId, List<Target> targetList) throws Exception{ Map<String, Object> postData = new HashMap(); postData.put("authToken", null); postData.put("action", "pushMessageToListAction"); postData.put("appkey", appKey); postData.put("contentId", contentId); boolean needDetails = GTConfig.isPushListNeedDetails(); postData.put("needDetails", needDetails); boolean async = GTConfig.isPushListAsync(); postData.put("async", async); boolean needAliasDetails = GTConfig.isPushListNeedAliasDetails(); postData.put("needAliasDetails", needAliasDetails); int limit; if (async && !needDetails) { limit = GTConfig.getAsyncListLimit(); } else { limit = GTConfig.getSyncListLimit(); } if (targetList.size() > limit) { throw new BizException("1000","target size:" + targetList.size() + " beyond the limit:" + limit); } else { List<String> clientIdList = new ArrayList(); List<String> aliasList = new ArrayList(); String appId = null; Iterator var12 = targetList.iterator(); while(true) { Target target; do { if (!var12.hasNext()) { postData.put("appId", appId); postData.put("clientIdList", clientIdList); postData.put("aliasList", aliasList); postData.put("type", 2); return postData; } target = (Target)var12.next(); String targetCid = target.getClientId(); String targetAlias = target.getAlias(); if (targetCid != null && !"".equals(targetCid.trim())) { clientIdList.add(targetCid); } else if (targetAlias != null && !"".equals(targetAlias.trim())) { aliasList.add(targetAlias); } } while(appId != null && !"".equals(appId.trim())); appId = target.getAppId(); } } } public TransmissionTemplate notificationTemplateDemo(String message) { // NotificationTemplate template = new NotificationTemplate(); // // 设置APPID与APPKEY // template.setAppId(appId); // template.setAppkey(appKey); // // 设置通知栏标题与内容 // template.setTitle("黔农E付收款通知"); // template.setText(message); // // 配置通知栏图标 // template.setLogo("icon.png"); // 配置通知栏网络图标 // template.setLogoUrl(""); // 设置通知是否响铃,震动,或者可清除 // template.setIsRing(true); // template.setIsVibrate(true); // template.setIsClearable(true); // 透传消息设置,1为强制启动应用,客户端接收到消息后就会立即启动应用;2为等待应用启动 // template.setTransmissionType(2); // template.setTransmissionContent(message); //ios 消息推送 // APNPayload apnPayload = new APNPayload(); // APNPayload.SimpleAlertMsg alertMsg = new APNPayload.SimpleAlertMsg(message); // apnPayload.setAlertMsg(alertMsg); // template.setAPNInfo(apnPayload); // return template; TransmissionTemplate template = new TransmissionTemplate(); template.setAppId(appId); template.setAppkey(appKey); //设置透传消息 template.setTransmissionContent(message); template.setTransmissionType(2); //设置三方通知 Notify no = new Notify(); no.setTitle("黔农E付收款通知!"); no.setContent(message); no.setIntent(""); //客户端生成提供给服务端 no.setType(GtReq.NotifyInfo.Type._intent); template.set3rdNotifyInfo(no); //设置ios推送消息 APNPayload payload = new APNPayload(); payload.setBadge(1); payload.setContentAvailable(1); payload.setSound("default"); payload.setCategory("$由客户端定义"); //简单模式APNPayload.SimpleMsg payload.setAlertMsg(new APNPayload.SimpleAlertMsg(message)); //字典模式使用下者 //payload.setAlertMsg(getDictionaryAlertMsg()); template.setAPNInfo(payload); return template; } private String getListAppContentId(Message message, String taskGroupName) { Map<String, Object> postData = new HashMap(); Map<String, Object> response = null; if (taskGroupName != null) { postData.put("taskGroupName", taskGroupName); } postData.put("action", "getContentIdAction"); postData.put("appkey", appKey); postData.put("clientData", Base64Util.getBASE64(message.getData().getTransparent().toByteArray())); postData.put("transmissionContent", message.getData().getTransmissionContent()); postData.put("isOffline", message.isOffline()); postData.put("offlineExpireTime", message.getOfflineExpireTime()); postData.put("pushType", message.getData().getPushType()); postData.put("type", 2); if (message instanceof ListMessage) { postData.put("contentType", 1); } else if (message instanceof AppMessage) { postData.put("contentType", 2); AppMessage appMessage = (AppMessage) message; ParamUtils.checkAppid(message, appMessage.getAppIdList()); postData.put("appIdList", appMessage.getAppIdList()); List<String> phoneTypeList = null; List<String> provinceList = null; List<String> tagList = null; new ArrayList(); if (appMessage.getConditions() == null) { phoneTypeList = appMessage.getPhoneTypeList(); provinceList = appMessage.getProvinceList(); tagList = appMessage.getTagList(); postData.put("phoneTypeList", phoneTypeList); postData.put("provinceList", provinceList); postData.put("tagList", tagList); } else { List<Map<String, Object>> conditions = appMessage.getConditions().getCondition(); } postData.put("speed", appMessage.getSpeed()); String pushTime = appMessage.getPushTime(); if (pushTime != null && !pushTime.isEmpty()) { postData.put("pushTime", pushTime); } } postData.put("pushNetWorkType", message.getPushNetWorkType()); //调用个推 获取绑定的个推平台信息绑定的id response = httpResponse(postData); String result = LangUtil.parseString(response.get("result")); String contentId = LangUtil.parseString(response.get("contentId")); if ("ok".equals(result) && contentId != null) { return contentId; } else { throw new RuntimeException("host:[" + host + "]获取contentId失败:" + result); } } /** * http 调用方法 * @param postData * @return */ public Map<String,Object> httpResponse(Map<String,Object> postData){ HttpURLConnection client = null; String response = null; try { postData.put("version", GTConfig.getSDKVersion()); postData.put("authToken",null); client = IGtpushHttpClient.buildConnect(postData.get("action").toString()); IGtpushHttpClient.sendMsg(client, IfspFastJsonUtil.mapTOjson(postData).toString()); response =IGtpushHttpClient.receiveMsg(client); client.disconnect(); Map<String,Object> respData =(Map<String,Object>)IfspFastJsonUtil.jsonTOmap(response); if ("ok".equals(respData.get("result"))){ return respData; } if("sign_error".equals(respData.get("result"))){ long timeStamp = System.currentTimeMillis(); String sign = SignUtil.getSign(appKey, masterSecret, timeStamp); Map<String, Object> postData1 = new HashMap(); postData1.put("action", "connect"); postData1.put("appkey", appKey); postData1.put("timeStamp", timeStamp); postData1.put("sign", sign); postData1.put("version", GTConfig.getSDKVersion()); client = IGtpushHttpClient.buildConnect(postData1.get("action").toString()); IGtpushHttpClient.sendMsg(client, IfspFastJsonUtil.mapTOjson(postData1).toString()); response =IGtpushHttpClient.receiveMsg(client); client.disconnect(); Map<String,Object> respdata =(Map<String,Object>)IfspFastJsonUtil.jsonTOmap(response); //放入authToken 值 postData.put("authToken",respdata.get("authtoken")); client = IGtpushHttpClient.buildConnect(postData.get("action").toString()); IGtpushHttpClient.sendMsg(client, IfspFastJsonUtil.mapTOjson(postData).toString()); response =IGtpushHttpClient.receiveMsg(client); client.disconnect(); respData =(Map<String,Object>)IfspFastJsonUtil.jsonTOmap(response); if ("ok".equals(respData.get("result"))){ return respData; } } } catch (Exception e) { e.printStackTrace(); } return null; } //苹果消息弹出框 private static APNPayload.DictionaryAlertMsg getDictionaryAlertMsg(){ APNPayload.DictionaryAlertMsg alertMsg = new APNPayload.DictionaryAlertMsg(); alertMsg.setBody("body"); alertMsg.setActionLocKey("ActionLockey"); alertMsg.setLocKey("LocKey"); alertMsg.addLocArg("loc-args"); alertMsg.setLaunchImage("launch-image"); // IOS8.2以上版本支持 alertMsg.setTitle("Title"); alertMsg.setTitleLocKey("TitleLocKey"); alertMsg.addTitleLocArg("TitleLocArg"); return alertMsg; } }
透传模板集成到通知中官方提供文档:
1. 概述
解决接入第三方推送遇到的兼容问题,降低接入成本。
2. 功能名字
2.1应用场景
为方便接入了第三方厂商推送的客户,更加简便的使用个推透传模板推送,特别对透传模板进行扩展,使透传模板支持以第三方厂商的通知方式进行下发。目前第三方厂商的通知到达率显著高于第三方透传的到达率。
3. 使用方式
3.1使用说明
使用透传模板时,设置notify对象的title, content, intent等参数。
Notify的说明
成员和方法名
类型
必填
说明
setTitle
String
是
通知栏标题
setContent
String
是
通知栏内容
setUrl
String
否
点击通知,打开应用的链接
setIntent
String
否
长度小于1000字节,通知带intent传递参数推荐使用,intent最好由Android开发工程师生成,生成方式见附录
setType
Type
否
取值为(Type._url、Type._intent),如果设置了url、intent,需要指定Type对应的类型;Type所在的包为:com.gexin.rp.sdk.dto.GtReq.NotifyInfo
public static TransmissionTemplate transmissionTemplateDemo(String appId, String appKey){
TransmissionTemplate template = new TransmissionTemplate();
template.setAppId(appId);
template.setAppkey(appKey);
template.setTransmissionContent("透传内容");
template.setTransmissionType(2);
Notify notify = new Notify();
notify.setTitle("请输入通知栏标题");
notify.setContent("请输入通知栏内容");
notify.setIntent("intent:#Intent;launchFlags=0x10000000;package=com.pp.yl;component=你的包名
/com.getui.demo.MainActivity;i.parm1=12;end");
notify.setType(Type._intent);
// notify.setUrl("https://dev.getui.com/");
//notify.setType(Type._url);
template.set3rdNotifyInfo(notify);//设置第三方通知
return template;
}
4. 功能代码示例
5. 附录:Andriod开发工程师生成intent参考
Andriod开发工程师生成intent,可以在Android代码中定义一个Intent,然后通过Intent.toUri(Intent.URI_INTENT_SCHEME)获取对应的Intent Uri。
Intent intent = new Intent();
5.1生成Intent
5.2设置 package(可选)
如果设置了package,则表示目标应用必须是package所对应的应用。
intent.setPackage("com.getui.demo");
5.3设置 component (必选)
component为目标页面的准确activity路径,component中包含应用包名和activity类的全路径。
intent.setComponent(new ComponentName("com.getui.demo", "com.getui.demo.TestActivity"));
5.4设置 launchFlags (不起作用)
launcherFlags 可指定在启动activity时的启动方式,个推sdk以及厂商sdk在启动activity时会自动设置为FLAG_ACTIVITY_NEW_TASK,所以该设置没有实际意义。
5.5设置参数
intent.putExtra("stringType", "string");
intent.putExtra("booleanType", true);
intent.putExtra("doubleType", 1.0);
intent.putExtra("floatType", 1.0f);
intent.putExtra("longType", 1L);
intent.putExtra("intType", 1);
// 还有其他类型的参数可以设置,通过查找`putExtra`方法的重载方法即可
可通过intent.putExtra方法设置一系列的参数。
5.6设置action
//设置action
intent.setAction("android.intent.action.oppopush");
由于oppo sdk仅支持通过action的方式打开activity,所以若您的应用需要支持oppo推送(打开指定页面),您需要在intent中设置action,并在AndroidManifet.xml配置对应的action,如下:
<!-- android:exported="true" 必须配置,否则在华为机型上无法启动-->
<activity android:name="com.getui.demo.TestActivity"
android:exported="true">
<intent-filter>
<!-- category 为必须设置项 设置为 android.intent.category.DEFAULT 即可-->
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.oppopush" />
</intent-filter>
</activity>
AndroidManifet.xml配置如下:
5.7生成 intent uri
Log.d(TAG, intent.toUri(Intent.URI_INTENT_SCHEME));
打印结果为:intent:#Intent;action=android.intent.action.oppopush;package=com.getui.demo;component=com.getui.demo/com.getui.demo.TestActivity;f.floatType=1.0;l.longType=1;B.booleanType=true;S.stringType=string;d.doubleType=1.0;i.intType=1;end
将生成的intent uri交给服务端开发人员,在对应模板的setIntent方法中设置即可。
=====================三更====================================
使用jmeter测试,发现每次访问个推平台消耗时间大概是300毫秒左右,发送一次推送如果走完所有的流程,需要大概2500毫秒,很耗时间。而且,目前在生产上有用户反馈收不到推送信息,经过排查得知当日的推送消息次数超过了200万次,而多推pushListMessage有这个次数限制。所以需要优化这个推送流程。经过询问个推的开发人员得知,在交互的过程中获取鉴权码,鉴权码的有效期是24个小时,另外接口pushSingleMessage推送是没有次数限制。本次优化的流程思路就是将鉴权码保存到redis中,并设置有效期,推送接口使用单推+多推相结合的方式行优化。目前从测试结果来开个推使用还是有些问题。问题一:个推使用多推时离线发送(只有ios,所谓离线就是退出app页面,个推离线推送的时候会将消息推送给厂商,厂商将消息推送到设备,这个时间不可控,测试环境中,有时间延时,个推厂商给解释说开发环境苹果厂商推送不稳定)会有1~2分钟的时延。问题二:个推使用单推的时候,ios手机推出app,收不到推送消息,个推开发人员解释是开发环境苹果厂商aoip 推送不稳。鉴于这两种给推送结果,目前占时开发出两套优化流程 1. 单推+多推(生产上测试,测试没有问题,优先使用这个),2.多推 (1不行才采用这个方)
优化后代码流程:
1.单推:
2.多推:
优化后代码:
/** *@author lxw *@date 2019/12/20 *@desc 安卓群推 *@param alias, message, msgSendType *@return void **/ public void IGTandroidPushMsg(String alias,String message){ log.info("安卓用户{}开始推送日志.....",alias); String respMsg = new String(); String[] arr = alias.split(","); // 通知透传模板 消息传递 NotificationTemplate nTemplate = notificationTemplateDemo(message); ListMessage messages = new ListMessage(); messages.setData(nTemplate); // 设置消息离线,并设置离线时间 messages.setOffline(true); // 离线有效时间,单位为毫秒,可选 messages.setOfflineExpireTime(24 * 1000 * 3600); // 配置推送目标 List targets = new ArrayList(); // taskId用于在推送时去查找对应的message try{ String taskId = getListAppContentId(messages,null); Map<String,Object> postData = null; for (int i = 0; i < arr.length; i++) { Target target = new Target(); target.setAppId(appId); target.setAlias(arr[i]); targets.add(target); //每50个别名(CID)推送一次 if (arr.length>50&&targets.size()==50){ postData =pushMessageToList(taskId,targets); Map<String,Object> response = httpResponse(postData); if (!IGParams.PUSH_MSG_OK.equals(response.get("result"))){ response = httpResponse(postData); respMsg = IfspFastJsonUtil.mapTOjson(response); } targets.clear(); } } if (targets.size()>0) { postData = pushMessageToList(taskId, targets); Map<String, Object> response = httpResponse(postData); if (IGParams.PUSH_NO_TARGET.equals(response.get("result"))||IGParams.PUSH_MSG_OK.equals(response.get("result"))) { log.info("用户{}个推推送结果是:" + response.get("result"),alias); }else { response = httpResponse(postData); respMsg = IfspFastJsonUtil.mapTOjson(response); log.info("用户{}个推第二次推送结果是:" + respMsg,alias); } } }catch (Exception e){ log.error("安卓用户{}推送失败,原因是:"+e.getMessage(),alias); } } /** *@author lxw *@date 2019/12/20 *@desc 苹果群推 *@param alias, message, msgSendType *@return void **/ public void IGTIosPushMsg(String alias,String message){ log.info("ios用户{}开始推送.....",alias); String respMsg = new String(); String[] arr = alias.split(","); //透传模板 消息传递 TransmissionTemplate tTemplate = transmissionTemplateDemo(message); ListMessage messages = new ListMessage(); messages.setData(tTemplate); // 设置消息离线,并设置离线时间 messages.setOffline(true); // 离线有效时间,单位为毫秒,可选 messages.setOfflineExpireTime(24 * 1000 * 3600); // 配置推送目标 List targets = new ArrayList(); // taskId用于在推送时去查找对应的message try{ String taskId = getListAppContentId(messages,null); Map<String,Object> postData = null; for (int i = 0; i < arr.length; i++) { Target target = new Target(); target.setAppId(appId); target.setAlias(arr[i]); targets.add(target); //每50个别名(CID)推送一次 if (arr.length>50&&targets.size()==50){ postData =pushMessageToList(taskId,targets); Map<String,Object> response = httpResponse(postData); if (!IGParams.PUSH_MSG_OK.equals(response.get("result"))){ response = httpResponse(postData); respMsg = IfspFastJsonUtil.mapTOjson(response); } targets.clear(); } } if (targets.size()>0){ postData =pushMessageToList(taskId,targets); Map<String,Object> response = httpResponse(postData); if (IGParams.PUSH_NO_TARGET.equals(response.get("result"))||IGParams.PUSH_MSG_OK.equals(response.get("result"))) { log.info("用户{}个推推送结果是:" + response.get("result"),alias); }else { response = httpResponse(postData); respMsg = IfspFastJsonUtil.mapTOjson(response); log.info("用户{}再次推送结果是:" + response.get("result"),alias); } } }catch (Exception e){ log.error("ios用户{}推送失败,原因是:"+e.getMessage(),alias); } } /** *@author lxw *@date 2019/12/20 *@desc 群推消息参数组装 *@param contentId, targetList *@return java.util.Map<java.lang.String,java.lang.Object> **/ public Map<String, Object> pushMessageToList(String contentId, List<Target> targetList) throws Exception{ log.info("pushMessageToList组装推送参数开始,用户taskid:{}。。。。",contentId); Map<String, Object> postData = new HashMap(); postData.put("authToken", getAuthTokenCache().opsForValue().get("authToken")==null?"":getAuthTokenCache().opsForValue().get("authToken")); postData.put("action", "pushMessageToListAction"); postData.put("appkey", appKey); postData.put("contentId", contentId); boolean needDetails = GTConfig.isPushListNeedDetails(); postData.put("needDetails", needDetails); boolean async = GTConfig.isPushListAsync(); postData.put("async", async); boolean needAliasDetails = GTConfig.isPushListNeedAliasDetails(); postData.put("needAliasDetails", needAliasDetails); int limit; if (async && !needDetails) { limit = GTConfig.getAsyncListLimit(); } else { limit = GTConfig.getSyncListLimit(); } if (targetList.size() > limit) { throw new BizException("1000","target size:" + targetList.size() + " beyond the limit:" + limit); } else { List<String> clientIdList = new ArrayList(); List<String> aliasList = new ArrayList(); String appId = null; Iterator var12 = targetList.iterator(); while(true) { Target target; do { if (!var12.hasNext()) { postData.put("appId", appId); postData.put("clientIdList", clientIdList); postData.put("aliasList", aliasList); postData.put("type", 2); return postData; } target = (Target)var12.next(); String targetCid = target.getClientId(); String targetAlias = target.getAlias(); if (targetCid != null && !"".equals(targetCid.trim())) { clientIdList.add(targetCid); } else if (targetAlias != null && !"".equals(targetAlias.trim())) { aliasList.add(targetAlias); } } while(appId != null && !"".equals(appId.trim())); appId = target.getAppId(); } } } //通知消息 public NotificationTemplate notificationTemplateDemo(String message) { NotificationTemplate template = new NotificationTemplate(); // 设置APPID与APPKEY template.setAppId(appId); template.setAppkey(appKey); Style0 style = new Style0(); style.setTitle("黔农E付收款通知"); // 设置通知栏标题与内容 style.setText(message); // 配置通知栏网络图标 style.setLogo("icon.png"); // 设置通知是否响铃,震动,或者可清除 style.setRing(true); style.setVibrate(true); style.setClearable(true); // 透传消息设置,1为强制启动应用,客户端接收到消息后就会立即启动应用;2为等待应用启动 template.setTransmissionType(1); template.setTransmissionContent(message); template.setStyle(style); return template; } //透传消息 public TransmissionTemplate transmissionTemplateDemo(String message) { Map<String,String> msgMap = new HashMap<>(); msgMap.put("黔农商户宝",message); TransmissionTemplate template = new TransmissionTemplate(); template.setAppId(appId); template.setAppkey(appKey); //透传消息设置 template.setTransmissionContent(IfspFastJsonUtil.mapTOjson(msgMap)); template.setTransmissionType(2); //ios 消息推送 APNPayload payload = new APNPayload(); payload.setAutoBadge("1"); payload.setContentAvailable(0); payload.setVoicePlayType(1); payload.setSound("default"); payload.setCategory("$由客户端定义"); VoIPPayload voIPPayload = new VoIPPayload(); Map<String,Object> vp = new HashMap<>(); vp.put("titile","黔农E付收款通知"); vp.put("message",message); vp.put("payload","payload"); voIPPayload.setVoIPPayload(IfspFastJsonUtil.mapTOjson(vp)); template.setAPNInfo(voIPPayload); return template; } //获取群推的taskid private String getListAppContentId(Message message, String taskGroupName) { Map<String, Object> postData = new HashMap(); Map<String, Object> response = null; if (taskGroupName != null) { postData.put("taskGroupName", taskGroupName); } postData.put("action", "getContentIdAction"); postData.put("appkey", appKey); postData.put("clientData", Base64Util.getBASE64(message.getData().getTransparent().toByteArray())); postData.put("transmissionContent", message.getData().getTransmissionContent()); postData.put("isOffline", message.isOffline()); postData.put("offlineExpireTime", message.getOfflineExpireTime()); postData.put("pushType", message.getData().getPushType()); postData.put("type", 2); if (message instanceof ListMessage) { postData.put("contentType", 1); } else if (message instanceof AppMessage) { postData.put("contentType", 2); AppMessage appMessage = (AppMessage) message; ParamUtils.checkAppid(message, appMessage.getAppIdList()); postData.put("appIdList", appMessage.getAppIdList()); List<String> phoneTypeList = null; List<String> provinceList = null; List<String> tagList = null; new ArrayList(); if (appMessage.getConditions() == null) { phoneTypeList = appMessage.getPhoneTypeList(); provinceList = appMessage.getProvinceList(); tagList = appMessage.getTagList(); postData.put("phoneTypeList", phoneTypeList); postData.put("provinceList", provinceList); postData.put("tagList", tagList); } else { List<Map<String, Object>> conditions = appMessage.getConditions().getCondition(); } postData.put("speed", appMessage.getSpeed()); String pushTime = appMessage.getPushTime(); if (pushTime != null && !pushTime.isEmpty()) { postData.put("pushTime", pushTime); } } postData.put("pushNetWorkType", message.getPushNetWorkType()); //调用个推 获取绑定的个推平台信息绑定的id response = httpResponse(postData); String result = LangUtil.parseString(response.get("result")); String contentId = LangUtil.parseString(response.get("contentId")); if (IGParams.PUSH_MSG_OK.equals(result) && contentId != null) { return contentId; } else { throw new RuntimeException("host:[" + host + "]获取contentId失败:" + result); } } /** * http 调用方法 * @param postData * @return */ public Map<String,Object> httpResponse(Map<String,Object> postData){ log.info("调用平台传参:"+IfspFastJsonUtil.mapTOjson(postData)); HttpURLConnection client = null; String response = null; Map<String,Object> respData = new HashMap<>(); try { postData.put("version", GTConfig.getSDKVersion()); postData.put("authToken",getAuthTokenCache().opsForValue().get("authToken")==null?"":getAuthTokenCache().opsForValue().get("authToken")); client = IGtpushHttpClient.buildConnect(postData.get("action").toString()); IGtpushHttpClient.sendMsg(client, IfspFastJsonUtil.mapTOjson(postData).toString()); response =IGtpushHttpClient.receiveMsg(client); client.disconnect(); respData =(Map<String,Object>)IfspFastJsonUtil.jsonTOmap(response); if(IGParams.PUSH_SIGN_ERROR.equals(respData.get("result"))){ long timeStamp = System.currentTimeMillis(); String sign = SignUtil.getSign(appKey, masterSecret, timeStamp); Map<String, Object> postData1 = new HashMap(); postData1.put("action", "connect"); postData1.put("appkey", appKey); postData1.put("timeStamp", timeStamp); postData1.put("sign", sign); postData1.put("version", GTConfig.getSDKVersion()); client = IGtpushHttpClient.buildConnect(postData1.get("action").toString()); IGtpushHttpClient.sendMsg(client, IfspFastJsonUtil.mapTOjson(postData1).toString()); response =IGtpushHttpClient.receiveMsg(client); client.disconnect(); Map<String,Object> respdata =(Map<String,Object>)IfspFastJsonUtil.jsonTOmap(response); //放入authToken 值 postData.put("authToken",respdata.get("authtoken")); //将authToken 放入redis 并设置超时时间 getAuthTokenCache().opsForValue().set("authToken",(String)respdata.get("authtoken"),TimeUnit.HOURS.toHours(24),TimeUnit.HOURS); client = IGtpushHttpClient.buildConnect(postData.get("action").toString()); IGtpushHttpClient.sendMsg(client, IfspFastJsonUtil.mapTOjson(postData).toString()); response =IGtpushHttpClient.receiveMsg(client); client.disconnect(); respData =(Map<String,Object>)IfspFastJsonUtil.jsonTOmap(response); return respData; } return respData; } catch (Exception e) { log.error("个推接收响应信息失败,原因是:"+e.getMessage()); } return respData; } //苹果消息弹出框 private static APNPayload.DictionaryAlertMsg getDictionaryAlertMsg(String message){ APNPayload.DictionaryAlertMsg alertMsg = new APNPayload.DictionaryAlertMsg(); alertMsg.setBody(message); alertMsg.setTitle("黔农E付收款通知"); return alertMsg; } /** *@author lxw *@date 2019/12/20 *@desc 单推消息参数组装 *@param message, target, requestId *@return java.util.Map<java.lang.String,java.lang.Object> **/ public Map<String, Object> pushMessageToSingle(Message message, Target target, String requestId) { HttpURLConnection client = null; String response = new String(); if (requestId == null || "".equals(requestId.trim())) { requestId = UUID.randomUUID().toString(); } Map<String, Object> pushResult = new HashMap<>(); ParamUtils.checkAppid(message, target); Map<String, Object> postData = this.getSingleMessagePostData(message, target, requestId); try { pushResult = httpResponse(postData); }catch (Exception e){ log.error("单推失败!"+e.getMessage()); } return pushResult; } /** *@author lxw *@date 2019/12/20 *@desc 单推上送消息组装 *@param message, target, requestId *@return java.util.Map<java.lang.String,java.lang.Object> **/ Map<String, Object> getSingleMessagePostData(Message message, Target target, String requestId) { Map<String, Object> postData = new HashMap(); postData.put("action", "pushMessageToSingleAction"); postData.put("appkey", this.appKey); if (requestId != null) { postData.put("requestId", requestId); } postData.put("clientData", Base64Util.getBASE64(message.getData().getTransparent().toByteArray())); postData.put("transmissionContent", message.getData().getTransmissionContent()); postData.put("isOffline", message.isOffline()); postData.put("offlineExpireTime", message.getOfflineExpireTime()); postData.put("appId", target.getAppId()); postData.put("clientId", target.getClientId()); postData.put("alias", target.getAlias()); postData.put("type", 2); postData.put("pushType", message.getData().getPushType()); postData.put("pushNetWorkType", message.getPushNetWorkType()); return postData; } /** *@author lxw *@date 2019/12/20 *@desc 安卓单推 *@param alias, message, msgSendType *@return void **/ public void iGSinglePust(String alias,String message) { log.info("安卓用户{}开始推送。。。",alias); NotificationTemplate template = notificationTemplateDemo(message); SingleMessage messages = new SingleMessage(); messages.setOffline(true); // 离线有效时间,单位为毫秒,可选 messages.setOfflineExpireTime(24 * 3600 * 1000); messages.setData(template); // 可选,1为wifi,0为不限制网络环境。根据手机处于的网络情况,决定是否下发 messages.setPushNetWorkType(0); Target target = new Target(); target.setAppId(appId); target.setAlias(alias); Map<String, Object> ret = null; try { ret = pushMessageToSingle(messages, target,null); } catch (RequestException e) { log.error("服务器响应异常:"+e.getMessage()); ret = pushMessageToSingle(messages, target, e.getRequestId()); } } /** *@author lxw *@date 2019/12/20 *@desc ios单推 *@param alias, message, msgSendType *@return void **/ public void iGSinglePustIOS(String alias,String message) { log.error("苹果用户{}开始推送",alias); TransmissionTemplate template = transmissionTemplateDemo(message); SingleMessage messages = new SingleMessage(); messages.setOffline(true); // 离线有效时间,单位为毫秒,可选 messages.setOfflineExpireTime(24 * 3600 * 1000); messages.setData(template); // 可选,1为wifi,0为不限制网络环境。根据手机处于的网络情况,决定是否下发 messages.setPushNetWorkType(0); Target target = new Target(); target.setAppId(appId); target.setAlias(alias); Map<String, Object> ret = null; try { ret = pushMessageToSingle(messages, target,null); } catch (RequestException e) { log.error("服务器响应异常:"+e.getMessage()); ret = pushMessageToSingle(messages, target, e.getRequestId()); } } //获取spring中redis 注册的bean public IfspRedisCacheOperation<String> getPushRedisCacheOperation(){ return (IfspRedisCacheOperation<String>) IfspSpringContextUtils.getInstance().getBean("pushRedisCache"); } public IfspRedisCacheOperation<String> getAuthTokenCache(){ return (IfspRedisCacheOperation<String>) IfspSpringContextUtils.getInstance().getBean("authTokenRedisCache"); }
http 链接还是使用最上方的链接
工具类:
public Static final String PUSH_SIGN_ERROR="sign_error"; public Static final String PUSH_NO_TARGET="NOTarget"; public Static final String PUSH_MSG_OK="ok";
总结:
1.目前生产使用模式是单推+多推结合的方式。
2.个推推送分为 离线推送、在线推送,在线推送是指 个推平台将消息推送给手机,离线推送是指 个推将消息推送给手机厂商,由厂商来推送消息。区分是离线推送还是在线推送,可以查看推送成功后的响应报文中有个推送状态,那里会显示是离线推送还是在线推送。
3.手机接收不到消息问题排查
3.1:核对配置参数是否正确(appId,appKey,masterScript.....);
3.2:如果是使用别名推送,查看推送是否绑定了cid(个推为每个手机设备分配的识别码,每个手机只能生成一个,不会变);个推平台提供了方法查询别名下绑定的cid 个数,每个别名只能绑定10个cid。别名绑定超过10时候,新绑定的cid 会将已经绑定的cid其中一个顶掉。别名下cid 查询:
import com.gexin.rp.sdk.base.IAliasResult; import com.gexin.rp.sdk.http.IGtPush; public class GETTUIaLIAS { private static String appId = "QFoBXS1TrJ9yc";//个推上配置的appId private static String appKey = "M2FJjXxnP3Ark";//个推上配置的appKey private static String masterSecret = "aWU9lqy8CX6EP";//个推上配置的masterSecret static String host = "http://sdk.open.api.igexin.com/apiex.htm"; public static void main(String[] args) { String Alias = "8201807060000001_13641064684";//要查询的别名 IGtPush push = new IGtPush(host, appKey, masterSecret); IAliasResult queryClient = push.queryClientId(appId, Alias); System.out.println("根据别名获取的CID:" + queryClient.getClientIdList()); } }
,服务端是获取不到每个手机的cid ,需要客户端(app端) 来获取cid。查看cid 所属苹果或者安卓可以去个推平台
中,输入别名下查询出cid ,就可以查看出手机型号和所属手机厂商。
-
C#基础知识
2022-02-15 08:55:01C#基础知识,跟随铁锰老师教学视频总结 -
.net C# 苹果消息推送 工具类
2016-11-29 22:04:00public class AppleAPNSMessage { /// <... /// 苹果信息推送 证书 路径(注意测试版跟正式发布版证书上不一样) /// </summary> private static string cerPath = ConfigurationMan... -
C#-Event事件
2022-03-09 10:01:52} 事件触发,我们都要检测是否有人关注 例如我有新的商品或者新的发布内容,那首先看看有没有人关注我,有的话我再推送给所有关注我的人 事件可以理解成是一种封装的受限制的委托 举个事件发生以及通知的整个过程 ... -
umeng友盟消息推送功能集成
2021-08-10 01:55:34集成步骤如下下载sdk注意:有两种sdk如果用户已经集成支付宝的就下载no-uid版本的sdk解压下载好的文件,并且把PushSDK 和eclipselibs 两文件夹下的libs包拷贝到android的lib目录下如图修改android下的AndroidManif..... -
C# 实现的ADB连接 android
2017-06-23 20:15:20C# 实现的ADB连接 android -
c# typescript_C#还是Java? TypeScript还是JavaScript? 基于机器学习的编程语言分类
2020-09-06 13:10:20c# typescriptBefunge, only known to very small communities.Befunge等深奥的语言,只有很小的社区才知道。 Figure 1: Top 10 programming languages hosted by GitHub by repository count图1:按存储库数量计,... -
C#关于HttpClient的应用(二):极光推送IM集成
2016-02-21 15:18:00HttpStatusCode.NoContent) { return new ResultDTO { Result = 1 , Info = string .Empty }; } else { var content = JsonHelper.ToObject (result.responseContent); return new ... -
邮件报表推送的个人建议方案(.net)
2020-10-12 21:45:07报表邮件推送方案: 1.Sqlserver报表=》直接将数据库的数据进行整理后进行发出,样式较为固定且高亮显示重要数据时,相对比较麻烦。优点是显示图表相对比较方便; 2.C#直接编写后台代码=》从数据库获取数据后,... -
C# 工具类分享(7~14)
2020-04-09 00:24:18目录 7. JSON操作类 8.ExcelHelper 类9 9.ImageHelper 10.Color Helper类 11.ImageClass 类 12.ImageDown 图片下载类 13. imageUpload 图片上传类 14. gif 可变长度压缩算法类 ...using System.Colle... -
Concurrency in C# Cookbook中文翻译 :1.3并发性概述:响应式编程入门(Rx)
2021-01-20 12:32:33A hot observable is a stream of events that is always going on, and if there are no subscribers when the events come in, they are lost. For example, mouse movement is a hot observable. A cold ... -
C# 1
2010-09-16 14:33:001.转移序列 @ 的用法: "C://...在switch中,C#只能执行一个case语句。不能像C++那样,在没遇到break之前,可以一次执行多个。 3.溢出检查环境: checked(expression) 检查是否溢出 -
vue+socket实现消息推送
2018-12-20 11:03:00是一个web消息推送系统,基于 < a rel ="nofollow" href ="https://github.com/walkor/phpsocket.io" > PHPSocket.IO a > 开发。 < br >< br >< br > < h3 > 支持以下特性: h3 > < ul > < li > 多... -
使用极光推送(www.jpush.cn)向安卓手机推送消息【服务端向客户端主送推送】C#语言...
2017-03-15 13:01:00<compilation debug="true" targetFramework="4.0"/> 添加类JPushV3,会弹出保存类在App_code文件夹下,确定。 JpushV3代码如下: using System; using System.Collections.Generic; using System.... -
C#基本语法<一>_入门经典
2018-11-22 15:21:00基本信息 CIL和JIT CIL通用中间语言 JIT just-in-time使得CIT代码仅在需要时才编译 程序集 包含可执行文件.exe和库函数.dll和资源文件...程序集的一个重要特征是它们包含元数据描述了对应代码中定义的类型和方法... -
C# ASP.NET開發筆記(一)
2015-12-07 10:28:30最近開發一個IT 運維報修的網站, 因為時間比較緊, 也沒有時間系統性的拿一本網頁開發的書來看, 匆匆忙忙就開始了, 還好有資深的WEB開發同事提供專業的意見, 已經到了收尾的階段了, 現把一些遇到的問題寫成筆記存下來... -
C# .NET杂学笔记, 绝对杂七杂八.
2014-05-05 17:06:26这意味着我么可以将一个函数赋值给一个变量、将函数作为参数传递或者像普通函数一样调用它…C#中可以通过匿名方法创建first-classfunction Func,string> myFunc = delegate(string var1) { return "some ... -
正则学习笔记 主要是C#或Javascript
2013-05-28 15:41:28\b([a-z]+)\b \1\b 来匹配,\1表示第一个括号里的内容,\2表示第2个,如此类推 3、捕获的顺序是按左括号的出现顺序,从1开始顺序递增 注:捕获就是把括号里的内容压入堆栈 例如:([+-])?(\d+(\.\d+)?)(.*) ... -
如何从另一个线程更新GUI?
2019-12-09 12:14:25从另一个线程更新Label的最简单方法是什么? 我在thread1上有一个Form , thread1开始,我开始了另一个线程( thread2 )。 当thread2处理某些文件时,我想 -
分享一个常用 Adb 命令
2021-06-04 10:45:24(source, target)) def push(self, source, target): """ 从电脑端推送文件到手机端 :param source: :param target: :return: """ self.adb('push %s %s' % (source, target)) def remove(self, path): """ 从手机端... -
Windows Phone 7编程实践—推送通知_剖析推送通知实现架构
2019-10-04 12:30:25Windows Phone推送通知类型 Windows Phone中存在三种默认通知类型:Tile、Push 和 Toast 通知。 Tile通知 每个应用程序可设置Tile—应用程序内容的可视化、 动态的表示形式。当应用程序被固定显示在启动屏幕... -
C#海康解码器上大屏代码事例
2014-06-17 09:12:35最近公司开发一个项目 需要用到海康的视频sh -
c#中的有用的方法
2007-11-07 13:54:00檀痰潭譚談坦毯袒碳探歎炭湯塘搪堂棠膛唐糖倘躺淌趟燙掏濤滔縧萄桃逃淘陶...推頹腿蛻褪退吞屯臀拖托脫鴕陀馱駝橢妥拓唾挖哇蛙窪娃瓦襪歪外豌彎灣玩頑丸烷完碗挽晚皖惋宛婉萬腕汪王亡枉網往旺望忘妄威巍微危韋違桅圍唯惟... -
60行代码爬取知乎神回复
2019-02-13 11:30:00A: 看论文时候一个"显然"推了我一下午 11 Q: 土豪程序员的设备都有啥? A: 女朋友。。。 12 Q: 祈求代码不出 bug 该拜哪个神仙? A: 拜雍正,专治八阿哥。 13 Q: 考上好大学学 IT 是不是当今中国穷人家孩子晋级中产... -
Unity SDK相关总结
2021-11-19 19:02:09Unity大概是构建了一个只有一个MainActivity的原生安卓,然后将C#的代码跨平台转换成java的UnityPlayer类,在Unity工程Plugins/Android下的库文件则会成为安卓工程的库文件。 1.2 注意事项 /// 1 AndroidManifest.... -
C#常用日期格式处理转换
2012-07-02 13:50:00C#常用日期格式处理转换[C#日期格式转换大全] 2011-04-20 13:39 有时候我们要对时间进行转换,达到不同的显示效果 默认格式为:2005-6-6 14:33:34 如果要换成成200506,06-2005,2005-6-6或更多的该怎么办呢...