目录

Android中WebView页面交互

代码

在android内打开一个网页的时候,有时我们会要求与网页有一些交互。而这些交互是在基于javaScript的基础上。

那么我们来学习一下android如何与网页进行JS交互。完整代码如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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();
        }
    }
}

切记:需要设置联网权限与网络状态获取权限!

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

其中CustomFrameLayout为界面切换控件,分别在无网络,网址错误等情况下进行提示:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
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() {
    }
}

界面布局为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?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 {*;}

测试分析

好了,这时候我们来添加一些数据进行测试:

用上面的方法为web页面中的param赋值,在asset文件夹中放入要进行测试的html文件,url赋值为:url = "file:///android_asset/html.html"; 如上html.html文件源码为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!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的方法。

如果webView展示页面的时候需要给网页传递cookie,即各种登录信息,则需要在调用webView.loadUrl(url)之前一句调用如下方法:注意!多个cookie值必须多次进行setCookie!

设置cookie方法一

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/**
 * 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信息。

1
2
3
4
5
6
7
8
/**
 * 清除当前用户存储到cookies表中的所有数据
 */
private void removeCookie() {
    CookieSyncManager.createInstance(this);
    CookieManager.getInstance().removeAllCookie();
    CookieSyncManager.getInstance().sync();
}

注意:在调用设置Cookie之后不能再设置如下的这类属性,否则设置cookie无效。

1
2
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(),按照提示加上如下代码之后正常。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/**
 * 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。

1
2
3
CookieSyncManager.createInstance(this);
CookieManager.getInstance().removeAllCookie();
CookieSyncManager.getInstance().sync();

清除当前用户存储到cookies表中的所有数据。

附:从浏览器网页打开应用

这部分网上也有很多博客与笔记。此处仅作记录。

添加步骤如下:

  • AndroidMainfext.xml中你需要接收跳转信息的Activity标签中添加如下代码,则该Activity可以接收和处理网页中类似于zrp://zrp_test.net/的链接:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<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中添加数据接收的代码:
1
2
3
4
5
6
7
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"));
    }
}
  • 添加一个测试网页:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<!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

  • 通过浏览器网页打开应用之后,可查看控制台的打印输出:
1
2
3
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。

/images/doge.gif

需要源码的同学看这里