Android中X5WebView详解

640?wx_fmt=png


今日科技快讯


据路透社报道,推特(Twitter)公司周五宣布,它已经禁止总部位于莫斯科的卡巴斯基实验室在其社交平台上打广告,称这家网络安全公司的商业模式与其广告规则存在冲突,并援引美国政府的说法称卡巴斯基实验室与俄罗斯情报机构有关联。推特通过电子邮件证实了这项封杀令。


作者简介


欢迎回到周一,新的一周继续加油努力吧!

本篇来自 骑小猪看流星 的投稿,分享了他如何封装X5WebView以及如何在项目中灵活运用的过程 ,一起来看看!希望大家喜欢。

骑小猪看流星 的博客地址:

https://www.jianshu.com/u/0111a7da544b


前言


这一篇的目标就是怎么样快速封装X5WebView,如何有效的同步以及管理Cookie,使用IntentService优化预加载,如何监听进度条等一些在项目中使用的常用功能。


正文


功能需求

需求一:客户端账号密码登录成功以后,调用H5界面(也就是使用X5webView,以下简称X5)。H5界面也需要去记录你的状态,比如你客户端本地登录成功以后,H5界面也需要显示登录成功的状态。那么,客户端和H5如何去同步状态?

需求二:因为X5加载的时候,会有一段时间会显示空白或者卡顿,如何去监听并利用这个进度并优化?

需求三:如何简单封装X5WebView基本功能?

需求分析

  • 需求一

(针对需求一,我真的是参考了很多哥们的技术博客,然后由于笔者上周也就是3月9号接到的开发需求,其中遇到了很多坑,所以我希望这篇博客可以把这个需求写的详细,尽可能的造福以后遇到同样需求的朋友让他们节约时间少走弯路)

对于Cookie,我们并不是很陌生,这里继续多一嘴,这个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我们可以有如下简单的写法:

if (Build.VERSION.SDK_INT <21){
   com.tencent.smtt.sdk.CookieSyncManager.getInstance().sync();
}else {
   com.tencent.smtt.sdk.CookieManager.getInstance().flush();
}

然后,笔者就遇到了第一个坑,按照如下写法以后,cookie居然神奇的不同步(下面是伪代码,下面是伪代码)

   public void syncCookie(String url, String cookie) {

       CookieSyncManager.createInstance(getContext());
       if (!TextUtils.isEmpty(url)) {
           CookieManager cookieManager = CookieManager.getInstance();
           cookieManager.setAcceptCookie(true);
           cookieManager.removeSessionCookie();// 移除
           cookieManager.removeAllCookie();
           String[] split = cookie.split(";");
           for (String string : split) {
               //为url设置cookie
//                 ajax方式下  cookie后面的分号会丢失
               cookieManager.setCookie(url, string);
           }

           String newCookie = cookieManager.getCookie(url);
           Log.i(TAG, "syncCookie: newCookie == " + newCookie);
//          sdk21之后CookieSyncManager被抛弃了,换成了CookieManager来进行管理。
           if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
               CookieSyncManager.getInstance().sync();//同步cookie
           } else {
               CookieManager.getInstance().flush();
           }
       } else {
       }

   }

之前笔者通过字符串拼接,也就是append字符串 ,拼接字符串以后,我想直接通过cookieManager.setCookie(url, cookie); 在x5WebView.loadUrl(url);调用之前去设置cookie,然后,cookie就是同步不了。没得办法,打印日志之后发现,手动设置的cookie值,神奇的只有一个分号 ! 

谷歌百度后,有哥们说是因为cookie Value的值在读取时,只会读取到第一个分号时,当发现第一个分号即认为读取结束。所以分号后面的cookie的值将不会读取,实际测试确实是这样。那么,我们该如何解决拼接cookie读取失败的问题?

解决办法如下,我们可以一个个手动设置cookie,即可拼接完整的Cookie。(这种办法虽然笨拙,但的确可以有效解决分号切割问题)

cookieManager.setCookie(url,"xxx");
cookieManager.setCookie(url,"xxx");
cookieManager.setCookie(url,"xxx");
cookieManager.setCookie(url,"xxx");

当然,我在公司项目里用的是Okhttp,(为什么这里用Okhttp,因为!这样就可以用Okhttp、Retrofit、OkGo等网络框架,直接集成 我在项目中给大家提供的拦截器、自定义CookieJar使用了)。通过自定义拦截器,实现CookieJar去完成同步客户端和H5的cookie状态。这里先上下最终效果图:(笔者的代码可能不是唯一实现功能需求的,但凑合还能用。实现方式有很多种,写的不好也请大家见谅)

OkHttpClient okHttpClient = new OkHttpClient.Builder()
       .connectTimeout(10, TimeUnit.SECONDS)
       .readTimeout(10, TimeUnit.SECONDS)
       .writeTimeout(10, TimeUnit.SECONDS)
       .addNetworkInterceptor(new HttpLoggingInterceptor().
        setLevel(HttpLoggingInterceptor.Level.BODY))
       .addInterceptor(new AddCookiesInterceptor())
       .addInterceptor(new SaveCookiesInterceptor())
       .cookieJar(new SaCookieManger(HomeApplication.getInstance())).build();

关于AddCookiesInterceptor以及SaveCookiesInterceptor这两个拦截器,主要就是存Cookie和使用Cookie,然后我们点进自定义cookieJar中的 SaCookieManger

public class SaCookieManger implements CookieJar {

   private static final String TAG = LogTAG.cookie;
   private static Context mContext;

   public SaCookieManger(Context context) {
       mContext = context;
   }

   @Override
   public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
       SaasCookieManager.loadCookie(cookies,url.host());
   }

   @Override
   public List<Cookie> loadForRequest(HttpUrl url) {
       return new ArrayList<>();
   }
}

这个类定义了一个context构造参数,在保存cookie的saveFromResponse方法中,调用了SaasCookieManager.loadCookie(cookies,url.host());方法,我们点进SaasCookieManager

public class SaasCookieManager {
   static final String TAG = LogTAG.cookie;
   /**
    * 获取cookie
    * @param cookies
    * @param url
    */

   public static void loadCookie(List<Cookie> cookies,String url){
       List<String> convertCookies  = new ArrayList<>();

       for (int i = 0; i < cookies.size(); i++) {
           String temp = cookies.get(i).toString();
           convertCookies.add(temp);
       }
       com.tencent.smtt.sdk.CookieManager cookieManager= com.tencent.smtt.sdk.CookieManager.getInstance();
       cookieManager.setAcceptCookie(true);

       for (String aCookiesArray : convertCookies) {
           cookieManager.setCookie(url, aCookiesArray);
       }
       if (Build.VERSION.SDK_INT <21){
           com.tencent.smtt.sdk.CookieSyncManager.getInstance().sync();
       }else {
           com.tencent.smtt.sdk.CookieManager.getInstance().flush();
       }
   }

通过遍历添加到集合里面,然后一个个的setCookie( url ,cookie )、接着判断SDK版本号进行同步刷新即可,具体可以参考项目源代码。当然大家也可以在这里面根据开发需求去增加自己的实际功能。

通过上面的步骤,我们就可以简单的实现 客户端与H5端同步cookie。笔者的项目里面,是客户端登录成功以后,进入H5页面,H5页面上直接显示已登录状态。

拓展:有部分手机使用后可能还是无法同步,那么我们可以尝试,设置跨域读取cookie,开启webview对第三方cookie的支持。

public final void setAcceptThirdPartyCookies() {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
       CookieManager.getInstance().setAcceptThirdPartyCookies(this, true);
   }else {
       CookieManager.getInstance().setAcceptCookie(true);
   }
}

当页面加载完毕的时候,我们可以通过下面的截图代码,去获取H5上面的cookie,我们也可以打印日志、进行同步

@Override
public void onPageFinished(WebView webView, String s) {
   CookieManager cookieManager = CookieManager.getInstance();
   cookieManager.setAcceptCookie(true);
   String endCookie = cookieManager.getCookie(s);
   Log.i(TAG, "onPageFinished: endCookie : " + endCookie);
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
       CookieSyncManager.getInstance().sync();//同步cookie
   } else {
       CookieManager.getInstance().flush();
   }
   super.onPageFinished(webView, s);
}
  • 需求二

监听进度,是这样,WebView里面的WebChromeClient这个类,里面有个onProgressChanged方法,重写这个方法就可以获取加载进度。获取到X5Webview的进度之后,还需要通过WebView的setWebChromeClient方法,将我们自定义的WebChromeClient对象传进去,即可完成进度监听。

    public void onProgressChanged(WebView view, int newProgress) {
           // TODO Auto-generated method stub
           progressBar.setProgress(newProgress);
           if (progressBar != null && newProgress != 100) {
//              Webview加载没有完成 就显示我们自定义的加载图
               progressBar.setVisibility(VISIBLE);
           } else if (progressBar != null) {
//              Webview加载完成 就隐藏进度条,显示Webview
               progressBar.setVisibility(GONE);
               imageView.setVisibility(GONE);
           }
       }
  • 需求三

封装X5Webview基本功能

常用设置

比如设置对JS的支持等等一些比较常用的,我们可以直接这样设置

setBackgroundColor(getResources().getColor(android.R.color.white));
setWebViewClient(client);
setWebChromeClient(chromeClient);
setDownloadListener(downloadListener);
addJavascriptInterface(new H5JsInterface(getContext(), this), "h5app");
setClickable(true);
setOnTouchListener(new OnTouchListener() {
   @Override
   public boolean onTouch(View v, MotionEvent event) {
       return false;
   }
});
WebSettings webSetting = getSettings();

webSetting.setJavaScriptEnabled(true);
webSetting.setBuiltInZoomControls(true);
webSetting.setJavaScriptCanOpenWindowsAutomatically(true);
webSetting.setDomStorageEnabled(true);
webSetting.setAllowFileAccess(true);
webSetting.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
webSetting.setSupportZoom(true);
webSetting.setUseWideViewPort(true);
webSetting.setSupportMultipleWindows(true);
webSetting.setAppCacheEnabled(true);
webSetting.setGeolocationEnabled(true);
webSetting.setAppCacheMaxSize(Long.MAX_VALUE);
webSetting.setPluginState(WebSettings.PluginState.ON_DEMAND);
webSetting.setRenderPriority(WebSettings.RenderPriority.HIGH);

滚动条(内侧、外侧的设置),隐藏或显示的基本使用

  getX5WebViewExtension().setScrollBarFadingEnabled(false);
       setHorizontalScrollBarEnabled(false);//水平不显示小方块
       setVerticalScrollBarEnabled(false); //垂直不显示小方块
       setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);//滚动条在WebView内侧显示
//      setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);//滚动条在WebView外侧显示

处理预加载

有的小伙伴说,X5预加载不是很友好。在加载X5内核的时候,X5内核需要进行一些初始化,这些初始化如果不明确指出运行的线程,它就会在你启动页面的时候,默认在主线程中执行,因此就会出现卡顿(这个现象时有时无,但是我们在代码层面尽可能的去规避使用风险),所以,我们可以写个 IntentService 去帮我们管理预加载问题:

public class X5NetService extends IntentService {

   public static final String TAG = LogTAG.x5webview;
   public X5NetService(){
       super(TAG);
   }
   public X5NetService(String name) {
       super(TAG);
   }

   @Override
   public void onHandleIntent(@Nullable Intent intent) {
       initX5Web();
   }

   public void initX5Web() {

       if (!QbSdk.isTbsCoreInited()) {
           QbSdk.preInit(getApplicationContext(), null);// 设置X5初始化完成的回调接口
       }
       QbSdk.initX5Environment(getApplicationContext(), cb);

   }

   QbSdk.PreInitCallback cb = new QbSdk.PreInitCallback() {

       @Override
       public void onViewInitFinished(boolean arg0) {
           // TODO Auto-generated method stub
       }

       @Override
       public void onCoreInitFinished() {
           // TODO Auto-generated method stub
       }

   };
}

多提一嘴:IntentService是Service的子类,比普通的Service增加了额外的功能。IntentService会创建独立的worker线程来处理所有的Intent请求;会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程的问题;所有请求处理完成后,IntentService会自动停止,开发者无需手动调用stopSelf()方法停止Service;

写完之后我们在去自定义Application里面注册服务:(别忘了去清单文件配置Services)

public class HomeApplication extends Application{

   private static HomeApplication homeApplication;
   @Override
   public void onCreate(){
       super.onCreate();
       homeApplication = this;
       preInitX5Core();
   }

   public static HomeApplication getInstance(){
       return homeApplication;
   }

   private void preInitX5Core(){
       //预加载x5内核
       Intent intent = new Intent(this,X5NetService.class);
       startService(intent);
   }
}

返回键的处理

这个就根据大家开发需求具体使用了,有的要求返回键按下两次才允许退出等等

生命周期的处理、释放资源的处理:这个就不说了,大家查阅资料集成功能即可

拦截广告的处理

有些哥们说,使用这个经常会出现广告,解决这个办法有两个办法

  1. 使用Https

  2. 设计拦截url规则,对允许的url进行放行加载,不允许的url,WebView禁止加载


结语


好了,文章到这里就结束了,具体demo地址如下:

https://github.com/zuowutan/ShareX5WebViewDemo


欢迎长按下图 -> 识别图中二维码

或者 扫一扫 关注我的公众号

640.png?

640?wx_fmt=jpeg

猜你喜欢

转载自blog.csdn.net/c10wtiybq1ye3/article/details/80046695