我们了解过Web,对于HTTP协议,我们不是做网站开发,Android端只需要简单的了解基本的原理就可以了。首先由客户端发送请求给服务端,然后服务端返回对应的指令,最后根据返回的指令进行解析和处理。这就是一个简单的网络交互协议。
1. WebView的用法
在做Android开发的时候,我们可能会碰到一些特殊的需求,比如说在应用 程序中加载一个网页,或者插入一些广告啥的,而且需求中又不允许打开系统的浏览器,怎么办?Android已经考虑到这种需要了,所以提供了一个WebView控件,在自己的应用程序中嵌入一个浏览器。这样就可以轻松展示各种网页。
WebView的用法很简单,和一般的控件都一样。
- 新建一个activity_main.xml,然后插入一个WebView 控件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<WebView
android:id="@+id/wb_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
- MainActivity 设置WebView的方法
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView wb_main= (WebView) findViewById(R.id.wb_main);
wb_main.getSettings().setJavaScriptEnabled(true);//支持JavaScriptEnable
wb_main.setWebViewClient(new WebViewClient());
wb_main.loadUrl("http://www.baidu.com");
}
}
通过getSetting()可以设置浏览器的属性,这里用了setJavaScriptEnabled()的来让WebView支持JavaScript脚本。然后调用WebView的setWebViewClient()的方法并传入一个实例。最后是希望加载的网页
3.最后一步,记得加上权限
<uses-permission android:name="android.permission.INTERNET"/>
这样就完成了一个简单的加载网页。
2. WebView的方法
2.1 加载url
根据加载的方式不同分为三种:
//方式1. 加载一个网页:
webView.loadUrl("http://www.baidu.com/"); // 加载url页面
//方式2:加载apk包中的html页面
webView.loadUrl("file:///android_asset/test.html");
//方式3:加载手机本地的html页面
webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");
// 另外1、: 加载 HTML 页面的一小段内容
WebView.loadData(String data, String mimeType, String encoding) 如:
webView_main.loadData(data, "text/html","utf-8");
// 参数说明:
// 参数1:需要截取展示的内容
// 内容里不能出现 ’#’, ‘%’, ‘\’ , ‘?’ 这四个字符,若出现了需用 %23, %25, %27, %3f 对应来替代,否则会出现异常
// 参数2:展示内容的类型
// 参数3:字节码
//另外2、:
webView_main.loadDataWithBaseURL(null, data, "text/html", "utf-8", null);
loadDataWithBaseURL()的作用呢?难道是结合体?因为webView_main.loadData(data, "text/html","utf-8");//这个方法中虽然设置了字符,但是还是会显示中文乱码。所以建议使用 webView_main.loadDataWithBaseURL(null, data, "text/html", "utf-8", null);
2.2 前进后退网页
//是否可以后退
Webview.canGoBack()
//后退网页
Webview.goBack()
//是否可以前进
Webview.canGoForward()
//前进网页
Webview.goForward()
//以当前的index为起始点前进或者后退到历史记录中指定的steps
//如果steps为负数则为后退,正数则为前进
Webview.goBackOrForward(intsteps)
如果在请求WebView的时候,进入二级的WebView的时候,点击Back键,实际上是整个Activity都结束,调用了finish()方法,所以这个时候需要调用goBack()方法。
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KEYCODE_BACK) && wb_main.canGoBack()) {
wb_main.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
或者.
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(event.getKeyCode() == KeyEvent.KEYCODE_BACK){
if(wb_main.canGoBack() ){
//获取浏览记录
WebBackForwardList backForwardList= wb_main.copyBackForwardList();
//这里的判断是为了让页面在有上一个页面的情况下,跳转到上一个html页面,而不是退出当前activity
if(backForwardList.getCurrentIndex()>0){
String historyUrl = backForwardList.getItemAtIndex(backForwardList.getCurrentIndex()-1).getUrl();
if(historyUrl !=url ){
wb_main.goBack();
return true;
}
}
} else{
return true;
}
}
return true;
}
2.3 缓存
//清除网页访问留下的缓存
//由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
Webview.clearCache(true);
//清除当前webview访问的历史记录
//只会webview访问历史记录里的所有记录除了当前访问记录
Webview.clearHistory();
//这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
Webview.clearFormData();
1.2 WebView的常用类
1.2.1 WebSetting类
WebView加载网页内容,但是对于HTML文本的Javascript代码却无法加载运行。为了解决这个问题要借助WebSettings类。WebSettings类除了可以设置是否支持Javascript外,还具有set系列方法来设置WebView的属性和状态。WebSettings对象通过WebView对象的getSettings()方法来获取
方法 | 描述 |
---|---|
setJavaScriptEnabled(boolean flag) | 设置是否支持Javascript |
setBlockNetworkImage(boolean flag) | 设置是否阻止网络图片加载 |
setBuiltInZoomControls(boolean enabled) | 将HTML文本内容加载到WebView中 |
setCacheMode(int mode) | 设置缓存模式 |
setDefaultFontSize(int size) | 设置默认字体大小 |
setFixedFontFamily(String font) | 设置固定使用的字体 |
setDefaultTextEncodingName(String encoding) | 设置解码时默认的字符集 |
setSupportZoom(boolean support) | 设置是否支持变焦 |
setAllowFileAccess(boolean allow) | 设置是否允许访问WebView中文件。就是file:///android_asset和file:///android_res路径下的资产和资源文件。默认允许访问。 |
注意:5.1以上默认禁止了https和http混用 这是开启
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
2.4 WebViewClient类
WebView解决了支持Javascript的问题。但是新的问题又出来了。当点击WebView中的超链接后,原本希望目标网页在当前WebView中显示,但是却打开了系统浏览器来加载目标网页。为了解决这个问题,要借助WebViewClient类。WebViewClient类专门用来辅助WebView处理各种通知、请求等事件。通过WebView对象调用setWebViewClient()方法来指定一个WebViewClient对象,重写WebViewClient对象中的shouldOverrideUrlLoading()方法,使得当有新连接时,使用当前WebView来显示网页。WebViewClient除此之外,还有其他的方法
方法 | 描述 |
---|---|
shouldOverrideUrlLoading() | 新的链接在当前WebView中打开 |
onPageStarted() | 网页开始加载 |
onPageFinished() | 网页加载完毕 |
doUpdateVisitedHistory() | 更新访问历史记录的数据库 |
onLoadResource() | 加载指定Url地址提供的资源 |
onFormResubmission() | 应用程序重新请求网页数据 |
onScaleChanged() | WebView发生改变 |
2.5 WebChromeClient类
对于网页中一般的Javascript都可以被WebView加载执行了。但是某些特殊的Javascript语句依然无法执行。例如Javascript语句中的Alert、Confirm、Prompt等对话框。为了解决这个问题,要借助WebChromeClient类。WebChromeClient类专门用来辅助WebView处理Javascript的对话框、网站图标、网站Title、加载进度等。
方法 | 描述 |
---|---|
onJsAlert() | 处理Javascript中Alert对话框 |
onJsConfirm() | 处理Javascript中Confirm对话框 |
onJsPrompt() | 处理Javascript中Prompt对话框 |
onProgressChanged() | 加载进度条改变 |
onCloseWindow() | 关闭WebView |
onCreateWindow() | 创建WebView |
onReceivedIcon() | Icon图标改变 |
onReceivedTitle() | 网页Title改变 |
onRequestFocus() | WebView显示焦点 |
3. Android 和Js的交互
在实际开发过程中,Android与Js的交互都是通过WebView来完成的。WebView是二者之间的桥梁。
- Android 调用Js的代码
- Js调用Android的代码
3.1 Android 调用JS的代码
我们是做安卓开发的,所以在实际的开发过程中,有h5的人会写好对应的方法,然后我们只需要根据它提供给我们的方法,调起js的代码就可以了。这里写一个例子来说明一下。
- 首先在一个布局文件中有这样两个控件。activity_main2.xml.一个Button,一个WebView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="@string/btn_click"
/>
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
- 接下来在assert文件下创建一个html文件。(创建asserts文件,main->New->Folder->Asserts Folder),(创建html文件,可以自己创建一个File文件,然后后缀加上 login.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
<script>
function callJS(){
alert("Android调用了JS的callJS方法");
}
</script>
</head>
</html>
- 接下来就是对布局的一些处理,上图的逻辑很简单,我们在点击按钮的时候出发弹窗,然后根据弹窗吊起Js。
public class Main2Activity extends AppCompatActivity {
WebView mWebView;
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mWebView =(WebView) findViewById(R.id.webView);
WebSettings webSettings = mWebView.getSettings();
// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);
// 设置允许JS弹窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// 先载入JS代码
// 格式规定为:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/login.html");
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 通过Handler发送消息
mWebView.post(new Runnable() {
@Override
public void run() {
// 注意调用的JS方法名要对应上
// 调用javascript的callJS()方法
mWebView.loadUrl("javascript:callJS()");
}
});
}
});
// 由于设置了弹窗检验调用结果,所以需要支持js对话框
// webview只是载体,内容的渲染需要使用webviewChromClient类去实现
// 通过设置WebChromeClient对象处理JavaScript的对话框
//设置响应js 的Alert()函数
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder b = new AlertDialog.Builder(Main2Activity.this);
b.setTitle("Alert");
b.setMessage(message);
b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
b.setCancelable(false);
b.create().show();
return true;
}
});
}
}
【注意】永远不要阻塞UI线程,这是开发Android程序的一个真理。虽然是真理,我们却往往不自觉的犯一些错误违背它,一个开发中常犯的错误就是:在UI线程中去等待JavaScript 的回调。Android 4.4中,提供了新的Api来做这件事情。evaluateJavascript() 就是专门来异步执行JavaScript代码的。专门用于异步调用JavaScript方法,并且能够得到一个回调结果。
调用方式 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
使用loadUrl() | 方便简洁 | 效率低;无返回值 | 无需返回值,对性能要求低 |
使用evaluateJavascript() | 效率高 | 向下兼容差(4.4以上) | Android 4.4以上 |
mWebView.evaluateJavascript(script, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//返回Js的结果
}
});
修改代码,既兼容4.4以上,有包括4.4以下
button.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public void onClick(View v) {
if(Build.VERSION.SDK_INT<19){
mWebView.post(new Runnable() {
@Override
public void run() {
mWebView.loadUrl("javascript:callJS()");
}
});
}else{
//已经是异步了,无需再开下线程了
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
Log.e(TAG, "onReceiveValue: "+s );
}
});
}
}
});
3.2 JS调用安卓的代码
在安卓实际开发过程中,JS的一些代码,也可以调用安卓的功能。方法有以下三种:
3.2.1 通过WebView的addJavascriptInterface()进行对象映射
具体的方法:
- 定义一个与JS对象映射关系的Android类,然后定义一个JS需要调用的方法
public class Main3JS extends Object {
// 定义JS需要调用的方法
// 被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
public void ToJs(String msg){
Log.e("tag","JS调用Android的代码");
}
}
- 编写一个html文件,registe.html。定义一个test对象,然后调用Android 映射的对象,最后在点击按钮的时候,其实就是在调用callAndroid()这个函数。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script>
function callAndroid(){
// 由于对象映射,所以调用test对象等于调用Android映射的对象
test.ToJs("js调用了android中的ToJs方法");
}
</script>
</head>
<body>
<button type="button" id="button1" onclick="callAndroid()" >JS点击调用安卓的代码</button>
</body>
</html>
- 新建一个Main3Activity文件,布局里面只有一个webView。设置加载JS的方法,然后调用addJavascriptInterface()这个方法,将第一步的Main3J类带入,然后是第二个test对象。
public class Main3Activity extends AppCompatActivity {
WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
mWebView = (WebView) findViewById(R.id.WebView);
WebSettings webSettings = mWebView.getSettings();
// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);
// 通过addJavascriptInterface()将Java对象映射到JS对象
//参数1:Javascript对象名
//参数2:Java对象名
mWebView.addJavascriptInterface(new Main3JS(this), "test");//AndroidtoJS类对象映射到js的test对象
// 加载JS代码
// 格式规定为:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/registe.html");
}
}
最后结果如上图所示。这种方法使用简单,仅将Android对象和JS对象映射即可。但是存在数据泄露的风险。
3.2.2 通过 WebViewClient 的方法shouldOverrideUrlLoading ()回调拦截 url
回调拦截url的原理很简单,Android 通过WebViewClient的回调方法shouldOverrideUrlLoading ()拦截 url,然后解析url,根据设定的url协议调用不同的方法。
- 具体步骤
- 编写一个html文件,定义一个协议。当Js通过Android 的加载的协议后,就会回调shouldOverrideUrlLoading ()方法。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script>
function callAndroid(){
//定义协议
document.location="js://webview?location=1";
}
</script>
</head>
<body>
<button type="button" id="button1" onclick="callAndroid()" >JS点击调用安卓的代码</button>
</body>
</html>
2.在Main4Activity 中定义webView,然后加载协议,最后调用webview的shouldOverrideUrlLoading ()方法。根据js.html的定义的协议格式:根据scheme(协议格式) & authority(协议名)判断(前两个参数)进行判断。
public class Main4Activity extends AppCompatActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main4);
webView = (WebView) findViewById(R.id.webView);
WebSettings webSettings= webView.getSettings();
//设置加载JS权限
webSettings.setJavaScriptEnabled(true);
//设置允许JS弹窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//加载file文件
webView.loadUrl("file:///android_asset/sencodJs.html");
webView.setWebViewClient(new WebViewClient(){
// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
// "js://webview?location=1"
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
//如果url的协议 = 预先约定的 js 协议
if(uri.getScheme().equals("js")){
// 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议
if(uri.getAuthority().equals("webview")){
// 执行JS所需要调用的逻辑
System.out.println("js调用了Android的方法");
// 可以在协议上带有参数并传递到Android上
// HashMap<String, String> params = new HashMap<>();
// Set<String> collection = uri.getQueryParameterNames();
AlertDialog.Builder mBuilder= new AlertDialog.Builder(Main4Activity.this);
mBuilder.setTitle("Alert");
mBuilder.setMessage("JS要调用Android的代码");
mBuilder.setCancelable(true);
mBuilder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
mBuilder.create().show();
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
});
}
}
通过解析协议里面的值来进行调用android里面的代码。这样可以避免数据邪路的风险,但是返回数据麻烦。当用户点击webview中的网页链接的时候,安卓系统默认会启动一个新的应用专门成立url的跳转。如果希望点击链接继续在当前webview中响应,而不是新开Android的系统browser中响应该链接,必须覆盖 WebView的WebViewClient对象. 并重写shouldOverrideUrlLoading方法。
3.2.3 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息
方法 | 作用 | 返回值 | 备注 |
---|---|---|---|
alert() | 弹出警告框 | 无 | 在文本中加入\n可换行 |
confirm() | 弹出确认框 | 两个 | 返回布尔值(true,false) |
prompt() | 弹出输入框 | 任意设置返回值 | 确认:返回输入框中的值;取消返回null |
- 具体方法
1.首先在three.html中设置点击按钮和调用的js方法
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
<script>
function clickprompt(){
// 调用prompt()
var result=prompt("js://webview?arg1=111&arg2=222");
alert("demo " + result);
}
</script>
</head>
<!-- 点击按钮则调用clickprompt() -->
<body>
<button type="button" id="button1" onclick="clickprompt()">点击调用Android代码</button>
</body>
</html>
2.然后在Main5Activity方法中,自顶一个WebView,然后调用WebView的WebChromeClient(),重写里面的回调拦截方法
public class Main5Activity extends AppCompatActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main5);
webView = (WebView) findViewById(R.id.webView);
WebSettings webSettings=webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webView.loadUrl("file:///android_asset/three.html");
webView.setWebChromeClient(new WebChromeClient(){
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
Uri uri = Uri.parse(message);
if (uri.getScheme().equals("js")) {
if (uri.getAuthority().equals("webview")) {
System.out.println("js调用了Android的方法");
// 可以在协议上带有参数并传递到Android上
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
//参数result:代表消息框的返回值(输入值)
result.confirm("js调用了Android的方法成功啦");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
});
}
}