iOS 10通知扩展-通知服务扩展

简介

推送基本上是每一个APP必备的功能,而iOS 10新增了UserNotificationKit框架,整合了之前的通知,而且新增了很多特性。

1.通知内容更加丰富

  • 由之前的alert到现在的title,subTitle,body。
  • 为推送增加了附件,包括符合格式和大小的图片、音频和视频。

2.方便对推送的周期进行管理

  • 更新推送
  • 删除推送
  • 查看推送

新框架

#ifdef NSFoundationVersionNumber_iOS_9_x_Max
#import <UserNotifications/UserNotifications.h>
#endif

通过UNUserNotificationCenter来管理本地和远程通知。

1.首先打开推送开关

工程配置

2. 获取权限

我们需要在 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions注册通知,代码如下

    UNUserNotificationCenter *notifiCenter = [UNUserNotificationCenter currentNotificationCenter];
    UNAuthorizationOptions options = UNAuthorizationOptionNone | UNAuthorizationOptionBadge| UNAuthorizationOptionSound | UNAuthorizationOptionAlert;
    [notifiCenter requestAuthorizationWithOptions:options completionHandler:^(BOOL granted, NSError * _Nullable error) {
        
    }];
[[UIApplication sharedApplication] registerForRemoteNotifications];

3. 注册APNS,获取deviceToken

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    
    [JPUSHService registerDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    
    NSLog(@"did Fail To Register For Remote Notifications With Error: %@", error);
}
4. iOS7以后如果想要在后台做一些操作
  • 需要在APNS增加字段 "content-available":1 ,"mutable-content":1
  • 需要在Background Modes中增加Remote notifications


    工程推送配置

5. 收到推送调用的方法

  • 这是应用处于前台时 收到推送触发
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger))completionHandler {
    NSDictionary * userInfo = notification.request.content.userInfo;
    
    UNNotificationRequest *request = notification.request; // 收到推送的请求
    UNNotificationContent *content = request.content; // 收到推送的消息内容
    
    NSNumber *badge = content.badge;  // 推送消息的角标
    NSString *body = content.body;    // 推送消息体
    UNNotificationSound *sound = content.sound;  // 推送消息的声音
    NSString *subtitle = content.subtitle;  // 推送消息的副标题
    NSString *title = content.title;  // 推送消息的标题
    
    if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        [JPUSHService handleRemoteNotification:userInfo];
        NSLog(@"iOS10 前台收到远程通知");
  }
    else {
        // 判断为本地通知
        NSLog(@"iOS10 前台收到本地通知:{\nbody:%@,\ntitle:%@,\nsubtitle:%@,\nbadge:%@,\nsound:%@,\nuserInfo:%@\n}",body,title,subtitle,badge,sound,userInfo);
    }
    completionHandler(UNNotificationPresentationOptionAlert); // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型可以设置
}
操作的回调方法:不管应用在前台、后台还是被手动划掉,下面三种情况将触发该方法。
  1. 点击通知进入应用

2.点击action

  1. 清除了category是UNNotificationCategoryOptionCustomDismissAction的通知
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
    
    NSDictionary * userInfo = response.notification.request.content.userInfo;
    UNNotificationRequest *request = response.notification.request; // 收到推送的请求
    UNNotificationContent *content = request.content; // 收到推送的消息内容
    
    NSNumber *badge = content.badge;  // 推送消息的角标
    NSString *body = content.body;    // 推送消息体
    UNNotificationSound *sound = content.sound;  // 推送消息的声音
    NSString *subtitle = content.subtitle;  // 推送消息的副标题
    NSString *title = content.title;  // 推送消息的标题
    
    if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        [JPUSHService handleRemoteNotification:userInfo];
        NSLog(@"iOS10 收到远程通知");   
    }
    else {
        // 判断为本地通知
        NSLog(@"iOS10 收到本地通知:{\nbody:%@,\ntitle:%@,\nsubtitle:%@,\nbadge:%@,\nsound:%@,\nuserInfo:%@\n}",body,title,subtitle,badge,sound,userInfo);
    }
    
    completionHandler();  // 系统要求执行这个方法
}

收到远程推送的回调方法:APNS带有"content-available":1字段,并且应用在前台或者后台时收到远程推送,将触发该方法。(注意:应用被手动划掉将无法触发)

可以在这个方法里做一些后台操作(下载数据,更新UI等),记得修改Background Modes。

- (void)JPush_application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    
    [JPUSHService handleRemoteNotification:userInfo];
    
    completionHandler(UIBackgroundFetchResultNewData);
}

UNNotificationContentExtension - 通知内容扩展

通知内容扩展需要新建一个 UNNotificationContentExtension Target,之后只需在 viewcontroller 的中实现相应的接口,即可以对 app 的通知页面进行自定义扩展,扩展主要用于自定义 UI。

UNNotificationServiceExtension - 通知服务扩展

通知服务扩展

UNNotificationServiceExtension 提供在远程推送将要被 push 出来前,处理推送显示内容的机会。此时可以对通知的 request.content 进行内容添加,如添加附件,userInfo 等。
使用UNNotificationServiceExtension,你有30秒的时间处理这个通知,可以同步下载图像和视频到本地,然后包装为一个UNNotificationAttachment扔给通知,这样就能展示用服务器获取的图像或者视频了。这里需要注意:如果数据处理失败,超时,extension会报一个崩溃信息,但是通知会用默认的形式展示出来,app不会崩溃。

新建通知扩展

Xcode File ->New ->Target


新建通知扩展

然后写名字,下一步,就可以了
此时我们的目录结构里面,已经多出了一个文件夹了


1523430761264.jpg
主工程bundleID

通知扩展bundleID

注意看上图,这里的bundleID是你的工程名字的bundleID加上通知扩展的名称。
不要修改,系统创建的时候就创建好了,不过我还是给大家说一下这个格式
如果你的工程的BundleID是comTaoShengyijiu.pushDemo,则这个扩展的BundleID就是comTaoShengyijiu.ZYBaseTestPushExtend, 最后的后缀是看咱们创建服务扩展时候的名字。其他的小细节,大家可以看看。
到这一步,我们就新建了一个服务通知类的扩展。

因为我们公司的APP是一款类似于支付宝的理财工具,产品的需求就是类似于支付宝收钱吧一类,只要APP收到一笔款,就要能实时播报出来,无论程序处于前台、后台还是杀死的情况下都要能正常播报。所以我在通知扩展中就是启动系统自带的音频服务读出推送内容。

普及知识时间

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler

这个函数是通知扩展类的最为核心的函数了,你可以理解为这个就是接受到苹果APNS 通知的一个钩子函数,每次当推送一条通知过来,都会执行到这个函数体内,所以说我们的语音播报逻辑也是在这个钩子函数中进行处理的

先来说下苹果通知的通知栏问题

在苹果通知中,当来一条通知时,我们的手机会叮一下,然后手机通知栏弹出通知。这里大家注意下,其实这个叮一下出来的通知栏也是有生命周期的。从通知栏被弹出来,到通知栏最终被收起,其实中间苹果给了限制时间,大概就6秒左右的时长(注意,如果你要播报的内容超过6秒,你就要去控制什么时候弹出通知栏了,要不然会出现语音无法全部播报出来的情况)。

说到6秒左右的时长,对于那些多条通知同时到达,需要串行来逐一播报,但是很多小伙伴们会遇到这样一个问题:就是当同时来了多条通知,总是只能播报2-3条,然后就语音中断了,后面的通知不会播报了,遇到这些问题的小伙伴们有没有注意到,其实只能播报2-3条,这个时间差其实就是6秒左右,也就是通知栏的生命周期时长。

出现上面的问题的原因就是:当第一条通知来了,弹出通知栏,然后开始播报第一条语音,第一条播报完了,开始播报第二条语音,可能当第二条语音播报到一半了,但是这个时候,通知栏周期的时间到了,这时通知栏就会收起,注意:当通知栏收起时,扩展类里面的代码就会终止执行,导致后面的语音播报终端

上面说到当通知栏收起时,扩展类的代码会终止执行,这里又引出了另一个注意点:就是我们创建的这个扩展类也是有生命周期的,并且这个生命周期和通知栏的生命周期他们是有依赖关系的。即:当通知栏收起时,扩展类就会被系统终止,扩展内里面的代码也会终止执行,只有当下一个通知栏弹出来,扩展类就恢复功能

上面说到通知栏的出现和收起能够影响到扩展类的功能,那我们是不是控制好通知栏的显示和隐藏,就能解决多条串行问题呢?

是的,我们只要控制好通知栏,就可以解决上面的棘手问题,那么问题又来了,我们怎么才能控制通知栏的显示和隐藏呢?感觉我们平时使用苹果的推送,从来没有关心过处理通知栏的显示与隐藏,感觉从来没有这样用过,是的,对应普通的需求,我们确实不需要关系通知栏显示隐藏,感觉这些苹果系统自己已经处理好了,通知来了就显示通知栏,等5秒左右,周期结束就隐藏通知栏。

其实啊,在扩展类里面中,苹果已经给我们指出了如何控制通知栏的显示和隐藏,核心就是这行代码:self.contentHandler(self.bestAttemptContent),当我们调用到这行代码,就是用来弹出通知栏的,通知栏的隐藏不需要我们来控制了,因为5秒左右的生命周期结束后,它会自动隐藏。

是不是对这样代码既熟悉有陌生啊,熟悉是因为你的扩展类文件中确实有这行代码,陌生是因为你之前从来都没有用过这行代码,不知道这行代码是用来干啥的。

好了,既然self.contentHandler(self.bestAttemptContent) 这行核心代码引用出来了,我们就回到最开始的问题,在没有做任何处理时,为什么当同时来多条通知是,语音播报就不能逐一播报呢,其实就是因为当每一条通知到达都会执行这个函数- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler 有没有发现,这个函数体里面 默认就是 执行了 self.contentHandler(self.bestAttemptContent)这行代码。

假设一次性同时来了10条 通知,就会一次性调用了 10次 didReceiveNotificationRequest这个函数, 也就执行了 10次 self.contentHandler(self.bestAttemptContent)。 按照上面的说法,同时执行10次,不就是同时弹出10次的通知栏吗,这里我调试时发现,当同时来10条通知时,通知栏并没有同时弹出来10次,可能只弹出来1-2次。也就只能在这1-2次的时间长度中进行语音播报了。

上面解释这么多,那么我们到底该如何做呢,细心的同学发现了,我们上面 贴出来的 .m 代码中,我们新增了一个 AVSpeechSynthesizer 类的代理函数,就是语音播报完成的函数,我们将 呼出通知栏的代码 self.contentHandler(self.bestAttemptContent)添加到这个代理函数中。意思就是:当第一条语音播放完成了,这时我们呼出通知栏显示播放的内容(通知栏的周期时间大概6秒左右),正好这时可以播放第二条语音,等第二条语音播放完成了,呼出第二个通知的通知栏,继续播放第三天语音,以此类推。

看到这里,想必大家应该都理解了为啥之前总是语音播报中断的问题。

还有一个很重要的函数:- (void)serviceExtensionTimeWillExpire{},我们上面只是提了下,具体他具体有什么功能呢?

我们发现serviceExtensionTimeWillExpire函数中,也调用了 self.contentHandler(self.bestAttemptContent)这行代码,它为啥也要调用这行代码呢?

这是因为:当我们在接受通知的钩子函数中(didReceiveNotificationRequest)没有调用self.contentHandler(self.bestAttemptContent)这行代码,这时就会出现一个现象:就是通知收到了,但是没有通知栏出现,这时苹果就不允许了。苹果规定,当一条通知达到后,如果在30秒内,还没有呼出通知栏,我就系统强制调用self.contentHandler(self.bestAttemptContent) 来呼出通知栏。 这时想必大家都知道 serviceExtensionTimeWillExpire函数的用途了吧。

小编自己Demo里的代码来了

#import "NotificationService.h"
#import <AVFoundation/AVFoundation.h>

@interface NotificationService ()<AVSpeechSynthesizerDelegate>

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

@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

/** 语音合成引擎 */
@property (nonatomic, strong) AVSpeechSynthesizer *voiceSpeaker;

/** 弹框是否已经展示 */
@property (nonatomic, assign) BOOL alertIsDisplayed;

@end
PS:这里通过判断文字个数来控制通知栏什么时候显示
/** 文字限制长度 暂定为15字*/
static int contentLengthLimit = 18;
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    NSError *activeErr = nil;
    NSError *cateroyErr = nil;

    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&cateroyErr];
    [[AVAudioSession sharedInstance] setActive:YES error:&activeErr];
    
    self.alertIsDisplayed = NO;
    
    // Modify the notification content here...
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@By涛声", self.bestAttemptContent.title];
    
    [self speakStringFromServer:self.bestAttemptContent.body];
}
#pragma mark - 处理并播放服务器返回的内容
- (void)speakStringFromServer:(NSString *)string {
    
    if (string.length == 0) {
        
        // 如果字符串长度为0,则直接弹出通知Alert,不执行任何操作 通知栏的隐藏不需要我们来控制,因为5秒左右的生命周期结束后,它会自动隐藏
        self.alertIsDisplayed = YES;
        self.contentHandler(self.bestAttemptContent);
        
        return;
    }

    // 如果文字过长的话,会导致文字播放到一半时出现通知的声音,故将通知声音关闭
    self.bestAttemptContent.sound = nil;
    
    if (string.length <= contentLengthLimit) {

        // 如果文字长度较短的话则直接弹出通知栏并且开启通知声音
        self.bestAttemptContent.sound = [UNNotificationSound defaultSound];
        
        // 如果需要播放的内容长度小于限制长度,5秒时间足以播放完毕,则直接弹出Alert。
        self.alertIsDisplayed = YES;
        self.contentHandler(self.bestAttemptContent);
    }
    
    NSString *needStr = [self getNumberFromString:string];
    
    [needStr stringByAppendingString:@""];
    
    NSString *tempStr = [NSString stringWithFormat:@",%@", needStr];
    
    NSString *finalStr = [string stringByReplacingOccurrencesOfString:needStr withString:tempStr];
    
    [self speakString:finalStr];
    
}

- (void)speakString:(NSString *)string {
    
    if (self.voiceSpeaker) {
        
        AVSpeechUtterance *aUtterance = [AVSpeechUtterance speechUtteranceWithString:string];
        
        [aUtterance setVoice:[AVSpeechSynthesisVoice voiceWithLanguage:@"zh-TW"]];
        
        aUtterance.rate = 0.5; //设置语速
        
        aUtterance.volume = 1;  //设置音量(0.0~1.0)默认为1.0
        
        aUtterance.pitchMultiplier = 1;  //设置语调 (0.5-2.0)
        
        [self.voiceSpeaker speakUtterance:aUtterance];
    }
}

#pragma mark - 获取字符串中的数字 以便在数值处特意停顿一下
- (NSString *)getNumberFromString:(NSString *)string {
    
    NSScanner *scanner = [NSScanner scannerWithString:string];
    
    [scanner scanUpToCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet] intoString:nil];
    
    float num ;
    
    [scanner scanFloat:&num];
    
    NSString *tempStr = [NSString stringWithFormat:@"%.2f", num];
    
    NSDecimalNumber *resultNum = [NSDecimalNumber decimalNumberWithString:tempStr];
    
    NSString *resultStr = [NSString stringWithFormat:@"%@", resultNum];
    
    return resultStr;
}

#pragma mark - AVSpeechSynthesizerDelegate
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didStartSpeechUtterance:(AVSpeechUtterance *)utterance
{
    NSLog(@"开始");
}

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance
{
    NSLog(@"结束");
}

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didPauseSpeechUtterance:(AVSpeechUtterance *)utterance
{
    NSLog(@"暂停");
}

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didContinueSpeechUtterance:(AVSpeechUtterance *)utterance
{
    NSLog(@"继续");
}

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didCancelSpeechUtterance:(AVSpeechUtterance *)utterance
{
    NSLog(@"取消");
    self.alertIsDisplayed = YES;
    self.contentHandler(self.bestAttemptContent);
}

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance {
    
    if (self.alertIsDisplayed == NO) {
        
        if (characterRange.location >= utterance.speechString.length / 4) {
            
            // 如果文字长度大于限制,则可能通知栏弹出5秒内无法播放完毕,则暂定为播放到1/4时再弹出状态栏
            self.alertIsDisplayed = YES;
            self.contentHandler(self.bestAttemptContent);
        }
    }
}
- (void)serviceExtensionTimeWillExpire {
    // Called just before the extension will be terminated by the system.
    // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
    
    // 当一条通知达到后,如果在30秒内,还没有呼出通知栏,系统就强制调用来呼出通知栏
    self.alertIsDisplayed = YES;
    self.contentHandler(self.bestAttemptContent);
}
#pragma mark - 懒加载
- (AVSpeechSynthesizer *)voiceSpeaker {
    
    if (!_voiceSpeaker) {
        
        _voiceSpeaker = [[AVSpeechSynthesizer alloc] init];
        
        _voiceSpeaker.delegate = self;
    }
    
    return _voiceSpeaker;
}

可能遇见的一些问题

  • Q.调试这个通知扩展类,为什么我跑程序的时候,打断点无反应?
    A. 因为你选择的这是因为你跑的target不对,正确的做法是,跑正确的 target,具体如下图


    1523432182686.jpg
1523432375212.jpg

这个时候再打断点调试,就OK了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 极光推送: 1.JPush当前版本是1.8.2,其SDK的开发除了正常的功能完善和扩展外也紧随苹果官方的步伐,SD...
    Isspace阅读 6,690评论 10 16
  • 介绍一下iOS10的通知新功能,用户体验的提升和开发者能够发挥的地方非常多,使得iOS更具有竞争力。 1.iOS ...
    F麦子阅读 3,760评论 3 4
  • 网上介绍iOS Push的文章有很多,但是大部分都总结得非常零散,加上之前也一直没好好总结过,对某些地方也不求甚解...
    纸简书生阅读 2,797评论 1 29
  • 转载请注明原创出处,谢谢! GreenMountains http://www.jianshu.com/u/2...
    GreenMountains阅读 1,296评论 0 2
  • 我的前半生简单平凡,婚前任着自己的性子潇洒自在,婚后相夫教子围着灶台转,所有的重心就家里厂里两点之间。儿子成了...
    不一样的我和你阅读 231评论 0 2