首页 > 其他分享 >安卓Webview网页秒开策略探索

安卓Webview网页秒开策略探索

时间:2024-08-30 09:51:02浏览次数:11  
标签:网页 String 安卓 public webView Override webview Webview 加载

痛点是什么?

网页加载缓慢,白屏,使用卡顿。

为何有这种问题?

1.调用loadUrl()方法的时候,才会开始网页加载流程
2.js臃肿问题
3.加载图片太多
4.webview本身问题

webiew是怎么加载网页的呢?

webview初始化->DOM下载→DOM解析→CSS请求+下载→CSS解析→渲染→绘制→合成

优化方向是?

1.webview本身优化

  • 提前内核初始化
    代码:
public class App extends Application {

    private WebView mWebView ;
    @Override
    public void onCreate() {
        super.onCreate();
        mWebView = new WebView(new MutableContextWrapper(this));
    }
}

效果:初次内核初始化大概2000ms,第二次50ms以内

  • webview复用池
    代码:
public class WebPools {


    private final Queue<WebView> mWebViews;

    private Object lock = new Object();
    private static WebPools mWebPools = null;

    private static final AtomicReference<WebPools> mAtomicReference = new AtomicReference<>();
    private static final String TAG=WebPools.class.getSimpleName();

    private WebPools() {
        mWebViews = new LinkedBlockingQueue<>();
    }
    public static WebPools getInstance() {

        for (; ; ) {
            if (mWebPools != null)
                return mWebPools;
            if (mAtomicReference.compareAndSet(null, new WebPools()))
                return mWebPools=mAtomicReference.get();

        }
    }
    public void recycle(WebView webView) {
        recycleInternal(webView);
    }
    public WebView acquireWebView(Activity activity) {
        return acquireWebViewInternal(activity);
    }
    private WebView acquireWebViewInternal(Activity activity) {
        WebView mWebView = mWebViews.poll();
        LogUtils.i(TAG,"acquireWebViewInternal  webview:"+mWebView);
        if (mWebView == null) {
            synchronized (lock) {
                return new WebView(new MutableContextWrapper(activity));
            }
        } else {
            MutableContextWrapper mMutableContextWrapper = (MutableContextWrapper) mWebView.getContext();
            mMutableContextWrapper.setBaseContext(activity);
            return mWebView;
        }
    }
    private void recycleInternal(WebView webView) {
        try {
            if (webView.getContext() instanceof MutableContextWrapper) {
                MutableContextWrapper mContext = (MutableContextWrapper) webView.getContext();
             mContext.setBaseContext(mContext.getApplicationContext());
                LogUtils.i(TAG,"enqueue  webview:"+webView);
                mWebViews.offer(webView);
            }
            if(webView.getContext() instanceof  Activity){
//            throw new RuntimeException("leaked");
                LogUtils.i(TAG,"Abandon this webview  , It will cause leak if enqueue !");
            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

带来的问题:内存泄漏

  • 独立进程,进程预加载
    代码:
        <service
            android:name=".PreWebService"
            android:process=":web"/>
        <activity
            android:name=".WebActivity"
            android:process=":web"/>

启动webview页面前,先启动PreWebService把[web]进程创建了,当启动WebActivity时,系统发发现[web]进程已经存在了,就不需要花费时间Fork出新的[web]进程了。

  • 使用x5内核
    直接使用腾讯的x5内核,替换原生的浏览器内核

  • 其他的解决方案:
    1.设置webview缓存
    2.加载动画/最后让图片下载
    3.渲染时关掉图片加载
    4.设置超时时间
    5.开启软硬件加速

2.加载资源时的优化
这种优化多使用第三方,下面有介绍

3.网页端的优化
由网页的前端工程师优化网页,或者说是和移动端一起,将网页实现增量更新,动态更新。app内置css,js文件并控制版本

注意:如果你寄希望于只通过webview的setting来加速网页的加载速度,那你就要失望了。只修改设置,能做的提升非常少。所以本文就着重分析比较下,现在可以使用的第三方webview框架的优缺点。

现在大厂的方法有以下几种:

  1. VasSonic
  2. TBS腾讯浏览服务
  3. 百度app方案
  4. 今日头条方案

VasSonic

参考文章:
https://blog.csdn.net/tencent__open/article/details/77324952
接入方法:
STEP1:

    //导入 Tencent/VasSonic
    implementation 'com.tencent.sonic:sdk:3.1.0'

STEP2:

//创建一个类继承SonicRuntime
//SonicRuntime类主要提供sonic运行时环境,包括Context、用户UA、ID(用户唯一标识,存放数据时唯一标识对应用户)等等信息。以下代码展示了SonicRuntime的几个方法。
public class TTPRuntime extends SonicRuntime
{
    //初始化
    public TTPRuntime( Context context )
    {
        super(context);
    }
    
    @Override
    public void log(
            String tag ,
            int level ,
            String message )
    {
        //log设置
    }
    
    //获取cookie
    @Override
    public String getCookie( String url )
    {
        return null;
    }
    
    //设置cookid
    @Override
    public boolean setCookie(
            String url ,
            List<String> cookies )
    {
        return false;
    }
    
    //获取用户UA信息
    @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信息
    @Override
    public String getCurrentUserAccount()
    {
        return "ttpp";
    }
    
    //是否使用Sonic加速
    @Override
    public boolean isSonicUrl( String url )
    {
        return true;
    }
    
    //创建web资源请求
    @Override
    public Object createWebResourceResponse(
            String mimeType ,
            String encoding ,
            InputStream data ,
            Map<String, String> headers )
    {
        return null;
    }
    
    //网络属否允许
    @Override
    public boolean isNetworkValid()
    {
        return true;
    }
    
    @Override
    public void showToast(
            CharSequence text ,
            int duration )
    { }
    
    @Override
    public void postTaskToThread(
            Runnable task ,
            long delayMillis )
    { }
    
    @Override
    public void notifyError(
            SonicSessionClient client ,
            String url ,
            int errorCode )
    { }
    
    //设置Sonic缓存地址
    @Override
    public File getSonicCacheDir()
    {
        return super.getSonicCacheDir();
    }
}

STEP3:

//创建一个类继承SonicSessionClien
//SonicSessionClient主要负责跟webView的通信,比如调用webView的loadUrl、loadDataWithBaseUrl等方法。
public class WebSessionClientImpl extends SonicSessionClient
{
    private WebView webView;
    
    //绑定webview
    public void bindWebView(WebView webView) {
        this.webView = webView;
    }
    
    //加载网页
    @Override
    public void loadUrl(String url, Bundle extraData) {
        webView.loadUrl(url);
    }
    
    //加载网页
    @Override
    public void loadDataWithBaseUrl(String baseUrl, String data, String mimeType, String encoding,
            String historyUrl) {
        webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
    }
    
    //加载网页
    @Override
    public void loadDataWithBaseUrlAndHeader(
            String baseUrl ,
            String data ,
            String mimeType ,
            String encoding ,
            String historyUrl ,
            HashMap<String, String> headers )
    {
        if( headers.isEmpty() )
        {
            webView.loadDataWithBaseURL( baseUrl, data, mimeType, encoding, historyUrl );
        }
        else
        {
            webView.loadUrl( baseUrl,headers );
        }
    }
}

STEP4:

//创建activity
public class WebActivity extends AppCompatActivity
{
    private String url = "http://www.baidu.com";
    private SonicSession sonicSession;
    
    @Override
    protected void onCreate( @Nullable Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_web);
        initView();
    }
    
    private void initView()
    {
        getWindow().addFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
        
        //初始化 可放在Activity或者Application的onCreate方法中
        if( !SonicEngine.isGetInstanceAllowed() )
        {
            SonicEngine.createInstance( new TTPRuntime( getApplication() ),new SonicConfig.Builder().build() );
        }
        //设置预加载
        SonicSessionConfig config = new SonicSessionConfig.Builder().build();
        SonicEngine.getInstance().preCreateSession( url,config );
        
        WebSessionClientImpl client = null;
        //SonicSessionConfig  设置超时时间、缓存大小等相关参数。
        //创建一个SonicSession对象,同时为session绑定client。session创建之后sonic就会异步加载数据了
        sonicSession = SonicEngine.getInstance().createSession( url,config );
        if( null!= sonicSession )
        {
            sonicSession.bindClient( client = new WebSessionClientImpl() );
        }
        //获取webview
        WebView webView = (WebView)findViewById( R.id.webview_act );
        webView.setWebViewClient( new WebViewClient()
        {
            @Override
            public void onPageFinished(
                    WebView view ,
                    String url )
            {
                super.onPageFinished( view , url );
                if( sonicSession != null )
                {
                    sonicSession.getSessionClient().pageFinish( url );
                }
            }
            
            @Nullable
            @Override
            public WebResourceResponse shouldInterceptRequest(
                    WebView view ,
                    WebResourceRequest request )
            {
                return shouldInterceptRequest( view, request.getUrl().toString() );
            }
            //为clinet绑定webview,在webView准备发起loadUrl的时候通过SonicSession的onClientReady方法通知sonicSession: webView ready可以开始loadUrl了。这时sonic内部就会根据本地的数据情况执行webView相应的逻辑(执行loadUrl或者loadData等)
            @Nullable
            @Override
            public WebResourceResponse shouldInterceptRequest(
                    WebView view ,
                    String url )
            {
                if( sonicSession != null )
                {
                    return (WebResourceResponse)sonicSession.getSessionClient().requestResource( url );
                }
                return null;
            }
        });
        //webview设置
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webView.removeJavascriptInterface("searchBoxJavaBridge_");
        //webView.addJavascriptInterface(new SonicJavaScriptInterface(sonicSessionClient, intent), "sonic");
        webSettings.setAllowContentAccess(true);
        webSettings.setDatabaseEnabled(true);
        webSettings.setDomStorageEnabled(true);
        webSettings.setAppCacheEnabled(true);
        webSettings.setSavePassword(false);
        webSettings.setSaveFormData(false);
        webSettings.setUseWideViewPort(true);
        webSettings.setLoadWithOverviewMode(true);

        //为clinet绑定webview,在webView准备发起loadUrl的时候通过SonicSession的onClientReady方法通知sonicSession: webView ready可以开始loadUrl了。这时sonic内部就会根据本地的数据情况执行webView相应的逻辑(执行loadUrl或者loadData等)。
        if( client != null )
        {
            client.bindWebView( webView );
            client.clientReady();
        }
        else
        {
            webView.loadUrl( url );
        }
    }
    
    @Override
    public void onBackPressed()
    {
        super.onBackPressed();
    }
    
    @Override
    protected void onDestroy()
    {
        if( null != sonicSession )
        {
            sonicSession.destroy();
            sonicSession = null;
        }
        super.onDestroy();
    }
}

简单分析下它的核心思想:
并行,充分利用webview初始化的时间进行一些数据的处理。在包含webview的activity启动时会一边进行webview的初始化逻辑,一边并行的执行sonic的逻辑。这个sonic逻辑就是网页的预加载
原理:

  • Quick模式
    模式分类:
    1.无缓存模式
    流程:

左边的webview流程:webview初始化后调用SonicSession的onClientReady方法,告知
webview已经初始化完毕。

client.clientReady();

右边的sonic流程:
1.创建SonicEngine对象
2.通过SonicCacheInterceptor获取本地缓存的url数据
3.数据为空就发送一个CLIENT_CORE_MSG_PRE_LOAD的消息到主线程
4.通过SonicSessionConnection建立一个URLConnection
5.连接获取服务器返回的数据,并在读取网络数据的时候不断判断webview是否发起资源拦截请求。如果发了,就中断网络数据的读取,把已经读取的和未读取的数据拼接成桥接流SonicSessionStream并赋值给SonicSession的pendingWebResourceStream,如果网络读取完成后webview还没有初始化完成,就会cancel掉CLIENT_CORE_MSG_PRE_LOAD消息,同时发送CLIENT_CORE_MSG_FIRST_LOAD消息
6.之后再对html内容进行模版分割及数据保存
7.如果webview处理了CLIENT_CORE_MSG_PRE_LOAD这个消息,它就会调用webview的loadUrl,之后webview会调用自身的资源拦截方法,在这个方法中,会将之前保存的pendingWebResourceStream返回给webview让其解析渲染,
8.如果webview处理的是CLIENT_CORE_MSG_FIRST_LOAD消息,webview如果没有loadUrl过就会调用loadDataWithBaseUrl方法加载之前读取的网络数据,这样webview就可以直接做解析渲染了。
2.有缓存模式
完全缓存流程:
左边webview的流程跟无缓存一致,右边sonic的流程会通过SonicCacheInterceptor获取本地数据是否为空,不为空就会发生CLIENT_CORE_MSG_PRE_LOAD消息,之后webview就会使用loadDataWithBaseUrl加载网页进行渲染了


TBS腾讯浏览服务

官网
集成方法,请按照官网的来操作即可。这里直接放上使用后的效果图吧


百度app方案

来看下百度app对webview处理的方案

  1. 后端直出
    后端直出-页面静态直出
    后端服务器获取html所有首屏内容,包含首屏展现所需的内容和样式。这样客户端获取整个网页并加载时,内核可以直接进行渲染。
    这里服务端要提供一个接口给客户端取获取网页的全部内容。而且
    获取的网页中一些需要使用客户端的变量的使用宏替换,在客户端加载网页的时候替换成特定的内容,已适应不同用户的设置,例如字体大小、页面颜色等等。
    但是这个方案还有些问题就是网络图片没有处理,还是要花费时间起获取图片。

2.智能预取-提前化网络请求
提前从网络中获取部分落地页html,缓存到本地,当用户点击查看时,只需要从缓存中加载即可。

3.通用拦截-缓存共享、请求并行
直出解决了文字展现的速度问题,但是图片加载渲染速度还不理想。
借由内核的shouldInterceptRequest回调,拦截落地页图片请求,由客户端调用图片下载框架进行下载,并以管道方式填充到内核的WebResourceResponse中。就是说在shouldInterceptRequest拦截所有URL,之后只针对后缀是.PNG/.JPG等图片资源,使用第三方图片下载工具类似于Fresco进行下载并返回一个InputStream。

总结:

  • 提前做:包括预创建WebView和预取数据
  • 并行做:包括图片直出&拦截加载,框架初始化阶段开启异步线程准备数据等
  • 轻量化:对于前端来说,要尽量减少页面大小,删减不必要的JS和CSS,不仅可以缩短网络请求时间,还能提升内核解析时间
  • 简单化:对于简单的信息展示页面,对内容动态性要求不高的场景,可以考虑使用直出替代hybrid,展示内容直接可渲染,无需JS异步加载

今日头条方案

那今日头条是怎么处理的呢?
1.assets文件夹内预置了文章详情页面的css/js等文件,并且能进行版本控制
2.webview预创建的同时,预先加载一个使用JAVA代码拼接的html,提前对js/css资源进行解析。
3.文章详情页面使用预创建的webview,这个webview已经预加载了html,之后就调用js来设置页面内容
3.对于图片资源,使用ContentProvider来获取,而图片则是使用Fresco来下载的

content://com.xposed.toutiao.provider.ImageProvider/getimage/origin/eJy1ku0KwiAUhm8l_F3qvuduJSJ0mRO2JtupiNi9Z4MoWiOa65cinMeX57xXVDda6QPKFld0bLQ9UckbJYlR-UpX3N5Smfi5x3JJ934YxWlKWZhEgbeLhBB-QNFyYUfL1s6uUQFgMkKMtwLA4gJSVwrndUWmUP8CC5xhm87izlKY7VDeTgLXZUtOlJzjkP6AxXfiR5eMYdMCB9PHneGHBzh-VzEje7AzV3ZvHYpjJV599w-uZWXvWadQR_vlAhtY_Bn2LKuzu_GGOscc1MfZ4veyTyNuuu4G1giVqQ==/6694469396007485965/3

整理下这几个大厂的思路
目的:网页秒开
策略:

  • 针对客户端
    1.预创建(application onCreate 时)webview
    1.1预创建的同时加载带有css/js的html文本
    2.webview复用池
    3.webview setting的设置
    4.预取网页并缓存,预先获取html并缓存本地,需要是从缓存中加载即可
    5.资源拦截并行加载,内核初始化和资源加载同时进行。
  • 针对服务端
    1.直出网页的拼装,服务端时获取网页的全部内容,客户端获取后直接加载
    2.客户端本地html资源的版本控制
  • 针对网页前端
    1.删减不必要的js/css
    2.配合客户端使用VasSonic,只对特定的内容进行页面更新与下载。

自己的想法:
1.网页秒开的这个需求,如果如果只是客户端来做,感觉只是做了一半,最好还是前后端一起努力来优化。
2.但是只做客户端方面的优化也是可以的,笔者实际测试了下,通过预取的方式,的确能做到秒开网页。
3.今年就上5G了,有可能在5G的网络下,网页加载根本就不是问题了呢。


小技巧

修复白屏现象:系统处理view绘制的时候,有一个属性setDrawDuringWindowsAnimating,这个属性是用来控制window做动画的过程中是否可以正常绘制,而恰好在Android 4.2到Android N之间,系统为了组件切换的流程性考虑,该字段为false,我们可以利用反射的方式去手动修改这个属性

/**
     * 让 activity transition 动画过程中可以正常渲染页面
     */
    private void setDrawDuringWindowsAnimating(View view) {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M
                || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            // 1 android n以上  & android 4.1以下不存在此问题,无须处理
            return;
        }
        // 4.2不存在setDrawDuringWindowsAnimating,需要特殊处理
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            handleDispatchDoneAnimating(view);
            return;
        }
        try {
            // 4.3及以上,反射setDrawDuringWindowsAnimating来实现动画过程中渲染
            ViewParent rootParent = view.getRootView().getParent();
            Method method = rootParent.getClass()
                    .getDeclaredMethod("setDrawDuringWindowsAnimating", boolean.class);
            method.setAccessible(true);
            method.invoke(rootParent, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * android4.2可以反射handleDispatchDoneAnimating来解决
     */
    private void handleDispatchDoneAnimating(View paramView) {
        try {
            ViewParent localViewParent = paramView.getRootView().getParent();
            Class localClass = localViewParent.getClass();
            Method localMethod = localClass.getDeclaredMethod("handleDispatchDoneAnimating");
            localMethod.setAccessible(true);
            localMethod.invoke(localViewParent);
        } catch (Exception localException) {
            localException.printStackTrace();
        }
    }

标签:网页,String,安卓,public,webView,Override,webview,Webview,加载
From: https://blog.csdn.net/qq_20314339/article/details/141709075

相关文章

  • Java Script网页设计案例
    1.JavaScript网页设计案例下面我将提供一个简单的JavaScript网页设计案例,该案例将实现一个动态的待办事项列表(TodoList)。用户可以在页面上添加新的待办事项,标记它们为已完成,以及删除它们。这个案例将使用HTML来构建页面结构,CSS来美化页面,以及JavaScript来添加动态功能。1.1HTM......
  • Java Script网页设计案例
    1.JavaScript网页设计案例下面我将提供一个简单的JavaScript网页设计案例,该案例将实现一个动态的待办事项列表(TodoList)。用户可以在页面上添加新的待办事项,标记它们为已完成,以及删除它们。这个案例将使用HTML来构建页面结构,CSS来美化页面,以及JavaScript来添加动态功能。1.1HT......
  • 全栈程序员 | 精通安卓、鸿蒙,小程序,Java、Vue.js、SpringBoot及更多技术
    我是一个全栈程序员,擅长多种开发技术,包括安卓开发、Java编程、Vue.js、SpringBoot以及小程序开发等。我在技术上有广泛的涉猎,并致力于将创新解决方案应用于实际项目中。无论是开发高性能的安卓应用,还是构建响应式网页、实现复杂的后端功能,我都能提供专业的技术支持和高质量的代......
  • EventSource事件流(允许网页与服务器之间建立一个持久的连接,服务器可以通过这个连接向
     EventSource是JavaScript中用于处理服务器发送事件(Server-SentEvents,SSE)的接口。它允许网页与服务器之间建立一个持久的连接,服务器可以通过这个连接向客户端推送更新。EventSource通常用于需要实时更新数据的场景,比如实时通知、股票价格更新等。 基本用法//创建一......
  • Streamlit制作交互式可视化网页应用
    一、介绍Streamlit 是一个开源Python框架,供数据科学家和AI/ML工程师使用几行代码交付动态数据应用程序。在几分钟内构建和部署功能强大的数据应用程序。(具体使用教程可查看官方文档:Streamlitdocumentation)11.简化开发流程2无代码界面:Streamlit允许开发人员使用简单......
  • 小程序开发指南 —— webview 站点使用指南
    webview站点是随着小程序一同发布的静态文件站点,减轻开发者部署静态html文件负担,支持离线模式的技术。开发者可以在小程序中使用webview组件加载webview站点,实现小程序与webview站点的无缝衔接。webview站点的特性支持离线模式,提高访问速度支持与小程序逻辑层通信......
  • 新版Edge浏览器自带网页翻译功能如何使用
        在信息爆炸的网络世界中,语言不应该成为获取知识的障碍。新版MicrosoftEdge浏览器以其强大的网页翻译功能,为用户打开了一扇通往多元文化和信息的大门。无论你是学术研究者、商务人士,还是对外语内容感兴趣的普通读者,Edge的翻译工具都能助你一臂之力。    ......
  • 基于ssm+vue个人网页系统【开题+程序+论文】
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展,个人展示与交流的需求日益增长。个人网页系统作为个人品牌塑造、信息分享与社交互动的重要平台,已成为当代社会不可或缺的一......
  • 安卓11报错:Failed to resolve: com.github.xxxx:14.0 Show in Project Structure dial
    本篇文章主要讲解,安卓11版本情况下项目运行报错Failedtoresolve:com.github.getActivity:Toaster:14.0ShowinProjectStructuredialogAffectedModules:app的主要原因及解决办法。作者:任聪聪独立博客:https://rccblogs.com/631.html日期:2024年8月28日具体......
  • Unity嵌入安卓工程黑屏问题
    最近遇到了个很怪异的问题:UnityAdroid嵌入其它工程开启unity,闪过logo后黑屏持续10多秒。大致经过:Unity导出AndroidStudio工程,并将导出的unityLibrary嵌入其它已经做好的AndroidStudio工程,直接从Unity导出的AndroidStudio工程直接连接手机调试就不会出现卡顿现象,所以......