WebView入門

webview是Android展現網頁信息的控件,本文就來簡要講解這個控件的用法。關於webview我找到了一些很不錯的文章,我就直接貼上鍊接,本身就不重複造輪子了。本文僅寫出我本人目前使用過的東西,將來將繼續補充。javascript

詳細的方法和高級技巧請參考下列博文:php

使用 WebView 處理javascript的經常使用對話框:http://blog.csdn.net/llbupt/article/details/7368844 (講解WebView與JS對話框的交互)css

webView常見問題和解決方法彙總:http://blog.csdn.net/t12x3456/article/details/13769731/html

Android中Java和JS交互:http://droidyue.com/blog/2014/09/20/interaction-between-java-and-javascript-in-android/java

WebView緩存策略:http://blog.csdn.net/moubenmao_jun/article/details/9076917linux

 

下列文章:powered  by miechal zhao. android

WebView開發詳解(一):http://blog.csdn.net/typename/article/details/39030091 (講解WebView的API)ios

WebView開發詳解(二):http://blog.csdn.net/typename/article/details/39495409 (講解WebViewClient & WebChromeClient)web

WebView開發詳解(三):http://blog.csdn.net/typename/article/details/40302351 (講解webSettings)ajax

系列博文地址:Android 瀏覽器內核研究與探祕

 

1、初識webview

1.1 放入本身的工程

使用控件仍舊是老樣子,在xml中寫入,在java中找到控件並進行處理。此外還要記得加訪問網絡的權限。

<uses-permission android:name="android.permission.INTERNET" /> 

xml

<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"
    tools:context="${relativePackage}.${activityClass}" >

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" />

</RelativeLayout>

Java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        WebView webView = (WebView) findViewById(R.id.webView);
        webView.loadUrl("http://www.baidu.com/");
}

 

這樣其實就能夠運行啦,可是你在運行的時候會發現程序啓動後會有下面的界面

也就是說這個控件當前沒有本身展現網頁的能力,還須要調用瀏覽器來實現。

 

2、WebView的方法

 

2.1 加載特定網頁

webView.loadUrl("http://www.baidu.com/");

2.2 加載本地的html文件

現將本地文件放入asset中,而後用下面的方法加載

webView.loadUrl("file:///android_asset/XX.html");  

2.3 加載html字符串

String htmlString = "<h1>Title</h1><p>This is HTML text<br /><i>Formatted in italics</i><br />Anothor Line</p>";
// 載入這個html頁面
webView.loadData(htmlString, "text/html", "utf-8");

2.4 結束加載

public void stopLoading() 

 2.5 獲取焦點

if (!view.hasFocus()) {
    view.requestFocus();
}

 2.6 添加js接口

addJavascriptInterface(object, name);

這裏的object是一個類對象,name是js中調用的方法名。

-- 構建類對象:

public class JsInteration {
      
      @JavascriptInterface
      public void toastMessage(String message) {
          Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
      }
      
      @JavascriptInterface
      public void onSumResult(int result) {
          Log.i(LOGTAG, "onSumResult result=" + result);
      }
  }

提供了兩個方法:toastMessage和onSumResult

-- 編寫js代碼,進行調用java的方法

   function toastMessage(message) {
        window.control.toastMessage(message)
    }

    function sumToJava(number1, number2){
       window.control.onSumResult(number1 + number2)
    }

經過window.control.xxx就能夠進行調用啦。具體請參考:http://www.cnblogs.com/tianzhijiexian/p/4244688.html

 

3、使用WebViewClient

webview能夠設置一個webviewclient對象,經過它就能夠來進行網頁加載的處理了。參考自:http://blog.csdn.net/typename/article/details/39495409

3.1 shouldOverrideUrlLoading

shouldOverrideUrlLoading並非每次都在onPageStarted以前開始調用的,就是說一個新的URL不是每次都通過shouldOverrideUrlLoading的,只有在調用webview.loadURL的時候纔會調用。而 設置loadUrl時候是用哪一個瀏覽器來加載。
     WebView webView = (WebView) findViewById(R.id.webView);
        webView.loadUrl("http://www.baidu.com/");
    webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
    }

如上面展現的,webview首先調用loadURL方法,而後webviewClient觸發了加載url的方法,而後將展現網頁的工做本身承擔了下來。重寫此方法返回true代表點擊網頁裏面的連接仍是在當前的webview裏跳轉,不跳到瀏覽器那邊。

若是你想要webview屏蔽某個網站的超連接,或者是要對某些特殊的url進行處理,那麼就須要在這裏進行修改啦。屏蔽鏈接的話,直接返回true便可,若是要進行url判斷,能夠以下操做

if (url.endsWith(".apk")) {
     download(url);//下載處理
}

3.2 onLoadResource

在加載頁面資源時會調用,每個資源(好比圖片)的加載都會調用一次。以下的load會在網頁加載元素時調用不少次。

        @Override
            public void onLoadResource(WebView view, String url) {
                // TODO 自動生成的方法存根
                super.onLoadResource(view, url);
                System.out.println("load");
            }    

3.3 onPageStarted & onPageFinished

網頁開始加載和結束加載時調用,通常的過程就是onpageStarted->onLoadResource->onPageFinished

        @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                // TODO 自動生成的方法存根
                super.onPageStarted(view, url, favicon);
                Log.i(TAG, "onPageStarted");
            }

            
            @Override
            public void onPageFinished(WebView view, String url) {
                // TODO 自動生成的方法存根
                super.onPageFinished(view, url);
                Log.i(TAG, "onPageFinished");
            }

 

更多方法能夠參考這篇博文:http://blog.csdn.net/typename/article/details/39495409

 

4、處理前進和後退操做

咱們應該對webview進行前進和後退的處理,須要在activity的按鍵時間中處理。這樣咱們就能經過手機的返回鍵進行網頁的返回了。

  public boolean onKeyDown(int keyCoder, KeyEvent event) {
        /**
         * canGoBack()檢查是否有能夠後退的記錄
         * anGoForward()方法能夠檢查是否有能夠前進的歷史記錄。
                若是你不執行這種檢查,一旦 goBack() 和 goForward()方法到達歷史記錄頂端,它們將什麼也不作。
         */
        if (webView.canGoBack() && keyCoder == KeyEvent.KEYCODE_BACK) {
            //webView.goForward(); // 前進
            webView.goBack(); // goBack()表示返回webView的上一頁面,後退
            return true;
        }
        return false;
    }

 

5、WebChromeClient

5.1 onReceivedTitle 獲取標題

當document 的title變化時,會通知應用程序。這個函數調用時機取決於網頁把title設置在什麼位置,大多數網頁通常把title設置到頁面的前面,所以不少狀況會比較早回調到這個函數。

            /* 
             * 設置應用程序的標題title
             * 
             * @param view
             * @param title
             */
            @Override
            public void onReceivedTitle(WebView view, String title) {
                MainActivity.this.setTitle(title);
                super.onReceivedTitle(view, title);
            }

5.2 onProgressChanged 獲取進度

            /* 
             * 設置網頁的加載的進度條
             * 
             * @param view
             * @param newProgress 最新的進度條數值
             */
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                // TODO 自動生成的方法存根
                super.onProgressChanged(view, newProgress);
                MainActivity.this.getWindow().setFeatureInt(Window.FEATURE_PROGRESS, newProgress);
                Log.i(TAG, "progress = " + newProgress);
            }

5.3 onJsAlert 處理JS警告框

下面的代碼用應用的對話框來接管JS對話框

         /*
             * 處理javascript中的alert對話框
             * 
             * 注意:這裏的result是JsResult
             */
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                // TODO 自動生成的方法存根
                // 構架一個builder來顯示網頁中的對話框
                Builder builder = new Builder(MainActivity.this);
                builder.setTitle("提示對話框");
                builder.setMessage(message);
                builder.setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 點擊肯定按鈕以後,繼續執行網頁中的操做
                        result.confirm();
                    }
                });
                builder.setCancelable(false);
                builder.create();
                builder.show();
                return true;
            }

 5.4 shouldInterceptRequest

參考自:http://droidyue.com/blog/2014/11/23/block-web-resource-in-webview/

從API 11(Android 3.0)開始, shouldInterceptRequest被引入就是爲了解決這一類的問題。

shouldInterceptRequest這個回調能夠通知主程序WebView處理的資源(css,js,image等)請求,並容許主程序進行處理後返回數據。若是主程序返回的數據爲null,WebView會自行請求網絡加載資源,不然使用主程序提供的數據。注意這個回調發生在非UI線程中,因此進行UI系統相關的操做是不能夠的。

shouldInterceptRequest有兩種重載。

  • public WebResourceResponse shouldInterceptRequest (WebView view, String url) 從API 11開始引入,API 21棄用
  • public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) 從API 21開始引入

本次例子暫時使用第一種,即shouldInterceptRequest (WebView view, String url)。

webView.loadUrl("http://m.sogou.com");
webView.setWebViewClient(new WebViewClient() {

            private String TAG = getClass().getSimpleName();

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {

                view.loadUrl(url);
                return true;
            }

            @Override
            public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
                Log.i(TAG, "shouldInterceptRequest url=" + url);
                WebResourceResponse response = null;
                if (url.contains("logo")) {
                    try {
                        InputStream localCopy = getAssets().open("droidyue.png");
                        response = new WebResourceResponse("image/png", "UTF-8", localCopy);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
                return response;
            }

        });

咱們來分析下shouldInterceptRequest的代碼:

參數是webview和url,咱們必須返回一個WebResourceResponse對象,表示本機處理的結果,若是不爲null,那麼webview就展現WebResourceResponse對象,若是爲null,那麼webview就會自行請求資源。

其中,WebResourceResponse須要設定三個屬性,MIME類型,數據編碼,數據(InputStream流形式)。

這裏首先放一個圖片到項目根目錄中的assets中

而後,判斷這個資源的url中是否包含logo這個字符,若是是就進行替換操做。

接着進行替換操做,從assets中獲得一個輸入流,經過WebResourceResponse的構造函數,將這個輸入流轉爲WebResourceResponse對象,最後返回。

          WebResourceResponse response = null;
                if (url.contains("logo")) {
                    try {
                        InputStream localCopy = getAssets().open("droidyue.png");
                        response = new WebResourceResponse("image/png", "UTF-8", localCopy);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
                return response;

效果:

左圖是原始的效果,右圖是咱們進行修改後的效果,替換的logo圖片。

      

 

6、自動填寫表單

自動填寫表單的功能是用JS實現的,參考了這篇博文:http://blog.csdn.net/y85171642/article/details/12558137

對於特定網頁填寫表單能夠用這樣的邏輯

     if (url.contains("telecom/mobile/jsp/otherlogin.jsp"))
        {
                //自動填信息
                //提交表單
        }

主要代碼:

       webView.loadUrl("http://usereg.tsinghua.edu.cn/login.php");
            @Override
            public void onPageFinished(WebView view, String url) {
                // TODO 自動生成的方法存根
                super.onPageFinished(view, url);
                Log.i(TAG, "onPageFinished");
                
                view.loadUrl("javascript:document.getElementsByName('user_login_name')[0].value='"+ "testName" + "'");
                view.loadUrl("javascript:document.getElementsByName('user_password')[0].value='"+ "testPsw" + "'");
            }

這個代碼是自動填寫表單的代碼所針對的網頁是清華大學校園網,在加載完畢那個網頁後就會自動填寫表單,若是你想要模擬提交能夠用一個button或者是直接在上面的代碼的最後寫一個js腳本,我這裏用button來實現模擬點擊按鈕。

        Button btn = (Button)findViewById(R.id.button);
        btn.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                webView.loadUrl("javascript:do_login()");
            }
        });    

清華網頁源碼以下:

http://usereg.tsinghua.edu.cn/login.php

 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2 <html xmlns="http://www.w3.org/1999/xhtml">
 3 <head>
 4 <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
 5 <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;">
 6 <title>清華大學校園網&nbsp;Tsinghua&nbsp;University&nbsp;Network</title>
 7 
 8 <link rel="stylesheet" type="text/css" href="css/styles_wired.css" />
 9 
10 <link rel="shortcut icon"
11 href="http://net.tsinghua.edu.cn/image/icon16-purple.ico" type="image/x-icon" />
12 <link rel="icon" href="http://net.tsinghua.edu.cn/image/icon32.png"  sizes="32x32" type="image/png" />
13 <script type="text/javascript" src="/script/ajax.js"></script>
14 <script type="text/javascript" src="/script/md5.js"></script>
15 <script type="text/javascript">
16     window.onload=function(){
17                document.getElementsByName("user_login_name")[0].focus();
18        }
19 function do_login()
20 {
21     if(document.form1.user_login_name.value == "")
22     {
23         alert("請填寫用戶名");
24         document.form1.user_login_name.focus();
25         return;
26     }
27     if(document.form1.user_password.value == "")
28     {
29         alert("請填寫密碼");
30         document.form1.user_password.focus();
31         return;
32     }
33     var user_login_name = document.form1.user_login_name.value;
34     var user_password = hex_md5(document.form1.user_password.value);
35     var d = "action=login&user_login_name="+user_login_name+"&user_password="+user_password;
36     var res = postData("/do.php", "post", d);
37     if(res != "ok")
38     {
39         alert(res);
40         return;
41     }
42     location = "main.php";
43 }
44 
45 function hidden_account()
46 {
47     document.getElementById("account").style.display="none";
48 }
49 //-->
50 </script>
51 </head>
52 
53 <body id="account_login">
54     <div id="center">
55         <div id="head">
56             <a id="title" href="index.html" title="清華大學校園網&#10;Tsinghua&nbsp;University&nbsp;Network"></a>
57         </div>
58         <div id="content">
59             <div class="greet">自服務&nbsp;Account&nbsp;Settings</div>
60             <div class="triangle_greet"></div>
61             <div id="login">
62                 <form id="login_form" name="form1" action="#" method="post" onsubmit="do_login();return false;">
63                     <label>
64                         <div class="label_text">用戶名<p class="english">User&nbsp;ID</p></div>
65                         <input type="text" name="user_login_name" id="userID" value="" autocorrect="off" autocapitalize="off" onfocus="hidden_account()">
66                         <div class="instruction" id="account">校園網帳戶<p class="english">Account&nbsp;of&nbsp;Tsinghua&nbsp;University&nbsp;Network</div>
67                     </label>
68                     <label>
69                     <div class="label_text">密碼<p class="english">Password</p></div>
70                     <input type="password" name="user_password" id="password" autocorrect="off" autocapitalize="off">
71                     </label>               
72                       <input type="submit" name="login_button" id="login_button" value="登陸&nbsp;Login">
73                    </form>
74                    <br></br>
75             <a href="./20120528.htm" id="note">無線網切換通知<span class="link_english">User&nbsp;&nbsp;Notification</span></a>
76             </div>
77             <div class="triangle_login"></div>
78             <div id="download">
79                 <ul id="system">
80                     <li><a class="windows" href="files/Tunet2013.rar" title="Windows">Windows</a></li>
81                     <li><a class="mac" href="files/TUNet_mac_os_x86.zip" title="MacOS">MacOS</a></li>
82                     <li><a class="linux" href="files/TUNet_linux.tar.gz" title="Linux">Linux</a></li>
83                     <li><a class="android" href="files/TUNet.apk" title="Android">Android</a></li>
84                     <li><a class="ios" href="files/TUNet_IOS.zip" title="iOS">iOS</a></li>
85                 </ul>
86             </div>
87             <ul id="widget">
88                 <li class="bookmark"><a href="#" title="收藏&#10;Bookmark&nbsp;This&nbsp;Page">收藏<p class="english">Bookmark&nbsp;This&nbsp;Page</p></a></li>
89                 <li class="help"><a href="help.htm" title="幫助&#10;Help">幫助<p class="english">Help</p></a></li>
90                 <li class="contact">
91                 <div class="title">聯繫<p class="english">Contact</p></div>
92                 <div class="detail english">+86-10-62784859<a href="mailto:support@tsinghua.edu.cn" title="電子郵件&#10;Email">support@tsinghua.edu.cn</a></div></li>
93             </ul>
94         </div>
95         <div id="foot">清華大學信息網絡工程研究中心<p class="english">Network Research Center of Tsinghua University</p></div>
96         
97     </div>
98 </body>
99 </html>
View Code

 

所有代碼:

package com.kale.webviewtest;

import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;


public class MainActivity extends ActionBarActivity {
    WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button btn = (Button)findViewById(R.id.button);
        btn.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                //webView.loadUrl("javascript:do_login()");
                webView.loadUrl("javascript:do_login()");
            }
        });
        webView = (WebView) findViewById(R.id.webView);
        //webView.loadUrl("http://210.29.65.198/");
        webView.loadUrl("http://usereg.tsinghua.edu.cn/login.php");

        
        
        // 調用WebSettings設置的全部函數是異步制定的,所以咱們設置某個狀態並不能立刻生效。
        WebSettings webSettings = webView.getSettings();
        webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); // 使用默認的緩存策略,cache沒有過時就用cache
        // webSettings.setBlockNetworkImage(true); // 不加載網頁圖片資源
        webSettings.setJavaScriptEnabled(true); // 設置支持javascript腳本
        webSettings.setSupportZoom(true); // 支持縮放
        webSettings.setBuiltInZoomControls(true); // 顯示縮放按鈕
        

        webView.setWebViewClient(new WebViewClient() {

            private String TAG = getClass().getSimpleName();

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {

                view.loadUrl(url);
                return true;
            }

            @Override
            public void onLoadResource(WebView view, String url) {
                // TODO 自動生成的方法存根
                super.onLoadResource(view, url);
                Log.i(TAG, "onLoadResource");
            }

            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                // TODO 自動生成的方法存根
                super.onPageStarted(view, url, favicon);
                Log.i(TAG, "onPageStarted");
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                // TODO 自動生成的方法存根
                super.onPageFinished(view, url);
                Log.i(TAG, "onPageFinished");
//                view.loadUrl("javascript:document.getElementsByName('uname')[0].value='"+ "testName" + "'");
//                view.loadUrl("javascript:document.getElementsByName('pass')[0].value='"+ "testPsw" + "'");
                
                
                view.loadUrl("javascript:document.getElementsByName('user_login_name')[0].value='"+ "testName" + "'");
                view.loadUrl("javascript:document.getElementsByName('user_password')[0].value='"+ "testPsw" + "'");
            }

        });
        

        webView.setWebChromeClient(new WebChromeClient() {
            private String TAG = getClass().getSimpleName();

            /* 
             * 設置應用程序的標題title
             * 
             * @param view
             * @param title
             */
            @Override
            public void onReceivedTitle(WebView view, String title) {
                MainActivity.this.setTitle(title);
                super.onReceivedTitle(view, title);
            }


            /* 
             * 設置網頁的加載的進度條
             * 
             * @param view
             * @param newProgress 最新的進度條數值
             */
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                // TODO 自動生成的方法存根
                super.onProgressChanged(view, newProgress);
                MainActivity.this.getWindow().setFeatureInt(Window.FEATURE_PROGRESS, newProgress);
                Log.i(TAG, "progress = " + newProgress);
            }

            /*
             * 處理javascript中的alert對話框
             * 
             * 注意:這裏的result是JsResult
             */
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                // TODO 自動生成的方法存根
                // 構架一個builder來顯示網頁中的對話框
                Builder builder = new Builder(MainActivity.this);
                builder.setTitle("提示對話框");
                builder.setMessage(message);
                builder.setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 點擊肯定按鈕以後,繼續執行網頁中的操做
                        result.confirm();
                    }
                });
                builder.setCancelable(false);
                builder.create();
                builder.show();
                return true;
            }

        });

    }

    public boolean onKeyDown(int keyCoder, KeyEvent event) {
        /**
         * canGoBack()檢查是否有能夠後退的記錄 anGoForward()方法能夠檢查是否有能夠前進的歷史記錄。
         * 若是你不執行這種檢查,一旦 goBack() 和 goForward()方法到達歷史記錄頂端,它們將什麼也不作。
         */
        if (webView.canGoBack() && keyCoder == KeyEvent.KEYCODE_BACK) {
            // webView.goForward(); // 前進
            webView.goBack(); // goBack()表示返回webView的上一頁面,後退
            return true;
        }
        return false;
    }
}

 

6、WebSettings

6.1 初始化

webSetting和webView生命週期一致,webView無效後webSetting也無效

WebSettings webSettings = webView.getSettings();

6.2 setCacheMode 緩存模式

詳細的緩存介紹請參考:http://blog.csdn.net/t12x3456/article/details/13745553

webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); // 使用默認的緩存策略,cache沒有過時就用cache

6.3 setBlockNetworkImage 是否屏蔽網絡圖片

爲了提升性能咱們能夠選擇不加載網絡圖片

webSettings.setBlockNetworkImage(true); // 不加載網頁圖片資源

6.4 支持JS

webSettings.setJavaScriptEnabled(true); // 設置支持javascript腳本

6.5 讓webView支持縮放

只有顯示放大縮小按鈕後才能支持雙指縮放,因此請附帶這條語句:webSettings.setBuiltInZoomControls(true); // 顯示縮放按鈕

webSettings.setSupportZoom(true); // 支持縮放

 6.6 webView保存數據

webView.setSaveEnabled(true);//保存數據

6.7 放大或縮小網頁

webview.setInitialScale(300);//放大300顯示網頁

6.8 是否顯示縮放按鈕

就是那個放大鏡和縮小鏡

webSettings.setBuiltInZoomControls(false); // 不顯示縮放按鈕

 

參考自:http://www.cnblogs.com/rayray/archive/2013/08/14/3258339.html

相關文章
相關標籤/搜索