Android WebView使用详解包括js互调(by 星空武哥)

               

目前很多android app都内置了可以显示web页面的界面,会发现这个界面一般都是由一个叫做WebView的组件渲染出来的,学习该组件可以为你的app开发提升扩展性。

先说下WebView的一些优点:

  • 可以直接显示和渲染web页面,直接显示网页
  • webview可以直接用html文件(网络上或本地assets中)作布局
  • 和JavaScript交互调用

   推荐两个学习的webView的文章:

   http://blog.csdn.net/fengyuzhengfan/article/details/38326861

   http://www.jianshu.com/p/3fcf8ba18d7f

一、基本使用

首先layout中即为一个基本的简单控件:

<WebView        android:id="@+id/webView1"        android:layout_width="fill_parent"        android:layout_height="fill_parent"        android:layout_marginTop="10dp" />

同时,因为要房访问网络,所以manifest中必须要加uses-permission:

<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内页面的加载进度:

复制代码
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            }        });
复制代码


二、通过webview控件下载文件

通常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);        }            }
复制代码

 

三、错误处理

当我们使用浏览器的时候,通常因为加载的页面的服务器的各种原因导致各种出错的情况,最平常的比如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即可。

 

四、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页面中可以自动的登陆了并显示登陆后的界面。

 

五、 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 οnlοad="javascript:demo.clickOnAndroid()">      ...</body>

 

六、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*


 Android 中Webview 自适应屏幕

webview中右下角的缩放按钮能不能去掉

settings.setDisplayZoomControls(false); //隐藏webview缩放按钮


让Webview加载的页面居中显示有我知道的几种方法

第一种方法:

WebSettings settings = webView.getSettings();settings.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); 

LayoutAlgorithm是一个枚举用来控制页面的布局,有三个类型:

1.NARROW_COLUMNS:可能的话使所有列的宽度不超过屏幕宽度

2.NORMAL:正常显示不做任何渲染

3.SINGLE_COLUMN:把所有内容放大webview等宽的一列中

用SINGLE_COLUMN类型可以设置页面居中显示,页面可以放大缩小,但这种方法不怎么好,有时候会让你的页面布局走样而且我测了一下,只能显示中间那一块,超出屏幕的部分都不能显示。

 
第二种方法:

//设置加载进来的页面自适应手机屏幕
        settings.setUseWideViewPort(true);        settings.setLoadWithOverviewMode(true); 

第一个方法设置webview推荐使用的窗口,设置为true。第二个方法是设置webview加载的页面的模式,也设置为true。

这方法可以让你的页面适应手机屏幕的分辨率,完整的显示在屏幕上,可以放大缩小。

两种方法都试过,推荐使用第二种方法

第三种方法:(主要用于平板,针对特定屏幕代码调整分辨率)
    DisplayMetrics metrics = new DisplayMetrics();    getWindowManager().getDefaultDisplay().getMetrics(metrics);    int mDensity = metrics.densityDpi;    if (mDensity == 120) {              settings.setDefaultZoom(ZoomDensity.CLOSE);          }else if (mDensity == 160) {              settings.setDefaultZoom(ZoomDensity.MEDIUM);          }else if (mDensity == 240) {              settings.setDefaultZoom(ZoomDensity.FAR);          }<span style="font-size:14px;"></span>

另一种讲解:

一、基本用法

1、加载在线URL

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. void loadUrl(String url)  
这个函数主要加载url所对应的网页地址,或者用于调用网页中的指定的JS方法(调用js方法的用法,后面会讲),但有一点必须注意的是:loadUrl()必须在主线程中执行!!!否则就会报错!!!。
注意:加载在线网页地址是会用到联网permission权限的,所以需要在AndroidManifest.xml中写入下面代码申请权限:
[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <uses-permission android:name="android.permission.INTERNET" />  
本示例效果为:

从效果图中可以明显看出本示例的布局: 
main.xml

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.               android:orientation="vertical"  
  4.               android:layout_width="fill_parent"  
  5.               android:layout_height="fill_parent"  
  6.         >  
  7.     <Button  
  8.             android:id="@+id/btn"  
  9.             android:layout_width="match_parent"  
  10.             android:layout_height="wrap_content"  
  11.             android:text
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值