# VasSonicDemo **Repository Path**: mayuanAndroid/VasSonicDemo ## Basic Information - **Project Name**: VasSonicDemo - **Description**: Android Webview 大型H5 秒开方案探讨+VasSonic实现h5首页加速 - **Primary Language**: Android - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: https://blog.csdn.net/u013068887/article/details/95460159 - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2020-11-19 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # VasSonicDemo #### 介绍 Android Webview 大型H5 秒开方案探讨+VasSonic实现h5首页加速 前言 现在许多app都嵌入了H5页面, 然而WebView加载速度慢这个问题却一直影响着用户的体验, 所以本文就如何提高H5页面的加载速度展开讨论。 问题原因 首先我们需要知道为什么WebView的加载速度那么慢。H5页面的渲染速度其实主要取决于两个 js解析效率 如果js文件较多、解析比较复杂, 就会导致渲染速度较慢。或者手机的硬件性能比较差的话, 也会导致渲染速度比较慢。 页面资源的下载 一般加载一个H5页面, 都会产生较多的网络请求, 如图片、js文件、css文件等, 需要将这些资源都下载完成之后才能完成渲染, 这样也会导致页面渲染速度变慢 对于上面的第一点, 其实主要是由前端代码和手机硬件决定的, 因为我们这里讨论的是对于app的性能优化, 暂时不考虑, 所以我们可以从第二点做文章, 主要思路就是一些资源文件都使用App本地资源, 而不需要从网络下载, 从而提高页面的打开速度。 方案实现 腾讯出品的一个轻量级的高性能的Hybrid框架,专注于提升页面首屏加载速度,完美支持静态直出页面和动态直出页面,兼容离线包等方案。优点是性能好, 速度快, 大厂出品, 缺点是配置复杂, 同时需要前后端接入。 首先在build.gradle导入 implementation 'com.tencent.sonic:sdk:3.1.0' 代码准备:   public class SonicRuntimeImpl extends SonicRuntime { public SonicRuntimeImpl(Context context) { super(context); } /** * 获取用户UA信息 * @return */ @Override public String getUserAgent() { return "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Mobile Safari/537.36"; } /** * 获取用户ID信息 * @return */ @Override public String getCurrentUserAccount() { return "sonic-demo-master"; } @Override public String getCookie(String url) { CookieManager cookieManager = CookieManager.getInstance(); return cookieManager.getCookie(url); } @Override public void log(String tag, int level, String message) { switch (level) { case Log.ERROR: Log.e(tag, message); break; case Log.INFO: Log.i(tag, message); break; default: Log.d(tag, message); } } @Override public Object createWebResourceResponse(String mimeType, String encoding, InputStream data, Map headers) { WebResourceResponse resourceResponse = new WebResourceResponse(mimeType, encoding, data); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { resourceResponse.setResponseHeaders(headers); } return resourceResponse; } @Override public void showToast(CharSequence text, int duration) { } @Override public void notifyError(SonicSessionClient client, String url, int errorCode) { } @Override public boolean isSonicUrl(String url) { return true; } @Override public boolean setCookie(String url, List cookies) { if (!TextUtils.isEmpty(url) && cookies != null && cookies.size() > 0) { CookieManager cookieManager = CookieManager.getInstance(); for (String cookie : cookies) { cookieManager.setCookie(url, cookie); } return true; } return false; } @Override public boolean isNetworkValid() { return true; } @Override public void postTaskToThread(Runnable task, long delayMillis) { Thread thread = new Thread(task, "SonicThread"); thread.start(); } @Override public File getSonicCacheDir() { if (BuildConfig.DEBUG) { String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "sonic/"; File file = new File(path.trim()); if(!file.exists()){ file.mkdir(); } return file; } return super.getSonicCacheDir(); } @Override public String getHostDirectAddress(String url) { return null; } } public class SonicJavaScriptInterface { private final SonicSessionClientImpl sessionClient; private final Intent intent; public static final String PARAM_CLICK_TIME = "clickTime"; public static final String PARAM_LOAD_URL_TIME = "loadUrlTime"; public SonicJavaScriptInterface(SonicSessionClientImpl sessionClient, Intent intent) { this.sessionClient = sessionClient; this.intent = intent; } @JavascriptInterface public void getDiffData() { // the callback function of demo page is hardcode as 'getDiffDataCallback' getDiffData2("getDiffDataCallback"); } @JavascriptInterface public void getDiffData2(final String jsCallbackFunc) { if (null != sessionClient) { sessionClient.getDiffData(new SonicDiffDataCallback() { @Override public void callback(final String resultData) { Runnable callbackRunnable = new Runnable() { @Override public void run() { String jsCode = "javascript:" + jsCallbackFunc + "('"+ toJsString(resultData) + "')"; sessionClient.getWebView().loadUrl(jsCode); } }; if (Looper.getMainLooper() == Looper.myLooper()) { callbackRunnable.run(); } else { new Handler(Looper.getMainLooper()).post(callbackRunnable); } } }); } } @JavascriptInterface public String getPerformance() { long clickTime = intent.getLongExtra(PARAM_CLICK_TIME, -1); long loadUrlTime = intent.getLongExtra(PARAM_LOAD_URL_TIME, -1); try { JSONObject result = new JSONObject(); result.put(PARAM_CLICK_TIME, clickTime); result.put(PARAM_LOAD_URL_TIME, loadUrlTime); return result.toString(); } catch (Exception e) { } return ""; } /* * * From RFC 4627, "All Unicode characters may be placed within the quotation marks except * for the characters that must be escaped: quotation mark, * reverse solidus, and the control characters (U+0000 through U+001F)." */ private static String toJsString(String value) { if (value == null) { return "null"; } StringBuilder out = new StringBuilder(1024); for (int i = 0, length = value.length(); i < length; i++) { char c = value.charAt(i); switch (c) { case '"': case '\\': case '/': out.append('\\').append(c); break; case '\t': out.append("\\t"); break; case '\b': out.append("\\b"); break; case '\n': out.append("\\n"); break; case '\r': out.append("\\r"); break; case '\f': out.append("\\f"); break; default: if (c <= 0x1F) { out.append(String.format("\\u%04x", (int) c)); } else { out.append(c); } break; } } return out.toString(); } } 最后在activity里调用: public void initWebViewInfo() { Intent intent = getIntent(); String url = intent.getStringExtra(PARAM_URL); showLoadding = intent.getBooleanExtra("showLoadding", showLoadding); Log.d(TAG, "showLoadding=" + showLoadding); int mode = intent.getIntExtra(PARAM_MODE, -1); if (TextUtils.isEmpty(url) || -1 == mode) { finish(); return; } // init sonic engine if necessary, or maybe u can do this when application created if (!SonicEngine.isGetInstanceAllowed()) { SonicEngine.createInstance(new SonicRuntimeImpl(getApplication()), new SonicConfig.Builder().build()); } SonicSessionClientImpl sonicSessionClient = null; // if it's sonic mode , startup sonic session at first time if (MainActivity.MODE_DEFAULT != mode) { // sonic mode SonicSessionConfig.Builder sessionConfigBuilder = new SonicSessionConfig.Builder(); sessionConfigBuilder.setSupportLocalServer(true); // if it's offline pkg mode, we need to intercept the session connection if (MainActivity.MODE_SONIC_WITH_OFFLINE_CACHE == mode) { sessionConfigBuilder.setCacheInterceptor(new SonicCacheInterceptor(null) { @Override public String getCacheData(SonicSession session) { return null; // offline pkg does not need cache } }); sessionConfigBuilder.setConnectionInterceptor(new SonicSessionConnectionInterceptor() { @Override public SonicSessionConnection getConnection(SonicSession session, Intent intent) { return new OfflinePkgSessionConnection(WebBrowserActivity.this, session, intent); } }); } // create sonic session and run sonic flow sonicSession = SonicEngine.getInstance().createSession(url, sessionConfigBuilder.build()); if (null != sonicSession) { sonicSession.bindClient(sonicSessionClient = new SonicSessionClientImpl()); } else { Toast.makeText(this, "create sonic session fail!", Toast.LENGTH_LONG).show(); } } webview.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); if (sonicSession != null) { sonicSession.getSessionClient().pageFinish(url); } } @TargetApi(21) @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { return shouldInterceptRequest(view, request.getUrl().toString()); } @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { if (sonicSession != null) { return (WebResourceResponse) sonicSession.getSessionClient().requestResource(url); } return null; } }); WebSettings webSettings = webview.getSettings(); // 设置与Js交互的权限 webSettings.setJavaScriptEnabled(true); webview.removeJavascriptInterface("searchBoxJavaBridge_"); intent.putExtra(SonicJavaScriptInterface.PARAM_LOAD_URL_TIME, System.currentTimeMillis()); webview.addJavascriptInterface(new SonicJavaScriptInterface(sonicSessionClient, intent), "sonic"); // init webview settings webSettings.setAllowContentAccess(true); webSettings.setDatabaseEnabled(true); webSettings.setDomStorageEnabled(true); webSettings.setAppCacheEnabled(true); webSettings.setSavePassword(false); webSettings.setSaveFormData(false); webSettings.setUseWideViewPort(true); webSettings.setLoadWithOverviewMode(true); // 设置允许JS弹窗 webSettings.setJavaScriptCanOpenWindowsAutomatically(true); webview.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Uri uri = Uri.parse(url); if (url.contains("baidu")) { finish(); return true; } return super.shouldOverrideUrlLoading(view, url); } @Override public void onPageStarted(WebView webView, String s, Bitmap bitmap) { super.onPageStarted(webView, s, bitmap); if (showLoadding) { llLoadding.setVisibility(View.VISIBLE); } } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { // 不要使用super,否则有些手机访问不了,因为包含了一条 handler.cancel() // super.onReceivedSslError(view, handler, error); // 接受所有网站的证书,忽略SSL错误,执行访问网页 handler.proceed(); } @Override public void onReceivedError(WebView webView, WebResourceRequest webResourceRequest, WebResourceError webResourceError) { super.onReceivedError(webView, webResourceRequest, webResourceError); } @Override public void onPageFinished(WebView webView, String s) { super.onPageFinished(webView, s); if (showLoadding) { llLoadding.setVisibility(View.GONE); } } }); // webview is ready now, just tell session client to bind if (sonicSessionClient != null) { sonicSessionClient.bindWebView(webview); sonicSessionClient.clientReady(); } else { // default mode webview.loadUrl(url); } } 布局xml里还是用原生webview接入就行了! --------------------- 作者:-每天进步一点点- 来源:CSDN 原文:https://blog.csdn.net/u013068887/article/details/95460159 版权声明:本文为博主原创文章,转载请附上博文链接!