目前不少android app都内置了能够显示web页面的界面,会发现这个界面通常都是由一个叫作WebView的组件渲染出来的,学习该组件能够为你的app开发提高扩展性。javascript
先说下WebView的一些优势:html
1、基本使用java
首先layout中即为一个基本的简单控件:android
<WebView android:id="@+id/webView1" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_marginTop="10dp" />
同时,由于要房访问网络,因此manifest中必需要加uses-permission:web
<uses-permission android:name="android.permission.INTERNET"/>
在activity中便可得到webview的引用,同时load一个网址:浏览器
webview = (WebView) findViewById(R.id.webView1); webview.loadUrl("http://www.baidu.com/");
//webview.reload();// reload page
这个时候发现一个问题,启动应用后,自动的打开了系统内置的浏览器,解决这个问题须要为webview设置 WebViewClient,并重写方法:服务器
webview.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } });
若本身定义了一个页面加载进度的progressbar,须要展现给用户的时候,能够经过以下方式获取webview内页面的加载进度:cookie
webview.setWebChromeClient(new WebChromeClient(){ @Override public void onProgressChanged(WebView view, int newProgress) { //get the newProgress and refresh progress bar } });
每一个页面,都有一个标题,好比www.baidu.com这个页面的title即“百度一下,你就知道”,那么如何知道当前webview正在加载的页面的title呢:网络
webview.setWebChromeClient(new WebChromeClient(){ @Override public void onReceivedTitle(WebView view, String title) { titleview.setText(title);//a textview } });
2、经过webview控件下载文件app
一般webview渲染的界面中含有能够下载文件的连接,点击该连接后,应该开始执行下载的操做并保存文件到本地中。webview来下载页面中的文件一般有两种方式:
1. 本身经过一个线程写java io的代码来下载和保存文件(可控性好)
2. 调用系统download的模块(代码简单)
方法一:
首先要写一个下载并保存文件的线程类
public class HttpThread extends Thread { private String mUrl; public HttpThread(String mUrl) { this.mUrl = mUrl; } @Override public void run() { URL url; try { url = new URL(mUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoInput(true); conn.setDoOutput(true); InputStream in = conn.getInputStream(); File downloadFile; File sdFile; FileOutputStream out = null; if(Environment.getExternalStorageState().equals(Environment.MEDIA_UNMOUNTED)){ downloadFile = Environment.getExternalStorageDirectory(); sdFile = new File(downloadFile, "test.file"); out = new FileOutputStream(sdFile); } //buffer 4k byte[] buffer = new byte[1024 * 4]; int len = 0; while((len = in.read(buffer)) != -1){ if(out != null) out.write(buffer, 0, len); } //close resource if(out != null) out.close(); if(in != null){ in.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
随后要实现一个DownloadListener接口,这个接口实现方法OnDownloadStart(),当用户点击一个能够下载的连接时,该回调方法被调用同时传进来该连接的URL,随后便可以对该URL塞入HttpThread进行下载操做:
//建立DownloadListener (webkit包) class MyDownloadListenter implements DownloadListener{ @Override public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { System.out.println("url ==== >" + url); new HttpThread(url).start(); } } //给webview加入监听 webview.setDownloadListener(new MyDownloadListenter());
方法二:
直接发送一个action_view的intent便可:
class MyDownloadListenter implements DownloadListener{ @Override public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { System.out.println("url ==== >" + url); //new HttpThread(url).start(); Uri uri = Uri.parse(url); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); } }
3、错误处理
当咱们使用浏览器的时候,一般由于加载的页面的服务器的各类缘由致使各类出错的状况,最日常的好比404错误,一般状况下浏览器会提示一个错误提示页面。事实上这个错误提示页面是浏览器在加载了本地的一个页面,用来提示用户目前已经出错了。
可是当咱们的app里面使用webview控件的时候遇到了诸如404这类的错误的时候,若也显示浏览器里面的那种错误提示页面就显得很丑陋了,那么这个时候咱们的app就须要加载一个本地的错误提示页面,即webview如何加载一个本地的页面。
1. 首先咱们须要些一个html文件,好比error_handle.html,这个文件里面就是当出错的时候须要展现给用户看的一个错误提示页面,尽可能作的精美一些。而后将该文件放置到代码根目录的assets文件夹下。
2. 随后咱们须要复写WebViewClient的onRecievedError方法,该方法传回了错误码,根据错误类型能够进行不一样的错误分类处理
webview.setWebViewClient(new WebViewClient(){ @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { switch(errorCode) { case HttpStatus.SC_NOT_FOUND: view.loadUrl("file:///android_assets/error_handle.html"); break; } } });
其实,当出错的时候,咱们能够选择隐藏掉webview,而显示native的错误处理控件,这个时候只须要在onReceivedError里面显示出错误处理的native控件同时隐藏掉webview便可。
4、webview同步cookies
cookies是服务器用来保存每一个客户的经常使用信息的,下次客户进入一个诸如登录的页面时服务器会检测cookie信息,若是经过则直接进入登录后的页面。
在webview中,若是以前已经登录过了,那么下次再进入一样的登录界面时,若须要再次登录的话,必定会很恼人,因此这里提供一个webview同步cookies的方法。
1.首先,咱们假设某个网站的登录界面须要提供两个参数,一个是name,一个是pwd,那么要是对这个页面进行登录,那么必须给与这两个信息。咱们假设服务器已经注册了name为jason,pwd为123456这个帐号。
2.下面,写一个Thread用来将name和pwd自动的登入,在服务器返回的response中得到cookie信息,稍后对这个cookie进行保存,这里先给出这个Thread的代码:
public class HttpCookie extends Thread { private Handler mHandler; public HttpCookie(Handler mHandler) { this.mHandler = mHandler; } @Override public void run() { HttpClient client = new DefaultHttpClient(); HttpPost post = new HttpPost("");//this place should add the login address List<NameValuePair> list = new ArrayList<NameValuePair>(); list.add(new BasicNameValuePair("name", "jason")); list.add(new BasicNameValuePair("pwd", "123456")); try { post.setEntity(new UrlEncodedFormEntity(list)); HttpResponse reponse = client.execute(post); if(reponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK){ AbstractHttpClient absClient = (AbstractHttpClient) client; List<Cookie> cookies = absClient.getCookieStore().getCookies(); for(Cookie cookie:cookies){ if(cookie != null){ //TODO //this place would get the cookies } } } } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClientProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
因为这是一个子线程,因此须要在主线程中建立并执行。
同时,由于其实子线程,那么里面必须含有一个handler的元素,用来当成功获取cookie后通知主线程进行同步和保存。初始化这个子线程的时候须要将主线程上的handler给传过来,随后在以上代码的TODO中发送消息,让主线程记录cookie,发送的这个消息须要将cookie信息包含进去:
if(cookie != null){ //TODO //this place would get the cookies Message msg = new Message(); msg.obj = cookie; if(mHandler != null){ mHandler.sendMessage(msg); return; } }
随后在主线程中(webview加载登录界面前),在handler中将会获取到cookie信息,下面将对该cookie进行保存和同步:
private Handler mHandler = new Handler(){ public void handleMessage(android.os.Message msg) { CookieSyncManager.createInstance(MainActivity.this); CookieManager cookieMgr = CookieManager.getInstance(); cookieMgr.setAcceptCookie(true); cookieMgr.setCookie("", msg.obj.toString());// this place should add the login host address(not the login index address) CookieSyncManager.getInstance().sync(); webview.loadUrl("");// login index address }; };
这个时候发现webview加载的login index页面中能够自动的登录了并显示登录后的界面。
5、 WebView与JavaScript的交互
1. webview调用js
mWebView.loadUrl("javascript:do()");
以上是webview在调用js中的一个叫作do的方法,该js所在的html文件大体以下:
<html> <script language="javascript"> /* This function is invoked by the webview*/ function do() { alert("1"); } </script> <body> <a onClick="window.demo.clickOnAndroid()"><div style="width:80px; margin:0px auto; padding:10px; text-align:center; border:2px solid #111111;" > <img id="droid" src="xx.png"/><br> Click me! </div></a> </body> </html>
2. js调用webview
咱们假设下列的本地类是要给js调用的:
package com.test.webview;
class DemoJavaScriptInterface { DemoJavaScriptInterface() { } /** * This is not called on the UI thread. Post a runnable to invoke * loadUrl on the UI thread. */ public void clickOnAndroid() { mHandler.post(new Runnable() { public void run() { //TODO } }); } }
首先给webview设置:
mWebview.setJavaScriptEnabled(true);
随后将本地的类(被js调用的)映射出去:
mWebView.addJavascriptInterface(new DemoJavaScriptInterface(), "demo");
“demo”这个名字就是公布出去给JS调用的,那么js久能够直接用下列代码调用本地的DemoJavaScriptInterface类中的方法了:
<body onload="javascript:demo.clickOnAndroid()">
...
</body>
6、WebView与JavaScript相互调用混淆问题
若webview中的js调用了本地的方法,正常状况下发布的debug包js调用的时候是没有问题的,可是一般发布商业版本的apk都是要通过混淆的步骤,这个时候会发现以前调用正常的js却没法正常调用本地方法了。
这是由于混淆的时候已经把本地的代码的引用给打乱了,致使js中的代码找不到本地的方法的地址。
解决这个问题很简单,即在proguard.cfg文件中加上一些代码,声明本地中被js调用的代码不被混淆。下面举例说明:
第五节中被js调用的那个类DemoJavaScriptInterface的包名为com.test.webview,那么就要在proguard.cfg文件中加入:
-keep public class com.test.webview.DemoJavaScriptInterface{ public <methods>; }
如果内部类,则大体写成以下形式:
-keep public class com.test.webview.DemoJavaScriptInterface$InnerClass{
public <methods>; }
若android版本比较新,可能还须要添加上下列代码:
-keepattributes *Annotation*
-keepattributes *JavascriptInterface*