iOS端使用replaykit錄制屏幕的技術(shù)細節(jié)

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

前面兩篇文章:
iOS端屏幕錄制(replaykit)調(diào)研
iOS端屏幕錄制Replaykit項目實踐
已經(jīng)對iOS端實現(xiàn)屏幕錄制的調(diào)研結(jié)果和簡單實踐進行了概述蜕提,本篇開始將分別對iOS9甜癞、iOS10糯累、iOS11、iOS12系統(tǒng)上具體實踐記錄一下,便于分享和自己查看猛蔽。

相比于安卓端蟀俊,iOS端的屏幕錄制發(fā)展太慢了,并且對開發(fā)者的需求滿足總是延遲很大殊校,就像其他功能一樣,這也許就是蘋果逐漸喪失他的競爭力的原因读存。本文將對iOS端使用replaykit在各個系統(tǒng)版本中實現(xiàn)細節(jié)進行描述为流。


iOS9:

對于iOS9的replaykit功能介紹可以參考官方wwdc視頻:支持錄制音頻、視頻让簿,還可以增加語音旁白評論等其他額外的定制化東西敬察。對于錄制的內(nèi)容,用戶可以回訪尔当、剪輯或者通過社交媒體軟件分享出去莲祸。

ReplayKit records the audio and visuals of your running application. It also allows you to use this to add voice commentary and so they can make their recordings more personal or just to provide additional context. It allows your users to play back, scrub and trim their recordings and finally share their recordings to their favorite social networks and video destination sites.

啟動錄制使用接口:

    [[RPScreenRecorder sharedRecorder] startRecordingWithMicrophoneEnabled:YES handler:^(NSError * _Nullable error) {
        ;
    }];

注意:

  • 使用 [RPScreenRecorder sharedRecorder] 啟動錄制,會首先請求用戶同意使用攝像頭和麥克風(fēng)居凶,主要考慮用戶的隱私和權(quán)限虫给,如果用戶拒絕了,將無法進行錄制侠碧。
  • 錄制的內(nèi)容不會包含系統(tǒng)的UI抹估,比如上方導(dǎo)航欄;
  • 錄制的內(nèi)容會經(jīng)過音視頻編碼弄兜,而不是原始的yuv或pcm數(shù)據(jù)药蜻;
  • 錄制的內(nèi)容無法直接查看瓷式,必須通過RPPreviewViewController才能查看預(yù)覽,或者分享语泽,或者保存到本地相冊中贸典。而這個RPPreviewViewController在停止錄制的接口回調(diào)中才能獲取,也就是說踱卵,只有停止錄制之后才能通過RPPreviewViewController操作錄制的音視頻廊驼。
[[RPScreenRecorder sharedRecorder] stopRecordingWithHandler:^(RPPreviewViewController *previewViewController, NSError *  error){
        
        [self presentViewController:previewViewController animated:YES completion:^{
            ;
        }];
    }];

預(yù)覽的vc展示出來如下圖:圖中圈中位置分別提供了預(yù)覽、保存到相冊惋砂、分享三個入口妒挎。



iOS10:

···
iOS9已經(jīng)實現(xiàn)了基本的app內(nèi)容錄制、預(yù)覽西饵、保存酝掩、分享,但是其輸出的結(jié)果其實是一個已經(jīng)將音頻眷柔、視頻編碼并交織到一起成為一個mp4文件期虾,開發(fā)者只能處理這個mp4文件,無法對原始音視頻數(shù)據(jù)進行處理驯嘱。對于有些app可能存在諸如分辨率減小镶苞、碼率減小、音頻編輯等各種需求鞠评,都需要對原始的yuv宾尚、pcm數(shù)據(jù)進行處理,或者對編碼過程進行定制化干預(yù)谢澈。
考慮到開發(fā)者這個需求,蘋果在iOS10的replaykit中開放了這部分api御板,通過extension形式將錄制進程展現(xiàn)給開發(fā)者锥忿。其實iOS9時錄制也是在一個獨立于app的進程中進行,只是未開放怠肋。iOS10提供了分發(fā)相關(guān)多個類和api敬鬓,用戶可以通過代理方法獲取到屏幕錄制的原始數(shù)據(jù),做進一步處理笙各。引入時需要通過xcode的file -> new -> target 找到兩個相關(guān)extension:


錄制

ios10的replaykit的錄制已經(jīng)跟iOS9差異很大钉答,ios10已經(jīng)支持錄制的原始音視頻數(shù)據(jù)的 【實時】獲取(iOS9只可以獲取到錄制停止后編碼的mp4)杈抢,開發(fā)者可以自己進行實時分發(fā)或者編碼后處理数尿。
主要步驟如下:

  1. 啟動備選界面:
    iOS10中由于錄制作為一個外部的extension,可以供所有系統(tǒng)中app使用惶楼,所以不能直接啟動這個錄制的進程右蹦。需要首先啟動支持錄制的列表sheet诊杆,通過下面接口
[RPBroadcastActivityViewController loadBroadcastActivityViewControllerWithHandler:^(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error) {
        
        self.broadcastAVC = broadcastActivityViewController;
        self.broadcastAVC.delegate = self;
        [self presentViewController:self.broadcastAVC animated:YES completion:nil];
    }];

這里我們設(shè)置代理,通過代理方法的回調(diào)我們才能啟動錄制進程何陆。



  1. 反饋已完成配置
    當(dāng)我們點擊了上圖sheet中我們自己制作的extension時晨汹,系統(tǒng)將會啟動我們在創(chuàng)建extension時其中一個target對應(yīng)的進程:xxxSetupUI進程,這個進程通常用于讓用戶輸入一些信息來鑒權(quán)贷盲,或者自定義其他界面淘这,在啟動錄制進程之間插入的一個交互的頁面,當(dāng)然也可以為空巩剖,但是不插入交互頁面時铝穷,我們需要在相關(guān)進程中反饋信息:
#import "BroadcastSetupViewController.h"
@implementation BroadcastSetupViewController

- (void)userDidFinishSetup {
    NSURL *broadcastURL = [NSURL URLWithString:@"http://apple.com/broadcast/streamID"];
    NSDictionary *setupInfo = @{ @"broadcastName" : @"example" };
    // Tell ReplayKit that the extension is finished setting up and can begin broadcasting
    [self.extensionContext completeRequestWithBroadcastURL:broadcastURL setupInfo:setupInfo];
}

- (void)userDidCancelSetup {
    [self.extensionContext cancelRequestWithError:[NSError errorWithDomain:@"YourAppDomain" code:-1 userInfo:nil]];
}

- (void)viewDidLoad
{
}
- (void)viewWillAppear:(BOOL)animated
{
    [self userDidFinishSetup];
}

這里的BroadcastSetupViewController就在xxxSetupUI的target中,是這個target建立時自動生成的模板vc球及,我們可以在這里添加自定義方法來建立一個vc氧骤,添加view,用于展示信息吃引,或者用戶鑒權(quán)筹陵,然后根據(jù)用戶輸入情況,決定是否讓用戶使用錄制進程镊尺。
如果我們同意用戶使用錄制進程朦佩,這里我們主要需要告知調(diào)用的進程我們xxxSetupUI進程已經(jīng)完成設(shè)置,可以開始廣播了庐氮。其中viewDidLoad语稠、viewWillAppear兩個方法是我后填寫的,這里主要是需要調(diào)用[self userDidFinishSetup]; 方法來完成通知調(diào)用方弄砍。

注意:

  • 必須調(diào)用[self userDidFinishSetup] 仙畦,調(diào)用進程里面的didFinishWithBroadcastController (下一步啟動錄制時用到)才能回調(diào)
  • 必須在viewWillAppear中才能調(diào)用,在viewDidLoad中無法生效(都是坑啊......)

  1. 啟動錄制:
    上一步音婶,xxxSetupUI進程通過self.extensionContext 將其extension進程中的信息反饋回來慨畸,我們的RPBroadcastActivityViewController的代理方法將會回調(diào):
- (void)broadcastActivityViewController:(RPBroadcastActivityViewController *)broadcastActivityViewController didFinishWithBroadcastController:(RPBroadcastController *)broadcastController error:(NSError *)error
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [broadcastActivityViewController dismissViewControllerAnimated:YES completion:nil];
    });
    
    self.broadcastController = broadcastController;
    [broadcastController startBroadcastWithHandler:^(NSError * _Nullable error) {

    }];
}

回調(diào)中我們需要首先將sheet界面dismiss。 然后通過回調(diào)回來的broadcastController衣式,調(diào)用接口啟動錄制寸士,這里需要將broadcastController引用下來,用于我們在合適時機使用它結(jié)束錄制碴卧。


  1. 接收原始音視頻數(shù)據(jù)
    上一步啟動錄制成功后弱卡,我們就可以在錄制進程中接收到相關(guān)回調(diào)了,錄制進程在target創(chuàng)建時住册,模板生成了SampleHandler婶博,其中已經(jīng)復(fù)寫了相關(guān)錄制進行的方法:
@implementation SampleHandler
- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
    // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional. 
}
- (void)broadcastPaused {
    // User has requested to pause the broadcast. Samples will stop being delivered.
}
- (void)broadcastResumed {
    // User has requested to resume the broadcast. Samples delivery will resume.
}
- (void)broadcastFinished {
    // User has requested to finish the broadcast.
}
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
    
    switch (sampleBufferType) {
        case RPSampleBufferTypeVideo:
            // Handle video sample buffer
            break;
        case RPSampleBufferTypeAudioApp:
            // Handle audio sample buffer for app audio
            break;
        case RPSampleBufferTypeAudioMic:
            // Handle audio sample buffer for mic audio
            break;
        default:
            break;
    }
}

首先會回調(diào)到broadcastStartedWithSetupInfo方法,這里我們通常進行了一些初始化界弧,例如進程間通知的監(jiān)聽等凡蜻。下面的幾個方法broadcastPaused搭综、broadcastResumed、broadcastFinished表示了錄制的進程變化划栓,通常我們會在其中添加進程通知兑巾,通過源app這些變化。最后的processSampleBuffer方法就是最終采集到的音頻忠荞、視頻原始數(shù)據(jù)蒋歌。其中音頻未做混音,包括麥克音頻pcm和app音頻pcm委煤,而視頻輸出為yuv數(shù)據(jù)堂油。

注意:

  • iOS10只支持app內(nèi)容錄制,所以當(dāng)app切到后臺碧绞,錄制內(nèi)容將停止府框;
  • 手機鎖屏?xí)r,錄制進程將停止讥邻;
  • 這幾個方法中的代碼不能阻塞(例如寫文件等慢操作)迫靖,否則導(dǎo)致錄制進程停止;

iOS11:

到了iOS11時代兴使,蘋果終于開放了對錄制內(nèi)容的升級系宜,從iOS10的app內(nèi)升級到整個系統(tǒng)級別的錄制。但是對于隱私方面的考慮发魄,蘋果還是增加了很多用戶使用門檻盹牧。iOS11中如果只是錄制app內(nèi)的內(nèi)容,直接使用iOS10的方法即可励幼,但是如果錄制系統(tǒng)內(nèi)容汰寓,則變化較多:

  1. 啟動錄制:
  • 對于錄制app內(nèi)容,iOS11增加了新接口苹粟,可以直接啟動想要的錄制進程踩寇,跳過中間列表sheet在點擊選擇的過程:
+ (void)loadBroadcastActivityViewControllerWithPreferredExtension:(NSString * _Nullable)preferredExtension handler:(nonnull void(^)(RPBroadcastActivityViewController * _Nullable broadcastActivityViewController, NSError * _Nullable error))handler API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);
  • 對于錄制系統(tǒng)內(nèi)容,iOS11不允許開發(fā)直接調(diào)用api來啟動系統(tǒng)界別的錄制六水,必須是用戶通過手動啟動。啟動方法很復(fù)雜:
    用戶點擊進入手機設(shè)置頁面-> 控制中心-> 自定義 , 找到屏幕錄制的功能按鈕辣卒,將其添加到上方:添加成功后掷贾,我們可以在手機上滑喚出控制界面中發(fā)現(xiàn)這個啟動按鈕:



注意:

在上方彈出的列表中,需要選擇我們創(chuàng)建target對應(yīng)的app圖標(biāo)荣茫,才能使用我們的錄制進程進行采集想帅。

  1. 通知啟動app:
    由于iOS11錄制的啟動為手動操作,并且開發(fā)者啟動錄制進程的app也無從知道是否已經(jīng)啟動啡莉,所以通常我們會在broadcastStartedWithSetupInfo中發(fā)出進程級通知港准,告知app旨剥,錄制已經(jīng)啟動。
  2. 結(jié)束錄制:
    從iOS11的接口設(shè)計上浅缸,我們推斷結(jié)束估計也跟啟動錄制一樣轨帜,不開放給開發(fā)者,所以起初我以為只能通過用戶自己再次點擊啟動錄制按鈕衩椒,選擇停止蚌父,才能主動停止錄制,開發(fā)者無法干預(yù)這個過程毛萌。使用方法同啟動錄制類似苟弛,彈出列表中,直接點擊下面的停止阁将。
    但是很明顯膏秫,這種設(shè)計對用戶體驗影響很大,如果我們的app已經(jīng)停止了對采集的數(shù)據(jù)的顯示或者分發(fā)做盅,但是由于無法干預(yù)錄制進程缤削,那個進程將持續(xù)在工作,最直觀體現(xiàn)在手機導(dǎo)航欄上方綠條(與手機通話時同樣的機制)言蛇,直到后來在RPBroadcastSampleHandler的方法里面發(fā)現(xiàn)了這個:
/*! @abstract Method that should be called when broadcasting can not proceed due to an error. Calling this method will stop the broadcast and deliver the error back to the broadcasting app through RPBroadcastController's delegate.
    @param error NSError object that will be passed back to the broadcasting app through RPBroadcastControllerDelegate's broadcastController:didFinishWithError: method.
 */
- (void)finishBroadcastWithError:(NSError *)error;

這個方法就藏在上面列出的broadcastStartedWithSetupInfo僻他、broadcastPaused、broadcastResumed腊尚、broadcastFinished等方法后面吨拗,被我誤以為是一個錄制狀態(tài)的回調(diào)。那么在啟動錄制進程的app中怎么使用這個 finishBroadcastWithError 方法來結(jié)束錄制呢婿斥?
由于是手動啟動錄制進程劝篷,在啟動錄制進程的app中,我們沒有相關(guān)回調(diào)能獲取到這個方法的 RPBroadcastSampleHandler實例民宿,所以無法直接啟動娇妓。只能在錄制進程中RPBroadcastSampleHandler實例自己調(diào)用,那么我們就可以通過進程通信的方法活鹰,前面已經(jīng)介紹了啟動錄制時我們先注冊進程通知哈恰,然后在收到進程通知時,我們調(diào)用 [self finishBroadcastWithError: nil]; 即可志群,這里的error入?yún)⒆疟粒覀兛梢宰远x一個字典,用于將錯誤信息展示進程結(jié)束時彈出的alert窗口中給用戶锌云。


iOS12:

iOS11的復(fù)雜操作啟動屏幕錄制荠医,不知道阻塞了多少用戶的繼續(xù)使用。進入到2018年的iOS12,蘋果終于想通了彬向,replaykit也迎來了柳暗花明兼贡,開發(fā)者企盼的api控制啟動錄制終于來了!
啟動錄制:
iOS12還是會考慮用戶的感知性娃胆,要求開發(fā)者必須通過replaykit提供的 RPSystemBroadcastPickerView 來展示啟動的view遍希,然后通過點擊view上面的按鈕才能啟動:

#ifdef IPHONE_OS_VERSION_iOS12
        _broadPickerView = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(20, 5, 20, 20)];
        _broadPickerView.preferredExtension = @"com.cmcc.xiaoximeeting.ScreenRecordUpload";
        [self addSubview:_broadPickerView];
#endif

如上面代碼,可以通過屬性preferredExtension直接加載我們想要的錄制進程缕棵。

優(yōu)化:

雖然我們迎來更多自主控制權(quán)孵班,但是悲催的是這里我們還是要等待彈出界面點擊啟動,才能開始錄制招驴。如果我們這個錄制只是作為我們本身app的功能點篙程,如何繞過這個點擊操作呢? 可以考慮用一些trick方式:

  1. 首先我們將_broadPickerView的frame合理設(shè)置别厘,使其隱藏在某個按鈕(通常是自定義的啟動錄制)后面虱饿;
  2. 當(dāng)我們點擊到這個按鈕時 ,響應(yīng)鏈會將點擊也傳遞給這個_broadPickerView触趴,那么這時我們可以再把點擊傳遞給_broadPickerView上面的開始按鈕:
- (void)clickedOnStartRecordButton:(UIButton *)sender
{
#ifdef IPHONE_OS_VERSION_iOS12
    if (sender.tag == TAG_SHARESCREEN)
    {
        for (UIView *view in _broadPickerView.subviews)
        {
            if ([view isKindOfClass:[UIButton class]])
            {
// 注意ios12 時 對應(yīng)UIControlEventTouchDown氮发,不知從哪個版本開始已經(jīng)變成UIControlEventTouchUpInside,所以為了兼容性還是使用UIControlEventAllTouchEvents
                //[(UIButton*)view sendActionsForControlEvents:UIControlEventTouchDown];
                //[(UIButton*)view sendActionsForControlEvents:UIControlEventTouchUpInside];
                [(UIButton*)view sendActionsForControlEvents:UIControlEventAllTouchEvents];
            }
        }
    }
    else
    {
#endif
   // 其他邏輯代碼
#ifdef IPHONE_OS_VERSION_iOS12
}
#endif

注意:

sendActionsForControlEvents:UIControlEventTouchDown傳遞的參數(shù)必須是UIControlEventTouchDown冗懦,我之前傳的是upinside事件爽冕,一直失敗,直到嘗試了UIControlEventAllTouchEvents披蕉,發(fā)現(xiàn)可以成功颈畸,才發(fā)覺事件不對,逐個嘗試其他事件后没讲,才定位到是UIControlEventTouchDown
【 ---注意---: ios12 時 對應(yīng)UIControlEventTouchDown眯娱,不知從哪個版本開始已經(jīng)變成UIControlEventTouchUpInside,所以為了兼容性還是使用UIControlEventAllTouchEvents
】爬凑。

  1. 當(dāng)我們點擊上層的按鈕時徙缴,自動點擊系統(tǒng)的_broadPickerView上面的開始錄制按鈕。

總結(jié):

本文主要論述各個iOS系統(tǒng)版本使用replaykit實現(xiàn)屏幕的技術(shù)細節(jié)嘁信,其他需要考慮的點暫不詳述于样,還包括:

  1. 屏幕方向變化,可以考慮使用RPVideoSampleOrientationKey 對采集的yuv數(shù)據(jù)結(jié)構(gòu)解析出來方向潘靖;
  2. 屏幕鎖定的通知百宇,雖然進程級通知提供了鎖屏的通知,但是appstore不允許使用秘豹,可以考慮使用appdelegate的代理方法來判斷;
  3. 采集到數(shù)據(jù)結(jié)構(gòu)中的yuv的緩存空間昌粤,不能占用(例如NSData的initWithBytesNoCopy方法雖然可以快速生成NSData既绕,但是將占用這個緩存)啄刹,否則將導(dǎo)致進程停止;
  4. 系統(tǒng)提供錄制進程的內(nèi)存空間約為50M凄贩,我們在內(nèi)存占用時需要注意超過50M, 進程將被停止誓军;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市疲扎,隨后出現(xiàn)的幾起案子昵时,更是在濱河造成了極大的恐慌,老刑警劉巖椒丧,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壹甥,死亡現(xiàn)場離奇詭異,居然都是意外死亡壶熏,警方通過查閱死者的電腦和手機句柠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來棒假,“玉大人溯职,你說我怎么就攤上這事∶毖疲” “怎么了谜酒?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長妻枕。 經(jīng)常有香客問我僻族,道長,這世上最難降的妖魔是什么佳头? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任鹰贵,我火速辦了婚禮,結(jié)果婚禮上康嘉,老公的妹妹穿的比我還像新娘碉输。我一直安慰自己,他們只是感情好亭珍,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布敷钾。 她就那樣靜靜地躺著,像睡著了一般肄梨。 火紅的嫁衣襯著肌膚如雪阻荒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天众羡,我揣著相機與錄音侨赡,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛羊壹,可吹牛的內(nèi)容都是我干的蓖宦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼油猫,長吁一口氣:“原來是場噩夢啊……” “哼稠茂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起情妖,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤睬关,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后毡证,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體电爹,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年情竹,在試婚紗的時候發(fā)現(xiàn)自己被綠了藐不。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡秦效,死狀恐怖雏蛮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阱州,我是刑警寧澤挑秉,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站苔货,受9級特大地震影響犀概,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜夜惭,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一姻灶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诈茧,春花似錦产喉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鸥昏,卻和暖如春塞俱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吏垮。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工障涯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留罐旗,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓唯蝶,卻偏偏與公主長得像尤莺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子生棍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 上一篇闡述了調(diào)研結(jié)果,而我們常用的應(yīng)用場景就是錄制屏幕內(nèi)容媳谁,然后將內(nèi)容分享給他人(直播或錄播)涂滴。流程如下:1.被錄...
    杭研融合通信iOS閱讀 18,389評論 35 29
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,074評論 25 707
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料晴音? 從這篇文章中你...
    hw1212閱讀 12,714評論 2 59
  • |柔纵,耐心聽同事說話 2,晚上參加朋友孩子的百天宴锤躁,看到共同熟人給的禮錢比自己多身體有點不舒服搁料,想早早散席。又遇到不...
    3d7af0d91fde閱讀 250評論 0 0
  • 憶農(nóng)校 劉陽 我 1980年參加高考系羞,...
    鹿門書院閱讀 847評論 0 4