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;
id client = 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;
}