首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android开发笔记(六十四)网页加载与JS调用

Android开发笔记(六十四)网页加载与JS调用

作者头像
aqi00
发布2019-01-18 14:02:49
6.2K0
发布2019-01-18 14:02:49
举报
文章被收录于专栏:老欧说安卓老欧说安卓

内置浏览器

网页视图WebView

如果一个网站已经有现成的网页及业务逻辑,那么使用WebView将其内嵌到app中,省去了app重画页面与http通信的事情,无疑是更经济的做法。WebView就是Android上的一个浏览器内核,可自动展示web页面,并且实现js代码的相互调用。 下面是WebView的常用方法: getSettings : 获取浏览器的web设置信息。 addJavascriptInterface : 添加本地的js代码接口。 removeJavascriptInterface : 移除本地的js代码接口。在4.0至4.2的Android系统上,Webview自己增加了searchBoxJavaBredge_,可能被黑客利用导致远程代码执行。为阻止该漏洞,需要手工移除接口searchBoxJavaBredge_。代码如下所示:

wv_web.removeJavascriptInterface("searchBoxJavaBredge_");

setWebViewClient : 设置浏览器的加载事件。 setWebChromeClient : 设置浏览器的交互事件。 setDownloadListener : 设置文件下载监听器。 loadData : 加载文本数据。第二个参数表示媒体类型,如"text/html";第三个参数表示数据的编码格式,"base64"表示采用base64编码,其余值(包括null)表示url编码。 loadUrl : 加载url。 canGoBack : 判断页面能否返回。 goBack : 返回到上一个页面。

网页设置WebSettings

WebSettings用于操作WebView的web设置信息。其对象由WebView的getSettings方法得到。下面是WebSettings的常用设置方法: setLoadsImagesAutomatically : 设置是否自动加载图片 setDefaultTextEncodingName : 设置默认的文本编码。如utf-8、gbk等等。 setJavaScriptEnabled : 设置是否支持Javascript setJavaScriptCanOpenWindowsAutomatically : 设置是否允许js自动打开新窗口(window.open()) 以上是基本的设置。 setSupportZoom : 设置是否支持缩放。 setBuiltInZoomControls : 设置是否出现缩放工具。 setUseWideViewPort : 当容器超过页面大小时,是否放大页面大小到容器宽度。 setLoadWithOverviewMode : 当页面超过容器大小时,是否缩小页面尺寸到页面宽度。 setLayoutAlgorithm : 设置自适应屏幕的算法,一般是LayoutAlgorithm.SINGLE_COLUMN。如果不做设置,4.2.2及之前版本自适应时可能会出现表格错乱的情况。 以上是网页与容器适配的设置。 setDomStorageEnabled : 设置是否启用本地存储。 setCacheMode : 设置是否使用缓存。LOAD_CACHE_ELSE_NETWORK表示优先使用缓存,LOAD_NO_CACHE表示不使用缓存,LOAD_CACHE_ONLY表示只使用缓存。 setAppCacheEnabled : 设置是否启用app缓存。 setAppCachePath : 设置app缓存文件的路径。 setAllowFileAccess : 设置是否允许访问文件,例如WebView访问sd卡的文件。不过assets与res文件不受此限制,仍然可以通过“file:///android_asset”和“file:///android_res”访问。 setDatabaseEnabled : 设置是否启用数据库。 以上是与存储有关的设置。

网页事件监听

主要有三个事件监听器,分别是WebViewClient、WebChromeClient和DownloadListener。 浏览器加载事件 WebViewClient主要用于处理。相关类名与方法说明如下: 监听器类名 : WebViewClient 设置监听器的方法 : setWebViewClient 监听器需要重写的方法 :  onPageStarted : 页面开始加载。一般在此弹出进度对话框ProgressFialog onPageFinished : 页面加载结束。一般在此关闭进度对话框。 onReceivedError : 收到错误信息。 onReceivedSslError : 收到ssl错误。 shouldOverrideUrlLoading : 重写该方法表明,点击网页里面的链接是在当前的webview里跳转,还是跳转到其它浏览器。如果想在当前的webview跳转,则加上下面这句代码:

view.loadUrl(url);

浏览器的交互事件 WebChromeClient主要用于。相关类名与方法说明如下: 监听器类名 : WebChromeClient 设置监听器的方法 : setWebChromeClient 监听器需要重写的方法 :  onReceivedTitle : 收到页面标题 onProgressChanged : 页面加载进度发生变化 onJsAlert : 弹出js警告框。 onJsConfirm : 弹出js确认框。 onJsPrompt : 弹出js提示框。 onGeolocationPermissionsShowPrompt : 网页请求定位权限。通常重写该方法弹出一个确认对话框,提示用户是否允许网页获得定位权限。下面代码表示允许定位权限:

callback.invoke(origin, true, false);

文件下载事件 DownloadListener主要用于。相关类名与方法说明如下: 监听器类名 : DownloadListener 设置监听器的方法 : setDownloadListener 监听器需要重写的方法 :  onDownloadStart : 文件开始下载。可在此设置文件下载的方式,以及文件的保存路径。

浏览器的应用场景

1、点击返回键返回上个页面,可监听返回键的按下事件,具体有两种方式,分别是重写onBackPressed函数,以及重写onKeyDown函数。 重写onBackPressed函数

	@Override
	public void onBackPressed() {
		if (wv_web.canGoBack()) {
			wv_web.goBack();
			return;
		} else {
			finish();
		}
	}

重写onKeyDown函数

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if ((keyCode == KeyEvent.KEYCODE_BACK) && wv_web.canGoBack()) {
			wv_web.goBack();
			return true;
		} else {
			return false;
		}
	}

2、显示页面加载进度。可在WebViewClient的onPageStarted方法中弹出进度对话框ProgressFialog,然后在onPageFinished方法中关闭进度对话框。要想动态显示当前页面的加载百分比,可在WebChromeClient的onProgressChanged方法中设置进度对话框的当前进度。 3、显示js的提示对话框。默认情况下,js对话框也能正常显示和操作,只是对话框标题默认为“网址为"***"的网页显示”,这个标题信息不够友好,所以我们需要重写WebChromeClient的三个js方法onJsAlert、onJsConfirm和onJsPrompt,在内部构造一个AlertDialog实例,分别设置标题、信息,以及肯定按钮和否定按钮。同时监听肯定按钮的点击事件,调用JsResult的confirm方法;监听否定按钮的点击事件,调用JsResult的cancel方法。 4、允许js调用本地java代码。先声明一个本地java代码的操作类,然后调用WebView的addJavascriptInterface方法,关联本地java对象与对象名称。代码示例如下: 声明一个本地java代码的操作类:

	class MyJavaScript {
		@JavascriptInterface
		public String toString() {
			return "injectedObject";
		}
	}

在WebView中注册该java代码的对象,并演示javascript的调用过程:

			wv_web.getSettings().setJavaScriptEnabled(true);
			wv_web.addJavascriptInterface(new MyJavaScript(), "injectedObject");
			wv_web.loadData("<!DOCTYPE html><title></title>", "text/html", null);
			wv_web.loadUrl("javascript:alert(injectedObject.toString())");

Android代码与html的js代码相互调用

Android代码调用js代码

下面是android的代码

		wv_local.getSettings().setJavaScriptEnabled(true);
		wv_local.setWebChromeClient(new WebChromeClient());
		wv_local.loadUrl("file:///android_asset/sample.html");
		wv_local.loadUrl("javascript:showMsg()"); //showMsg方法是sample.html里面的js方法

下面是js的代码

<script type="text/javascript">
   function showMsg(){
      alert("hello world!");
   }
</script>

js代码调用Android代码

下面是android的代码

	wv_local.getSettings().setJavaScriptEnabled(true);
	wv_local.addJavascriptInterface(new Contact(), "contact");
	wv_local.loadUrl("file:///android_asset/sample.html");

	private final class Contact {
		@JavascriptInterface
		public void showMsg(String msg) {
			AlertDialog.Builder builder = new AlertDialog.Builder(LocalActivity.this);
			builder.setMessage(msg).create().show();
		}
	}

下面是js的代码

<script type="text/javascript">
   function showMsgInAndroid(){
      contact.showMsg("hello world 3");
   }
</script>
<button id="btntest" onclick="javascript:showMsgInAndroid()">调用android方法</button>

Android与js互调获得返回值

js调用Android方法,可直接获取调用的返回值,难点在于Android调用js方法的返回值。因为WebView加载网页是异步的,所以loadUrl方法调用js无法直接获得返回值。多数情况只能在js代码中回调Android方法,从而间接获取Android调用js的返回值。不过从Android4.4.2开始,我们可通过调用WebView的evaluateJavascript方法来直接得到js返回值,下面是Android的示例代码:

	wv_local.evaluateJavascript("getMsg()", new ValueCallback<String>() {
		@Override
		public void onReceiveValue(String value) {
			AlertDialog.Builder builder = new AlertDialog.Builder(LocalActivity.this);
			builder.setMessage(UnicodeToString(value)).create().show();
		}
	});

	public static String UnicodeToString(String str) {
		if (str != null && str.trim().length() > 0) {
			String un = str.trim();
			StringBuffer sb = new StringBuffer();
			int idx = un.indexOf("\\u");
			while (idx >= 0) {
				if (idx > 0) {
					sb.append(un.substring(0, idx));
				}

				String hex = un.substring(idx + 2, idx + 2 + 4);
				sb.append((char) Integer.parseInt(hex, 16));
				un = un.substring(idx + 2 + 4);
				idx = un.indexOf("\\u");
			}
			sb.append(un);
			return sb.toString();
		}
		return "";
	}

下面是js的代码

<script type="text/javascript">
   function getMsg(){
      return "来自网页的消息: hello world!";
   }
</script>

注意事项

下面是android与js相互调用的几个注意点: 1、WebView要调用setWebChromeClient方法设置js的解释客户端,来解决js中alert不弹出的问题,因为js页面的渲染需要WebChromeClient去实现。 2、如果js调用android代码时,logcat报错“Uncaught TypeError: Object [object Object] has no method”,那是因为Android4.2以上版本默认不开放js调用本地方法的权限,得给开放js调用的方法加上“@JavascriptInterface注释”,该注释允许js代码访问android的方法。 3、evaluateJavascript是Android4.4.2之后才引入的新方法,如果是4.4.2之前的Android版本,需要注意做兼容处理。 4、js获取Android方法返回值中的中文是正常,但Android获取js方法返回值的中文却是“\u”打头的字符串,所以Android要先将js返回的字符串做转义处理,即调用上面的UnicodeToString方法,转义后的字符串才是正常的汉字。 5、如果android与js存在嵌套调用(即A调用B,B内部又去调用A),那么在Android4.4.2之后务必要保证两个调用在同一个线程中,不然运行时会报错“java.lang.Throwable: A WebView method was called on thread 'JavaBridge'. All WebView methods must be called on the same thread.”。具体的解决方法,是调用WebView对象的post方法,在post的Runnable任务中再去调用js方法,示例代码如下:

	private final class Contact {
		//网页的js调用android的showcontacts方法,然后showcontacts内部又去调用js的show<span style="font-family: Arial, Helvetica, sans-serif;">方法</span>
		@JavascriptInterface
		public void showcontacts() {
			wv_local.post(new Runnable() {
				@Override
				public void run() {
					wv_local.loadUrl("javascript:show()");
				}
			});
		}
	}

HTML5框架

PhoneGap

以下说明文字来自百度百科的PhoneGap词条: “PhoneGap是一个用基于HTML,CSS和JavaScript的,创建移动跨平台移动应用程序的快速开发平台。它使开发者能够利用iPhone、Android、Palm、Symbian、WP7、WP8、Bada和Blackberry智能手机的核心功能——包括地理定位,加速器,联系人,声音和振动等,此外PhoneGap拥有丰富的插件,可以调用。 它需要特定平台提供的附加软件,例如iPhone的iPhone SDK,Android的Android SDK等,也可以和DW5.5及以上版本配套开发。使用PhoneGap只比为每个平台分别建立应用程序好一点点,因为虽然基本代码是一样的,但是你仍然需要为每个平台分别编译应用程序。” 对于Android开发来说,PhoneGap其实就是在assets下面新建了www目录,然后入口Activity通过嵌入WebView来加载www下的html页面,接下来就是在各个html之间互相跳转,从而实现Android原生app界面交互的效果。

Cordova

虽然PhoneGap号称跨平台,但是html5+js也只能完成网站那样的网页操作,却无法直接操作手机设备的功能。因此PhoneGap引入了Cordova,Cordova提供了一组与设备相关的API,通过这组API,app能够以JavaScript访问原生的设备功能,如摄像头、麦克风等。Cordova还提供了一组统一的JavaScript类库,以及为这些类库所用的设备相关的原生后台代码。Cordova是驱动PhoneGap的核心引擎,有人说它们的关系类似于Webkit和Google Chrome的关系。

WeX5

WeX5是国内基于Phonegap的一个HTML5开发框架,它有自己的开发工具“WeX5 studio”,其实就是个定制版的Eclipse。在这个IDE上,可以通过拖曳控件来画html页面,这个倒是很像ios的开发工具Xcode。另外,WeX5的Cordova插件还集成了国内常用的几个工具包,与PhoneGap相比,WeX5更适合于国内的开发者。

代码示例

下面是WebView使用的代码例子:

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.inputmethod.InputMethodManager;
import android.webkit.DownloadListener;
import android.webkit.GeolocationPermissions.Callback;
import android.webkit.JavascriptInterface;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class WebActivity extends Activity 
	implements OnClickListener, OnLongClickListener {
	
	private final static String TAG = "WebActivity";
	private Context mContext;
	private EditText et_web_url;
	private Button btn_web_go;
	private WebView wv_web;
	private ProgressDialog m_pd;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_web);

		mContext = this;
		et_web_url = (EditText) findViewById(R.id.et_web_url);
		et_web_url.setOnLongClickListener(this);
		et_web_url.setText("222.77.181.14/news/zapb.aspx");
		wv_web = (WebView) findViewById(R.id.wv_web);
		btn_web_go = (Button) findViewById(R.id.btn_web_go);
		btn_web_go.setOnClickListener(this);
		
		initWebViewSettings();
	}
	
	@SuppressLint("SetJavaScriptEnabled")
	private void initWebViewSettings() {
		WebSettings settings = wv_web.getSettings();
		//设置是否自动加载图片
		settings.setLoadsImagesAutomatically(true);
		//设置默认的文本编码
		settings.setDefaultTextEncodingName("utf-8");
		
		//设置是否支持Javascript
		settings.setJavaScriptEnabled(true);
		//设置是否允许js自动打开新窗口(window.open())
		settings.setJavaScriptCanOpenWindowsAutomatically(false);
		
		// 设置是否支持缩放 
		settings.setSupportZoom(true);
		// 设置是否出现缩放工具 
		settings.setBuiltInZoomControls(true);
		//当容器超过页面大小时,是否放大页面大小到容器宽度
		settings.setUseWideViewPort(true);
		//当页面超过容器大小时,是否缩小页面尺寸到页面宽度
		settings.setLoadWithOverviewMode(true);
		//设置自适应屏幕。4.2.2及之前版本自适应时可能会出现表格错乱的情况
		settings.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN);

		//设置是否启用本地存储
		settings.setDomStorageEnabled(true);
		//优先使用缓存
		//settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
		//设置是否使用缓存
		settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
		//设置是否启用app缓存
		settings.setAppCacheEnabled(true);
		//设置app缓存文件的路径
		settings.setAppCachePath("");
		//设置是否允许访问文件,如WebView访问sd卡的文件。不过assets与res文件不受此限制,仍然可以通过“file:///android_asset”和“file:///android_res”访问
		settings.setAllowFileAccess(true);
		//设置是否启用数据库
		settings.setDatabaseEnabled(true);
	}

	@Override
	public void onClick(View v) {
		if (v.getId() == R.id.btn_web_go) {
			InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
			imm.hideSoftInputFromWindow(et_web_url.getWindowToken(), 0);
			String url = "http://" + et_web_url.getText().toString();
			Log.d(TAG, "url="+url);
			//禁止执行远程代码。在4.0至4.2的Android系统上,Webview自己增加了searchBoxJavaBredge_,可能让黑客利用导致远程代码执行
			wv_web.removeJavascriptInterface("searchBoxJavaBredge_");
			wv_web.addJavascriptInterface(new MyJavaScript(), "injectedObject");
			
			//加载远程URL
			wv_web.loadUrl(url);
			//加载本地网页
			//wv_web.loadUrl("file:///android_asset/example.html");
			wv_web.setWebViewClient(mWebViewClient);
			wv_web.setWebChromeClient(mWebChrome);
			wv_web.setDownloadListener(mDownloadListener);
		}
	}

	@Override
	public boolean onLongClick(View v) {
		if (v.getId() == R.id.et_web_url) {
			et_web_url.setText("");
		}
		return true;
	}
	
	@Override
	public void onBackPressed() {
		if (wv_web.canGoBack()) {
			wv_web.goBack();
			return;
        } else {
        	finish();
        }
	}

//	@Override
//	public boolean onKeyDown(int keyCode, KeyEvent event) {
//		if ((keyCode == KeyEvent.KEYCODE_BACK) && wv_web.canGoBack()) {
//			wv_web.goBack();
//			return true;
//		} else {
//			return false;
//		}
//	}

	private WebViewClient mWebViewClient = new WebViewClient() {

		@Override
		public void onReceivedSslError(WebView view,
				android.webkit.SslErrorHandler handler,
				android.net.http.SslError error) {
			handler.proceed();
		};

		@Override
		public void onPageStarted(WebView view, String url, Bitmap favicon) {
			super.onPageStarted(view, url, favicon);
			Log.d(TAG, "onPageStarted:" + url);
			if (m_pd == null || m_pd.isShowing() == false) {
				m_pd = new ProgressDialog(mContext);
				m_pd.setTitle("稍等");
				m_pd.setMessage("页面加载中……");
				m_pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
				m_pd.show();
			}
		}

		@Override
		public void onPageFinished(WebView view, String url) {
			super.onPageFinished(view, url);
			Log.d(TAG, "onPageFinished:" + url);
			if (m_pd != null && m_pd.isShowing() == true) {
				m_pd.dismiss();
			}
		}

		@Override
		public void onReceivedError(WebView view, int errorCode,
				String description, String failingUrl) {
			super.onReceivedError(view, errorCode, description, failingUrl);
			Log.d(TAG, "onReceivedError: url=" + failingUrl+", errorCode="+errorCode+", description="+description);
			if (m_pd != null && m_pd.isShowing() == true) {
				m_pd.dismiss();
			}
			Toast.makeText(mContext, "页面加载失败,请稍候再试",Toast.LENGTH_LONG).show();
		}

		@Override
		public boolean shouldOverrideUrlLoading(WebView view, String url) {
			view.loadUrl(url);
			return true;
		}

	};

	private WebChromeClient mWebChrome = new WebChromeClient() {
		private String mTitle = "";

		@Override
	    public void onReceivedTitle(WebView view, String title) {
			mTitle = title;
		}
		
		@Override
		public void onProgressChanged(WebView view, int progress) {
			if (m_pd != null && m_pd.isShowing() == true) {
				m_pd.setProgress(progress);
			}
		}

		@Override
		public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
			AlertDialog.Builder builder = new AlertDialog.Builder(mContext)
					.setTitle(mTitle)
					.setMessage(message)
					.setPositiveButton("确定",
							new DialogInterface.OnClickListener() {
								@Override
								public void onClick(DialogInterface dialog, int which) {
									result.confirm();
								}
							})
					//onJsAlert一般无需设置setNeutralButton,onJsConfirm才可能需要设置
					.setNeutralButton("取消",
							new DialogInterface.OnClickListener() {
								@Override
								public void onClick(DialogInterface dialog, int which) {
									result.cancel();
								}
							});
			builder.setCancelable(true).create().show();
			return true;
		}

		@Override
		public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
			callback.invoke(origin, true, false);
			super.onGeolocationPermissionsShowPrompt(origin, callback);
		}
	};

	private DownloadListener mDownloadListener = new DownloadListener() {
		@Override
		public void onDownloadStart(String url, String userAgent, String contentDisposition,
				String mimetype, long contentLength) {
			//此处操作文件下载
		}
	};

	class MyJavaScript {
		@JavascriptInterface
		public String toString() {
			return "injectedObject";
		}
	}

}

点击下载本文用到的网页加载与JS调用的工程代码 点此查看Android开发笔记的完整目录

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年02月15日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 内置浏览器
    • 网页视图WebView
      • 网页设置WebSettings
        • 网页事件监听
          • 浏览器的应用场景
          • Android代码与html的js代码相互调用
            • Android代码调用js代码
              • js代码调用Android代码
                • Android与js互调获得返回值
                  • 注意事项
                  • HTML5框架
                    • PhoneGap
                      • Cordova
                        • WeX5
                        • 代码示例
                        相关产品与服务
                        容器服务
                        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档