WebView完全解读

41 篇文章 4 订阅
订阅专栏
29 篇文章 2 订阅
订阅专栏

概述

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);

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.36

使用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

再此仅作为记录。

微信小程序技术解读
weixin_38113097的博客
06-04 1398
文章目录小程序原理简介视图层(View)逻辑层(AppService)系统层小程序开发简介开发流程小程序发布一些常见问题用户信息获取网络请求页面路由跨平台兼容小程序码官方文档小程序第三方平台结语 自今年初微信正式发布小程序,已有大半年时间。这期间,行业对微信小程序的关注经历了三个阶段。 第一个阶段是刚发布时的火热,大家都盼望这是“又一个公众号”,尝试从各个不同角度解读、预测小程序的价值所在,担心像几年前公众号刚上线时那样再次作出“误判”、错过“流量红利”。 第二个阶段是发布一两个星期后的失望和舆论冷
WebView属性
09-25
WebView属性的详情解释,还有负责原生和js交互的具体写法方便你们套取写
WebView 属性大全
qq_41804086的博客
04-17 763
//通过Webview 创建出WebSettings WebSettings settings = wv.getSettings(); //设置的WebView用户代理字符串 settings.setUserAgentString(""); //启用JavaScript settings.setJavaScrip...
解决小程序web-view两个恶心问题
最新发布
林恒的博客
07-24 954
算是小完美的解决了吧,这里记录一下,看看就行,勿喷。
1、 WebView相关知识
lanxuan1993的博客
04-19 141
1.WebView:onReceiveError的应用与变迁 https://www.jianshu.com/p/fcebd23cbebb 2. WebView的缓存 https://blog.csdn.net/lanxuan1993/article/details/80348223 3.WebView的ajax或javascript响应 https://blog.csdn.net/lanxuan1993/article/details/80347321 4. WebView的传参交互 ht.
Android中webview的相关知识点
水月洞天
08-03 467
webview是Android中用来加载网页的一个组件。有一定开发基础的Android程序员应该都是很熟悉这个组件的。有时候我们可能直接使用webview,有时候我们喜欢使用类似AgentWeb之类的三方框架。这里我总结一下webview的一些相关常用方法。今后不论怎么使用webview,这些常用方法都是必不可少的。 1.webview的常用方法 loadUrl:加载指定的URL,URL可以是HTTP打头的 外部网址,也可以是file打头的资产网页。 getSettings:获取浏览器的网页设置信息。
WebView简介
邵东的博客
08-07 648
WebView简介 WebView(网络视图)能加载显示网页,可以将其视为一个浏览器。它使用WebKit渲染引擎加载显示网页 当前主流的浏览器内核 Trident Gecko WebKit Prosto 实例化WebView的对象 (1)webview=new WebView(context); setContentView(webview); (2)setCont...
androidwebview停用,Android高级工程师必看系列,赶紧收藏!
m0_53537576的博客
03-27 333
前言 全网唯一一份,对标阿里P7年薪60w+的Android高级工程师学习进阶路线(图未完全展开,怕大家看不清楚): 本篇文章都会围绕这份脑图来写,详细的介绍你处于哪个阶段该如何进阶,以及年薪层次高低对应该学的的技术。 一、Java基础 我知道大家一定有很久都没有注意到这个点了,平时的工作应该也很少涉及到这些底层知识吧,但是这些东西很重要。如果是想要跳槽加薪或者是应对即将到来的面试,这些都是不可忽视的知识。 在这一点里,需要重视的点有: Java泛型 (泛型的作用定义,通配符与嵌套,泛型的上下边界……
Flutter 2.8 | 一文解读重点更新
谷歌开发者
12-24 2349
作者 / Chris Sells, Flutter 开发者体验产品经理又到了北半球每年的这个时节: 树叶变黄、气温下降,而今年的最终稳定版本也在此时发布: Flutter 2.8 闪亮登场...
GaiaX开源解读 | 基于优酷业务特色的跨平台技术
阿里文娱技术
10-27 1434
文章会从优酷的业务特色、客户端研发效能的瓶颈问题、提出解决研发效能问题的思路这三个方面分别来进行介绍,带大家进一步了解GaiaX的起源。
Cube 技术解读 | 支付宝新一代动态化技术架构与选型综述
腾讯千帆连接器的博客
05-31 153
本文介绍了支付宝新一代动态化技术架构与选型综述,并对Cube 技术进行了详细解读
webView属性详解以及用法
10-15
详细介绍说明了webview属性以及基本的用法
什么是webview
weixin_30586085的博客
02-02 193
WebView(网络视图)能加载显示网页,可以将其视为一个浏览器。它使用了WebKit渲染引擎加载显示网页,实现WebView有以下两种不同的方法:第一种方法的步骤:1.在要Activity中实例化WebView组件:WebView webView = new WebView(this);2.调用WebView的loadUrl()方法,设置WevView要显示的网页:互联网用:webView.lo...
Webview是什么?
Lele_MM的博客
10-10 1万+
1.webview是什么?作用是什么?和浏览器有什么关系? Webview 是一个基于webkit引擎,可以解析DOM 元素,展示html页面的控件,它和浏览器展示页面的原理是相同的,所以可以把它当做浏览器看待。(chrome浏览器也是基于webkit引擎开发的,Mozilla浏览器是基于Gecko引擎开发的) Android的Webview在低版本和高版本采用了不同的webkit版本内核,4.4...
WebView属性
qq_28031701的博客
06-28 336
1.WebView.getSettings().setSupportZoom(true); //设置可以支持缩放 2.WebView.getSettings().setBuiltInZoomControls(true); //设置出现缩放工具 3.WebSettings常用方法: setAllowFileAccess 启用或禁止WebView访问文件数据 setBloc
什么是webview
热门推荐
暖暖的博客
02-02 2万+
Andoid 平台,SDK 中有一个控件叫 WebView
Android webView 使用概述
zolty
05-29 102
1.webActivity.java 新建 package cn.com.lenew.bluetooth.activity; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import cn.com
webview是什么东西_做性能优化前需要考虑什么?
weixin_39836726的博客
11-23 182
高手过招往往不是考量谁知道的知识点多,而是招招致敌!很多人在刚开始学习做性能优化的时候,就是眉毛胡子一把抓,把知道的、想做的、能做的都一股脑都做了,这就是典型的看上去很勤奋的奋斗逼!苦哈哈的干了很多活,也落了地,确实性能得到了优化。但是要知道代码是一坨一坨的长大的,它不是你在那里优化一下,它就不动了的,于是乎过不了多长时间,又是一番难以想象的光景!真正懂得优化的高手是真正懂得“权衡”真谛...
WebView的一些属性
u012437397的博客
01-04 394
WebView 1、概念        WebView是Android种基于webkit内核浏览器渲染引擎的组件,它可以通过HTML显示方便地网页。  2、权限       WebView需要添加网络权限:    3、属性      1)想和Js进行交互?那么加上             mWebView.getSettings().setJavaScri
Android WebView加载网页与数据完全指南
"本文将详细介绍如何在Android应用中使用原生组件WebView来加载和展示网页及数据。通过学习,开发者可以了解到如何实现在自己的Activity中嵌入网页浏览功能,而无需依赖外部浏览器应用。" 在Android开发中,WebView...
写文章

热门文章

  • Linux字符截取命令-cut 92641
  • Oracle-分析函数之sum(...) over(...) 84982
  • 小工匠聊架构文章一览【不间断持续更新】 78162
  • Spring-AOP @AspectJ切点函数之@annotation() 77516
  • Spring-AOP @AspectJ切点函数之@within()和@target 72080

分类专栏

  • 【小工匠聊架構】 付费 134篇
  • 【实战-并发编程】 付费 10篇
  • 【加密与安全】 46篇
  • 【凤凰架构】 4篇
  • 【Simple RPC】 7篇
  • 【Netty原理与 RPC 实践】 2篇
  • 【Netty入门到精通】 47篇
  • 【一起学Golang】 2篇
  • 【EDA事件驱动架构】 1篇
  • 【大厂案例】 7篇
  • 【AIGC】 23篇
  • 【大数据&云计算】 6篇
  • 【Nacos架构与原理】 10篇
  • 【开源项目鉴赏】 22篇
  • 【DDD-领域驱动设计】 2篇
  • 【每日一博】 71篇
  • 【高可用架构】 8篇
  • 【计算机网络】 30篇
  • 【设计模式GOF】 23篇
  • 【异步编程】 14篇
  • 【JVM性能调优实战】 61篇
  • 【实战-Spring Cloud Alibaba】 1篇
  • 【Spring响应式编程】
  • 【Spring Cloud Alibaba】 29篇
  • 【Spring Cloud Finchley】 20篇
  • 【实战-Spring Cloud Finchley实战】 7篇
  • 【Spring5.x 源码】 26篇
  • 【Spring WebFlux】 3篇
  • 【开发规范】 2篇
  • 【OS System】 3篇
  • 【APM】 7篇
  • 【MyBatis源码解析】 6篇
  • 【LeetCode】 2篇
  • 【Tomcat架构&源码&调优】 11篇
  • 【Data Structures & Algorithms】 15篇
  • 【MQ-Apache RocketMQ】 11篇
  • 【MQ-Apache Kafka】 58篇
  • 【MQ-RabbitMQ】 2篇
  • 【ZK-Apache ZooKeeper】 27篇
  • 【ES-Elasticsearch】 84篇
  • 【Spring Boot2.X】 89篇
  • 【Spring Session】 3篇
  • 【实战-SSM In Action】 48篇
  • 【MyBatis】 31篇
  • 【Redis-入门到精通】 34篇
  • 【Redis-进阶实战】 34篇
  • 【Java并发编程】 75篇
  • 【JVM高级特性】 37篇
  • 【Java - Java 8】 34篇
  • 【J.U.C源码】 24篇
  • 【Java - Java Base】 57篇
  • 【Java工具类】 2篇
  • 【Java设计模式】 19篇
  • 【Spring-IOC】 41篇
  • 【Spring-AOP】 25篇
  • 【Spring-AOP进阶】 23篇
  • 【Spring-JDBC】 20篇
  • 【Spring-Cache】 9篇
  • 【Spring-MVC】 19篇
  • 【Spring-OXM】 5篇
  • 【Quartz任务调度】 20篇
  • 【MySQL深度剖析】 23篇
  • 【MySQL基础篇】 26篇
  • 【Oracle基础】 63篇
  • 【Oracle通用优化】 24篇
  • 【Git】 10篇
  • 【Maven】 14篇
  • 【系统运维-Shell】 39篇
  • 【Dubbo】 1篇
  • 【容器技术-Docker】 18篇
  • 【系统运维-Linux】 74篇
  • 【Nginx】 43篇
  • 【软考-恰狗狮】 10篇
  • 【Hadoop】 5篇
  • 【Python】 18篇
  • 【前端开发-Vue2.x基础篇】 5篇
  • 【前端开发-JavaScript基础篇】 4篇
  • 【移动开发-Android基础篇】 29篇
  • 【移动开发-Android网络编程】 3篇
  • 【移动开发-AndroidMD】 12篇
  • 【移动开发-Android常见UI】 3篇
  • 【移动开发-Android-图片处理】 8篇
  • 【移动开发-Android杂货箱】 17篇
  • 【移动开发-Android中级篇】 11篇
  • 【移动开发-AndroidStudio】 17篇
  • 【万花筒】 12篇

最新评论

  • 实战并发编程 - 08基于Guarded Suspension模式优化轮询while(true)

    weixin_43894223: 这TM不是书上的内容吗,居然变成付费了

  • 计网 - 域名解析的工作流程

    土豆土豆还有茄子: 图太清晰明了了表情包表情包表情包

  • J.U.C Review - 基本概念:进程、线程、线程组、优先级

    CSDN-Ada助手: 推荐 C 技能树:https://edu.csdn.net/skill/c?utm_source=AI_act_c

  • Spring Cloud【Finchley】实战-06使用/actuator/bus-refresh端点手动刷新配置 + 使用Spring Cloud Bus自动更新配置

    weixin_55409526: 改了半天,我发现只刷新了配置中心的配置,不刷新其他服务的配置

  • Spring Boot - 通过ServletRequestHandledEvent事件实现接口请求的性能监控

    码踏云端: 优质好文,博主的文章细节很到位,兼顾实用性和可操作性,感谢博主的分享,期待博主持续带来更多好文!愿博主空闲十分也能到小弟寒舍煮酒论英雄~

大家在看

  • 某花顺爬虫逆向分析 133
  • 学习Linux的线程管理(基于Ubuntu)
  • Golang | Leetcode Golang题解之第423题从英文中重建数字
  • C++ | Leetcode C++题解之第423题从英文中重建数字

最新文章

  • 加密与安全_三种常见的注入攻击
  • 加密与安全_HTTPS TLS 1.2 连接(RSA 握手)的整个过程解读
  • 加密与安全_优雅存储二要素(AES-256-GCM )
2024
09月 25篇
08月 17篇
07月 10篇
06月 10篇
05月 24篇
04月 3篇
03月 19篇
02月 32篇
01月 33篇
2023年303篇
2022年151篇
2021年286篇
2020年230篇
2019年166篇
2018年159篇
2017年202篇
2016年219篇
2015年31篇

目录

目录

分类专栏

目录

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值

玻璃钢生产厂家深圳玻璃钢透光雕塑批发郑州景观玻璃钢彩绘雕塑定做厂家大型商场创意商业美陈价格阜阳玻璃钢雕塑厂家品牌安吉玻璃钢花盆花器大型玻璃钢雕塑哪家好服务广西玻璃钢鹿雕塑大连公园主题玻璃钢卡通雕塑费用供销玻璃钢花盆玻璃钢仿铜雕塑定制商场美陈气球装饰拍照三明玻璃钢海豚雕塑价格上海步行街玻璃钢雕塑多少钱神农架玻璃钢雕塑制作厂家黔南玻璃钢座椅雕塑造价安阳室内玻璃钢仿铜雕塑厂家园林景观雕塑弯桥玻璃钢雕塑小区玻璃钢卡通雕塑设计滁州定制玻璃钢雕塑价位附近玻璃钢雕塑制作厂家广安公园玻璃钢雕塑摆件国内设计腾冲市玻璃钢雕塑费用多少石碣玻璃钢卡通雕塑上海玻璃钢卡通牛雕塑批发龙岩玻璃钢海豚雕塑陕西玻璃钢花盆供应商百货商场三八节美陈曲阳玻璃钢人物雕塑贵州佛像玻璃钢雕塑供应商福建商场创意商业美陈风格香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化