svg动画导致持续占用CPU
1、在一次性能优化中突然发现一个svg矢量图动画导致CPU持续占用的问题,该svg在web中使用,
即使webview释放之后,CPU依然占用达到10%,6s+上测试结果
svg如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | < svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="none" x="0px" y="0px" width="25px" height="25px" viewBox="0 0 25 25"> < path stroke="#777777" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" fill="none" stroke-dasharray="64,6" stroke-dashoffset="0" d=" M 3.4 5.65 Q 9.5 2.2 11.2 1.15 12.6 0.4 14 1.3 L 21.65 5.75 Q 22.9 6.65 22.85 8.2 L 22.85 17.4 Q 22.8 18.6 21.5 19.25 14.85 23.25 13.6 23.9 12.3 24.55 10.8 23.7 4.3 19.9 3.1 19.1 2.35 18.6 2.15 17.75 L 2.1 7.95 Q 2.1 7.25 2.75 6.35 L 3.4 5.65 Z"> < animate attributeName="stroke-dashoffset" begin="0s" dur="1.5s" from="0" to="70" repeatCount="indefinite"/> </ path > < path stroke="#777777" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" fill="none" stroke-dasharray="37,4" stroke-dashoffset="41" d=" M 7.15 8.35 L 11.7 5.7 Q 12.55 5.25 13.35 5.8 L 17.85 8.4 Q 18.6 8.95 18.55 9.85 L 18.55 15.25 Q 18.5 15.95 17.75 16.35 13.85 18.7 13.1 19.05 12.35 19.45 11.45 18.95 L 6.95 16.25 Q 6.55 16 6.4 15.45 L 6.35 9.7 Q 6.35 9.25 6.75 8.75 L 7.15 8.35 Z"> < animate attributeName="stroke-dashoffset" begin="0s" dur="1.5s" from="41" to="0" repeatCount="indefinite"/> </ path > </ 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的框架,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | + ( 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方法中,实现请求,并返回结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | - ( void )startLoading { self .clientRunLoop = CFRunLoopGetCurrent(); NSURLRequest * request = self .request; id < NSURLProtocolClient > 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 ; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架