1、在一次性能优化中突然发现一个svg矢量图动画导致CPU持续占用的问题,该svg在web中使用,
即使webview释放之后,CPU依然占用达到10%,6s+上测试结果
svg如下所示:
注意该动画的时间是无限长,指定时间结束之后,CPU将不再占用,因此这可能是webkit中的bug。
svg是HTML5中的标准,每个webview都应该支持。未测试WKWebView兼容情况
2、问题解决
可以看到该svg是由web中的css文件引用,真正的请求是:https://egame.gtimg.cn/club/pgg/v2.5/v2/img/global/loading-fecf2b8eea.svg
为了可以将这个问题解决掉,在前端暂无法修复的情况下,我选择使用NSURLProtocol中的方法,hook该请求,并返回404
具体操作使用了一个OHHTTPStubs的框架,代码如下:
+ (void)addLoadingSVG_Patch{ [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { NSString *url = [request.URL description]; if([url hasSuffix:@"svg"] && [[url lastPathComponent] hasPrefix:@"loading"]) { QG_Event(MODULE_LIVE_ASS, @"patch fuck loading animated svg request!!! = %@", request); return YES; } return NO; } withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) { return [OHHTTPStubsResponse responseWithFileAtPath:@"" statusCode:404 headers:@{@"Content-Type":@"image/jpeg"}]; }];}
问题解决
3、NSURLProtocol是苹果的一个协议,其中通过 + (BOOL)canInitWithRequest:(NSURLRequest *)request; 这个方法返回是否要手动接管这个请求
通常web的离线缓存基于此实现,接管请求之后,需要自己实现请求的代码,并将返回结果通过NSURLProtocol的client对象回调给上层,例如:
在startLoading方法中,实现请求,并返回结果。
- (void)startLoading{ self.clientRunLoop = CFRunLoopGetCurrent(); NSURLRequest* request = self.request; idclient = self.client; if (!self.stub) { NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: @"It seems like the stub has been removed BEFORE the response had time to be sent.", NSLocalizedFailureReasonErrorKey, @"For more info, see https://github.com/AliSoftware/OHHTTPStubs/wiki/OHHTTPStubs-and-asynchronous-tests", NSLocalizedRecoverySuggestionErrorKey, request.URL, // Stop right here if request.URL is nil NSURLErrorFailingURLErrorKey, nil]; NSError* error = [NSError errorWithDomain:@"OHHTTPStubs" code:500 userInfo:userInfo]; [client URLProtocol:self didFailWithError:error]; if (OHHTTPStubs.sharedInstance.afterStubFinishBlock) { OHHTTPStubs.sharedInstance.afterStubFinishBlock(request, self.stub, nil, error); } return; } OHHTTPStubsResponse* responseStub = self.stub.responseBlock(request); if (OHHTTPStubs.sharedInstance.onStubActivationBlock) { OHHTTPStubs.sharedInstance.onStubActivationBlock(request, self.stub, responseStub); } if (responseStub.error == nil) { NSHTTPURLResponse* urlResponse = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:responseStub.statusCode HTTPVersion:@"HTTP/1.1" headerFields:responseStub.httpHeaders]; // Cookies handling if (request.HTTPShouldHandleCookies && request.URL) { NSArray* cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:responseStub.httpHeaders forURL:request.URL]; if (cookies) { [NSHTTPCookieStorage.sharedHTTPCookieStorage setCookies:cookies forURL:request.URL mainDocumentURL:request.mainDocumentURL]; } } NSString* redirectLocation = (responseStub.httpHeaders)[@"Location"]; NSURL* redirectLocationURL; if (redirectLocation) { redirectLocationURL = [NSURL URLWithString:redirectLocation]; } else { redirectLocationURL = nil; } [self executeOnClientRunLoopAfterDelay:responseStub.requestTime block:^{ if (!self.stopped) { // Notify if a redirection occurred if (((responseStub.statusCode > 300) && (responseStub.statusCode < 400)) && redirectLocationURL) { NSURLRequest* redirectRequest = [NSURLRequest requestWithURL:redirectLocationURL]; [client URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:urlResponse]; if (OHHTTPStubs.sharedInstance.onStubRedirectBlock) { OHHTTPStubs.sharedInstance.onStubRedirectBlock(request, redirectRequest, self.stub, responseStub); } } // Send the response (even for redirections) [client URLProtocol:self didReceiveResponse:urlResponse cacheStoragePolicy:NSURLCacheStorageNotAllowed]; if(responseStub.inputStream.streamStatus == NSStreamStatusNotOpen) { [responseStub.inputStream open]; } [self streamDataForClient:client withStubResponse:responseStub completion:^(NSError * error) { [responseStub.inputStream close]; NSError *blockError = nil; if (error==nil) { [client URLProtocolDidFinishLoading:self]; } else { [client URLProtocol:self didFailWithError:responseStub.error]; blockError = responseStub.error; } if (OHHTTPStubs.sharedInstance.afterStubFinishBlock) { OHHTTPStubs.sharedInstance.afterStubFinishBlock(request, self.stub, responseStub, blockError); } }]; } }]; } else { // Send the canned error [self executeOnClientRunLoopAfterDelay:responseStub.responseTime block:^{ if (!self.stopped) { [client URLProtocol:self didFailWithError:responseStub.error]; if (OHHTTPStubs.sharedInstance.afterStubFinishBlock) { OHHTTPStubs.sharedInstance.afterStubFinishBlock(request, self.stub, responseStub, responseStub.error); } } }]; }}- (void)stopLoading{ self.stopped = YES;}