WebView完全解读

简介: WebView完全解读

概述

WebView控件可以在自己的应用程序中显示本地或者Internet上的网页。

WebView是一个使用WebKit引擎(4.4之后基于Chromium)的浏览器控件。因此可以将WebView当做一个完整的浏览器使用。

WebView不仅支持HTML、CSS等静态元素,还支持JavaScript,而且在JavaScript中还可以调用Java的方法。

官方文档

Building Web Apps in WebView

常用类:

仅列举日常开发中常用的几个类和常用方法,其余请自行查阅官方API。

WebView官方API


WebChromeClient:辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等! 部分方法如下:


WebViewClient:辅助WebView处理各种通知与请求事件! 部分方法如下:


WebSettings:WebView相关配置的设置,比如setJavaScriptEnabled()设置是否允许JS脚本执行 部分方法如下:

方法说明:

  • loadUrl():直接显示网页内容(单独显示网络图片),一般不会出现乱码。
  • loadData(data, “text/html”, “UTF-8”):用来加载URI格式的数据,不能通过网络来加载内容, 不能加载图片,而且经常会遇到乱码的问题,我们知道String类型的数据主要是Unicode编码的, 而WebView一般为了节省资源使用的是UTF-8编码,尽管我们按上面写了,但是还需要为webView设置: webview.getSettings().setDefaultTextEncodingName(“UTF -8”);
  • loadDataWithBaseURL(baseUrl, string, “text/html”, “utf-8”, null):loadData类的一个 增强类,可以加载图片,baseUrl为你存储的图片路径,而且只需在这里设置utf-8就可以解决乱码 问题了。

功能演示

加载之前显示进度框-重写WebViewClient.onPageStarted()

可以重写onPageStarted 方法

webView.setWebViewClient(new WebViewClient() {
            // 设置WebView点击打开的网页在当前界面显示,而不是跳到新的浏览器中
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                LogUtils.e("onPageStarted");
                showProgressDialog();
            }
            @Override
            public void onPageFinished(WebView view, String url) {
                LogUtils.e("onPageFinished");
                closeProgressDialog();
            }
            @Override
            public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
                super.onReceivedError(view, request, error);
                LogUtils.e("onReceivedError");
                closeProgressDialog();
            }
        });

根据URL加载网页-直接在Activity上加载一个WebView

运行图


WebView00.java

package com.turing.base.activity.webview;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import com.apkfuns.logutils.LogUtils;
/**
 * 根据URL加载网页-直接在Activity上加载一个WebView
 */
public class WebView00 extends AppCompatActivity {
    private WebView webView;
    private long exitTime = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 初始化webView
        webView = new WebView(this);
        webView.setWebViewClient(new WebViewClient() {
            // 设置WebView点击打开的网页在当前界面显示,而不是跳到新的浏览器中
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });
        // 设置WebView属性,允许执行JS脚本,不然加载出来的网页很难看
        webView.getSettings().setJavaScriptEnabled(true);
        // 调用LoadUrl,载入url
        webView.loadUrl("http://www.baidu.com");
        // 调用Activity的setContentView 将webView 显示出来
        setContentView(webView);
    }
    /**
     * 重写回退按钮的时间,当用户点击回退按钮:
     * 1.webView.canGoBack()判断网页是否能后退,可以则goback()
     * 2.如果不可以连续点击两次退出App,否则弹出提示Toast
     */
    @Override
    public void onBackPressed() {
        if (webView.canGoBack()) {
            webView.goBack();
        } else {
            // 第一次的差,肯定大于2S,此时,弹出Toast,将当前时间设置给exitTime,然后再进行比较
            if (System.currentTimeMillis() - exitTime > 2000) {
                Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
                exitTime = System.currentTimeMillis();
            } else {
                /**
                 * finish():结束当前Activity,不会立即释放内存。遵循android内存管理机制。
                 * exit():结束当前组件如Activity,并立即释放当前Activity所占资源。
                 * killProcess():结束当前组件如Activity,并立即释放当前Activity所占资源。
                 */
                // 并不能彻底退出APP,只是会结束当前Act,释放资源
                LogUtils.e("PID:" + android.os.Process.myPid());
                android.os.Process.killProcess(android.os.Process.myPid());
                System.exit(0);
            }
        }
    }
}

根据URL加载网页-布局代码中设置WebView

运行图


WebView01.java

package com.turing.base.activity.webview;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.turing.base.R;
public class WebView01 extends AppCompatActivity implements View.OnClickListener {
    private Button btn_back;
    private TextView txt_title;
    private Button btn_top;
    private Button btn_refresh;
    private WebView wView;
    private long exitTime = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web_view01);
        btn_back = (Button) findViewById(R.id.btn_back);
        txt_title = (TextView) findViewById(R.id.txt_title);
        btn_top = (Button) findViewById(R.id.btn_top);
        btn_refresh = (Button) findViewById(R.id.btn_refresh);
        wView = (WebView) findViewById(R.id.wView);
        // 设置WebView属性,允许执行JS脚本,不然加载出来的网页很难看
        wView.getSettings().setJavaScriptEnabled(true);
        wView.loadUrl("http://www.baidu.com");
        wView.setWebChromeClient(new WebChromeClient() {
            //这里设置获取到的网站title
            @Override
            public void onReceivedTitle(WebView view, String title) {
                super.onReceivedTitle(view, title);
                txt_title.setText(title);
            }
        });
        wView.setWebViewClient(new WebViewClient() {
            //在webview里打开新链接
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });
        btn_back.setOnClickListener(this);
        btn_refresh.setOnClickListener(this);
        btn_top.setOnClickListener(this);
    }
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_back:
                finish();          //关闭当前Activity
                break;
            case R.id.btn_refresh:
                wView.reload();    //刷新当前页面
                break;
            case R.id.btn_top:
                wView.setScrollY(0);   //滚动到顶部
                break;
        }
    }
    @Override
    public void onBackPressed() {
        if (wView.canGoBack()) {
            wView.goBack();
        } else {
            if ((System.currentTimeMillis() - exitTime) > 2000) {
                Toast.makeText(getApplicationContext(), "再按一次退出程序",Toast.LENGTH_SHORT).show();
                exitTime = System.currentTimeMillis();
            } else {
                finish();
            }
        }
    }
}

activity_web_view01.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:background="#2D97CB">
        <Button
            android:id="@+id/btn_back"
            android:layout_width="64dp"
            android:layout_height="48dp"
            android:layout_alignParentLeft="true"
            android:background="@android:color/transparent"
            android:text="Back" />
        <Button
            android:id="@+id/btn_top"
            android:layout_width="64dp"
            android:layout_height="48dp"
            android:layout_alignParentRight="true"
            android:background="@android:color/transparent"
            android:text="回顶部" />
        <Button
            android:id="@+id/btn_refresh"
            android:layout_width="64dp"
            android:layout_height="48dp"
            android:layout_toLeftOf="@id/btn_top"
            android:background="@android:color/transparent"
            android:text="刷新" />
        <TextView
            android:id="@+id/txt_title"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:layout_toLeftOf="@id/btn_refresh"
            android:layout_toRightOf="@id/btn_back"
            android:gravity="center"
            android:singleLine="true"
            android:text="文章标题"
            android:textColor="#FFFFFF"
            android:textSize="18sp" />
    </RelativeLayout>
    <WebView
        android:id="@+id/wView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

WebView滚动事件的监听

监听滚动事件一般都是设置setOnScrollChangedListener,可惜的是 WebView并没有给我们提供这样的方法,但是我们可以重写WebView,覆盖里面的一个方法: protected void onScrollChanged(final int l, final int t, final int oldl,final int oldt){} 然后再对外提供一个接口~

运行图


MyWebView.java

package com.turing.base.activity.webview;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
/**
 * MyApp
 *
 * @author Mr.Yang on 2016-03-24  09:37.
 * @version 1.0
 *          监听滚动事件一般都是设置setOnScrollChangedListener,
 *          可惜的是 WebView并没有给我们提供这样的方法,
 *          但是我们可以重写WebView,覆盖里面的一个方法:
 *          protected void onScrollChanged(final int l, final int t,
 *          final int oldl,final int oldt){}
 *          然后再对外提供一个接口
 */
public class MyWebView extends WebView {
    /**
     * 接口对象
     */
    private OnScrollChangedCallback onScrollChangedCallback;
    /**
     * 构造函数
     *
     * @param context
     */
    public MyWebView(Context context) {
        super(context);
    }
    /**
     * 构造函数
     *
     * @param context
     */
    public MyWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    /**
     * 构造函数
     *
     * @param context
     */
    public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    /**
     * get方法
     *
     * @return
     */
    public OnScrollChangedCallback getOnScrollChangedCallback() {
        return onScrollChangedCallback;
    }
    /**
     * set方法
     *
     * @param onScrollChangedCallback
     */
    public void setOnScrollChangedCallback(OnScrollChangedCallback onScrollChangedCallback) {
        this.onScrollChangedCallback = onScrollChangedCallback;
    }
    /**
     * 重写onScrollChanged方法
     *
     * @param l
     * @param t
     * @param oldl
     * @param oldt
     *
     */
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (onScrollChangedCallback != null) {
            onScrollChangedCallback.onScroll(l - oldl, t - oldt);
        }
    }
    /**
     * 对外接口
     */
    public static interface OnScrollChangedCallback {
        //这里的dx和dy代表的是x轴和y轴上的偏移量,也可以自己把l, t, oldl, oldt四个参数暴露出来
        public void onScroll(int dx, int dy);
    }
}

WebViewScrollChanged.java

package com.turing.base.activity.webview;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.Toast;
import com.turing.base.R;
public class WebViewScrollChanged extends AppCompatActivity {
    private MyWebView myWebView;
    private Button toTopBtn;
    private long exitTime = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web_view_scroll_changed);
        // 初始化组件
        myWebView = (MyWebView) findViewById(R.id.id_webview);
        toTopBtn = (Button) findViewById(R.id.btn_icon);
        // 加载webView
        myWebView.getSettings().setJavaScriptEnabled(true);
        myWebView.loadUrl("http://sports.sina.com.cn/");
        myWebView.setWebViewClient(new WebViewClient() {
            //在webview里打开新链接
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });
        // 当页面发生滚动,显示Button
        myWebView.setOnScrollChangedCallback(new MyWebView.OnScrollChangedCallback() {
            @Override
            public void onScroll(int dx, int dy) {
                if (dy > 0) {
                    toTopBtn.setVisibility(View.VISIBLE);
                } else {
                    toTopBtn.setVisibility(View.GONE);
                }
            }
        });
        toTopBtn.setOnClickListener(new View.OnClickListener() {
            @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
            @Override
            public void onClick(View v) {
                myWebView.setScrollY(0);
                toTopBtn.setVisibility(View.GONE);
            }
        });
    }
    @Override
    public void onBackPressed() {
        if (myWebView.canGoBack()) {
            myWebView.goBack();
        } else {
            if ((System.currentTimeMillis() - exitTime) > 2000) {
                Toast.makeText(getApplicationContext(), "再按一次退出程序",
                        Toast.LENGTH_SHORT).show();
                exitTime = System.currentTimeMillis();
            } else {
                finish();
            }
        }
    }
}

activity_web_view_scroll_changed.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!--使用自定义的WebView方便监听滚动事件-->
    <com.turing.base.activity.webview.MyWebView
        android:id="@+id/id_webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></com.turing.base.activity.webview.MyWebView>
    <Button
        android:id="@+id/btn_icon"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:text="回到顶部"
        android:visibility="gone" />
</RelativeLayout>

滚动条的问题

  • setHorizontalScrollBarEnabled(false);//水平不显示
  • setVerticalScrollBarEnabled(false); //垂直不显示
  • setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);//滚动条在WebView内侧显示
  • setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY)//滚动条在WebView外侧显示

设置缩放以及自适应屏幕

WebView 只需要开启设置相关属性就可以支持缩放。

缩放自适应屏幕

WebSettings settings = webView.getSettings();
 // 缩放相关属性设置
 settings.setUseWideViewPort(true);//设定支持viewport
 settings.setLoadWithOverviewMode(true); //自适应屏幕
 settings.setBuiltInZoomControls(true);
 settings.setDisplayZoomControls(false);// 隐藏缩放控件
 settings.setSupportZoom(true);//设定支持缩放

取消缩放控件:

settings.setDisplayZoomControls(false);

自行设置初始的缩放比例:

webView.setInitialScale(25);//为25%,最小缩放等级,整个网页缩放

仅对字体进行缩放

settings.setTextZoom(int);
或者
settings.setTextSize(TextSize.LARGER);

Android自带五个可选字体大小的值:SMALLEST(50%),SMALLER(75%),NORMAL(100%),LARGER(150%), LARGEST(200%)。


获取WebView的Cookie数据

我们都知道Cookie其实只是一个代表用户唯一标识的字符串,情景一般是: 用户输入账号密码后,点击登陆,用户要拿着这个Cookie去访问服务器提供的相关服务! 我们可以把cookie的获取写到onPageFinsihed的方法中,简单的可以这样写:

@Override
public void onPageFinished(WebView view, String url) {             
    CookieManager cookieManager = CookieManager.getInstance();
    String CookieStr = cookieManager.getCookie(url);
    Log.e("Cookie", "Cookies = " + CookieStr);
    super.onPageFinished(view, url);
}

设置WebView的Cookie数据

我们上面获取到了Cookie或者通过其他途径获得了Cookie,如何为WebView设置Cookie呢?我们可以在需要设置Cookie的地方加入下述代码:

CookieSyncManager.createInstance(MainActivity.this);  
CookieManager cookieManager = CookieManager.getInstance();  
cookieManager.setAcceptCookie(true);  
cookieManager.setCookie(url, cookies);  //cookies是要设置的cookie字符串 
CookieSyncManager.getInstance().sync();

上述代码需要写在loadUrl()之前,而且如果设置了Cookie了,尽量别再进行其他的设置 不然可能会无效,建议设置cookie的写在webView相关设置的最后面~loadUrl()之前!

WebView和JavaScrip交互

这里我们要演示的是通过:HTML -> JS ->Java来完成HTML5端与Android手机间的 互访。

说明:示例使用到的HTML都是以文件的形式放到assets目录下,只需通过 loadUrl(“file:///android_asset/~”)即可加载对应的HTML~

核心步骤

  • 首先,我们定义一个类,用于将数据暴露出来,JS通过该类暴露的方法(Public)来调用Android!
  • 接着,我们在WebView所在页面Activity,使用下述代码:
webview.getSettings().setJavaScriptEnabled(true);
webview.addJavascriptInterface(object,"name");
  • 然后js或者html中调用name.xxx调用对象里的暴露的方法:
    比如:
    < input type="button" value="Toast提示" onclick="name.showToast('提示信息');"/>

另外,setJavaScriptEnabled是在Android 4.4以前的系统才有效。


常见示例

HTML通过JS显示Toast与普通列表的对话框

效果图

assets/demo1.html

先准备我们的HTML文件,创建好后放到assets目录下:

<html>
<head>
    <title>Js调用Android</title>
</head>
<body>
<input type="button" value="Toast提示" onclick="myObj.showToast('JS触发的Toast');"/>
<input type="button" value="列表对话框" onclick="myObj.showDialog();"/>
</body>
</html>
MyObject.java

自定义一个Object对象,js通过该类暴露的方法来调用Android

package com.turing.base.activity.webview.WebView_Js_inter;
import android.content.Context;
import android.support.v7.app.AlertDialog;
import android.webkit.JavascriptInterface;
import android.widget.Toast;
import com.turing.base.R;
/**
 * MyApp
 *
 * @author Mr.Yang on 2016-03-24  14:28.
 * @version 1.0
 *          自定义一个Object对象,js通过该类暴露的方法来调用Android
 */
public class MyObject {
    private Context context;
    public MyObject(Context context) {
        this.context = context;
    }
    /**
     * If you've set your targetSdkVersion to 17 or higher,
     * you must add the @JavascriptInterface annotation to any method
     * that you want available to your JavaScript (the method must also be public).
     * If you do not provide the annotation,
     * the method is not accessible by your web page
     * when running on Android 4.2 or higher.
     *
     * @param name
     */
    //将显示Toast和对话框的方法暴露给JS脚本调用
    @JavascriptInterface
    public void showToast(String name) {
        Toast.makeText(context, name, Toast.LENGTH_SHORT).show();
    }
    @JavascriptInterface
    public void showDialog() {
        new AlertDialog.Builder(context)
                .setTitle("联系人列表").setIcon(R.drawable.flag_mark_blue)
                .setItems(new String[]{"111", "222", "333", "444", "555", "666"}, null)
                .setPositiveButton("确定", null)
                .create()
                .show();
    }
}
WebViewAndJs01.java

最后在Act中,启用JavaScript支持,然后通过addJavascriptInterface暴露对象~

package com.turing.base.activity.webview.WebView_Js_inter;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.webkit.WebSettings;
import android.webkit.WebView;
import com.turing.base.R;
/**
 * 启用JavaScript支持,然后通过addJavascriptInterface暴露对象~
 */
public class WebViewAndJs01 extends AppCompatActivity {
    private WebView webView ;
    @SuppressLint("JavascriptInterface")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web_view_and_js01);
        webView = (WebView) findViewById(R.id.id_webview_JS);
        //加载assets目录下的html文件
        webView.loadUrl("file:///android_asset/demo1.html");
        WebSettings webSettings = webView.getSettings();
        //①设置WebView允许调用js
        webSettings.setJavaScriptEnabled(true);
        webSettings.setDefaultTextEncodingName("UTF-8");
        //②将object对象暴露给Js,调用addjavascriptInterface
        webView.addJavascriptInterface(new MyObject(WebViewAndJs01.this), "myObj");
    }
}
activity_web_view_and_js01.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <WebView
        android:id="@+id/id_webview_JS"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

HTML通过JS调用三种不同的对话框

效果图

assets/demo2.html

先往assets目录下塞一个html文件: demo2.html:

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"
    <title>测试Js的三种不同对话框</title>
    <script language="JavaScript">
        function alertFun()
        {
            alert("Alert警告对话框!");
        }
        function confirmFun()
        {
            if(confirm("访问百度?"))
            {location.href = "http://www.baidu.com";}
            else alert("取消访问!");
        }
        function promptFun()
        {
            var word = prompt("Prompt对话框","请输入点什么...:");
            if(word)
            {
                alert("你输入了:"+word)
            }else{alert("呵呵,你什么都没写!");}
        }
    </script>
</head>
<body>
<p>三种对话框的使用</p>
<p>Alert对话框</p>
<p>
    <input type="submit" name="Submit1" value="展示1" onclick="alertFun()"/>
</p>
<p>Confirm对话框</p>
<p>
    <input type="submit" name="Submit2" value="展示2" onclick="confirmFun()"/>
</p>
<p>Prompt对话框</p>
<p>
    <input type="submit" name="Submit3" value="展示3" onclick="promptFun()"/>
</p>
</body>
</html>
WebviewJS02.java
package com.turing.base.activity.webview.WebView_Js_inter;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.EditText;
import android.widget.TextView;
import com.turing.base.R;
/**
 * HTML通过JS调用三种不同的对话框
 */
public class WebviewJS02 extends AppCompatActivity {
    private WebView wView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webview_js02);
        wView = (WebView) findViewById(R.id.wView);
        //获得WebSetting对象,支持js脚本,可访问文件,支持缩放,以及编码方式
        WebSettings webSettings = wView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setAllowFileAccess(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setDefaultTextEncodingName("UTF-8");
        //设置WebChromeClient,处理网页中的各种js事件
        wView.setWebChromeClient(new MyWebChromeClient());
        wView.loadUrl("file:///android_asset/demo2.html");
    }
    //这里需要自定义一个类实现WebChromeClient类,并重写三种不同对话框的处理方法
    //分别重写onJsAlert,onJsConfirm,onJsPrompt方法
    class MyWebChromeClient extends WebChromeClient {
        @Override
        public boolean onJsAlert(WebView view, String url, String message,
                                 final JsResult result) {
            //创建一个Builder来显示网页中的对话框
            new AlertDialog.Builder(WebviewJS02.this).setTitle("Alert对话框").setMessage(message)
                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.confirm();
                        }
                    }).setCancelable(false).show();
            return true;
        }
        @Override
        public boolean onJsConfirm(WebView view, String url, String message,
                                   final JsResult result) {
            new AlertDialog.Builder(WebviewJS02.this).setTitle("Confirm对话框").setMessage(message)
                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.confirm();
                        }
                    })
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.cancel();
                        }
                    }).setCancelable(false).show();
            return true;
        }
        @Override
        public boolean onJsPrompt(WebView view, String url, String message,
                                  String defaultValue, final JsPromptResult result) {
            //①获得一个LayoutInflater对象factory,加载指定布局成相应对象
            final LayoutInflater inflater = LayoutInflater.from(WebviewJS02.this);
            final View myview = inflater.inflate(R.layout.prompt_view, null);
            //设置TextView对应网页中的提示信息,edit设置来自于网页的默认文字
            ((TextView) myview.findViewById(R.id.text)).setText(message);
            ((EditText) myview.findViewById(R.id.edit)).setText(defaultValue);
            //定义对话框上的确定按钮
            new AlertDialog.Builder(WebviewJS02.this).setTitle("Prompt对话框").setView(myview)
                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            //单击确定后取得输入的值,传给网页处理
                            String value = ((EditText) myview.findViewById(R.id.edit)).getText().toString();
                            result.confirm(value);
                        }
                    })
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.cancel();
                        }
                    }).show();
            return true;
        }
    }
}
activity_webview_js02.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <WebView
        android:id="@+id/wView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dp" />
</RelativeLayout>
prompt_view.xml
<?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">
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <EditText
        android:id="@+id/edit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollHorizontally="true"
        android:selectAllOnFocus="true" />
</LinearLayout>  

HTML通过JS读取Android联系人并显示

思路

实现思路:通过js读取Android手机中联系列表,然后显示到HTML中 当我们点击某个电话号码时,会直接跳转到拨号页面 。

实现关键: 利用onload()在网页加载的时候加载相应的js脚本,而js脚本中定义的一个函数是 取出传递过来的对象,获取里面的数据,通过for循环以单元行的形式打印出来!

assets/demo3.html
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>显示获取的联系人列表</title>
    <script language="JavaScript">
        function show(jsondata)  
        {  
            //将传递过来的Json转换为对象  
            var jsonobjs = eval(jsondata);  
            //获取下面定义的表格  
            var table = document.getElementById("PersonTable");  
            //遍历上面创建的Json对象,将每个对象添加为  
            //表格中的一行,而它的每个属性作为一列  
            for(var i = 0;i < jsonobjs.length;i++)  
            {  
                //添加一行,三个单元格:  
                var tr = table.insertRow(table.rows.length);  
                var td1 = tr.insertCell(0);  
                var td2 = tr.insertCell(1);  
                td2.align = "center";  
                var td3 = tr.insertCell(2);  
                //设置单元格的内容和属性  
                //其中innerHTML为设置或者获取位于对象起始和结束标签内的HTML  
                //jsonobjs[i]为对象数组中的第i个对象  
                td1.innerHTML = jsonobjs[i].id;  
                td2.innerHTML = jsonobjs[i].name;  
                //为现实的内容添加超链接,超链接会调用Java代码中的  
                //call方法并且把内容作为参数传递过去  
                td3.innerHTML = "<a href = 'javascript:sharp.call(\""+jsonobjs[i].phone + "\")'>"  
                +jsonobjs[i].phone + "</a>";;  
            }  
        }  
    </script>
</head>
<!-- onload指定该页面被加载时调用的方法,这里调用的是Java代码中的contactlist方法-->
<body style="margin:0px; background-color:#FFFFFF; color:#000000;" onload = "javascript:sharp.contactlist()">
<!--定义一个表格-->
<table border = "0" width = "100%" id = "PersonTable" cellspacing = "0">
    <tr>
        <td width = "15%">用户id</td>
        <td align = "center">姓名</td>
        <td width = "15%">号码</td>
    </tr>
</table>
</body>
</html> 
业务类Contact.java
package com.turing.base.activity.webview.WebView_Js_inter;
/**
 * MyApp
 *
 * @author Mr.Yang on 2016-03-24  15:24.
 * @version 1.0
 * @desc
 */
public class Contact {
    private String id;
    private String name;
    private String phone;
    public Contact() {
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    @Override
    public String toString() {
        return this.id + "~" + this.name + "~" + this.phone;
    }
}
WebViewReadContactsAct.java
package com.turing.base.activity.webview.WebView_Js_inter;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.v7.app.AppCompatActivity;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import com.turing.base.R;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
 * 读取联系人
 *
 * 该代码实现的是通过js读取Android手机中联系列表,
 * 然后显示到HTML中 当我们点击某个电话号码时,会直接跳转到拨号页面
 * 实现关键: 利用onload()在网页加载的时候加载相应的js脚本,
 * 而js脚本中定义的一个函数是 取出传递过来的对象,
 * 获取里面的数据,通过for循环以单元行的形式打印出来!
 */
public class WebViewReadContactsAct extends AppCompatActivity {
    private WebView wView;
    @SuppressLint("JavascriptInterface")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web_view_read_contacts);
        //设置WebView的相关设置,依次是:
        //支持js,不保存表单,不保存密码,不支持缩放
        //同时绑定Java对象
        wView = (WebView) findViewById(R.id.wView);
        wView.getSettings().setJavaScriptEnabled(true);
        wView.getSettings().setSaveFormData(false);
        wView.getSettings().setSavePassword(false);
        wView.getSettings().setSupportZoom(false);
        wView.getSettings().setDefaultTextEncodingName("UTF-8");
        wView.addJavascriptInterface(new SharpJS(), "sharp");
        wView.loadUrl("file:///android_asset/demo3.html");
    }
    //自定义一个Js的业务类,传递给JS的对象就是这个,调用时直接javascript:sharp.contactlist()
    public class SharpJS {
        /**
         * 所有的WebView方法都应该在同一个线程程中调用
         */
        @JavascriptInterface
        public void contactlist() {
            wView.post(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("contactlist()方法执行了!");
                        String json = buildJson(getContacts());
                        wView.loadUrl("javascript:show('" + json + "')");
                    } catch (Exception e) {
                        System.out.println("设置数据失败" + e);
                    }
                }
            });
        }
        @JavascriptInterface
        public void call(String phone) {
            System.out.println("call()方法执行了!");
            Intent it = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + phone));
            startActivity(it);
        }
    }
    //将获取到的联系人集合写入到JsonObject对象中,再添加到JsonArray数组中
    public String buildJson(List<Contact> contacts) throws Exception {
        JSONArray array = new JSONArray();
        for (Contact contact : contacts) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("id", contact.getId());
            jsonObject.put("name", contact.getName());
            jsonObject.put("phone", contact.getPhone());
            array.put(jsonObject);
        }
        return array.toString();
    }
    //定义一个获取联系人的方法,返回的是List<Contact>的数据
    public List<Contact> getContacts() {
        List<Contact> Contacts = new ArrayList<Contact>();
        //①查询raw_contacts表获得联系人的id
        ContentResolver resolver = getContentResolver();
        Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
        //查询联系人数据
        Cursor cursor = resolver.query(uri, null, null, null, null);
        while (cursor.moveToNext()) {
            Contact contact = new Contact();
            //获取联系人姓名,手机号码
            contact.setId(cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)));
            contact.setName(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)));
            contact.setPhone(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
            Contacts.add(contact);
        }
        cursor.close();
        return Contacts;
    }
}
activity_web_view_read_contacts.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.turing.base.activity.webview.WebView_Js_inter.WebViewReadContactsAct">
    <WebView
        android:id="@+id/wView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dp" />
</RelativeLayout>

Android 4.4后WebView的一些注意事项

从Android 4.4开始,Android中的WebView不再是基于WebKit的,而是开始基于Chromium,这个改变 使得WebView的性能大幅提升,并且对HTML5,CSS,JavaScript有了更好的支持!

虽然chromium完全取代了以前的WebKit for Android,但Android WebView的API接口并没有变, 与老的版本完全兼容。这样带来的好处是基于WebView构建的APP,无需做任何修改, 就能享受chromium内核的高效与强大。

对于4.4后的WebView,我们需要注意下下面这些问题:

多线程

如果你在子线程中调用WebView的相关方法,而不在UI线程,则可能会出现无法预料的错误。 所以,当你的程序中需要用到多线程时候,也请使用runOnUiThread()方法来保证你关于 WebView的操作是在UI线程中进行的:

runOnUiThread(newRunnable(){
@Override
publicvoid run(){
   // Code for WebView goes here
   }
});

线程阻塞

永远不要阻塞UI线程,这是开发Android程序的一个真理。虽然是真理,我们却往往不自觉的 犯一些错误违背它,一个开发中常犯的错误就是:在UI线程中去等待JavaScript 的回调。 例如:

// This code is BAD and will block the UI thread
webView.loadUrl("javascript:fn()"); 
while(result ==null) {  
    Thread.sleep(100); 
}

千万不要这样做,Android 4.4中,提供了新的Api来做这件事情。 evaluateJavascript() 就是专门来异步执行JavaScript代码的。


evaluateJavascript() 方法

专门用于异步调用JavaScript方法,并且能够得到一个回调结果。

mWebView.evaluateJavascript(script, new ValueCallback<String>() {
 @Override
 public void onReceiveValue(String value) {
      //TODO
 }
});

处理WebView中url的跳转

新版WebView对于自定义scheme的url跳转,新增了更为严格的限制条件。 当你实现了 shouldOverrideUrlLoading() 或 shouldInterceptRequest() 回调,WebView 也只会在跳转url是合法Url时才会跳转。 例如,如果你使用这样一个url :

<a href="showProfile">Show Profile</a>

shouldOverrideUrlLoading() 将不会被调用。

正确的使用方式是:

<a href="example-app:showProfile">Show Profile</a>

对应的检测Url跳转的方式:

/ The URL scheme should be non-hierarchical (no trailing slashes)
 privatestaticfinalString APP_SCHEME ="example-app:";
 @Override 
 publicboolean shouldOverrideUrlLoading(WebView view,String url){
     if(url.startsWith(APP_SCHEME)){
         urlData =URLDecoder.decode(url.substring(APP_SCHEME.length()),"UTF-8");
         respondToData(urlData);
         returntrue;
     }
     returnfalse;
}

当然,也可以这样使用:

webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA,null,"UTF-8",null);• 1

UserAgent变化

如果你的App对应的服务端程序,会根据客户端传来的UserAgent来做不同的事情,那么你需要注意 的是,新版本的WebView中,UserAgent有了些微妙的改变:

Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16H)
AppleWebKit/537.36(KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0
Mobile Safari/537.3

使用getDefaultUserAgent()方法可以获取默认的UserAgent,也可以通过:

mWebView.getSettings().setUserAgentString(ua);
mWebView.getSettings().getUserAgentString();

来设置和获取自定义的UserAgent。


使用addJavascriptInterface()的注意事项

从Android4.2开始。 只有添加 @JavascriptInterface 声明的Java方法才可以被JavaScript调用, 例如:

class JsObject {
    @JavascriptInterface
    public String toString() { return "injectedObject"; }
}
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");

Remote Debugging

新版的WebView还提供了一个很厉害的功能:使用Chrome来调试你运行在WebView中的程序 具体可以看:remote-debugging

WebView文件下载

调用其它浏览器下载文件

运行图


Code

布局文件,只有一个webview,就不贴了,下面看下主Act

ProgressDialog还不严谨,粗略演示下~

如果存在多个应用可以下载,系统会弹出选择框


package com.turing.base.activity.webview.WebView_Download;
import android.annotation.TargetApi;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.webkit.DownloadListener;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.apkfuns.logutils.LogUtils;
import com.turing.base.R;
/**
 * 调用其它浏览器下载文件:
 * 只需为WebView设置setDownloadListener,
 * 然后重写DownloadListener的 onDownloadStart,
 * 然后在里面写个Intent,然后startActivity对应的Activity即可!
 */
public class WebViewDownload extends AppCompatActivity {
    private WebView webView;
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web_view_download);
        webView = (WebView) findViewById(R.id.id_webview_download);
        webView.setWebViewClient(new WebViewClient() {
            // 设置WebView点击打开的网页在当前界面显示,而不是跳到新的浏览器中
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                LogUtils.e("onPageStarted");
                showProgressDialog();
            }
            @Override
            public void onPageFinished(WebView view, String url) {
                LogUtils.e("onPageFinished");
                closeProgressDialog();
            }
            @Override
            public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
                super.onReceivedError(view, request, error);
                LogUtils.e("onReceivedError");
                closeProgressDialog();
            }
        });
        WebSettings settings = webView.getSettings();
        // 允许执行JS脚本
        settings.setJavaScriptEnabled(true);
        // 缩放相关属性设置
        settings.setUseWideViewPort(true);//设定支持viewport
        settings.setLoadWithOverviewMode(true); //自适应屏幕
        settings.setBuiltInZoomControls(true);
        settings.setDisplayZoomControls(false);// 隐藏缩放控件
        settings.setSupportZoom(true);//设定支持缩放
        // 载入URL
        webView.loadUrl("http://www.oschina.net/app");
        // 然后,找到下载的地方,这个时候点击下载,就可以调用手机内置的浏览器下下载了
        //WebView默认没有开启文件下载的功能,
        // 如果要实现文件下载的功能,需要设置WebView的DownloadListene
        webView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
                Uri uri = Uri.parse(url);
                Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                startActivity(intent);
            }
        });
    }
    /**
     * 载入前加载
     */
    ProgressDialog mDialog;
    private void showProgressDialog() {
        mDialog = new ProgressDialog(this);
        mDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);//设置风格为圆形进度条
        mDialog.setMessage("正在加载 ,请等待...");
        mDialog.setIndeterminate(false);//设置进度条是否为不明确
        mDialog.setCancelable(true);//设置进度条是否可以按退回键取消  默认true
        mDialog.setCanceledOnTouchOutside(false);//设置在点击Dialog外是否取消Dialog进度条  默认true
        mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                mDialog = null;
            }
        });
        mDialog.show();
    }
    private void closeProgressDialog() {
        mDialog.dismiss();
        mDialog = null;
    }
}

自己写线程下载文件

当然,你可能不想把下载文件放到默认路径下,或者想自己定义文件名等等,你都可以自己来写 一个线程来下载文件,实现示例代码如下:

DownLoadThread.java

package com.turing.base.activity.webview.WebView_Download;
import android.os.Environment;
import com.apkfuns.logutils.LogUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
 * MyApp
 *
 * @author Mr.Yang on 2016-03-24  17:47.
 * @version 1.0
 * @desc
 */
public class DownLoadThread implements Runnable {
    private String url;
    public DownLoadThread(String url) {
        this.url = url;
    }
    /**
     * 使用HttpURLConnection下载
     */
    @Override
    public void run() {
        // 处理下载业务逻辑
        LogUtils.e( "开始下载~~~~~" + url);
        InputStream in = null;
        FileOutputStream fout = null;
        try {
            // 实例化URl
            URL httpUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) httpUrl.openConnection();
            conn.setDoInput(true);
            conn.setDoOutput(true);
            in = conn.getInputStream();
            File downloadFile, sdFile;
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                downloadFile = Environment.getExternalStorageDirectory();
                sdFile = new File(downloadFile, "dowload.apk");
                fout = new FileOutputStream(sdFile);
            }else{
                LogUtils.e("SD卡不存在或者不可读写");
            }
            // 缓冲区
            byte[] buffer = new byte[1024];
            int len;
            // 循环读取
            while ((len = in.read(buffer)) != -1) {
                fout.write(buffer, 0, len);
            }
            LogUtils.e("下载完毕~~~~");
        } catch (Exception e) {
            e.printStackTrace();
            LogUtils.e("下载异常~~~~");
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fout != null) {
                try {
                    fout.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

WebViewDownLoadWithSelfThread.java

package com.turing.base.activity.webview.WebView_Download;
import android.annotation.TargetApi;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.webkit.DownloadListener;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.apkfuns.logutils.LogUtils;
import com.turing.base.R;
public class WebViewDownLoadWithSelfThread extends AppCompatActivity {
    private WebView webView;
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web_view_down_load_with_self_thread);
        webView = (WebView) findViewById(R.id.id_webview);
        webView.setWebViewClient(new WebViewClient() {
            // 设置WebView点击打开的网页在当前界面显示,而不是跳到新的浏览器中
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                LogUtils.e("onPageStarted");
                showProgressDialog();
            }
            @Override
            public void onPageFinished(WebView view, String url) {
                LogUtils.e("onPageFinished");
                closeProgressDialog();
            }
            @Override
            public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
                super.onReceivedError(view, request, error);
                LogUtils.e("onReceivedError");
                closeProgressDialog();
            }
        });
        WebSettings settings = webView.getSettings();
        // 允许执行JS脚本
        settings.setJavaScriptEnabled(true);
        // 缩放相关属性设置
        settings.setUseWideViewPort(true);//设定支持viewport
        settings.setLoadWithOverviewMode(true); //自适应屏幕
        settings.setBuiltInZoomControls(true);
        settings.setDisplayZoomControls(false);// 隐藏缩放控件
        settings.setSupportZoom(true);//设定支持缩放
        // 载入URL
        webView.loadUrl("http://www.csdn.net/app/");
        // 然后,找到下载的地方,这个时候点击下载,调用自己写的下载程序
        //WebView默认没有开启文件下载的功能,
        // 如果要实现文件下载的功能,需要设置WebView的DownloadListener
        webView.setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
                // 自己实现的下载逻辑线程
                new Thread(new DownLoadThread(url)).start();
            }
        });
    }
    /**
     * 载入前加载
     */
    ProgressDialog mDialog;
    private void showProgressDialog() {
        mDialog = new ProgressDialog(this);
        mDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);//设置风格为圆形进度条
        mDialog.setMessage("正在加载 ,请等待...");
        mDialog.setIndeterminate(false);//设置进度条是否为不明确
        mDialog.setCancelable(true);//设置进度条是否可以按退回键取消  默认true
        mDialog.setCanceledOnTouchOutside(false);//设置在点击Dialog外是否取消Dialog进度条  默认true
        mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                mDialog = null;
            }
        });
        mDialog.show();
    }
    private void closeProgressDialog() {
        mDialog.dismiss();
        mDialog = null;
    }
}

清单文件配置权限

<uses-permission android:name="android.permission.INTERNET"/>
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

WebView缓存问题

现在很多门户类信息网站,信息阅读类的APP,很多 都是直接嵌套一个WebView用来显示相关资讯的,这可能就涉及到了WebView的缓存了!

所谓的页面缓存 就是指:保存加载一个网页时所需的HTML,JS,CSS等页面相关的数据以及其他资源,当没网的时候或者 网络状态较差的时候,加载本地保存好的相关数据!而实现这个缓存的方式有两种,一种是后台写一个 下载的Service,将文章相关的数据按自己的需求下载到数据库或者保存到相应文件夹中,然后下次加载 对应URL前先判断是否存在本地缓存,如果存在优先加载本地缓存,不存在则执行联网请求,同时缓存 相关资源,典型的如旧版本的36Kr,在进去后会先离线文章,然后再显示!

当然,这里要讲解的不是 这种自己写逻辑的方式,而是通过WebView本身自带的缓存功能来缓存页面,这种方式使用起来非常 简单,我们只需为WebView设置开启相关功能,以及设置数据库的缓存路径即可完成缓存!具体的 实现我们下面一一道来~


缓存的分类

首先要说的一点是缓存的分类,我们缓存的数据分为:页面缓存和数据缓存

  • 页面缓存:加载一个网页时的html、JS、CSS等页面或者资源数据,这些缓存资源是由于浏览器 的行为而产生,开发者只能通过配置HTTP响应头影响浏览器的行为才能间接地影响到这些缓存数据。
    而缓存的索引放在:/data/data/<包名>/databases
    对应的文件放在:/data/data/package_name/cache/webviewCacheChromunm下
  • 数据缓存:分为AppCache和DOM Storage两种 我们开发者可以自行控制的就是这些缓存资源,
  • AppCache:我们能够有选择的缓冲web浏览器中所有的东西,从页面、图片到脚本、css等等。 尤其在涉及到应用于网站的多个页面上的CSS和JavaScript文件的时候非常有用。其大小目前通常是5M。 在Android上需要手动开启(setAppCacheEnabled),并设置路径(setAppCachePath)和容量 (setAppCacheMaxSize),而Android中使用ApplicationCache.db来保存AppCache数据!
  • DOM Storage:存储一些简单的用key/value对即可解决的数据,根据作用范围的不同,有Session Storage和Local Storage两种,分别用于会话级别的存储(页面关闭即消失)和本地化存储(除非主动 删除,否则数据永远不会过期)在Android中可以手动开启DOM Storage(setDomStorageEnabled), 设置存储路径(setDatabasePath)Android中Webkit会为DOMStorage产生两个文件(my_path/localstorage/http_blog.csdn.net_0.localstorage和my_path/Databases.db)

另外还要说下几种缓存的模式:

  • LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
  • LOAD_DEFAULT: 根据cache-control决定是否从网络上取数据。
  • LOAD_CACHE_NORMAL: API level 17中已经废弃, 从API level 11开始作用同LOAD_DEFAULT模式
  • LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
  • LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。

总结:根据以上两种模式,建议缓存策略为,判断是否有网络,有的话,使用LOAD_DEFAULT, 无网络时,使用LOAD_CACHE_ELSE_NETWORK。

http://www.runoob.com/w3cnote/android-tutorial-webview-cache.html


为WebView开启缓存功能

流程解析: 1.进入页面后默认加载url,然后随便点击一个链接跳到第二个页面,退出APP 2.关闭wifi以及移动网络,然后重新进入,发现无网络的情况下,页面还是加载了, 打开第一个链接也可以加载,打开其他链接就发现找不到网页! 3.点击清除缓存,把应用关闭,重新进入,发现页面已经打不开!

package com.turing.base.activity.webview.WebView_Cache;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import com.apkfuns.logutils.LogUtils;
import com.turing.base.R;
/**
 * 开启缓存的功能,以及设置缓存模式以及缓存的数据的路径
 */
public class WebViewCacheAct extends AppCompatActivity {
    private WebView wView;
    private Button btn_clear_cache;
    private Button btn_refresh;
    private static final String APP_CACHE_DIRNAME = "/webcache"; // web缓存目录
    private static final String URL = "http://blog.csdn.net/yangshangwei";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web_view_cache);
        wView = (WebView) findViewById(R.id.wView);
        btn_clear_cache = (Button) findViewById(R.id.btn_clear_cache);
        btn_refresh = (Button) findViewById(R.id.btn_refresh);
        wView.loadUrl(URL);
        wView.setWebViewClient(new WebViewClient() {
            //设置在webView点击打开的新网页在当前界面显示,而不跳转到新的浏览器中
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });
        WebSettings settings = wView.getSettings();
        settings.setJavaScriptEnabled(true);
        //设置缓存模式
        settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
        // 开启DOM storage API 功能
        settings.setDomStorageEnabled(true);
        // 开启database storage API功能
        settings.setDatabaseEnabled(true);
        String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACHE_DIRNAME;
        LogUtils.e("cachePath:"+ cacheDirPath);
        // 设置数据库缓存路径
        settings.setAppCachePath(cacheDirPath);
        settings.setAppCacheEnabled(true);
        LogUtils.e("databasepath:"+ settings.getDatabasePath());
        // 清除缓存
        btn_clear_cache.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                wView.clearCache(true);
            }
        });
        // 刷新
        btn_refresh.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                wView.reload();
            }
        });
    }
    //重写回退按钮的点击事件
    @Override
    public void onBackPressed() {
        if(wView.canGoBack()){
            wView.goBack();
        }else{
            super.onBackPressed();
        }
    }
}

删除WebView的缓存数据

上面的示例,我们通过调用WebView的clearCache(true)方法,已经实现了对缓存的删除! 除了这种方法外,还有下述方法:

  • setting.setCacheMode(WebSettings.LOAD_NO_CACHE);
  • deleteDatabase(“WebView.db”);和deleteDatabase(“WebViewCache.db”);
  • webView.clearHistory();
  • webView.clearFormData();
  • getCacheDir().delete();
  • 手动写delete方法,循环迭代删除缓存文件夹!

当然,前面也说,我们能这直接操作的只是数据部分,而页面缓存是由于浏览器 的行为而产生,我们只能通过配置HTTP响应头影响浏览器的行为才能间接地影响到 这些缓存数据。所以上述的方法仅仅是删除的数据部分的缓存!


WebView处理网页返回的错误码信息

假如你们公司是做HTML5端的移动APP的,就是通过WebView来显示网页的,假如你访问的网页 不存在,或者其他错误,报404,401,403,30X等错误的状态码,如果直接弹出WebView默认的错误 提示页面,可能显得不那么友好,我们可以重写WebViewClient的onReceivedError()方法来实现我们 想要的效果,一般的做法有两种,一种是:我们自己在assets目录下创建一个用于显示错误信息的 HTML页面,当发生错误,即onReceivedError()被调用的时候我们调用webView的loadUrl跳到我们 的错误页面,比如:wView.loadUrl(“file:///android_asset/error.html”);!又或者我们另外写 一个布局或者直接一个大大的图片,平时设置为不可见,当页面错误时,让该布局或者图片可见!


页面错误,加载自定义网页

wView.setWebViewClient(new WebViewClient() {
//设置在webView点击打开的新网页在当前界面显示,而不跳转到新的浏览器中
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    view.loadUrl(url);
    return true;
}
@Override
public void onReceivedError(WebView view, int errorCode, String description,
    String failingUrl) {
        super.onReceivedError(view, errorCode, description, failingUrl);
        wView.loadUrl("file:///android_asset/error.html");
    }
});

页面错误,显示相应的View

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private WebView wView;
    private ImageView img_error_back;
    private Button btn_refresh;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        wView = (WebView) findViewById(R.id.wView);
        img_error_back = (ImageView) findViewById(R.id.img_error_back);
        btn_refresh = (Button) findViewById(R.id.btn_refresh);
        wView.loadUrl("http://www.baidu.com");
        wView.setWebViewClient(new WebViewClient() {
            //设置在webView点击打开的新网页在当前界面显示,而不跳转到新的浏览器中
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
            @Override
            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                super.onReceivedError(view, errorCode, description, failingUrl);
                wView.setVisibility(View.GONE);
                img_error_back.setVisibility(View.VISIBLE);
            }
        });
        btn_refresh.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        wView.loadUrl("http://www.baidu.com");
        img_error_back.setVisibility(View.GONE);
        wView.setVisibility(View.VISIBLE);
    }
}

除此之外:

Android 调用js有个漏洞:详情请查看原作者博文:

http://blog.csdn.net/leehong2005/article/details/11808557

还有简书上的一片博文,可以参考下

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

再此仅作为记录。


相关文章
|
7月前
|
JavaScript 前端开发 安全
webview使用
webview使用
94 0
|
JavaScript 前端开发 Android开发
Android AgentWeb WebView 与js交互总结
Android AgentWeb WebView 与js交互总结
355 0
|
JavaScript Android开发
Android:WebView与js交互方式
通过WebView的addJavascriptInterface()进行对象映射 将JS代码javascript.html格式放到src/main/assets文件夹里 javascript.html
339 0
|
Android开发 开发者 iOS开发
关于WebView 控件,你了解多少?
大家需要知道,不管什么技术,最终在 App 里面显示网页,一定需要一个网页引擎,这样才能解析网页。 通常情况下,App 内部会使用 WebView 控件作为网页引擎。这是系统自带的控件,专门用来显示网页。应用程序的界面,只要放上 WebView,就好像内嵌了浏览器窗口,可以显示网页。
202 0
|
JavaScript API Android开发
Flutter WebView与JS交互简易指南
本文采用Flutter官方WebView插件:https://pub.dartlang.org/packages/webview_flutter   WebView与JS互相调用是一个刚需,但是貌似现在大家写的文章讲的都不是很清楚,我这个简易指南简单粗暴地分为两部分:JS调用Flutter和Flutter调用JS,拒绝花里胡哨,保证一看就懂,一学就会。
|
缓存 JavaScript Android开发
WebView使用相关笔记(一)
一、WebView常用的方法: 1、加载url // 1、加载一个网页 wvDeviceInfo.loadUrl("https://www.
1170 0
|
JavaScript 前端开发 Android开发
|
Android开发 移动开发 存储
ListView嵌套webView 事件冲突解决
如图,红色部分为WebView,作为ListView头部存在,测试视频(h5)要左右滑动,ListView要上下滑动,保证视频控件和下面评论部分可以显示,但是这个时候就存在WebView横向滑动和ListView竖向滑动之间的冲突。
1405 0