Android中WebView性能优化

前言

  • 在Android在使用H5开发具有快速开发的特点,所以现在大多数APP都采用了原生Android+WebView的Hybrid开发模式。
  • 缺点:Android下的WebView有一些痛点,由于受到Android手机的碎片化原因,性能低下,加载速度慢和浪费流量等缺点。
  • 今天我们就针对WebView中的性能问题进行优化。

WebView首次启动优化-常用资源预加载

  • 提前加载JS、CSS、图片资源,等H5页面加载完成后进行替换。
  • 原理: WebView中引入了ShouldInterceptRequest方法可以实现,需要注意的是该方法API11-API21和API21之后不同,需要做兼容性处理。
例子
  • 事先将一些更新频率比较低的常用资源(JS、CSS、图片资源)放置在本地,然后通过拦截H5对其资源进行本地化加载。
  • 加载网址:http://ip.cn/
  • 替换H5页面中的资源图片:http://s.ip-cdn.com/img/logo.gif
  • 代码如下:
webview.loadUrl("http://ip.cn/");//加载http://ip.cn/
webview.setWebViewClient(new WebViewClient(){
  @Override
  public boolean shouldOverrideUrlLoading(WebView view, String url) {
       view.loadUrl(url);
       return true;
  }

  //api21以下
  @Override
  public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
       if(url.contains("logo.gif")){
          InputStream is = null;
          try {
                 is = getApplicationContext().getAssets().open("images/index.jpg");
          } catch (IOException e) {
                 e.printStackTrace();
          }
          WebResourceResponse response = new WebResourceResponse("image/jpg","utf-8",is);
                    return response;
     }
          return super.shouldInterceptRequest(view, url);
   }

  //api21以上
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  @Override
  public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
      if (request.getUrl().toString().contains("logo.gif")){
          InputStream is = null;
          try {
                is = getApplicationContext().getAssets().open("images/index.jpg");
          } catch (IOException e) {
                e.printStackTrace();
          }
          WebResourceResponse response = new WebResourceResponse("image/jpg","utf-8",is);
              return response;
       }
              return super.shouldInterceptRequest(view, request);
  }
});

  • 替换前的图片
图片.png
  • 替换后的图片
图片.png

常用JS本地化及延迟加载

  • WebView渲染H5页面性能差归结于两个原因:
    1.JS解析过程比较复杂,解析速度比较慢导致。
    2.Android设备碎片化导致设备性能参差不齐,导致Android手机硬件部分无法很好的支持WebView的渲染。
  • 解决方案:将一些常用的JS脚步本地化处理,将JS文件编写好后放置在asserts文件夹中,直接打包到APK中。在WebView调用OnPageFinished()方法后进行加载。

WebView二次启动优化

  • 我们可以为WebView加入缓存机制,在二次启动的时候,首先去缓存中进行命中,是否可以命中缓存,不可以的时候才进行网络情况,可以进一步的优化WebView的启动速度。
  • 这里有几种比较常用的方案:
WebView的缓存模式
  • LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据。
  • LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
  • LOAD_NO_CACHE: 不使用缓存,只从网络获取数据。
  • LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
//优先使用缓存:
WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
// 不使用缓存: 
WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
  • 建议:
    判断是否有网络,有的话,使用LOAD_DEFAULT,
    无网络时,使用LOAD_CACHE_ELSE_NETWORK。
浏览器缓存机制-前端开发人员负责(静态文件缓存)
  • 在HTTP 协议头里有这样一个字段 Cache-Control是用来控制缓存的。
 //代表本地缓存,且从发起请求算起,接下来500s,如果有该资源请求都不会进行HTTP请求,而是读取本地的缓存
 Cache-Control:max-age=500
Dom Storage
  • 主要用于存储一些临时、简单的数据缓存和cookie。
  • DOM Storage 分为 SessionStorage 和 LocalStorage。
  • SessionStorage是临时性的,在WebView关闭后就会失效。
  • LocalStorage是持久性的,在WebView关闭后不会失效,可以进行使用。
//通过webSettings.setDomStorageEnabled(true);//生效。
webSettings.setDomStorageEnabled(true)
IndexDB
  • IndexDB是NoSQL 数据库,通过Key - Value来提供存储功能。
  • 在Android4.4开始对其进行支持
//通过JS执行开发进行使能
webSettings.setJavaScriptEnabled(true);
缓存总结
  1. 存储静态资源文件(JS、CSS、图片资源)- 采用浏览器缓存机制。
  2. 存储临时、简单的数据 - 采用Dom Storage缓存机制。
  3. 存储结构复杂、数据量大的数据 - 采用IndexDB缓存机制。

WebView防止内存泄漏

  • 导致内存泄漏的原因:WebView持有对Activity的引用,导致WebVIew的内存无法释放。
解决方案
  • 使用AIDL跨进程通信,WebView开启一个新的进程,通过AIDL与主进程进行通信,WebView所在的进程在业务合适的阶段对WebView进行销毁。
  • 自己封装WebView,不在XML文件中直接定义,在代码中new出来,并传入Application的context来防止Activity引用导致的泄漏。
activity_webview.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rl_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</RelativeLayout>
WebViewActivity.java
public class WebViewActivity extends BaseActivity {

    private RelativeLayout rl_layout;
    private WebView webview;
    private WebSettings webSettings;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webview);
        initWebView();
    }

    private void initWebView(){
        //避免WebView内存泄露
        rl_layout = (RelativeLayout) findViewById(R.id.rl_layout);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
        webview = new WebView(getApplicationContext());
        webview.setLayoutParams(params);
        rl_layout.addView(webview);

        webSettings = webview.getSettings();
        webSettings.setUseWideViewPort(true);//将图片调整到适合webview的大小
        webSettings.setLoadWithOverviewMode(true);// 缩放至屏幕的大小

        webSettings.setSupportZoom(true);//支持缩放,默认为true。是下面那个的前提。
        webSettings.setBuiltInZoomControls(true);//支持缩放,默认为true。是下面那个的前提。
        webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件

        webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存
        webSettings.setAllowFileAccess(true); //设置可以访问文件
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
        webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
        webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式
        webSettings.setDomStorageEnabled(true);// 开启 DOM storage API 功能
        webSettings.setAppCacheEnabled(true);// 开启 Application Caches 功能

        //优先使用缓存:
        // WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
        // 缓存模式如下:
        // LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
        // LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
        // LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
        // LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
        // 不使用缓存: WebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
        //根据以上两种模式,建议缓存策略为:
        // 判断是否有网络,有的话,使用LOAD_DEFAULT,
        // 无网络时,使用LOAD_CACHE_ELSE_NETWORK。
        webview.setWebViewClient(new WebViewClient(){
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }

            //api21以下
            @Override
            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
                if(url.contains("logo.gif")){
                    InputStream is = null;
                    try {
                        is = getApplicationContext().getAssets().open("images/index.jpg");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    WebResourceResponse response = new WebResourceResponse("image/jpg","utf-8",is);
                    return response;
                }
                return super.shouldInterceptRequest(view, url);
            }

            //api21以上
            @TargetApi(Build.VERSION_CODES.LOLLIPOP)
            @Override
            public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
                if (request.getUrl().toString().contains("logo.gif")){
                    InputStream is = null;
                    try {
                        is = getApplicationContext().getAssets().open("images/index.jpg");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    WebResourceResponse response = new WebResourceResponse("image/jpg","utf-8",is);
                    return response;
                }
                return super.shouldInterceptRequest(view, request);
            }

        });

        webview.loadUrl("http://ip.cn/");
    }

    @Override
    protected void onResume() {
        super.onResume();
        webSettings.setJavaScriptEnabled(true);
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onStop() {
        super.onStop();
        webSettings.setJavaScriptEnabled(false);
    }

    @Override
    protected void onDestroy() {
        //避免WebView内存泄露
        if (webview != null){
            webview.loadDataWithBaseURL(null,"","tex/html","utf-8",null);
            webview.clearHistory();
            ((ViewGroup)webview.getParent()).removeView(webview);
            webview.destroy();
            webview = null;
        }
        super.onDestroy();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && webview.canGoBack()){
            webview.goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容