Android中WebView的使用——JS交互、goBack跳转、内存占用 等

WebView基本用法

WebView是一个用来显示网页的控件,它包含了一系列强悍的网页获取与解析功能,稍加修饰就可以做成一个自己的浏览器。

如下是一个简单的示例。

wvSelSrc = (WebView) findViewById(R.id.webViewSelectSrcUrl);

//用ProgressBar显示WebView加载进度
final static int PROGRESS_MAX=100;
pbWebView = (ProgressBar)findViewById(R.id.progressBarWebView);
pbWebView.setMax(PROGRESS_MAX);

wvSelSrc.setWebChromeClient(new WebChromeClient()
{
	public void onProgressChanged(WebView view, int newProgress)
	{
		pbWebView.setProgress(newProgress);
	}
});


wvSelSrc.setWebViewClient(new WebViewClient()
{
	//在WebView中点击链接,重载此函数直接在WebView中打开此链接.
	//否则会弹出对话框,提示选择其他浏览器软件打开。
	public boolean shouldOverrideUrlLoading(WebView view, String url)
	{
		Log.i(LOG_TAG, "UrlLoading " + url);
		return false;
	}
	
	//页面加载完成
	public void onPageFinished(WebView view, String url)
	{
		Log.i(LOG_TAG, "onPageFinished " + url);
	}
	
	//页面开始加载
	public void onPageStarted(WebView view, String url, Bitmap favicon) 
	{
		pbWebView.setVisibility(View.VISIBLE);
		actionbar.setTitle(url);
	}
});

//获取WebView设置接口
wsParaConfig = wvParaConfig.getSettings();

//使能Javascript
wsParaConfig.setJavaScriptEnabled(true);

//页面自适应屏幕
wsParaConfig.setLoadWithOverviewMode(true);

//打开网页
wvParaConfig.loadUrl("http://www.gk969.com");

WebView Javascript接口

随着处理器和操作系统性能的不断提高,使用HTML5也能构建出媲美原生方法的UI。原生方法需要一大堆Java和XML代码才能做出来的,用HTML5往往能很简洁地搞定。更重要的是HTML5的跨平台特性,使得我们不必为每个平台都重头到尾搞一套。另外HTML5学习成本相较原生方法也小得多,是快速开发中的利器。

在App中使用HTML5构建UI,底层逻辑方面比如数据存储就需要与原生方法交互。下面是Android中WebView与Javascript交互的方法和注意事项。

1.使能WebView的Javascript

wsParaConfig.setJavaScriptEnabled(true);

2.添加Javascript接口。

此处”paraConfig”即Javascript接口名,在Javascript中通过这个接口调用Java函数。这里使用Activity作为目标对象,所以Activity类中必须有public属性的接口函数供Javascript调用。

wvParaConfig.getSettings().addJavascriptInterface(this, "paraConfig");

3.添加接口函数。

如果App的targetSdkVersion大于等于API19(Android4.4.X)的话,代码中暴露给Javascript的接口函数必须添加@JavascriptInterface标记,同时要import android.webkit.JavascriptInterface包。否则Javascript中不能调用此接口函数。

据说这是为了修复一个有关安全性的BUG。因为之前版本的SDK中没有这个接口标记,接口类中的所有public成员都暴露给了Javascript,现在就只暴露那些添加了接口标记的函数。

另外由于Javascript调用Java接口时运行在非UI线程,所以此接口函数内不能直接操作UI控件。需要通过Handler向UI线程发消息,然后UI线程响应并做相关操作。

private Handler	mHandler = new Handler();

@JavascriptInterface
public void setHomeUrl(String url)
{
	mHandler.post(new Runnable()
	{
		public void run()
		{
			wvParaConfig.goBack();
			Log.i(LOG_TAG, "setHomeUrl");
		}
	});
	
	if(!url.isEmpty())
	{
		Editor editor = getSharedPreferences(SelSrcActivity.SPMAIN_NAME, 0).edit();
		editor.putString(SelSrcActivity.HOME_URL_KEY, url);
		editor.commit();
		
		Toast.makeText(this, "已设置主页:" + url, Toast.LENGTH_SHORT).show();
	}
}

4.Javascript调用Java接口函数

百度

5.Java调用Javascript接口函数

wvParaConfig.loadUrl("javascript:test('aa')");

6.WebView调用本地HTML

调用工程assets目录下的HTML文档,如果HTML中调用了其他的本地JS或者CSS文件,WebView会以assets作为根目录自动加载相应文件。

wvParaConfig.loadUrl("file:///android_asset/paraConfig.html");

其他注意事项

1.在WebView中长按会出现选择复制文字界面,像下面这样重载长按事件后不会再出现此界面。

wvParaConfig.setOnLongClickListener(new WebView.OnLongClickListener()
{
	public boolean onLongClick(View v)
	{
		return true;
	}
});

2.HTML中可点击的元素,比如a或者设置了onclick的div,点击时元素背景色会自动变为淡蓝色,并且点击后元素的四周会出现一个橙色的框用于指示元素已获取焦点。如不需要这两种效果,可添加如下的CSS代码。

取消点击元素时的淡蓝背景色

body
{
	-webkit-tap-highlight-color:rgba(0,0,0,0);
}

取消点击后的橙色框。此处只设置了input、a、div,也可继续添加其他标签。

input:focus,a:focus,div:focus
{
	outline:none;
}

3.URL重定向导致WebView的goBack后退失效。出现此种现象时,很大可能是代码中使用了网上绝大多数文章中提到的在当前WebView中打开被点击链接方法,代码如下:

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

在开始时我也像这样盲从了大多数人的方法。但是被URL重定向导致不能后退的问题困扰了好几天,期间尝试了各种方法,包括判断URL是否为会重定向、维护自己的历史记录等,效果均不理想。后来才发现问题的根源居然在这个地方。

首先看一下shouldOverrideUrlLoading的注释吧。

Give the host application a chance to take over the control when a new url is about to be loaded in the current WebView. If WebViewClient is not provided, by default WebView will ask Activity Manager to choose the proper handler for the url. If WebViewClient is provided, return true means the host application handles the url, while return false means the current WebView handles the url. This method is not called for requests using the POST “method”.

意思就是这个函数的作用是让主程序有机会去控制如何处理一个新链接。如果WebView的WebViewClient没有设置,WebView就会调用系统浏览器软件用以打开这个链接;如果设置了WebViewClient,同时在shouldOverrideUrlLoading中返回true意味着主程序要控制如何打开新链接,反之返回false的话,WebView会自动处理并打开新链接。

这样按照上面那段“广为流传”的代码,WebView就不会自动处理新链接,即使这个URL会重定向到其他URL,WebView还是会被动地loadUrl,然后将这个URL放到历史记录中。在此之后如果调用了WebView的goBack,返回到这个会重定向到另一URL的URL时,就会再次重定向进而导致goBack后退失效。

因此为了解决这个问题,正确的方式应该是下面这样直接返回false,然后WebView就会自动处理这些URL。它内部很容易判断URL是否会重定向,能够确保历史记录的准确性。这样在后退的时候就不会再出现失效的现象:

public boolean shouldOverrideUrlLoading(WebView view, String url)
{
	return false;
}

WebView占用内存过大导致OOM

WebView解析网页时会申请Native堆内存用于保存页面元素,当页面较复杂时会有很大的内存占用。如果页面包含图片,内存占用会更严重。并且打开新页面时,为了能快速回退,之前页面占用的内存也不会释放。有时浏览十几个网页,都会占用几百兆的内存。这样加载网页较多时,会导致系统不堪重负,最终强制关闭应用,也就是出现应用闪退或重启。

由于占用的都是Native堆内存,所以实际占用的内存大小不会显示在常用的DDMS Heap工具中(这里看到的只是Java虚拟机分配的内存,一般即使Native堆内存已经占用了几百兆,这里显示的还只是几兆或十几兆)。只有使用adb shell中的一些命令比如dumpsys meminfo 包名,或者在程序中使用Debug.getNativeHeapSize()才能看到。

据说由于WebView的一个BUG,即使它所在的Activity(或者Service)结束也就是onDestroy()之后,或者直接调用WebView.destroy()之后,它所占用这些内存也不会被释放。Holy Crap!!!

解决这个问题最直接的方法是:把使用了WebView的Activity(或者Service)放在单独的进程里。然后在检测到应用占用内存过大有可能被系统干掉或者它所在的Activity(或者Service)结束后,调用System.exit(0),主动Kill掉进程。由于系统的内存分配是以进程为准的,进程关闭后,系统会自动回收所有内存。真所谓一了百了。

Android中WebView的使用——JS交互、goBack跳转、内存占用 等》上有1条评论

  1. Mr.Lyn

    收益匪浅,公司最近正好需要嵌入式与后台通信。可能android还是要我来做。

    回复

发表评论

电子邮件地址不会被公开。

*