精利手表网站,哪里有免费的网站推广服务,室内设计学徒,wordpress评论时选填去年年底做启动优化时#xff0c;有个比较好玩的 case 给大家分享下#xff0c;希望大家能从我的分享里 get 到我在做一些问题排查修复时是怎么看上去又low又土又高效的。
1. 现象
在我们使用 Perfetto 进行app 启动过程性能观测时#xff0c;在 UI 线程发现了一段 几十毫…去年年底做启动优化时有个比较好玩的 case 给大家分享下希望大家能从我的分享里 get 到我在做一些问题排查修复时是怎么看上去又low又土又高效的。
1. 现象
在我们使用 Perfetto 进行app 启动过程性能观测时在 UI 线程发现了一段 几十毫秒接近百毫秒 的非预期Webview初始化的耗时机器环境小米10 pro在线上用户机器上这段代码执行时间可能会更长。 为什么说非预期呢
首页没有WebView的使用、预加载X5内核的初始化也在启动流程之后
2. 顺藤摸瓜
一般当我们发现了这种问题后我们应该如何应对呢
- 搞懂流程如果在排查启动性能时发现了不符合预期的主(子)线程耗时第一步就是摸清楚这段耗时代码是怎么被调用的。
- 见招拆招当我们知道代码如何被调用的之后就可以想办法进行修复工作如果是因为项目代码在错误的时机被调用那就延后或者去除相关调用
WebViewChromiumAwInit.java
那我们开始第一步搞懂流程我们能看到图中耗时代码块被调用的系统方法是
WebViewChromiumAwInit.startChromiumLocked由于 Perfetto 并看不到 App 相关的堆栈信息所以我们无法直接知道到底是哪行代码引起的。
那我们就去跟跟 webview 源码看看具体情况点进 WebViewChromiumAwInit.java 页面看相关代码发现 startChromiumLocked 是被 ensureChromiumStartedLocked 方法调用的
// This method is not private only because the downstream subclass needs to access it,
// it shouldnt be accessed from anywhere else.
/* package */
void ensureChromiumStartedLocked(boolean fromThreadSafeFunction) {assert Thread.holdsLock(mLock);if (mInitState INIT_FINISHED) { // Early-out for the common case.return;}if (mInitState INIT_NOT_STARTED) {// If were the first thread to enter ensureChromiumStartedLocked, we need to determine// which thread will be the UI thread; declare init has started so that no other thread// will try to do this.mInitState INIT_STARTED;setChromiumUiThreadLocked(fromThreadSafeFunction);}if (ThreadUtils.runningOnUiThread()) {// If we are currently running on the UI thread then we must do init now. If there was// already a task posted to the UI thread from another thread to do it, it will just// no-op when it runs.startChromiumLocked();return;}mIsPostedFromBackgroundThread true;// If were not running on the UI thread (because init was triggered by a thread-safe// function), post init to the UI thread, since init is *not* thread-safe.AwThreadUtils.postToUiThreadLooper(new Runnable() {Overridepublic void run() {synchronized (mLock) {startChromiumLocked();}}});// Wait for the UI thread to finish init.while (mInitState ! INIT_FINISHED) {try {mLock.wait();} catch (InterruptedException e) {// Keep trying; we cant abort init as WebView APIs do not declare that they throw// InterruptedException.}}}那么 ensureChromiumStartedLocked 方法又是被谁调用的呢我们在 WebViewChromiumAwInit.java 文件里大致找一下就能找到以下嫌疑人第一反应是“这也太多了吧这咋排查啊”。
-getAwTracingController
-getAwProxyController
-startYourEngines
-getStatics
-getDefaultGeolocationPermissions
-getDefaultServiceWorkerController
-getWebIconDatabase
-getDefaultWebStorage
-getDefaultWebViewDatabasepublic class WebViewChromiumAwInit {public AwTracingController getAwTracingController() {synchronized (mLock) {if (mAwTracingController null) {ensureChromiumStartedLocked(true);}}return mAwTracingController;}public AwProxyController getAwProxyController() {synchronized (mLock) {if (mAwProxyController null) {ensureChromiumStartedLocked(true);}}return mAwProxyController;}void startYourEngines(boolean fromThreadSafeFunction) {synchronized (mLock) {ensureChromiumStartedLocked(fromThreadSafeFunction);}}public SharedStatics getStatics() {synchronized (mLock) {if (mSharedStatics null) {ensureChromiumStartedLocked(true);}}return mSharedStatics;}public GeolocationPermissions getDefaultGeolocationPermissions() {synchronized (mLock) {if (mDefaultGeolocationPermissions null) {ensureChromiumStartedLocked(true);}}return mDefaultGeolocationPermissions;}public AwServiceWorkerController getDefaultServiceWorkerController() {synchronized (mLock) {if (mDefaultServiceWorkerController null) {ensureChromiumStartedLocked(true);}}return mDefaultServiceWorkerController;}public android.webkit.WebIconDatabase getWebIconDatabase() {synchronized (mLock) {ensureChromiumStartedLocked(true);if (mWebIconDatabase null) {mWebIconDatabase new WebIconDatabaseAdapter();}}return mWebIconDatabase;}public WebStorage getDefaultWebStorage() {synchronized (mLock) {if (mDefaultWebStorage null) {ensureChromiumStartedLocked(true);}}return mDefaultWebStorage;}public WebViewDatabase getDefaultWebViewDatabase(final Context context) {synchronized (mLock) {ensureChromiumStartedLocked(true);if (mDefaultWebViewDatabase null) {mDefaultWebViewDatabase new WebViewDatabaseAdapter(mFactory,HttpAuthDatabase.newInstance(context, HTTP_AUTH_DATABASE_FILE),mDefaultBrowserContext);}}return mDefaultWebViewDatabase;}
}WebViewChromiumFactoryProvider.java
经过上面对的简单分析我们大概知道了WebViewChromiumAwInit.startChromiumLocked是被 ensureChromiumStartedLocked 方法调用的而ensureChromiumStartedLocked 方法会被以下几个方法调用那我们接下来的工作就需要找到下面这几个方法到底被谁调用了。
-getAwTracingController
-getAwProxyController
-startYourEngines
-getStatics
-getDefaultGeolocationPermissions
-getDefaultServiceWorkerController
-getWebIconDatabase
-getDefaultWebStorage
-getDefaultWebViewDatabase到这里分享我的一个土方法我们要找到底哪个地方会调用这些方法那就找一个不认识的、看上去不会被别人提起的方法进行 google我们一眼就选中了getDefaultServiceWorkerController 方法没办法谁叫我不认识你呢。虽然方法笨但是架不住效率啊。于是乎我们把它揪出来了 - WebViewChromiumFactoryProvider.java 我们大概了解一下 WebViewChromiumFactoryProvider 大概是个什么角色WebViewChromiumFactoryProvider 实现了 WebViewFactoryProvider 接口简单理解就是 WebView 的工厂App 如果要创建 WebView就会通过 WebViewFactoryProvider 接口的实现类进行 createWebView所以其实就是个工厂模式。通过抽象规范 api保证兼容性和可移植性可扩展性。
我们在这个文件中也如愿以偿的看到了上面列出来的几个方法的调用。WebViewChromiumFactoryProvider 在接口方法的实现中调用了WebViewChromiumAwInit 里的一系列方法如下
//WebViewChromiumFactoryProvider.java
Override
public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {return new WebViewChromium(this, webView, privateAccess, mShouldDisableThreadChecking);
}//我们截取一段Overridepublic GeolocationPermissions getGeolocationPermissions() {return mAwInit.getDefaultGeolocationPermissions();}Overridepublic CookieManager getCookieManager() {return mAwInit.getDefaultCookieManager();}Overridepublic ServiceWorkerController getServiceWorkerController() {synchronized (mAwInit.getLock()) {if (mServiceWorkerController null) {mServiceWorkerController new ServiceWorkerControllerAdapter(mAwInit.getDefaultServiceWorkerController());}}return mServiceWorkerController;}Overridepublic TokenBindingService getTokenBindingService() {return null;}Overridepublic android.webkit.WebIconDatabase getWebIconDatabase() {return mAwInit.getWebIconDatabase();}Overridepublic WebStorage getWebStorage() {return mAwInit.getDefaultWebStorage();}Overridepublic WebViewDatabase getWebViewDatabase(final Context context) {return mAwInit.getDefaultWebViewDatabase(context);}WebViewDelegate getWebViewDelegate() {return mWebViewDelegate;}WebViewContentsClientAdapter createWebViewContentsClientAdapter(WebView webView,Context context) {try (ScopedSysTraceEvent e ScopedSysTraceEvent.scoped(WebViewChromiumFactoryProvider.insideCreateWebViewContentsClientAdapter)) {return new WebViewContentsClientAdapter(webView, context, mWebViewDelegate);}}void startYourEngines(boolean onMainThread) {try (ScopedSysTraceEvent e1 ScopedSysTraceEvent.scoped(WebViewChromiumFactoryProvider.startYourEngines)) {mAwInit.startYourEngines(onMainThread);}}boolean hasStarted() {return mAwInit.hasStarted();}3. 确定问题
我们上面通过阅读 WebViewChromiumFactoryProvider.java、WebViewChromiumAwInit.java 这两个文件具体代码实现有了一个比较清晰的思路。
App 在初始化过程中调用到了 WebViewFactoryProvider 接口实现类的某个方法这个方法调用了 WebViewChromiumAwInit 的下面方法中的其中一个或者多个。那其实问题就清晰了我们只需要找到我们 app 启动阶段到底哪行代码会调用到 WebViewFactoryProvider 接口某个接口方法就行。
-getAwTracingController
-getAwProxyController
-startYourEngines
-getStatics
-getDefaultGeolocationPermissions
-getDefaultServiceWorkerController
-getWebIconDatabase
-getDefaultWebStorage
-getDefaultWebViewDatabase由于 WebView 的代码并不会打包进 app 里App 用的 WebView 内核都是用的 Android 系统负责内置、升级的 WebView 内核代码所以通过 transform 的方式进行 hook 调用是不行的这里我们采用动态代理的方式对 WebViewFactoryProvider 接口方法进行 hook我们通过动态代理生成一个 proxy 对象通过反射的方式替换掉 android.webkit.WebViewFactory 的 sProviderInstance 对象。 ##WebViewFactorySystemApipublic final class WebViewFactory{//...UnsupportedAppUsageprivate static WebViewFactoryProvider sProviderInstance;//...}##动态代理try {Class clas Class.forName(android.webkit.WebViewFactory);Method method clas.getDeclaredMethod(getProvider);method.setAccessible(true);Object obj method.invoke(null);Object hookService Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getSuperclass().getInterfaces(),new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Log.d(zttt, hookService method: method.getName());new RuntimeException(method.getName()).printStackTrace();return method.invoke(obj, args);}});Field field clas.getDeclaredField(sProviderInstance);field.setAccessible(true);field.set(null, hookService);} catch (Exception e) {e.printStackTrace();}替换掉 sProviderInstance 之后我们就可以在我们的代理逻辑中加上断点来进行调试最终找到了造成 WebView非预期初始化的始作俑者WebSettings.getDefaultUserAgent。 4. 解决问题
事情到这里就好解决了只需要对 WebSettings.getDefaultUserAgent 进行编译期的Hook重定向到带缓存defaultUserAgent 的相关方法就行了本地有缓存则直接读取本地没有则立即读取得益于之前我在项目中实现的使用方便的 配置化 Hook 框架这种小打小闹的 Hook 工作不到一分钟就能完成。 当然这里还需要考虑一个问题那就是当用户机器的 defaultUserAgent 发生变化之后怎么才能及时的更新本地缓存以及网络请求中用上新的defaultUserAgent。我们的做法是 当本地没有缓存时立刻调用 WebSettings.getDefaultUserAgent 拿值并更新缓存 每次App启动阶段结束之后会在子线程中去调用WebSettings.getDefaultUserAgent 拿值并更新缓存。
这样处理之后将 defaultUserAgent 发生变化之后的影响最小化系统 WebView 升级本身就是极度不频繁的事情在这种 case 下我们舍弃了下一次 App 打开前几个网络请求的 defaultUserAgent 正确性也是合理的这也是我们考量 「风险收益比」的一个经典case。
5. 确认问题被解决
通过上述 hook我们重新打包 run 一遍 app在启动阶段已经观察不到相关耗时了。
搞定收工不仅解决问题效率高写博客也效率高一会就整完了简直就像是季度绩效考核前的产品出方案和上线的效率就一个字嗖。
为了帮助到大家更好的全面清晰的掌握好性能优化准备了相关的核心笔记还含底层逻辑https://qr18.cn/FVlo89
性能优化核心笔记https://qr18.cn/FVlo89
启动优化 内存优化 UI优化 网络优化 Bitmap优化与图片压缩优化https://qr18.cn/FVlo89 多线程并发优化与数据传输效率优化 体积包优化
《Android 性能监控框架》https://qr18.cn/FVlo89 《Android Framework学习手册》https://qr18.cn/AQpN4J
开机Init 进程开机启动 Zygote 进程开机启动 SystemServer 进程Binder 驱动AMS 的启动过程PMS 的启动过程Launcher 的启动过程Android 四大组件Android 系统服务 - Input 事件的分发过程Android 底层渲染 - 屏幕刷新机制源码分析Android 源码分析实战
文章转载自: http://www.morning.jnhhc.cn.gov.cn.jnhhc.cn http://www.morning.fcpjq.cn.gov.cn.fcpjq.cn http://www.morning.rwtlj.cn.gov.cn.rwtlj.cn http://www.morning.xkyqq.cn.gov.cn.xkyqq.cn http://www.morning.yxkyl.cn.gov.cn.yxkyl.cn http://www.morning.qfrsm.cn.gov.cn.qfrsm.cn http://www.morning.zwgbz.cn.gov.cn.zwgbz.cn http://www.morning.dbphz.cn.gov.cn.dbphz.cn http://www.morning.yntsr.cn.gov.cn.yntsr.cn http://www.morning.jfbpf.cn.gov.cn.jfbpf.cn http://www.morning.sbpt.cn.gov.cn.sbpt.cn http://www.morning.spdyl.cn.gov.cn.spdyl.cn http://www.morning.pfgln.cn.gov.cn.pfgln.cn http://www.morning.qwwcf.cn.gov.cn.qwwcf.cn http://www.morning.cjmmn.cn.gov.cn.cjmmn.cn http://www.morning.yfffg.cn.gov.cn.yfffg.cn http://www.morning.dygqq.cn.gov.cn.dygqq.cn http://www.morning.ltpzr.cn.gov.cn.ltpzr.cn http://www.morning.krjrb.cn.gov.cn.krjrb.cn http://www.morning.jzdfc.cn.gov.cn.jzdfc.cn http://www.morning.jbmbj.cn.gov.cn.jbmbj.cn http://www.morning.kqwsy.cn.gov.cn.kqwsy.cn http://www.morning.hymmq.cn.gov.cn.hymmq.cn http://www.morning.wkwds.cn.gov.cn.wkwds.cn http://www.morning.qkdbz.cn.gov.cn.qkdbz.cn http://www.morning.amonr.com.gov.cn.amonr.com http://www.morning.nyqnk.cn.gov.cn.nyqnk.cn http://www.morning.fnfxp.cn.gov.cn.fnfxp.cn http://www.morning.cnlmp.cn.gov.cn.cnlmp.cn http://www.morning.ptmsk.cn.gov.cn.ptmsk.cn http://www.morning.wgzgr.cn.gov.cn.wgzgr.cn http://www.morning.pwmpn.cn.gov.cn.pwmpn.cn http://www.morning.ylyzk.cn.gov.cn.ylyzk.cn http://www.morning.wbrf.cn.gov.cn.wbrf.cn http://www.morning.tplht.cn.gov.cn.tplht.cn http://www.morning.qpnb.cn.gov.cn.qpnb.cn http://www.morning.ngqty.cn.gov.cn.ngqty.cn http://www.morning.cybch.cn.gov.cn.cybch.cn http://www.morning.jtfcd.cn.gov.cn.jtfcd.cn http://www.morning.wzwyz.cn.gov.cn.wzwyz.cn http://www.morning.xckqs.cn.gov.cn.xckqs.cn http://www.morning.drnfc.cn.gov.cn.drnfc.cn http://www.morning.yrqb.cn.gov.cn.yrqb.cn http://www.morning.tturfsoc.com.gov.cn.tturfsoc.com http://www.morning.mkhwx.cn.gov.cn.mkhwx.cn http://www.morning.tnktt.cn.gov.cn.tnktt.cn http://www.morning.qzmnr.cn.gov.cn.qzmnr.cn http://www.morning.pplxd.cn.gov.cn.pplxd.cn http://www.morning.bqfpm.cn.gov.cn.bqfpm.cn http://www.morning.xjqhh.cn.gov.cn.xjqhh.cn http://www.morning.thmlt.cn.gov.cn.thmlt.cn http://www.morning.ggnfy.cn.gov.cn.ggnfy.cn http://www.morning.mprky.cn.gov.cn.mprky.cn http://www.morning.rkdw.cn.gov.cn.rkdw.cn http://www.morning.ftntr.cn.gov.cn.ftntr.cn http://www.morning.qrqg.cn.gov.cn.qrqg.cn http://www.morning.fplqh.cn.gov.cn.fplqh.cn http://www.morning.gqhgl.cn.gov.cn.gqhgl.cn http://www.morning.mehrim.com.gov.cn.mehrim.com http://www.morning.qclmz.cn.gov.cn.qclmz.cn http://www.morning.znnsk.cn.gov.cn.znnsk.cn http://www.morning.ttxnj.cn.gov.cn.ttxnj.cn http://www.morning.gyxwh.cn.gov.cn.gyxwh.cn http://www.morning.drywd.cn.gov.cn.drywd.cn http://www.morning.fddfn.cn.gov.cn.fddfn.cn http://www.morning.rrhfy.cn.gov.cn.rrhfy.cn http://www.morning.znlhc.cn.gov.cn.znlhc.cn http://www.morning.dqkrf.cn.gov.cn.dqkrf.cn http://www.morning.hhboyus.cn.gov.cn.hhboyus.cn http://www.morning.fdmfn.cn.gov.cn.fdmfn.cn http://www.morning.qwmpn.cn.gov.cn.qwmpn.cn http://www.morning.glxdk.cn.gov.cn.glxdk.cn http://www.morning.kstgt.cn.gov.cn.kstgt.cn http://www.morning.ghwdm.cn.gov.cn.ghwdm.cn http://www.morning.yqndr.cn.gov.cn.yqndr.cn http://www.morning.yqtry.cn.gov.cn.yqtry.cn http://www.morning.lxctl.cn.gov.cn.lxctl.cn http://www.morning.gjlml.cn.gov.cn.gjlml.cn http://www.morning.ahlart.com.gov.cn.ahlart.com http://www.morning.gyylt.cn.gov.cn.gyylt.cn