Android中WebView頁面交互

代碼

在android內打開一個網頁的時候,有時咱們會要求與網頁有一些交互。而這些交互是在基於javaScript的基礎上。那麼咱們來學習一下android如何與網頁進行JS交互。完整代碼以下:javascript

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentActivity;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.JavascriptInterface;
import android.webkit.URLUtil;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

/**
 * 軟件內通用打開網頁的容器頁面
 *
 * @author ZRP
 */
public class CommonWebActivity extends FragmentActivity {

    private ProgressBar progress_bar;
    protected CustomFrameLayout customFrameLayout;
    protected TextView titleText, errorTxt;
    protected View refresh;// 刷新按鈕
    protected WebView webView;

    protected String url = "";// 網址url
    protected String param = "";// 交互參數,如json字符串

    protected WebChromeClient chromeClient = new WebChromeClient() {
        public void onProgressChanged(WebView view, int newProgress) {
            if (newProgress == 100) {
                progress_bar.setVisibility(View.GONE);
                // 判斷有無網絡
                if (!NetUtils.isAvailable(CommonWebActivity.this)) {
                    customFrameLayout.show(R.id.common_net_error);
                    refresh.setVisibility(View.VISIBLE);
                    refresh.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            webView.loadUrl(url);
                        }
                    });
                } else {
                    // 判斷網絡請求網址是否有效
                    if (!URLUtil.isValidUrl(url)) {
                        customFrameLayout.show(R.id.common_net_error);
                        errorTxt.setText("無效網址");
                    } else {
                        customFrameLayout.show(R.id.common_web);
                    }
                }
            } else {
                progress_bar.setVisibility(View.VISIBLE);
                progress_bar.setProgress(newProgress);
            }
        }

        // 獲取到url打開頁面的標題
        public void onReceivedTitle(WebView view, String title) {
            titleText.setText(title);
        }

        // js交互提示
        public boolean onJsAlert(WebView view, String url, String message, android.webkit.JsResult result) {
            return super.onJsAlert(view, url, message, result);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        url = getIntent().getStringExtra("url");
        param = getIntent().getStringExtra("param");
        setContentView(R.layout.common_web_activity);
        initView();
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void initView() {
        findViewById(R.id.back).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        titleText = (TextView) findViewById(R.id.title);
        progress_bar = (ProgressBar) findViewById(R.id.progress_bar);
        customFrameLayout = (CustomFrameLayout) findViewById(R.id.web_fram);
        customFrameLayout.setList(new int[]{R.id.common_web, R.id.common_net_error});
        refresh = findViewById(R.id.error_btn);
        errorTxt = (TextView) findViewById(R.id.error_txt);

        webView = (WebView) findViewById(R.id.common_web);
        webView.getSettings().setDefaultTextEncodingName("utf-8");
        webView.getSettings().setJavaScriptEnabled(true);
        synCookies();//格式化寫入cookie,需寫在setJavaScriptEnabled以後
        webView.setWebChromeClient(chromeClient);
        webView.setWebViewClient(new WebViewClient() {// 讓webView內的連接在當前頁打開,不調用系統瀏覽器
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });
        webView.addJavascriptInterface(new JavaScriptInterface(), "zrp");
        webView.loadUrl(url);

        new Handler().postDelayed(new Runnable() {//異步傳本地數據給網頁
            @Override
            public void run() {
                onNotifyListener(param);
            }
        }, 1000);
    }

    /**
     * CookieManager會將這個Cookie存入該應用程序/data/data/databases/目錄下的webviewCookiesChromium.db數據庫的cookies表中
     * 須要在當前用戶退出登陸的時候進行清除
     */
    private void synCookies() {
        String[] split = App.cookie.split(";");
        for (int i = 0; i < split.length; i++) {
            CookieSyncManager.createInstance(CommonWebActivity.this);
            CookieManager.getInstance().setCookie(url, split[i]);
            CookieSyncManager.getInstance().sync();
        }
    }

    /**
     * 回調網頁中的腳本接口。
     *
     * @param notify 傳給網頁的通知內容。
     */
    public void onNotifyListener(String notify) {
        if (webView != null) {
            webView.loadUrl("javascript:test('" + notify + "')");
        }
    }

    /**
     * android js交互實現:
     * - window.zrp.command("");//在網頁的方法中添加該代碼獲取android內容
     * - webView.loadUrl("javascript:test('param')");//android給網頁傳值,須異步執行
     */
    public class JavaScriptInterface {
        
        @JavascriptInterface
        public void command(String jsonString) {
            if (TextUtils.isEmpty(jsonString)) {
                return;
            }
            //根據網頁交互回傳的json串進行操做
            Toast.makeText(CommonWebActivity.this, jsonString, Toast.LENGTH_LONG).show();
        }
    }
}

切記:須要設置聯網權限與網絡狀態獲取權限!html

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

其中CustomFrameLayout爲界面切換控件,分別在無網絡,網址錯誤等狀況下進行提示:java

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;

/**
 * 用於狀態切換的佈局,只顯示1個狀態
 */
public class CustomFrameLayout extends FrameLayout {
    private int[] list;

    public CustomFrameLayout(Context context) {
        super(context);
        initView();
    }

    public CustomFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public CustomFrameLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    /**
     * 設置子面板id數組
     *
     * @param list
     */
    public void setList(int[] list) {
        this.list = list;
        show(0);
    }

    /**
     * 顯示某個面板
     *
     * @param id
     */
    public void show(int id) {
        if (list == null) {
            for (int i = 0; i < getChildCount(); ++i) {
                View view = getChildAt(i);

                if (id == view.getId()) {
                    view.setVisibility(View.VISIBLE);
                } else {
                    view.setVisibility(View.GONE);
                }
            }
            return;
        }

        for (int aList : list) {
            View item = findViewById(aList);
            if (item == null) {
                continue;
            }
            if (aList == id) {
                item.setVisibility(View.VISIBLE);
            } else {
                item.setVisibility(View.GONE);
            }
        }
    }

    /**
     * 隱藏全部面板
     */
    public void GoneAll() {
        if (list == null) {
            for (int i = 0; i < getChildCount(); ++i) {
                View view = getChildAt(i);
                view.setVisibility(View.GONE);
            }
            return;
        }

        for (int aList : list) {
            View item = findViewById(aList);
            if (item == null) {
                continue;
            }
            item.setVisibility(View.GONE);
        }
    }

    /**
     * 切換。
     *
     * @param index 佈局在fram中的index
     */
    public void showOfIndex(int index) {
        for (int i = 0; i < getChildCount(); ++i) {
            View view = getChildAt(i);

            if (index == i) {
                view.setVisibility(View.VISIBLE);
            } else {
                view.setVisibility(View.GONE);
            }
        }
    }

    public void initView() {
    }
}

界面佈局爲:android

<?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"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/back"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:gravity="center"
        android:orientation="horizontal"
        android:paddingLeft="10dp">

        <ImageView
            android:layout_width="15dp"
            android:layout_height="15dp"
            android:src="@drawable/ic_back" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="返回"
            android:textSize="15sp" />
    </LinearLayout>

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:text="加載中"
        android:textSize="15sp" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_below="@+id/title"
        android:background="#666666" />

    <ProgressBar
        android:id="@+id/progress_bar"
        style="@style/update_progress"
        android:layout_width="match_parent"
        android:layout_height="3dp"
        android:layout_marginTop="51dp"
        android:max="100" />

    <com.zrp.webviewdemo.web.CustomFrameLayout
        android:id="@+id/web_fram"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="51dp">

        <WebView
            android:id="@+id/common_web"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <include layout="@layout/common_net_error" />

    </com.zrp.webviewdemo.web.CustomFrameLayout>

</RelativeLayout>

混淆打包

JavaScript是經過調用android中指定的方法名來進行值傳遞的,因此android部分的代碼不能進行混淆,須要添加keep語句:-keep class com.zrp.webviewdemo.web.CommonWebActivity$JavaScriptInterface {*;}git

測試分析

好了,這時候咱們來添加一些數據進行測試:github

用上面的方法爲web頁面中的param賦值,在asset文件夾中放入要進行測試的html文件,url賦值爲:url = "file:///android_asset/html.html";
如上html.html文件源碼爲:web

<!DOCTYPE html>
<html>
    <head>
        <title>測試容器調用</title>
    </head>
    <body>
        <div class="main">
            <button onclick="test2()">獲取網頁的值</button>
        </div>

        <script type="text/javascript">

           function test(json){
               alert(json);
           }

            function test2(){
                alert("網頁傳值給android");
                window.zrp.command("{'type':1,'text':'hello boy'}");
            }
        </script>
    </body>
</html>
android傳值給web:可經過在java代碼中異步執行 webView.loadUrl("javascript:test("這是須要傳給網頁的數據")");來調用網頁代碼中的test()方法傳值給web端。

web傳值給android:在網頁代碼中能夠看到button的點擊事件添加了window.zrp.command("{'type':1,'text':'hello boy'}");來回傳一個json串給java頁面,即js經過以上的方法調用了java的方法。chrome

若是webView展現頁面的時候須要給網頁傳遞cookie,即各類登陸信息,則須要在調用webView.loadUrl(url)以前一句調用以下方法:注意!多個cookie值必須屢次進行setCookie!數據庫

設置cookie方法一:json

/**
 * CookieManager會將這個Cookie存入該應用程序/data/data/databases/目錄下的webviewCookiesChromium.db數據庫的cookies表中
 * 須要在當前用戶退出登陸的時候進行清除
 */
private void synCookies() {
    CookieSyncManager.createInstance(this);
    CookieManager cookieManager = CookieManager.getInstance();
    cookieManager.setAcceptCookie(true);
    cookieManager.removeSessionCookie();

    String[] split = App.cookie.split(";");
    for (int i = 0; i < split.length; i++) {
        cookieManager.setCookie(url, split[i]);
    }
    CookieSyncManager.getInstance().sync();
}

在當前用戶退出登陸的時候清除掉保存的cookie信息。

/**
 * 清除當前用戶存儲到cookies表中的全部數據
 */
private void removeCookie() {
    CookieSyncManager.createInstance(this);
    CookieManager.getInstance().removeAllCookie();
    CookieSyncManager.getInstance().sync();
}

注意:在調用設置Cookie以後不能再設置以下的這類屬性,不然設置cookie無效。

webView.getSettings().setBuiltInZoomControls(true);  
webView.getSettings().setJavaScriptEnabled(true);

可是!在如上設置cookie以後,每次設置cookie的時候都有延時,要是點擊頻率較快的時候,會出現cookie沒有被設置進去的問題!

設置方法二:
通過一番查找,找到以下兩個解決辦法:

  1. http://blog.csdn.net/swust_chenpeng/article/details/37699841,這個是開了一個異步任務來進行等待,而後再次設置cookie,感受沒有從根源上解決這個問題啊。。。可是思路值得參考
  2. http://stackoverflow.com/questions/22637409/android-how-to-pass-cookie-to-load-url-with-webview,按照第二個回答進行測試,可行。可是我用一個2.3.6的低版本手機去測的時候報錯崩潰。java.lang.IllegalStateException: CookieSyncManager::createInstance() needs to be called before CookieSyncManager::getInstance(),按照提示加上以下代碼以後正常。
/**
 * CookieManager會將這個Cookie存入該應用程序/data/data/databases/目錄下的webviewCookiesChromium.db數據庫的cookies表中
 * 須要在當前用戶退出登陸的時候進行清除
 */
private void synCookies() {
    String[] split = App.cookie.split(";");
    for (int i = 0; i < split.length; i++) {
        CookieSyncManager.createInstance(CommonWebActivity.this);
        CookieManager.getInstance().setCookie(url, split[i]);
        CookieSyncManager.getInstance().sync();
    }
}

同時,在退出登陸的時候清除cookie。

CookieSyncManager.createInstance(this);
CookieManager.getInstance().removeAllCookie();
CookieSyncManager.getInstance().sync();

清除當前用戶存儲到cookies表中的全部數據。

附:從瀏覽器網頁打開應用

這部分網上也有不少博客與筆記。此處僅做記錄。
添加步驟以下:

  • AndroidMainfext.xml中你須要接收跳轉信息的Activity標籤中添加以下代碼,則該Activity能夠接收和處理網頁中相似於zrp://zrp_test.net/的連接:
<intent-filter>
    <action android:name="android.intent.action.VIEW" />

    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />

    <data
        android:host="zrp_test.net"
        android:scheme="zrp" />
</intent-filter>
  • 在你接收跳轉信息的activity中添加數據接收的代碼:
private void initScheme(Intent intent) {
    Log.d(TAG, "initScheme: ---DataString--->" + intent.getDataString());
    Uri uri = intent.getData();
    if (uri != null && "zrp_test.net".equals(uri.getHost())) {
        Log.d(TAG, "initScheme: ---data--->" + uri.getQueryParameter("data"));
    }
}
  • 添加一個測試網頁:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
    <title>test</title>
</head>

<body>
<p>從瀏覽器打開應用的測試網址。</p>
<script LANGUAGE='javascript'>
var str = {"uid":123456,"nickname":"sex lady","sex":"female"};
window.location = "zrp://zrp_test.net?data="+JSON.stringify(str);
</script>
</body>

</html>

該網頁文件我在雲存儲放了一份,你們能夠經過訪問這個網址來進行測試:http://sanchi.56ef923d5f32c.d01.nanoyun.com/blog_test/app_browser_test.html

  • 經過瀏覽器網頁打開應用以後,可查看控制檯的打印輸出:
D/MainActivity: initScheme: ---DataString--->zrp://zrp_test.net?data={"uid":123456,"nickname":"sex lady","sex":"female"}
D/MainActivity: initScheme: ---host--->zrp_test.net
D/MainActivity: initScheme: ---data--->{"uid":123456,"nickname":"sex lady","sex":"female"}

一切OK。

須要源碼的同窗看這裏

相關文章
相關標籤/搜索