之前提到过,web 产物的优化问题。我就在想那能不能把产物包拆开,有些写到原生apk中?于是有了本次的方案尝试。
特定方式编译 web 产物
这里以 alita 的项目为例,因为在alita的项目中,初始项目只有一个 alita 的依赖。设想是将用户的 alita 依赖包,编译到一个 vendors 文件中,用户添加的其他第三方依赖,编译到另一个 micro 文件中。这样可以保证每个项目编译出来的 vendors 文件都是一样的,或者说,可以使得多个项目共用一个 vendors 文件。(经过多次尝试,虽然每次编译出来的 vendors 文件大小,不完全一致,但是交换着使用,却没有任何问题。)
设置默认移除依赖
我们读取用户项目的 package.json 文件,取得 dependencies 里面定义的第三方依赖。定义一个排除数组。这里是 'alita' 和 'alita' 中已经包含的常用依赖。如果在 umi 项目中,那可能是 umi、umi-preset-react 和 umi-plugin-xxx 等。
const exclude = ['alita', 'classnames'];
设置默认引入依赖
由于项目中的 antd 和 antd-mobile 是按需加载的,即最终被引入到项目中的依赖库,如。
import Button from 'antd/es/button';
在编译的时候,webpack 以为的包名是 antd/es/button
而不是 antd
。因此我们可以手动把 antd 相关的东西再加回来。定义一个需要导入的第三方依赖。
const include = ['antd-mobile', 'antd', 'rc-', 'rmc-'];
结合两次定义的数组,取得我们需要包含的真实的依赖。
const dependencies = api.pkg.dependencies || {}; const pkgNames = Object.keys(dependencies) .filter((i) => !exclude.includes(i)) .concat(include);
增加 chainWebpack 配置(这在上一个文章中,有较为详细的说明)。把满足我们定义的第三方依赖,打包到 micro 文件中,剩余的 打包到 vendors 中。
cacheGroups: { micro: { name: 'micro', chunks: 'all', enforce: true, test: (module: any, chunks: any) => { if (module.resource) { for (let key = 0; key <= pkgNames.length; key++) { if ( module.resource.includes(`/node_modules/${pkgNames[key]}`) ) { return true; } } } return false; }, priority: -9, }, vendors: { name: 'vendors', chunks: 'all', test: /[\\/]node_modules[\\/]/, priority: -12, }, }, },
这是的判断条件是包含,因为存在 rc-*
等库。
执行编译之后,产物中就会包含三个js文件,umi.js
,micro.js
和 vendors.js
。
增加 chunks 配置
chunks: ['micro', 'umi']
需要注意的是,我们产物是三个文件,但是我们只用了两个。并不包含 vendors.js
。
生成的index.html,如下所示:
<body> <div id="root"></div> <script src="./micro.js"></script> <script src="./umi.js"></script> </body>
webview 前置引入vendors.js
上面我们提到,产物是三个js文件,但是我们在 html 中却只用了两个。还有一个我们放到原生app中。(我们安卓开发的同事提供的方法)
WebView mWebView; // 取到 js 文件 final String jsStr = FileUtil.getJsStr(mActivity,"vendors.js"); BaseJavaScript javaScript = new BaseJavaScript(); mWebView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); Log.i("caicai", "shouldOverrideUrlLoading"); return true; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); Log.i("caicai", "onPageStarted"); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); } @Override public void onLoadResource(WebView view, String url) { super.onLoadResource(view, url); Log.i("caicai",url); view.evaluateJavascript(jsStr, new ValueCallback<String>() { @Override public void onReceiveValue(String value) { Log.i("caicai", "jsStr:"+value); } }); } });
安卓中使用 view.evaluateJavascript
方法,将js文件前置添加到 webview 中。ios中有类似的方法(来自ios开发同事)
WKUserContentController *userContentController = configuration.userContentController; [userContentController addUserScript:script];
这种方式加载,可以减少大文件的下载时间,一个正常的项目,只需要下载100k左右的js文件。在对接到已知系统的情况,可以显著的提升用户体验。
安卓添加腾讯X5内核
同一个web应用,在ios上和安卓上,同样用webview打开的方式访问,在安卓上确实无法和iOS上对比。但是只要改动几个配置,引入腾讯X5内核,在安卓端的体验就可以有一个质的飞跃。接入方式非常简单,不会安卓原生开发的小白,就可以完成。比如我!
app/build.gradle 增加模块
// 这个不同的项目引入方式还不一样,注意看下上下文的引入方式,不要全信官方文档 implementation 'com.tencent.tbs.tbssdk:sdk:43903'
app/src/main/AndroidManifest.xml 中增加访问权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <application> // service 写在 application 里面 <service android:name="com.tencent.smtt.export.external.DexClassLoaderProviderService" android:label="dexopt" android:process=":dexopt" > </service> </application>
替换所有引用的库
把原生 android.webkit 的引用修改为 com.tencent.smtt.sdk ,根据官方文档,一个一个的全局覆盖就好了。
开启预启动功能
这个是需要搭配上面提到的 service 服务一起使用才会有效的。
// app/src/main/java/com/example/minialita/MainActivity.java @Override protected void init() { HashMap map = new HashMap(); map.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true); map.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true); QbSdk.initTbsSettings(map); }
其他地方都不用修改,还是使用保留之前的用法。但是 web 应用的滚动流畅性非常好,页面跳转切换,也有接近原生的感受。(还是差一点点)
虽然说,这作为一个尝试方案,但是在真实项目中使用,却可以明显的提高用户体验。实践成本也很小。如果在你们有类似的使用场景,不妨尝试一下。