前言
通过了解HyBrid技术,我才深刻的体会到了WebView这个View的强大。
在网上,很少有关于HyBird开发的技术性博文。我找了好久都没有找到关于这部分的技术博文。前阵子在学习了ヾ(o◕∀◕)ノヾ叶小钗博主的博文后,我才了解并学习了HyBird开发技术。各位看官可以看看博主的博文,不过博文是讲Web部分的,并没有Android部分的。
- 叶小钗 - 浅谈Hybrid技术的设计与实现
- 叶小钗 - 浅谈Hybrid技术的设计与实现第二弹
- 叶小钗 - 浅谈Hybrid技术的设计与实现第三弹——落地篇
HyBrid技术指的是混合开发技术,融合了Html&CSS&JS技术和Android技术(主要容器是WebView)。HyBird App底层依赖于Native提供的容器(WebView),上层建筑的搭建采用Html&CSS&JS技术。换而言之,就是HyBrid开发中,Android Native部分只要专注于开发提供JS调用Native的接口以及一些通讯设计、并发设计、异常处理、日志监控、安全模块等等的功能,而H5部分则做业务开发,底层透明化、上层多多样化这些部分部分。ヽ(°◇° )ノ干正事~
这里,我们来一起学习这个View,从学习中找到HyBrid开发的方法。
继承关系
- java.lang.Object
- android.view.View
- android.view.View
- android.widhet.AbsoluteLayout
- android.webkit.WebView
- android.widhet.AbsoluteLayout
- android.view.View
- android.view.View
介绍
WebView,简而言之,就是一个用于浏览网页的UI控件。
官方介绍:
A View that displays web pages. This class is the basis upon which you can roll your own web browser or simply display some online content within your Activity. It uses the WebKit rendering engine to display web pages and includes methods to navigate forward and backward through a history, zoom in and out, perform text searches and more.
大致就是说,这是一个基于WebKit渲染引擎的具有缩放显示,网页浏览历史,前进后退等等功能的用于呈现网页信息的View。
WebView中还提供一些强大的接口给我们,使我们可以定制我们需要的一些功能。
权限设置
使用WebView需要用到Android的网络权限,因此,需要在Manifest.xml中增加
<uses-permission android:name="android.permission.INTERNET" />
基本用法
WebView
我们先定义一个Uri。
private String TestUri = "http://www.baidu.com";
说明:以下都是加载在线的资源的。
一、基本用法一
/** * 直接调用Android手机内的默认浏览器来打开网页 */
private void Usage1() {
Uri uri = Uri.parse(TestUri);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
二、基本用法二
/** * 我们换种方式,使用webView,还是打开了系统默认的浏览器~ */
private void Usage2() {
WebView webView = new WebView(this);
// 加载网页
webView.loadUrl(TestUri);
this.setContentView(webView);
}
但是这两中方法调用的都是系统默认的浏览器,原因是我们没有调用setWebViewClient()方法,设置WebView视图。
三、设置WebView视图
/** * 使用webView的setWebViewClient() */
private void Usage3() {
WebView webView = new WebView(this);
// 加载网页
webView.loadUrl(TestUri);
webView.setWebViewClient(new WebViewClient());
this.setContentView(webView);
}
四、重写返回事件
但是,在这里我们也会发现一个问题,一旦我们按了返回键的时候,就直接退出了该Activity了,而不是回退到历史记录的前一个页面。这里,我们需要重写一个方法:在AppCompatActivity中就直接重写onBackPressed()方法,在低版本中就重写onKeyDown()方法。
说明:
- webview.canGoBack();
- 英文版:Gets whether this WebView has a back history item。
- 假如WebView中有浏览历史且当前页面的前一个页面存在的话就返回当前页面的前一个历史页面。
1.在AppCompatActivity中重写onBackPressed()方法
@Override
public void onBackPressed() {
// 判断webView中是否还有返回页面,如果有就返回webview历史缓存的页面,没有就直接退出当前Activity
if (mWebView.canGoBack()) {
// 返回webview历史缓存的页面
mWebView.goBack();
} else {
// 退出当前Activity
super.onBackPressed();
}
}
2.在低版本中重写onKeyDown()方法
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && webview.canGoBack()) {
webview.goBack(); //goBack()表示返回WebView的浏览历史的上一页面
return true;
}
return false;
}
五、加载本地Html
当我们加载本地HTML资源,是不需要设置setWebViewClient()的。
Html放置的文件夹就是项目目录下的asset下,则url格式为file:///android_asset/…。
- 阿方的博客 - Android studio的使之新建例如Assets文件夹的步骤
/** * 加载本地的HTML */
private void Usage6() {
WebView webView = new WebView(this);
// 加载网页
webView.loadUrl("file:///android_asset/index.html");
webView.addJavascriptInterface(new JSBridge(), "android");
this.setContentView(webView);
}
本地的HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<h1>这是本地的Html文件</h1>
<input type="button" value="js调native" onclick="button()">
</head>
<body>
<script type="text/javascript"> function button() { android.toastMessage("阴吹思婷"); } </script>
</body>
</html>
六、JS调用Native
我们使用下前面的源码,改下,让他支持JS
/** * JS交互 */
private void Usage4() {
WebView webView = new WebView(this);
// 支持JS
webView.getSettings().setJavaScriptEnabled(true);
// 加载网页
webView.loadUrl("file:///android_asset/index.html");
webView.setWebViewClient(new WebViewClient());
webView.addJavascriptInterface(new JSBridge(), "android");
this.setContentView(webView);
}
七、Native调用JS
核心代码:
webView.loadUrl("javascript:toast()");
主要代码:
/** * Native调用JS */
private void Usage7() {
final WebView webView = new WebView(this);
// 支持JS
webView.getSettings().setJavaScriptEnabled(true);
// 加载网页
webView.loadUrl("file:///android_asset/index.html");
webView.addJavascriptInterface(new JSBridge(), "android");
// 添加一个LinearLayou作为容器,包含一个Button和webview
LinearLayout l = new LinearLayout(this);
l.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
);
l.setLayoutParams(layoutParams);
Button button = new Button(this);
button.setText("点击调用JS");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
webView.loadUrl("javascript:toast()");
}
});
l.addView(button);
l.addView(webView);
this.setContentView(l);
}
html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<h1>这是本地的Html文件</h1>
<p id = "p1">66666</p>
<input type="button" value="js调native" onclick="button()">
</head>
<body>
<script type="text/javascript"> function button() { android.toastMessage("阴吹思婷"); } function toast() { document.getElementById("p1").innerHTML="Hahahahha~"; } </script>
</body>
</html>
八、常用的Methods
九、WebSettings
使用WebView.getSettings()获取到WebSettings对象。
1.适应手机屏幕
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().setLoadWithOverviewMode(true);
2.放大设置
webView.getSettings().setDisplayZoomControls(true);
3.无限放大
webView.getSettings().setBuiltInZoomControls(true);
4.比例缩放
webView.setInitialScale(50);
5.允许使用JavaScript
- JS与Native交互必须将以下设置为true。
webView.getSettings().setJavaScriptEnabled(true);
有大神写的很详细了,让我们看大神的博文吧~
- WebView控件之WebSettings各种配置方法源码总结
WebViewClient
设置WebView加载事件
- 以下两个方法可以用来做一些加载时的显示操作的,比如当加载时间过长的时候,我们可以加上个进度条,优化用户体验。
URL拦截
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
这个函数的作用是给予开发者一个机会可以重置URL在加载的时候的动作,甚至改变URL。
假如我们要告诉APP我们已经拦截了URL并且已经对URL做出了处理,需要离开当前WebView的时候,就返回true,否则返回false,交由系统用默认的方法处理当前URL。
其他
扩展应用
在WebView中有很多可定制化的接口,我们可以在这些接口上扩展出我们需要的功能。
比如,我们可以通过继承WebChromeClient这个类,重写其中某些方法(进度条、JS……)来满足业务开发。
又比如,我们可以通过继承WebViewClient这个类,重写其中的shouldOverrideUrlLoading()来拦截当前浏览的网页的响应点击事件的URL,通过拦截该URL,我们可以做很多事情……ヾ(o◕∀◕)ノヾ比如,我们可以在网页跳转的时候加入点动画特效,或者说,我们可以把URL拦截、解析之后做出相应的Actionヽ(°◇° )ノ……
看到这里,你可能会想,我要是在Android和IOS上搭建好底层,提供一致的,约定好的API接口,那么就是一套H5拯救全世界的节奏了。
很牛逼,是吧?我也是这么觉得的。在学习了HyBrid的开发思想后,我深觉混合开发有潜在的价值。比如,当你要快速开发市场的时候,一套H5代码就可以搞定所有端的业务逻辑代码,那岂不是节省了很多时间(Time is moneyʅ(´◔౪◔)ʃ)……
可以查看以下博文了解下Hybrid。
- 云Sir - HybridApp解决方案No1混合模式(Hybrid)App开发概述
HyBird开发步骤
在此,我们可以推测出Hybrid的开发流程。
- 新建一个WebView
- 创建WebChromeClient的子类,重写其中的方法,并用webView.setWebViewClient(WebChromeClient)
- 其实这个也不是很必要。
- 创建WebViewClient的子类,重写其中部分方法
- onPageStarted():页面加载时
- onPageFinished():页面加载结束
- shouldOverrideUrlLoading():拦截、处理URL
- onReceivedError():收到错误信息的时候,执行一些方法
- shouldInterceptRequest():拦截并通知App来自WebView上的页面的资源请求,并允许App返回数据
- ……
- 修改WebView的WebSettings
- setCacheMode(int mode) :设置缓存
- LOAD_DEFAULT 默认加载方式
- LOAD_CACHE_ELSE_NETWORK 按网络情况使用缓存
- LOAD_NO_CACHE 不使用缓存
- LOAD_CACHE_ONLY 只使用缓存
- setDomStorageEnabled(boolean)
- 是否存储页面的DOM结构,默认是false
- setAppCacheEnabled(boolean)
- 是否允许Cache,默认false。
- 考虑需要存储缓存,应该为缓存指定存储路径setAppCachePath
- setUserAgentString(String)
- 设置WebView代理,默认使用默认值
- setAllowFileAccess(boolean)
- 是否允许访问WebView内部文件,默认true
- setJavaScriptEnabled(boolean)
- 最主要的是设置这个方法,这个方法可以允许我们将Java Object注入到WebView中,让WebView可以调用Java Object,进而实现H5和Native的交互。
- ……
- setCacheMode(int mode) :设置缓存
- 注入JAVA OBJECT到WebView中。
- 在HyBird中,JS和Native交互一般都是JS调用Native来实现页面效果。
- 使用addJavascriptInterface(Object,String)方法注入。
- 第一个是Java对象,第二个是一个String,这个String是提供给H5调用的
- 这里有个隐患:假如在Activity中直接将this注入到WebView中的话,那么有一些人可能利用这个漏洞随意调用我们的方法~o_O……
- 所以,我们一般都是重新写个类,把必要的东西放在这个类里……再通过适当方法传递到其他需要的类中。
- 处理一些安全问题:
- 至于WebView的安全问题,可以去:启舰 - WebView使用详解(一)——Native与JS相互调用(附JadX反编译)这里看看
- 腾讯修复了WebView的一些安全问题,即TBS-X5SDK
- X5SDK是通过调用微信/手机QQ/空间的X5内核,解决系统webview兼容性差、加载速度慢、功能缺陷等问题,开发接入便捷,大小只有253K,仅需几行代码,即可解决一切令开发者们头疼的问题,为用户提供最优秀的浏览体验。
实例
下面是一个简单的例子,通过拦截URL,实现在Native容器中页面跳转时动画效果。这也是HyBird开发中必须的一个功能吧。ヽ(゚∀゚*)ノ━━━ゥ♪
package com.notzuonotdied.webviewtest;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.net.URL;
import static android.R.attr.host;
public class HyBridWebViewActivity extends AppCompatActivity {
private HyBridWebView hybridwebview;
private ProgressBar hybridprogressbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hy_brid_web_view);
this.hybridprogressbar = (ProgressBar) findViewById(R.id.hybrid_progressbar);
this.hybridwebview = (HyBridWebView) findViewById(R.id.hybrid_webview);
initConfig(hybridwebview);
}
/** * 从intent传过来的参数获取到URL */
protected String getUrl() {
Uri data = getIntent().getData();
String url;
if (null == data) {
url = getIntent().getStringExtra("URL");
Log.i("Notzuonotdied","第二场");
} else {
url = data.toString();
Log.i("Notzuonotdied", "进入->" + url);
}
return url;
}
/** * 需要设置webview的属性则重写此方法 * * @param webView */
protected void initConfig(WebView webView) {
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
webView.addJavascriptInterface(new HybridJsInterface(), "android");
settings.setDomStorageEnabled(true);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
webView.setWebViewClient(new WebViewClient() {
@Override// 拦截URL,方法内为拦截URL期间的自定义执行过程
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//
if (!TextUtils.isEmpty(url)) {
Log.i("Notzuonotdied", "shouldOverrideUrlLoading");
// 在这里可以通过解析url,如url中携带参数的话,可以通过截取来进行处理,响应自定义的Action。
Intent intent = new Intent(HyBridWebViewActivity.this, HyBridWebViewActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("URL", url);
startActivity(intent);
overridePendingTransition(R.anim.hybrid_right_in, R.anim.hybrid_left_out);
return true;
}
return false;
}
@Override// 当WebView中页面刚开始加载的时候
public void onPageStarted(WebView view, String url, Bitmap favicon) {
hybridprogressbar.setVisibility(View.VISIBLE);
super.onPageStarted(view, url, favicon);
}
@Override// 当WebView中页面刚结束加载的时候
public void onPageFinished(WebView view, String url) {
hybridprogressbar.setVisibility(View.GONE);
super.onPageFinished(view, url);
}
});
settings.setJavaScriptCanOpenWindowsAutomatically(true);
String url = getUrl();
if (!TextUtils.isEmpty(url)) {
webView.loadUrl(url);
}
}
@Override
public void onBackPressed() {
// 判断webView中是否还有返回页面,如果有就返回webview历史缓存的页面,没有就直接退出当前Activity
if (this.hybridwebview.canGoBack()) {
// 返回webview历史缓存的页面
this.hybridwebview.goBack();
} else {
// 退出当前Activity
super.onBackPressed();
}
}
@Override
public void finish() {
// Activity退出时动画
overridePendingTransition(R.anim.hybrid_bottom_out, R.anim.hybrid_bottom_in);
super.finish();
}
/** * JavaObject注入WebView * */
public class HybridJsInterface {
@JavascriptInterface
public void goToNewPage(String message) {
Toast.makeText(HyBridWebViewActivity.this, message, Toast.LENGTH_SHORT).show();
}
}
}
附录
源码地址:Github下载
- 启舰 - WebView使用详解(一)——Native与JS相互调用(附JadX反编译)
- 启舰 - WebView使用详解(二)——WebViewClient与常用事件监听
- 启舰 - WebView使用详解(三)——WebChromeClient与LoadData补充
- soaringEveryday - Android WebView使用深入浅出
- 云Sir - HybridApp解决方案No1混合模式(Hybrid)App开发概述