UIWebView白屏的監(jiān)控&監(jiān)控JS文件加載

祭出demo
現(xiàn)在很多App都改用h5來處理了咕别。使用過程中引發(fā)了很多白屏現(xiàn)象技健,很多種原因會引起白屏。比如網(wǎng)絡(luò)不通惰拱,JS加載慢雌贱,加載失敗,JS文件缺失等。

就我測試看帽芽,h5沒有本地化的,網(wǎng)絡(luò)不通翔冀,頁面白屏非常正常导街,這個都不用去監(jiān)控了。JS文件不存在這種情況的可能性也不大纤子,如果JS文件缺失導(dǎo)致白屏搬瑰,已經(jīng)構(gòu)成了生產(chǎn)事故了。

我在這里講講JS加載慢和JS加載失敗導(dǎo)致的白屏現(xiàn)象控硼。

1泽论、如何監(jiān)控到JS文件 加載

通過監(jiān)控UIWebView里的網(wǎng)絡(luò)請求來監(jiān)控js文件的加載

1.1、創(chuàng)建NSURLProtocol的子類

@interface QLRainURLProtocol : NSURLProtocol

@end

@implementation QLRainURLProtocol
@end

1.2卡乾、 從所有請求里甄別到j(luò)s文件的請求

//對js文件做監(jiān)控
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    
    NSString * urlstring = [request.URL absoluteString];
    
    if ([urlstring containsString:@".js"]) {
        //看看是否已經(jīng)處理過了翼悴,防止無限循環(huán)
        if ([NSURLProtocol propertyForKey:rainMarkKey inRequest:request]) {
            return NO;
        }
        return YES;
    }
    return NO;
}

1.3、 對URLRequest做處理幔妨,同時標(biāo)記處理

//我們可以對request進行處理鹦赎。。最后返回一個處理后的request實例误堡。
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    NSMutableURLRequest *mutableReqeust = [request mutableCopy];
    [mutableReqeust willChangeValueForKey:@"timeoutInterval"];
    mutableReqeust.timeoutInterval = 30.0f;//設(shè)置超時時間為30秒,經(jīng)測試沒什么用古话。
    [mutableReqeust didChangeValueForKey:@"timeoutInterval"];
    [NSURLProtocol setProperty:@YES
                        forKey:rainMarkKey
                     inRequest:mutableReqeust];
    return [mutableReqeust copy];
}

1.4、開始加載

- (void)startLoading
{
    self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:YES];
}

1.5锁施、 加載結(jié)束

- (void)stopLoading {
    //因為要處理失敗的文件陪踩,所以就不在這里移除失敗的內(nèi)容了。
    [self.connection cancel];
}

1.6悉抵、發(fā)起connection的代理方法

#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    
    [self.client URLProtocol:self didFailWithError:error];
}

- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection{
    return YES;
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
    [self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];
}

- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    [self.client URLProtocol:self didCancelAuthenticationChallenge:challenge];
}


#pragma mark - NSURLConnectionDataDelegate
-(NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response{
    if (response != nil) {
        [self.client URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
    }
    return request;
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.client URLProtocol:self didLoadData:data];
}

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                  willCacheResponse:(NSCachedURLResponse *)cachedResponse {
    return cachedResponse;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    
    
    [[self client] URLProtocolDidFinishLoading:self];
}
/*
每個connection都有個clinet 通過client可以把請求過程中的各種狀態(tài)和數(shù)據(jù)轉(zhuǎn)發(fā)出去肩狂,交給原始請求。
*/

到這里對js文件請求的監(jiān)控都能獲取到了基跑。

2婚温、如何判斷JS文件加載慢
JS 文件是否加載慢,可以通過抓包查看媳否,如下圖


js文件抓包

從上面的圖片可以看到vendor.*****.js這個文件花了23秒栅螟。js文件沒加載完成前,客戶端上看到的現(xiàn)象就是白屏的篱竭。
我們可以判斷JS文件加載超過5秒力图,我們就認(rèn)為是慢加載了。其實超過1秒的加載就可以認(rèn)為是慢加載掺逼,因為一個文件1秒吃媒,10個文件就是10秒了,對用戶講,10秒已經(jīng)很長時間了赘那。

3刑桑、如何判斷JS文件加載失敗

在下面的方法里可以知道JS文件加載失敗了

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    
    [self.client URLProtocol:self didFailWithError:error];
}
/*
這里可以監(jiān)控哪個JS文件加載失敗了
*/

4、如何跟蹤所有的JS文件加載
我想了一個策略募舟,對每個JS請求都做一個標(biāo)記祠斧,并且另起一個單例來管理這些標(biāo)記。這樣就不會混亂拱礁,不知道哪個請求是哪個文件的了琢锋。

- (void)startLoading
{
    self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:YES];
    
    NSString *fileName = [self.request.URL.absoluteString lastPathComponent];
    NSString *markKey = [fileName stringByAppendingFormat:@"%@_%ld",fileName,(long)[UrlProtocolManager shareUrlProtocolManager].requestTag];
    self.remarkKey = markKey;
    [UrlProtocolManager shareUrlProtocolManager].requestTag++;
    [[UrlProtocolManager shareUrlProtocolManager] addRequestWithRemarkKey:markKey andFileName:fileName];
    
}

5、上傳監(jiān)控到的JS文件名
當(dāng)你掃描請求時呢灶,發(fā)現(xiàn)請求超過5秒的就開始上報文件名吴超,發(fā)現(xiàn)請求失敗的也上報文件名。

6鸯乃、管理請求和標(biāo)記的單例
它的作用是記錄請求標(biāo)記鲸阻,同時記錄請求的開始時間戳。

@interface UrlProtocolManager:NSObject
@property (nonatomic, strong)NSMutableDictionary <NSString *,NSDictionary*>*requestDictionary;//存儲請求標(biāo)識
@property (nonatomic, assign)NSInteger requestTag;
@property (nonatomic, strong) dispatch_source_t timer;
@end

@implementation UrlProtocolManager
+ (instancetype)shareUrlProtocolManager {
    static UrlProtocolManager *shareInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shareInstance = [[UrlProtocolManager alloc]init];
    });
    return shareInstance;
}
-(instancetype)init {
    self = [super init];
    if (self) {
        self.requestDictionary = @{}.mutableCopy;
        self.requestTag = 0;
        [self createTimer];
    }
    return self;
}

//啟動計時器
- (void)createTimer {
    NSLog(@"%s",__FUNCTION__);
    NSInteger timeout = 7;//7秒執(zhí)行一次
    //獲取全局隊列
    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //創(chuàng)建一個定時器飒责,并將定時器的任務(wù)交給全局隊列執(zhí)行(并行赘娄,不會造成主線程阻塞)
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, global);
    
    // 設(shè)置觸發(fā)的間隔時間
    dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    //timeout * NSEC_PER_SEC  代表設(shè)置定時器觸發(fā)的時間間隔為timeout s
    //0 * NSEC_PER_SEC    代表時間允許的誤差是 0s
    __weak typeof(self)weakSelf = self;
    dispatch_source_set_event_handler(_timer, ^{
        NSLog(@"start timer");
        NSMutableString *resultStr = @"".mutableCopy;
        NSArray *allKeys = weakSelf.requestDictionary.allKeys;
        NSInteger allKeyCount = allKeys.count;
        if (allKeyCount > 0) {
            for (NSInteger i = 0; i < allKeyCount; i ++) {
                NSString *dicKey = allKeys[i];
                NSDictionary *dic = [weakSelf.requestDictionary objectForKey:dicKey];
                if (dic != nil) {
                    NSNumber *timeMarkLong = [dic objectForKey:fileUrlProtocolRecodeTimeKey];
                    NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
                    if (currentTime - [timeMarkLong doubleValue] >= sexSeconds ) {
                        NSString *fileName = [dic objectForKey:fileUrlProtocolNameKey];
                        [resultStr appendFormat:@"%@_",fileName];
                        [weakSelf.requestDictionary removeObjectForKey:dicKey];//監(jiān)控到的key記錄之后就移除掉
                    }
                }
                
            }
            NSLog(@"resultStr is \n%@",resultStr);
        }
        
    });
    
    dispatch_resume(_timer);
    
}

- (void)addRequestWithRemarkKey:(NSString *)remarkKey andFileName:(NSString *)fileName {
    NSTimeInterval timeMark = [[NSDate date] timeIntervalSince1970];
    NSDictionary *fileDic = @{
                              fileUrlProtocolNameKey:fileName,
                              fileUrlProtocolRecodeTimeKey:@(timeMark)
                              };
    [[UrlProtocolManager shareUrlProtocolManager].requestDictionary setObject:fileDic forKey:remarkKey];
}

- (void)removeRequestWithRemarkKey:(NSString *)remarkKey {
    [[UrlProtocolManager shareUrlProtocolManager].requestDictionary removeObjectForKey:remarkKey];
}

- (void)destroyTimer {
    dispatch_source_cancel(_timer);
}

@end

7、開發(fā)過程中遇到的坑

7.1宏蛉、監(jiān)控到的請求會被循環(huán)監(jiān)控遣臼,進入死循環(huán)
通過在+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request里監(jiān)控到的請求加個標(biāo)識,在+ (BOOL)canInitWithRequest:(NSURLRequest *)request里識別這個標(biāo)識拾并,當(dāng)發(fā)現(xiàn)有標(biāo)識時就不監(jiān)控了揍堰,但是沒有標(biāo)識的,就告知需要監(jiān)控嗅义。這樣就不會死循環(huán)了屏歹。
7.2、定時掃描監(jiān)控的標(biāo)志請求之碗。
當(dāng)時想過用NSTimer蝙眶,但是NSTimer會被用戶的UI操作中斷,所以就選了GCD里使用Timer這樣就不會中斷了褪那。我的demo里是每7秒掃描一次標(biāo)志請求幽纷,發(fā)現(xiàn)請求超過6秒的就定義為慢加載,同時移除該請求標(biāo)志博敬,避免重復(fù)識別友浸。

祭出demo

更新時間: 2018-08-22

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市偏窝,隨后出現(xiàn)的幾起案子收恢,更是在濱河造成了極大的恐慌武学,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伦意,死亡現(xiàn)場離奇詭異火窒,居然都是意外死亡,警方通過查閱死者的電腦和手機驮肉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門沛鸵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缆八,你說我怎么就攤上這事〖埠矗” “怎么了奈辰?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乱豆。 經(jīng)常有香客問我奖恰,道長,這世上最難降的妖魔是什么宛裕? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任瑟啃,我火速辦了婚禮,結(jié)果婚禮上揩尸,老公的妹妹穿的比我還像新娘蛹屿。我一直安慰自己,他們只是感情好岩榆,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布错负。 她就那樣靜靜地躺著,像睡著了一般勇边。 火紅的嫁衣襯著肌膚如雪犹撒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天粒褒,我揣著相機與錄音识颊,去河邊找鬼。 笑死奕坟,一個胖子當(dāng)著我的面吹牛祥款,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播执赡,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼镰踏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了沙合?” 一聲冷哼從身側(cè)響起奠伪,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤跌帐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后绊率,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谨敛,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年滤否,在試婚紗的時候發(fā)現(xiàn)自己被綠了脸狸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡藐俺,死狀恐怖炊甲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情欲芹,我是刑警寧澤卿啡,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站菱父,受9級特大地震影響颈娜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浙宜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一官辽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粟瞬,春花似錦同仆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至清酥,卻和暖如春扶镀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背焰轻。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工臭觉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辱志。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓蝠筑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親揩懒。 傳聞我的和親對象是個殘疾皇子什乙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

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