一、目的
1、为了让基于前端框架vue.js的html5代码跨平台运行(此处仅分析Android),就需要搞清楚平台加载html5的机制;
2、Android平台各系统版本对应的SDK各不相同,需要在代码层面做好版本兼容适配。
二、步骤
1、先分析下Android平台加载html5的机制。Android平台内置浏览器内核WebKit/Blink,Android在此内核基础上封装了一个WebView对象,通过该对象可以加载html5甚至自定义加载方式;
2、WebView对象中又定义了3个子对象:WebChromeClient、WebViewClient、WebSetting来分别定义浏览器相关、加载相关以及通用配置相关的配置参数;
3、WebView精简代码如下:
package com.justinsoft.webview;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.justinsoft.util.LogUtil;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
/**
* 复写webview,添加进度条显示效果
*/
public class BtWebView2 extends WebView
{
// 日志标记
private static final String TAG = LogUtil.getClassTag(BtWebView2.class);
private BtWebChromeClient2 webChromeClient;
public BtWebView2(Context context)
{
super(context);
}
public BtWebView2(Context context, AttributeSet attrs)
{
super(context, attrs);
this.webChromeClient = new BtWebChromeClient2(context);
setWebChromeClient(this.webChromeClient);
this.webChromeClient.addProgressBar(this);
setWebViewClient(new BtWebViewClient());
initSettings();
}
public BtWebView2(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
public BtWebView2(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
}
private void initSettings()
{
WebSettings webSettings = getSettings();
// 让WebView能够执行javaScript
webSettings.setJavaScriptEnabled(true);
// 让JavaScript可以自动打开windows
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// 设置缓存
webSettings.setAppCacheEnabled(true);
// 设置缓存模式,一共有四种模式
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
// 设置缓存路径
// webSettings.setAppCachePath("");
// 支持缩放(适配到当前屏幕)
webSettings.setSupportZoom(true);
// 将图片调整到合适的大小
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);
// 设置webview保存表单数据
webSettings.setSaveFormData(true);
webSettings.setSupportMultipleWindows(true);
// 支持内容重新布局,一共有四种方式
// 默认的是NARROW_COLUMNS
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
// 设置可以被显示的屏幕控制
webSettings.setDisplayZoomControls(false);
// 设置默认字体大小
webSettings.setDefaultFontSize(12);
// 设置默认编码
webSettings.setDefaultTextEncodingName("UTF-8");
// 启用地理定位
webSettings.setGeolocationEnabled(true);
// ***最重要的方法,一定要设置,这就是出不来的主要原因
webSettings.setDomStorageEnabled(true);
// 是否可访问Content Provider的资源,默认值 true
webSettings.setAllowContentAccess(true);
// 设置可以访问文件
webSettings.setAllowFileAccess(true);
// 是否允许通过file url加载的Javascript读取本地文件,默认值 false
webSettings.setAllowFileAccessFromFileURLs(false);
// 是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https),默认值 false
webSettings.setAllowUniversalAccessFromFileURLs(false);
// 支持自动加载图片
webSettings.setLoadsImagesAutomatically(true);
webSettings.setLoadWithOverviewMode(true);
// 自定义user agent
webSettings.setUserAgentString("android");
// 设置允许跨域访问
allowAcrossRequest(webSettings);
setLongClickable(true);
setScrollbarFadingEnabled(true);
setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
setDrawingCacheEnabled(true);
}
private void allowAcrossRequest(WebSettings webSettings)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
{
webSettings.setAllowUniversalAccessFromFileURLs(true);
}
else
{
try
{
Class<?> clazz = webSettings.getClass();
Method method = clazz.getMethod("setAllowUniversalAccessFromFileURLs", boolean.class);
if (method != null)
{
method.invoke(webSettings, true);
}
}
catch (NoSuchMethodException e)
{
Log.e(TAG, "No method error:", e);
}
catch (InvocationTargetException e)
{
Log.e(TAG, "InvocationTargetException:", e);
}
catch (IllegalAccessException e)
{
Log.e(TAG, "IllegalAccessException:", e);
}
}
}
}
代码解析:
1)通过不同参数的构造方法,可以兼容不同的系统版本;
2)通过this.webChromeClient.addProgressBar(this)来给此应用添加了一个进度条,进度条是在WebChromeClient中定义;
3)尤其要注意浏览器的缓存设置,比如webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK)是设置本地资源没有时就加载网络数据(仅第一次加载网络),此策略效率较高。但是请注意,如果系统部分请求是需要实时查询网络,而部分请求仅需要第一次查询网络时,最好的办法就是使用此策略,然后在需要实时查询的请求后面再附加一个时间戳参数,这样就能兼顾需求和效率;
4)由于本项目是要跨平台运行的,所以很重要的一点是代码要识别各浏览器,像微信Android、微信IOS、Chrome浏览器的UserAgent就能很好的区别,android/ios app则最好使用自定义的UserAgent来区分。
4、WebChromeClient精简代码:
package com.justinsoft.webview;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import com.justinsoft.util.LogUtil;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.ColorDrawable;
import android.util.Log;
import android.view.Gravity;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.widget.AbsoluteLayout.LayoutParams;
import android.widget.ProgressBar;
public class BtWebChromeClient2 extends WebChromeClient
{
// 日志标记
private static final String TAG = LogUtil.getClassTag(BtWebChromeClient2.class);
private ProgressBar progressBar;
/**
* 构造方法
*
* @param context
*/
public BtWebChromeClient2(Context context)
{
initProgressBar(context);
}
@Override
public void onProgressChanged(WebView view, int newProgress)
{
if (newProgress == 100)
{
progressBar.setVisibility(GONE);
}
else
{
if (progressBar.getVisibility() == GONE)
{
progressBar.setVisibility(VISIBLE);
}
progressBar.setProgress(newProgress);
}
super.onProgressChanged(view, newProgress);
}
/**
* 添加进度条
*
* @param webView
*/
public void addProgressBar(WebView webView)
{
Log.d(TAG, "start to add progressbar.");
webView.addView(this.progressBar);
}
private void initProgressBar(Context context)
{
this.progressBar = new ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal);
this.progressBar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dp2px(context, 3), 0, 0));
ClipDrawable drawable = new ClipDrawable(new ColorDrawable(Color.BLUE), Gravity.LEFT, ClipDrawable.HORIZONTAL);
this.progressBar.setProgressDrawable(drawable);
}
/**
* 方法描述:根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
private int dp2px(Context context, float dpValue)
{
final float scale = context.getResources().getDisplayMetrics().density;
return (int)(dpValue * scale + 0.5f);
}
}
6、WebViewClient精简代码:
package com.justinsoft.webview;
import com.justinsoft.util.LogUtil;
import android.net.http.SslError;
import android.util.Log;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class BtWebViewClient extends WebViewClient
{
// 日志标记
private static final String TAG = LogUtil.getClassTag(BtWebViewClient.class);
public boolean shouldOverrideUrlLoading(WebView view, String url)
{
Log.i(TAG, "start to override url loading 0");
view.loadUrl(url);
return true;
}
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse)
{
super.onReceivedHttpError(view, request, errorResponse);
Log.e(TAG, "failed to receive msg");
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)
{
// 注意:super句话一定要删除,或者注释掉,否则又走handler.cancel() 默认的不支持https的了。
// super.onReceivedSslError(view, handler, error);
Log.e(TAG, "failed to receive ssl msg");
// 接受所有网站的证书
handler.proceed();
}
}
三、总结
1、上述代码仅实现了可以加载html5的WebView基础代码,为了避免代码揉在一起,特意分成了3个类来实现;
2、上述缓存设置以及UserAgent设置都是泪的教训;
3、上述跨系统版本的兼容方法仅参考了各网友,并没有在对应版本一一验证,请有兴趣的朋友自己验证下。
四、参考资料
[1]https://blog.csdn.net/itluochen/article/details/53336460
上一篇 Vue.js实战——开发Android Hybird App之权限设置_11 下一篇:原 Vue.js实战——开发Android H5 App之Webview高级配置_13