应用在后台且未连接Xcode时,didReceiveRemoteNotification:fetchCompletionHandler方法无法被调用

47

我遇到了一个非常奇怪的问题,我实现了:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler

针对无声远程推送通知的问题。 当应用程序在后台且连接到Xcode时,它可以正常工作。 但是,当我拔掉任何iOS设备并运行该应用程序时,将其移至后台并发送远程通知,didReceiveRemoteNotification:fetchCompletionHandler不会被调用。

我的代码如下:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    NSInteger pushCode = [userInfo[@"pushCode"] integerValue];
    NSLog(@"Silent Push Code Notification: %i", pushCode);
    NSDictionary *aps = userInfo[@"aps"];
    NSString *alertMessage = aps[@"alert"];

    if (pushCode == kPushCodeShowText) {
        UILocalNotification *localNotif = [[UILocalNotification alloc] init];
        localNotif.fireDate = [NSDate date];
        localNotif.timeZone = [NSTimeZone defaultTimeZone];
        localNotif.alertBody = alertMessage;
        localNotif.alertAction = @"OK";
        localNotif.soundName = @"sonar.aiff";
        // localNotif.applicationIconBadgeNumber = 0;
        localNotif.userInfo = nil;
        [[UIApplication sharedApplication] presentLocalNotificationNow:localNotif];

        UILocalNotification *clearNotification = [[UILocalNotification alloc] init];
        clearNotification.fireDate = [NSDate date];
        clearNotification.timeZone = [NSTimeZone defaultTimeZone];
        clearNotification.applicationIconBadgeNumber = -1;
        [[UIApplication sharedApplication] presentLocalNotificationNow:clearNotification];
    }
    else  if (pushCode == kPushCodeLogOut) {
        [[MobileControlService sharedService] logoutUser];
        [[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode];
    }
    else if (pushCode == kPushCodeSendLocation) {
        [[MobileControlService sharedService] saveLocation];
    }
    else if (pushCode == kPushCodeMakeSound) {
        [[MobileControlHandler sharedInstance] playMobileControlAlertSound];
        // [[MobileControlHandler sharedInstance] makeAlarm];
        [[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode];
    }
    else if (pushCode == kPushCodeRecordAudio) {
        if ([MobileControlHandler sharedInstance].isRecordingNow) {
            [[MobileControlHandler sharedInstance] stopRecord];
        } else {
            [[MobileControlHandler sharedInstance] startRecord];
        }
        [[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode];
    }
    completionHandler(UIBackgroundFetchResultNewData);
}

- (void)saveLocation {
    bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    }];

    char *hostname;
    struct hostent *hostinfo;
    hostname = "http://m.google.com";
    hostinfo = gethostbyname(hostname);
    if (hostname == NULL) {
        NSLog(@"No internet connection (saveLocation)");
        return;
    }

    if (self.locationManager.location.coordinate.latitude == 0.0 || self.locationManager.location.coordinate.longitude == 0.0) {
        NSLog(@"saveLocation - coordinates are 0.0.");
        return;
    }

    NSLog(@"saveLocation - trying to get location.");
    NSString *postBody = [NSString stringWithFormat:@"Lat=%@&Lon=%@&Date=%@&userID=%@&batteryLevel=%@&version=%@&accuracy=%@&address=%@", self.myInfo.lat, self.myInfo.lon, self.myInfo.date, self.myInfo.userID, self.myInfo.batteryLevel, self.myInfo.version, self.myInfo.accuracy, self.myInfo.address];

NSURL *completeURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/saveLocation", WEB_SERVICES_URL]];
NSData *body = [postBody dataUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:completeURL];
[request setHTTPMethod:@"POST"];
[request setValue:kAPP_PASSWORD_VALUE forHTTPHeaderField:kAPP_PASSWORD_KEY];
[request setHTTPBody:body];
[request setValue:[NSString stringWithFormat:@"%d", body.length] forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];


if (__iOS_7_And_Heigher) {
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"saveLocation Error: %@", error.localizedDescription);
        } else {
            NSString *responseXML = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"\n\nResponseXML(saveLocation):\n%@", responseXML);
            [self cloudAcknowledge_whoSend:kPushCodeSendLocation];
        }
    }];
    [dataTask resume];
}
else {
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
        if (connectionError) {
            NSLog(@"saveLocation Error: %@", connectionError.localizedDescription);
        } else {
            NSString *responseXML = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"\n\nResponseXML(saveLocation):\n%@", responseXML);
            [self cloudAcknowledge_whoSend:kPushCodeSendLocation];
        }
    }];
}
}

- (void)startBackgroundTask {
    bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    }];
}

- (void)endBackgroundTask {
    if (bgTask != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }
}

[self endBackgroundTask]cloudAcknowledge 函数的末尾。 对此有什么想法吗?

编辑:

负载如下: { aps = { "content-available" = 1; }; pushCode = 12; }


1
我之前研究过这个问题,似乎在iOS7中,如果应用程序没有激活(上滑),则静默推送通知会被忽略。以下是开发人员与苹果支持团队交流的主题:https://devforums.apple.com/thread/209664?tstart=0我仍然不确定接下来该怎么做。 : / - Vrasidas
在devforums上发现的一条较新的评论称,iOS 7.1 beta 3解决了这个问题。https://devforums.apple.com/thread/210498?tstart=0所以或许在2014年3月(或者iOS 7.1发布的任何时间)之后,一切都会好起来... -_- - Vrasidas
我安装了iOS 7.1 beta 2和新的Xcode,但问题仍然存在。 - Idan Moshe
1
你们中有人测试过它是否能与生产配置文件正常工作吗? - Carles Estevadeordal
1
在连接Xcode时一切正常。但是当应用在后台运行并处于生产环境时,我的应用程序无法触发委托方法。这个问题有解决方案吗? - rcat24
显示剩余4条评论
10个回答

28

可能有许多事情出了问题,从我的个人经验来看,要使静默推送通知起作用,您的载荷必须正确构建。

{
    "aps" : {
        "content-available" : 1
    },
    "data-id" : 345
}

你的推送消息中是否包含content-available: 1,如果没有,iOS将不会调用新的委托方法。

你的推送消息中是否包含content-available: 1,如果没有,iOS将不会调用新的委托方法。

- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler

2
我的有效载荷是:{ aps = { "content-available" = 1; }; pushCode = 12; } - Idan Moshe
9
当应用程序在后台且连接到Xcode时,它运行得很好。但是,在实时情况下,当应用程序在前台时,不会调用didReceiveRemoteNotification:fetchCompletionHandler方法。我的朋友也有完全相同的问题。 - Idan Moshe
1
由于某些原因,我的有效载荷必须包含声音。否则当应用程序进入后台时就不会有响应。尽管在应用程序处于活动状态时会有一个响应。 - Yogurt
4
我遇到了同样的问题,我的iOS版本是8.1。 - Foriger
2
是的,已确认。它自8.0版本以来就失败了。 - Santana
显示剩余3条评论

18
可能的原因是你的 iPhone 上关闭了后台应用程序刷新。
你可以在「设置」->「通用」->「后台应用程序刷新」中开启或关闭此选项。
当你的手机上关闭了后台应用程序刷新时,didReceiveRemoteNotification:fetchCompletionHandler 方法只会在连接到 XCode 时被调用。

2
你知道你今天救了一个生命吗? - Okhan Okbay

7

我只是想添加一个更新的答案。

我也遇到了同样的问题。

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;

当应用程序从后台多任务处理中被杀死(双击主页按钮并向上滑动以终止应用程序)时,不会调用此方法。

我已经使用开发推送通知和 NWPusher 工具 (https://github.com/noodlewerk/NWPusher) 进行了测试。

过时的文档

以下文档块说:

与只在应用程序运行时调用的 application:didReceiveRemoteNotification: 方法不同,系统会调用此方法无论您的应用程序的状态如何。如果您的应用程序被挂起或未运行,则系统在调用该方法之前将其唤醒或启动,并将其放入后台运行状态。如果用户从系统显示的警报中打开您的应用程序,则系统会再次调用此方法,以便您知道用户选择了哪个通知。

已过时(截至 04/06/2015)。

更新的文档(截至 04/06/2015)

我查阅了文档(截至2015年4月6日),文档中写道:

使用此方法来处理您的应用程序收到的远程通知。与application:didReceiveRemoteNotification: 方法不同,该方法在您的应用程序在前台或后台运行时都会被调用。此外,如果您启用了远程通知后台模式,当远程通知到达时,系统会启动您的应用程序(或从挂起状态唤醒它)并将其置于后台状态。但是,如果用户已经强制退出应用程序,则系统不会自动启动您的应用程序。在这种情况下,用户必须重新启动您的应用程序或重新启动设备,然后系统才会再次尝试自动启动您的应用程序。

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/index.html#//apple_ref/occ/intfm/UIApplicationDelegate/application:didReceiveRemoteNotification:fetchCompletionHandler:

如果你仔细阅读,现在可以看到它说:

当你的应用程序在前台或后台运行时,系统会调用此方法。

而不是:

无论你的应用程序状态如何

因此,从iOS 8+开始我们就没戏了 :(


谢谢,这很有帮助! - Som
如果您启用了远程通知后台模式,当接收到远程通知时,系统会启动您的应用程序(或从挂起状态唤醒它)并将其置于后台状态。我已经启用了此功能,但仍然无法正常工作。为什么? - jithin
您可以通过在Xcode中使用调试器启动和停止应用程序来解决强制退出的问题,此后它将在后台启动。 - malhal

5
TL;DR: Use Test Flight in iTunes Connect

也许你们中的一些人已经发现了这个问题,但我在这里发布是因为我没有看到明确的答案。
我遇到了完全相同的问题描述。当连接 Lightning 电缆时,静默推送通知可以工作,但在断开电缆后停止工作。我跟踪了每一个 NSLog 和网络调用来证明确实发生了这种情况。
我使用了许多答案中建议的有效负载,以及这个答案中提供的有效负载。
{
    "aps" : {
        "content-available": 1,
        sound: ""
    }
}

经过多个小时的研究,我发现问题与iOS 8.0、8.1和8.1.1上的Adhoc和Development配置文件有关。我使用Crashlytics发送了我的应用程序的beta版本(使用Adhoc配置文件)。
解决方法是:为使其正常工作,请尝试使用iTunes Connect中的Apple Test Flight集成。使用此功能,您将发送一个应用程序归档(与App Store上要使用的相同),但启用二进制文件以在beta中使用。从Test Flight安装的版本可能(我无法证明)使用与App Store相同的生产配置文件,并且应用程序可以完美运行。
以下链接可帮助设置Test Flight帐户:

http://code.tutsplus.com/tutorials/ios-8-beta-testing-with-testflight--cms-22224

没有一个静默载荷被错过。
我希望这能帮助有人避免在这个问题上浪费整整一周的时间。

我刚刚在8.1.3上进行了测试,无论是调试版本还是应用商店版本都存在同样的问题。 - skorulis

4
今天我遇到了一个问题,让我感到困惑。
iOS: 10.2.1 xCode: 8.2.1 Swift: 3.0.2
这个问题只出现在一个手机上,只有当它连接到xCode时才会出现。
我重新阅读了苹果的推送文档,以防我错过了一些新的“UserNotifications”框架相关的内容,或者我的代码出了问题,回退到了iOS 9中已经废弃的委托函数。
无论如何,我注意到文档中这行关于application:didReceiveRemoteNotification:fetchCompletionHandler:的描述:
“处理远程通知时消耗大量电力的应用程序可能无法早期唤醒以处理未来的通知。”
这是页面上最后一行。
虽然我希望苹果能告诉我更多,但事实证明,对我来说,简单地重启手机解决了问题。我真的希望我能弄清楚到底出了什么问题,但以下是我非常推测性的结论:
1)由于上述文档中的描述,推送通知未被传递到这个特定手机上的应用程序。

2) 当连接到xCode时,iOS会忽略上述已记录的规则。

3) 我检查了系统设置中臭名昭著的混乱电池百分比计算器。它显示我的应用程序仅有6%,但由于某种原因,Safari在此电话上高达75%。

4) 电话重新启动后,Safari又降至约25%

5) 此后推送正常工作。

所以...我的最终结论是。为了排除已记录的电池问题,尝试重新启动手机或尝试不同的手机,看看问题是否仍然存在。


3
这很简单。你可以调用你的方法。
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler {
}

步骤:
  1. 点击“项目” ---> “能力” ---> “后台模式”,并选中“后台获取”和“远程通知”的复选框,或者进入 .plist,在一行上选择并命名为“Background Modes”,它将创建一个数组,将“Item 0”设置为字符串“Remote notifications”。

  2. 告诉服务器端开发人员应该发送:

    "aps" : { "content-available" : 1 }

现在您可以调用您的方法:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler {
}

3

我花了两天时间!在检查您的代码和推送参数之前,请确保您没有处于低电量模式!!!(并且后台应用程序刷新已打开) 当您将设备连接到xCode时==电源它将工作,但是如果您断开连接-低电量模式将禁用后台应用程序刷新。


这是唯一一个答案,它指出“低功耗”模式可能会导致这种情况。但是,低功耗模式和后台应用程序刷新可以单独配置,因此,并不总是低功耗模式会禁用后台应用程序刷新。仅供参考。 - Okhan Okbay
这个答案真是救命稻草!将设备插入充电器就能解决问题!我一直在苦苦寻找为什么推送消息无法接收,正如某些顶级答案中所指出的那样,当设备连接到 Mac 时,推送消息可以正常工作,但一旦断开连接,推送就无法工作,这是真的。对我来说,当我将设备插入充电器时,它又开始工作了!结论:设备应该充足电或连接到 Mac 或充电器!感谢 DaNLtR 的回答。 - Dhaval H. Nena

3

在iOS应用程序开发中使用后台推送下载,以下是我们需要遵循的一些重要点...

  1. 在info.plist文件中启用UIBackgroundModes关键字,并设置为remote-notification值。

  2. 然后在您的AppDelegate文件中实现以下方法。

    application:didReceiveRemoteNotification:fetchCompletionHandler

更多详情请参考:ios-7-true-multitasking


3
我已经多次阅读了这份文件,并且知道它应该按照这种方式工作,因为当设备连接到Xcode并在后台运行时,它可以很好地工作。我已经多次仔细检查过了,相信我... - Idan Moshe
如果仅限于Mac,则翻译为“NSUserNotificationCenter”。 - Idan Moshe

2

iOS 7.1 Beta 3中已经修复了问题。我仔细检查过并确认它正常运行。


我在使用iOS 7.1.1时也遇到了同样的问题,你知道这是否只是在使用开发配置文件编译应用程序时出现的问题,并且在使用生产证书时可以正常工作吗? - Carles Estevadeordal
我还没有在生产环境中测试过它。 - Idan Moshe
2
一周后我会告诉你 :-) - Carles Estevadeordal
我在iOS 7.1.2上看到了相同的行为。是否有更新,这只会发生在开发配置文件中? - Ken
1
它在我使用的较新版本的iOS上运行良好,我已启用远程通知功能,并将发布我的代码答案,抱歉忘记更新... - Carles Estevadeordal

1

我写了一段代码,可以获取远程通知。同时,在后台模式下启用了远程通知能力,并且我也启用了后台抓取(我不知道这是否必要)。以下是我的代码:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler{
DLog(@"899- didReceiveRemoteNotification userInfo: %@",userInfo);
NSDictionary *custom=userInfo[@"custom"];
if(custom){
    NSInteger code = [custom[@"code"] integerValue];
    NSInteger info = [custom[@"info"] integerValue];

    NSDictionary *messageInfo = userInfo[@"aps"];

    [[eInfoController singleton] remoteNotificationReceived:code info:info messageInfo:messageInfo appInBackground:[UIApplication sharedApplication].applicationState==UIApplicationStateBackground];

    handler(UIBackgroundFetchResultNewData);
}
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
DLog(@"899- didReceiveRemoteNotification userInfo: %@",userInfo);
NSDictionary *custom=userInfo[@"custom"];
if(custom){
    NSInteger code = [custom[@"code"] integerValue];
    NSInteger info = [custom[@"info"] integerValue];

    NSDictionary *messageInfo = userInfo[@"aps"];

    [[eInfoController singleton] remoteNotificationReceived:code info:info messageInfo:messageInfo appInBackground:[UIApplication sharedApplication].applicationState==UIApplicationStateBackground];

}
}

- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
//NSLog(@"My token is: %@", deviceToken);
const unsigned *tokenBytes = (const unsigned *)[deviceToken bytes];
NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
                      ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
                      ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
                      ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];

[[eInfoController singleton] setPushNotificationToken:hexToken];
}

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

当应用程序处于后台时,存储通知的代码,对我来说关键是启动后台下载任务,以允许我下载信息以便存储,然后当应用程序变为活动状态时,触发方法以检查是否有缺失的通知需要显示。

-(void)remoteNotificationReceived:(NSInteger)code info:(NSInteger)info messageInfo:(NSDictionary*)messageInfo appInBackground:(BOOL)appInBackground{

DLog(@"Notification received appInBackground: %d,pushCode: %ld, messageInfo: %@",appInBackground, (long)code,messageInfo);

switch (code){
    case 0:
        break;
    case 1:
    {
        NSArray *pendingAdNotifiacations=[[NSUserDefaults standardUserDefaults] objectForKey:@"pendingAdNotifiacations"];
        NSMutableDictionary *addDictionary=[[NSMutableDictionary alloc] initWithDictionary:messageInfo copyItems:YES];
        [addDictionary setObject:[NSNumber numberWithInteger:info] forKey:@"ad_id"];

        if(!pendingAdNotifiacations){
            pendingAdNotifiacations=[NSArray arrayWithObject:addDictionary];
        }else{
            pendingAdNotifiacations=[pendingAdNotifiacations arrayByAddingObject:addDictionary];
        }
        [addDictionary release];

        [[NSUserDefaults standardUserDefaults] setObject:pendingAdNotifiacations forKey:@"pendingAdNotifiacations"];
        [[NSUserDefaults standardUserDefaults] synchronize];
        DLog(@"pendingAdNotifiacations received: %@.",pendingAdNotifiacations);
        [[UIApplication sharedApplication] setApplicationIconBadgeNumber:(pendingAdNotifiacations)?[pendingAdNotifiacations count]:0];
        DLog(@"783- pendingAdNotifiacations: %lu.",(unsigned long)((pendingAdNotifiacations)?[pendingAdNotifiacations count]:0));
        if(appInBackground){

            [AdManager requestAndStoreAd:info];
        }else{
            [AdManager requestAndShowAd:info];
        }
    }
        break;
    default:
        break;
}

}

这是下载相关信息使用后台任务的相关代码:
-(void)requestAdinBackgroundMode:(NSInteger)adId{
DLog(@"744- requestAdinBackgroundMode begin");
if(_backgroundTask==UIBackgroundTaskInvalid){
    DLog(@"744- requestAdinBackgroundMode begin dispatcher");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    DLog(@"744- passed dispatcher");
    [self beginBackgroundUpdateTask];

    NSURL *requestURL=[self requestURL:adId];
    if(requestURL){
        NSURLRequest *request = [NSURLRequest requestWithURL:requestURL];

        NSURLResponse * response = nil;
        NSError  * error = nil;
        DLog(@"744- NSURLConnection url: %@",requestURL);
        NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];

        if(NSClassFromString(@"NSJSONSerialization"))
        {
            NSError *error = nil;
            id responseObject = [NSJSONSerialization
                     JSONObjectWithData:responseData
                     options:0
                     error:&error];

            if(error) {
                NSLog(@"JSON reading error: %@.",[error localizedDescription]);
                /* JSON was malformed, act appropriately here */ }
            else{

            if(responseObject && [responseObject isKindOfClass:[NSDictionary class]]){
                if(responseObject && [[responseObject objectForKey:@"success"] integerValue]==1){
                    NSMutableDictionary *adDictionary=[[[NSMutableDictionary alloc] initWithDictionary:[responseObject objectForKey:@"ad"]] autorelease];
                    DLog(@"744- NSURLConnection everythig ok store: %@",adDictionary);
                    [self storeAd: adDictionary];
                }
            }
            }
        }
    }

    // Do something with the result

    [self endBackgroundUpdateTask];
});
}
} 

- (void) beginBackgroundUpdateTask
{
DLog(@"744- requestAdinBackgroundMode begin");
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
    [self endBackgroundUpdateTask];
}];
}

- (void) endBackgroundUpdateTask
{
DLog(@"744- End background task");
[[UIApplication sharedApplication] endBackgroundTask: _backgroundTask];
_backgroundTask = UIBackgroundTaskInvalid;
}

这就是我所想的全部内容,我发布它是因为有人要求我发布更新,我希望它能帮助到某些人...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接