前端资源加载重试

介绍

对于TO C的应用,用户网络千差万别,总有各种网络问题导致资源加载失败,使得访问时出现白屏,样式错乱等。资源加载重试,则是提高用户体验中重要的一环。

最近开始尝试用 Vue 整套技术体系进行开发。如何在 Vue 中做资源加载重试?

资源分类

目前常见的前端资源分为

  • script 脚本
  • css 样式文件
  • img 图片
  • background-img 背景图

而在 webpack 构建体系里,根据加载方式可以细分为

  • 内联到html的script,link标签
  • img图片
  • import() 或 require.ensure 异步加载的chunk,通过webpack内置的加载器完成

实践方案

内联资源重试

assets-reload

通过 script, link, img 等标签上的 onerror 回调来进行资源加载重试,并且替换的URL规则可定制。而背景图则是读取样式表的规则,匹配到 background-img,则重新插入一条 background-img 样式,用于重试。

具体的实现欢迎点击该模块参考。

另外配合webpack构建自动化的能力,将这些onerror函数进行绑定。

script

通过这个模块,再利用script-ext-html-webpack-plugin 配置script的onerror属性

    new ScriptExtHtmlWebpackPlugin({
        custom: {
        test: /.js$/,
        attribute: 'onerror="attackCatch(this)"'
        }
    })

link

另外写个简单的插件将head处内联的link标签加上onerror属性。

class MyPlugin {
  apply (compiler) {
    compiler.hooks.compilation.tap('css-attr-plugin', (compilation) => {
      compilation.hooks.htmlWebpackPluginAlterAssetTags
      .tapAsync('myPlugin', function (data, cb) {
        data.head.forEach(el=>{
          if(el.tagName === 'link'){
            el.attributes.onerror = 'attackCatch(this)';
          }
        })
        cb(null ,data);
      });
    })
  }
}

module.exports = MyPlugin

img

img目前暂未找到适配的插件,稍后将自行添加对应的插件。也欢迎各位推荐

background-img 背景图

背景图这一块,则因为没有事件监听,只能进行全量替换,目前的应用仅在测试域名环境下,将所有背景图资源替换为当前域名下。

webpack内置异步加载器

webpack-plugin-import-retry

阅读了webpack资源加载器部分的代码,重写了下加载器部分,实现了重试的能力。同时支持,传入格式化URL函数用于自定义重试时的链接。

对加载失败的chunk,进行重试。

一个chunk,有时候会包括 JS及CSS资源,其中一个加载失败便会发起重试,直到有一个资源重试了2次就判断为失败。

通过资源加载重试,可大大减少 router 中,加载异步的页面文件时,失败而导致白屏的问题。

/******/     __webpack_require__.oldE = __webpack_require__.e;
/******/     __webpack_require__.e = function newRequireEnsure (chunkId, options) {
/******/                         return __webpack_require__.oldE(chunkId, options).then(function () {}, function (err) {
/******/                             console.error(err);
/******/                             var type;
/******/                             if (/.*.css??/.test(err.request)) {
/******/                                 type = 'LINK';
/******/                             } else if (/.*.js??.*/.test(err.request)) {
/******/                                 type = 'SCRIPT';
/******/                             }
/******/                             if (options === undefined) {
/******/                                 options = {
/******/                                     LINK: 0,
/******/                                     SCRIPT: 0
/******/                                 };
/******/                             }
/******/                             options[type]++;
/******/                             // 最小值为1
/******/                             if (options[type] <= 2) {
/******/                                 return newRequireEnsure(chunkId, options);
/******/                             }
/******/                         })
/******/                     }

重试规则

我们项目中,前端部署的架构为将前端项目文件发布到自己的静态资源服务器,CDN再来进行回源请求文件。

URL仅为域名不同,路径相同。

因此,我们的重试规则为 加上reloadAssets=1参数,用于标识是第几次重试。

第二次重试时,将CDN域名替换为当前域名。

因为CDN域名也会有不稳定的时候,将CDN域名替换为当前访问的域名,成功率会高些。

因为不同业务的CDN资源替换为主站资源路径未必相同。因此都支持自定义规则。

测试域名应用

对于测试环境,我们一般会启用一个测试域名用于访问。

此时,增量文件尚未发布到CDN,导致访问测试域名时,增量文件请求不到,而为此提前将增量文件发布到线上,则比较麻烦。

因此,我们的自定义规则内,会添加是否为测试环境的判断,如果为测试环境,第一次重试的时候就直接替换为当前的测试域名进行访问。

以此达到同一套代码适配不同域名。

Gif-Load-ReTry-Refresh:只需要一张Gif图,一行代码支持初次加载重试加载加载后再次刷新原理 :遍历View树,在Framelayout中动态插入和移除加载布局,与生命周期绑定,避免内存泄漏;功能 :目前支持在Activity,Fragment中使用(支持任何方式实现的沉浸式状态栏和透明状态栏);封装 :接口化调用,支持MVP结构中使用(View层implement LoadRetryRefreshListener接口,然后直接在Activity/Fragment传入this即可)。示例Activity中加载成功Activity中加载失败在Activity中加载成功,然后再次加载刷新在Activity中加载失败,然后重试加载加载成功后刷新加载Fragment中加载成功Fragment中加载失败在Fragment中加载成功,然后再次加载刷新在Fragment中加载失败,然后重试加载加载成功后刷新加载使用   初步配置引入配置属性示例代码在Activity中使用1、注册2、开始加载3、加载结果回调4、解除绑定布局代码(勿遗漏第4步,防止内存泄漏)在Fragment中使用1、注册2、开始加载3、加载结果回调4、解除绑定布局代码(勿遗漏第4步,防止内存泄漏)反馈与建议初步配置引入Step 1. Add it in your root build.gradle at the end of repositories:allprojects { repositories { ... maven { url 'https://jitpack.io' } } }Step 2. Add the dependencydependencies {         compile 'com.github.NoEndToLF:Gif-Load-ReTry-Refresh:1.1.2' }配置属性方法参数作用setGifR.drawable.*加载页面的Gif图setBackgroundColorR.color.*加载页面整体背景颜色setBtnNormalColorR.color.*加载页面按钮未按下时的颜色setBtnPressedColorR.color.*加载页面按钮按下时的颜色setBtnBorderColorR.color.*加载页面按钮边框的颜色setBtnTextColorR.color.*加载页面按钮文字的颜色setBtnRadiusFloat加载页面按钮的圆角弧度setBtnTextString加载页面按钮的显示文字setLoadTextString正在加载中的提示文字setLoadAndErrorTextColorR.color.*加载页面的提示文字和加载失败提示文字的颜色示例代码,建议在 Application的 onCreate中进行初始化LoadRetryRefreshConfig config=new LoadRetryRefreshConfig();         config.setBackgroundColor(R.color.white);         config.setBtnNormalColor(R.color.blue_normal);         config.setBtnPressedColor(R.color.blue_press);         config.setBtnBorderColor(R.color.oringe_normal);         config.setBtnRadius(10f);         config.setBtnText("点击重新加载");         config.setLoadText("测试加载2秒钟...");         config.setBtnTextColor(R.color.white);         config.setLoadAndErrorTextColor(R.color.gray);         config.setGif(R.drawable.zhufaner);                 LoadReTryRefreshManager.getInstance().setLoadRetryRefreshConfig(config);在 Activit中使用布局中,请在 Toolbar下的需要加载的内容最外层套一层 FrameLayout(为何需要这样做),如:<LinearLayout android:layout_width="match_parent"     android:layout_height="match_parent"     xmlns:app="http://schemas.android.com/apk/res-auto"     android:orientation="vertical"     xmlns:android="http://schemas.android.com/apk/res/android">     <android.support.v7.widget.Toolbar         app:contentInsetStart="0dp"         android:layout_width="match_parent"         android:layout_height="?attr/actionBarSize"         android:gravity="center_vertical"         android:id="@ id/toolbar"         android:background="@color/color_toolbar"         >         <FrameLayout             android:layout_width="match_parent"             android:layout_height="match_parent">                          **********************************             **********************************             你的内容布局,如Linearlayout等            **********************************             **********************************                           </FrameLayout></LinearLayout>代码中方法简介方法参数作用registerActivity,LoadRetryRefreshListener注册startLoadActivity开始加载unRegisterActivity解除绑定onLoadSuccessActivity,ShowRefreshViewListener关闭加载View和刷新时的Dialog、下拉刷新等onLoadFailed  Activity,String,ShowRefreshViewListener显示加载失败原因,关闭加载View和刷新时的Dialog、下拉刷新等1、注册,一般在 onCreate中调用LoadReTryRefreshManager.getInstance().register(this, new LoadRetryRefreshListener() {                       @Override             public void loadAndRetry() {                         //执行你的网络请求             //dosomething();             }            @Override             public void showRefreshView() {                         //显示你刷新时的加载View,如Dialog,下拉刷新等                              }         });2、开始加载,无需判断是初次加载还是刷新,已自动进行判断,只需要在想要加载或刷新的地方直接调用(加载失败重试加载已封装到 Button事件中)LoadReTryRefreshManager.getInstance().startLoad(this);3、加载结果回调,在你的请求成功和失败的回调中加入加载结果回调@Override             public void onSuccess(Integer value) {             //加载成功你要做的事.....                              //加载结果回调                 LoadReTryRefreshManager.getInstance().onLoadSuccess(FailedActivity.this,                  new ShowRefreshViewListener() {                     @Override                     public void colseRefreshView() {                      //关闭你的刷新View,如Dialog,下拉刷新等                       }                 });             }             @Override             public void onFailed(Throwable e) {             //加载失败你要做的事.....                              //加载结果回调                 LoadReTryRefreshManager.getInstance().onLoadFailed(FailedActivity.this,                  e.getMessage(), new ShowRefreshViewListener() {                     @Override                     public void colseRefreshView() {                        //关闭你的刷新View,如Dialog,下拉刷新等                     }                 });             }4、解除绑定,可以直接写在 BaseActivity的 onDestory方法中,会自动判断然后进行解绑Override     protected void onDestroy() {                 super.onDestroy();                  LoadReTryRefreshManager.getInstance().unRegister(this);     }在 Fragment中使用布局中,同 Activity中使用一致,请在 Toolbar下的需要加载的内容最外层套一层 FrameLayout(为何需要这样做)代码中方法简介方法参数作用registerFragment,View,LoadRetryRefreshListener注册(View为Fragment在onCreateView中返回的View)startLoadFragment开始加载unRegisterFragment解除绑定onLoadSuccessFragment,ShowRefreshViewListener关闭加载View和刷新时的Dialog、下拉刷新等onLoadFailedFragment,String,ShowRefreshViewListener显示加载失败原因,关闭加载View和刷新时的Dialog、下拉刷新等1、注册,一般在 onCreateView中调用LoadReTryRefreshManager.getInstance().register(this, contentView,new LoadRetryRefreshListener() {             @Override             public void loadAndRetry() {             //执行你的网络请求             //dosomething();             }             @Override             public void showRefreshView() {             //显示你刷新时的加载View,如Dialog,下拉刷新等                              }         });2、开始加载,无需判断是初次加载还是刷新,已自动进行判断,只需要在想要加载或刷新的地方直接调用(加载失败重试加载已封装到 Button事件中)LoadReTryRefreshManager.getInstance().startLoad(this);3、加载结果回调,在你的请求成功和失败的回调中加入加载结果回调@Override             public void onSuccess(Integer value) {             //加载成功你要做的事.....                              //加载结果回调                 LoadReTryRefreshManager.getInstance().onLoadSuccess(FailedActivity.this,                  new ShowRefreshViewListener() {                     @Override                     public void colseRefreshView() {                      //关闭你的刷新View,如Dialog,下拉刷新等                       }                 });             }             @Override             public void onFailed(Throwable e) {             //加载失败你要做的事.....                              //加载结果回调                 LoadReTryRefreshManager.getInstance().onLoadFailed(FailedActivity.this,                  e.getMessage(), new ShowRefreshViewListener() {                     @Override                     public void colseRefreshView() {                        //关闭你的刷新View,如Dialog,下拉刷新等                     }                 });             }4、解除绑定,可以直接写在 BaseFragment的 onDestroyView方法中,会自动判断然后进行解绑@Override     public void onDestroyView() {         super.onDestroyView();         LoadReTryRefreshManager.getInstance().unRegister(this);     }为何必须在布局中套一层 FrameLayout目前为了在4.4,5.0,6.0,7.0及以上的版本中实现沉浸式状态栏或者是透明式状态栏的适配,实现方式主要在低版本中有所不同,有的是设置全屏然后给Toolbar加一个PaddingTop来留出StatusBar的高度,有的是设置全屏StatusBar透明,然后再动态插入一个大小一致的View来占位,达到设置状态栏颜色的目的,因此,如果单纯的在DecorView中来插入加载布局,难以控制加载页面的MarginTop,可能会遮盖到Toolbar,所以退而求其次,在布局中需要加载的部分包一层FrameLayout,再通过递归View树来找到需要添加加载布局的地方,进行动态插入,这样就不需要处理兼容沉浸式状态栏或者是透明式状态栏的适配造成的问题(当然如果有更好的想法,强烈欢迎Issues或者邮箱建议)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值