本文已独家授权 郭霖 ( guolin_blog ) 公众号发布!
撸完了上一篇Android-X5WebView简介 之后,有些大兄弟可能觉得不过瘾呐,说你那样的都是很基础的啊(的确很基础),项目里面用起来不爽啊(的确很不爽),不能让我直接CV啊(的确不能直接复制粘贴)等等,那这篇文章的目标就是怎么样快速封装X5WebView,如何有效的同步以及管理Cookie,如何使用IntentService优化预加载,如何监听进度条以及8.1系统的踩坑适配,等一些在项目中的常用功能。
文章用到的源码:项目地址
最近更新内容:
X5Webview适配Android 8.1系统
Https与Http混合加载
功能需求:
需求一:客户端账号密码登录成功以后,调用H5(也就是使用X5webView,以下简称X5)。H5界面也需要去记录你的状态。比如你客户端本地登录成功以后,H5界面也需要显示登录成功的状态。那么,客户端和H5如何去同步状态?
需求二:因为X5加载的时候,会有一段时间会显示空白或者卡顿,如何去监听并利用这个进度并优化?
需求三:如何简单封装X5WebView基本功能,方便日后快速使用?
需求分析:
需求一:
(针对需求一,真的是参考了很多哥们的技术博客,然后由于笔者上周也就是3月9号接到的开发需求,其中遇到了很多坑,所以我希望这篇博客可以把这个需求写的详细,尽可能的造福以后遇到同样需求的朋友让他们节约时间少走弯路)
对于Cookie,我们并不陌生,如果不是很了解的建议首先参考Cookie、Session、Token那点事儿 先大致掌握一下。这里多提一嘴,Cookie 简单理解,它主要是用来进程保活的,其具有时效性(持久化和非持久化),它是通过服务器的请求,在响应头里面拿到,然后在第二次http请求上以请求头的方式将参数带过去,优点是减少后台查库压力等等,更加具体的细节和说明可以参考上面的链接文章,那么,在Android中,也就是WebView中,我们如何去管理Cookie?
首先:CookieSyncManager与CookieManager
Android中关于Cookie的说明:
早期的cookie是由CookieSyncManager进行管理的,之后CookieSyncManager被抛弃了,换成了CookieManager来进行管理。两个版本的分割线就是Android SDK -- 21。
Android中Cookie的存储位置:
目前Android系统WebView是将cookie存储data/data/package_name/app_webview这个目录下的一个叫Cookies的数据中。
CookieSyncManager在内存和存储器之间同步浏览器的cookie。另外,CookieSyncManager的同步策略是在一个独立的线程里定时进行同步。
注意:每次同步的时间间隔是5分钟。
CookieSyncManager类下的常用API介绍
cookie同步策略:
CookieSyncManager.createInstance(context);
CookieSyncManager.getInstance().startSync();
cookie停止同步:
CookieSyncManager.getInstance().stopSync()
cookie立即同步:调用了该方法会立即进行cookie的同步,代码如下:
CookieSyncManager.getInstance().sync()
删除cookie操作:
CookieSyncManager.createInstance(this);
CookieManager.getInstance().removeAllCookie();
CookieManager.getInstance().removeSessionCookie();
CookieSyncManager.getInstance().sync();
CookieSyncManager.getInstance().startSync();
CookieManager管理cookie:从sdk21之后,webview已经内置了cookie的同步操作了。虽然不再需要关注cookie的同步,但是依然需要掌握删除cookie的操作。
删除cookie操作:底层实现是异步清除数据库的记录
CookieManager.getInstance().removeAllCookies(null);
CookieManager.getInstance().flush();
立即同步:注意到这个flush()方法就是立即同步cookie的操作,本质上与CookieSyncManager中的sync()方法是一样的。于是乎,关于同步cookie我们可以有如下简单的写法:
然后,笔者就遇到了第一个坑,按照如下写法以后,cookie居然神奇的不同步(下面是伪代码,下面是伪代码)
之前笔者通过字符串拼接,也就是append字符串 ,拼接字符串以后,我想直接通过cookieManager.setCookie(url, cookie); 在x5WebView.loadUrl(url);调用之前去设置cookie,
然后,cookie就是同步不了。没得办法,打印日志之后发现,手动设置的cookie值,神奇的只有一个分号 !
谷歌百度后,有哥们说是因为cookie Value的值在读取时,只会读取到第一个分号时,当发现第一个分号即认为读取结束。所以分号后面的cookie的值将不会读取,实际测试确实是这样。那么,我们该如何解决拼接cookie读取失败的问题?
解决办法如下图,我们可以一个个手动设置cookie,即可拼接完整的Cookie。(这种办法虽然笨拙,但的确可以有效解决分号切割问题)
当然,我在公司项目里用的是Okhttp,(为什么这里用Okhttp,因为!这样就可以用Okhttp、Retrofit、OkGo等网络框架,直接集成 我在项目中给大家提供的拦截器、自定义CookieJar使用了)。通过自定义拦截器,实现CookieJar去完成同步客户端和H5的cookie状态。这里先上下最终效果图:(笔者的代码可能不是唯一实现功能需求的,但凑合还能用。实现方式有很多种,写的不好也请大家见谅)
关于AddCookiesInterceptor以及SaveCookiesInterceptor这两个拦截器,主要就是存Cookie和使用Cookie,具体说明可以参考 两个拦截器的说明 ,然后我们点进自定义cookieJar中的 SaCookieManger
这个类定义了一个context构造参数,在保存cookie的saveFromResponse方法中,调用了SaasCookieManager.loadCookie(cookies,url.host());方法,我们点进SaasCookieManager
通过遍历添加到集合里面,然后一个个的setCookie( url ,cookie )、接着判断SDK版本号进行同步刷新即可,具体可以参考项目源代码。
当然大家也可以在这里面根据开发需求去增加自己的实际功能。
通过上面的步骤,我们就可以简单的实现 客户端与H5端同步cookie。
笔者的项目里面,是客户端登录成功以后,进入H5页面,H5页面上直接显示已登录状态。
拓展:有部分手机使用后可能还是无法同步,那么我们可以尝试,设置跨域读取cookie,开启webview对第三方cookie的支持。
当页面加载完毕的时候,我们可以通过下面的截图代码,去获取H5上面的cookie,我们也可以打印日志、进行同步
需求二:
监听进度,是这样,WebView里面的WebChromeClient这个类,里面有个onProgressChanged方法,重写这个方法就可以获取加载进度。获取到X5Webview的进度之后,还需要通过WebView的setWebChromeClient方法,将我们自定义的WebChromeClient对象传进去,即可完成进度监听。
拓展:WebViewClient 内置的shouldOverrideUrlLoading(WebView view, String url)这个函数的主要意思是指:检查URL主机是否与特定域匹配。 如果它匹配,则该方法返回false以便不覆盖URL加载(它允许WebView像往常一样加载URL); 如果URL主机不匹配,则Intent创建一个以启动默认活动以处理URL(解析为用户的默认Web浏览器)。
需求三:封装X5Webview基本功能
常用设置:
比如设置对JS的支持等等一些比较常用的,我们可以直接这样设置
滚动条(内侧、外侧的设置),隐藏或显示的基本使用:
处理预加载:
有的小伙伴说,X5预加载不是很友好。在加载X5内核的时候,X5内核需要进行一些初始化,这些初始化如果不明确指出运行的线程,它就会在你启动页面的时候,默认在主线程中执行,因此就会出现卡顿(这个现象时有时无,但是我们在代码层面尽可能的去规避使用风险),所以,我们可以写个 IntentService 去帮我们管理预加载问题:
多提一嘴:IntentService是Service的子类,比普通的Service增加了额外的功能。
IntentService会创建独立的worker线程来处理所有的Intent请求;
会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程的问题;
所有请求处理完成后,IntentService会自动停止,开发者无需手动调用stopSelf()方法停止Service;
写完之后我们在去自定义Application里面注册服务:(别忘了去清单文件配置Services)
返回键的处理:
这个就根据大家开发需求具体使用了,有的要求返回键按下两次才允许退出等等
生命周期的处理、释放资源的处理:这个就不说了,大家查阅资料集成功能即可
拦截广告的处理:
有些哥们说,使用这个经常会出现广告,解决这个办法有两个办法
1:使用Https
2:设计拦截url规则,对允许的url进行放行加载,不允许的url,WebView禁止加载
Android8.0系统适配:
2017年8月份,谷歌正式发布了Android8.0系统,时隔3个多月谷歌正式发布了Android 8.1的正式版。但是目前,Android用户持有的系统版本百分之85的系统是介于Android4.4-Android7.0这个系统版本之间,好了,这样真机调试就会有一定的局限性。现在出现了这样一种情况,使用X5去加载Url,8.1系统以下的都正常,8.1系统以上的的就会有问题。这不,腾讯的X5社区也有别的开发者反馈了这个问题,但是没人回答:
具体他是什么问题我不清楚(最后分析可能是遇到了同样的问题),这里先细说我遇到的问题。还是文章内部的代码,到了8.1系统以后,我这边通过X5WebView加载H5游戏就有问题。我们知道 初始化的时候,首先需要调用 QbSdk.PreInitCallback 这个回调函数,也就是onViewInitFinished ( boolean ininTag )这个回调函数,这个函数主要是完成x5內核初始化,其中,这里的ininTag为true则表示x5内核加载成功,false即表示x5内核加载失败,如果为false,X5内核会自动切换到系统内核。
现在的一个情况是,加载到X5内核以后,这个initTag有一定几率会失败。你可能会说既然初始化失败那就使用系统的WebView呀,嗯,没错,但是现在切换原生的WebView以后,就加载不出来了(蜜汁尴尬)这个原因后面也会说,基于此这里引用一段谷歌针对8.0系统的官方文档:
多个 Android 应用和服务可以同时运行。 例如,用户可以在一个窗口中玩游戏,同时在另一个窗口中浏览网页,并使用第三个应用播放音乐。同时运行的应用越多,对系统造成的负担越大。 如果还有应用或服务在后台运行,这会对系统造成更大负担,进而可能导致用户体验下降;例如,音乐应用可能会突然关闭。应用在两个方面受到限制:
后台服务限制:处于空闲状态时,应用可以使用的后台服务存在限制。 这些限制不适用于前台服务,因为前台服务更容易引起用户注意。
广播限制:除了有限的例外情况,应用无法使用清单注册隐式广播。 它们仍然可以在运行时注册这些广播,并且可以使用清单注册专门针对它们的显式广播。
注:默认情况下,这些限制仅适用于针对 O 的应用。
在后台中运行的服务会消耗设备资源,这可能降低用户体验。 为了缓解这一问题,系统对这些服务施加了一些限制。
系统可以区分 前台 和 后台 应用。 (用于服务限制目的的后台定义与内存管理使用的定义不同;一个应用按照内存管理的定义可能处于后台,但按照能够启动服务的定义又处于前台。)如果满足以下任意条件,应用将被视为处于前台:
具有可见 Activity(不管该 Activity 已启动还是已暂停)。
具有前台服务。
另一个前台应用已关联到该应用(不管是通过绑定到其中一个服务,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的服务,那么该应用处于前台:
A:IME
B:壁纸服务
C:通知侦听器
D:语音或文本服务
E:如果以上条件均不满足,应用将被视为处于后台。
F:绑定服务不受影响,这些规则不会对绑定服务产生任何影响。 如果您的应用定义了绑定服务,则不管应用是否处于前台,其他组件都可以绑定到该服务。
为了降低发生这些问题的几率,Android 8.0 对应用在用户不与其直接交互时可以执行的操作施加了限制。
在 Android 8.0 之前,创建前台服务的方式通常是先创建一个后台服务,然后将该服务推到前台。
Android 8.0 有一项复杂功能;系统不允许后台应用创建后台服务。 因此,Android 8.0 引入了一种全新的方法,即Context.startForegroundService(),以在前台启动新服务。
以上文档截取自8.0系统谷歌官方文档,那么这一段话主要的意思就是新系统限制了服务,因此,我们写的IntentService就不要这样写了(谷歌有新的推荐方式我这里怎么快就怎么来吧),直接将IntentService里面初始化X5内核的代码拷贝出来,放在自定义Application里面的onCreate()即可。
还有一点,可能是手机或者种种问题,我这边初始化成功的概率大概是百分之90(测试了十次,有一次内核没有初始化成功),为了规避这种初始化失败又切换到系统内核加载不出来的风险,我找了个笨方法,那就是如果初始化失败就让他继续初始化直到X5初始化成功(这里使用的是EventBus去通知结果),初始化成功以后才使用X5Webview去加载url。
下面是代码截图:
当然,这只是笔者自己的解决方案(笨办法),如果腾讯技术团队有最新的说明或者开发者有更好的解决方案,请直接在评论区指出,欢迎斧正,谢谢。
最新增加:Https与Http混合加载
我想,这个问题的出现,是之前X5内核在8.1系统上初始化失败,笔者做的一个实验尝试。因为X5内核在8.1系统上初始化失败(解决办法可以参考上面的),所以笔者又将WebView换成了原生的。好的,老铁没毛病,用了原生的WebView以后,问题又出现了,H5游戏加载不出来,这就尴尬到新的地步了?
带着黑人小哥三个问号的我,没办法呐,花时间跟踪调试,最后发现:这个H5内置的脚本资源,居然是Https和Http两种JS脚本混合的!!!
但是!!!X5内部默认实现了混合加载!!!所以现在切换到原生WebView以后,加载不出来了。因此针对这种特殊情况,需要加上新的API。
我们知道Https简单讲就是HTTP的安全版,为什么它是安全的版本?因为Https提供了身份验证与加密通讯方法(确切的说是SSL)但是,Https需要证书,但是Android这边针对WebView加载Https的一般做法是忽略SSL证书错误,继续加载页面。所以需要重写WebViewClient的onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)方法,实现代码如下:
public WebViewClient client = new WebViewClient() {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
// 忽略SSL证书错误,继续加载页面
handler.proceed();
}
}
证书的问题我们忽略以后,接下来还需要重写WebSettings内置的API,一行代码搞定:
WebSettings webSetting = getSettings();
//允许混合加载
webSetting.setAllowUniversalAccessFromFileURLs(true);
这样就可以实现原生WebView混合加载的问题。
附录:项目地址
如果这篇文章对您有开发or学习上的些许帮助,希望各位看官留下宝贵的star,谢谢。
Ps:著作权归作者所有,转载请注明作者, 商业转载请联系作者获得授权,非商业转载请注明出处(开头或结尾请添加转载出处,添加原文url地址),文章请勿滥用、开源项目仅供学习交流、也希望大家尊重笔者的劳动成果,谢谢。