之前提到过,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 应用的滚动流畅性非常好,页面跳转切换,也有接近原生的感受。(还是差一点点)
虽然说,这作为一个尝试方案,但是在真实项目中使用,却可以明显的提高用户体验。实践成本也很小。如果在你们有类似的使用场景,不妨尝试一下。