【android学习】WebView(网络视图)

1,概念

1)Native app(原生app)

i>场景

适用企业:游戏、电子杂志、管理应用、物联网等无需经常更新程序框架的APP应用。
B2C开发模式的app。终端用户更期望简单而优良的用户体验。因此,你应该更加注重界面、性能、响应性、易操作性等。如果你的应用能够很方便地调用原生平台特有的一些功能的话,那对用户来说当然是极好的。

2)web app(框架APP)

i>了解

使用浏览器运行;纯Web前端架构,很多重要手机特性无法访问,例如联系人以及Push notification之类的;Single Page App;销售渠道多限于浏览器。

3)hybrid app(混合app)

i>了解

主要是前端js和android端的通信。混合app即将网页嵌入在android界面中,界面主要采用HTML,CSS,JS。
和浏览器使用这些网页感受一样,不同的是,在混合app中,网页可以方便的使用设备中的各种传感器,而单纯的web app是无法访问系统调用的。

ii>场景

经典案例:facebook,app store,微信。
适用企业:电子商务、金融、新闻资讯、企业集团需经常更新内容的APP应用。
B2B开发模式的app:满足自己的业务需求,相对于用户体验来说,更加注重业务过程,并且他们也有能力来培训自己的用户来使用APP。

iii>特点

①安装包小巧,只包含框架文件,而大量的UI元素、数据内容刚存放在云端;
②跨平台特性。不同的平台上面展示同一个交互层。
虽然分担了原生app界面开发的压力,但同时,也造成一些问题。比如说要仿造一个iOS的默认设置界面,就需要大量的html以及css代码了,而且效果不一定和iPhone上面的界面一样好。
而对于前端的兼容性来说,依然不甚完美。
③开发简单,但却有所有原生应用的特性。
④便于调试,开发的时候可以通过浏览器的方式进行调试,工具丰富。
但是,当涉及到一些需求,需要进入手机底层做处理,单纯的web调试无法实现。
⑤因为是混合开发,交互层的效率由Native解决,架构基本由在app内写网页解决。
⑥运行效率低,渲染界面慢。需要好好权衡。比如facebook因为web渲染效率底下,把整个应用改回了原生app。

iv>实现方案

方案一:以webView作为用户界面层,以javaScript作为基本逻辑。这种架构一般会非常依赖WebView层的性能。
方案二:在开发原生应用的基础上,嵌入webview,整体的架构使用原生应用提供。需要Native开发人员以及web前端开发人员组成。Native开发人员写好基本的架构以及API让web开发人员开发界面以及大部分的渲染。

4)B2B( BTB,Business-to-Business)

指企业与企业之间(进行电子商务交易的供需双方都是商家)通过专用网络或Internet,进行数据信息的交换、传递,开展交易活动的商业模式。它将企业内部网和企业的产品及服务,通过 B2B 网站或移动客户端与客户紧密结合起来,通过网络的快速反应,为客户提供更好的服务,从而促进企业的业务发展。

5)B2C(商对客,Business-to-Customer)

”商对客”是电子商务的一种模式,也就是通常说的直接面向消费者销售产品和服务商业零售模式。这种形式的电子商务一般以网络零售业为主,主要借助于互联网开展在线销售活动。B2C即企业通过互联网为消费者提供一个新型的购物环境——网上商店,消费者通过网络在网上购物、网上支付等消费行为。

6)WebView(网络视图)

能加载显示网页,可以将其视为一个浏览器。它使用了WebKit渲染引擎加载显示网页。

2,方法

1)网页回退:goBack();

if (webView_guarantee.canGoBack()) {  
    webView_guarantee.goBack();  
}  

注意:
a.因为重定向而无法goBack
解决方案:
http://www.baidu.com网址改为https://www.baidu.com.
原因:
web服务器如:apache等,配置好https后,需要设置url重定向规则,使网站页面的http访问都自动转到https访问。所以造成网页的webView.canGoBack()返回永远为true。
http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。http的连接很简单,是无状态的。HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

2)网页重载:reload()

if (null != webView_guarantee) {  
    showMessage("正在刷新,请稍后");  
    webView.reload();  
}  

3)网页/JS加载

①loadUrl(url)

直接网页显示 :

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

直接网络图片显示 :

WebView  
.loadUrl("http://www.gstatic.com/codesite/ph/images/code_small.png");  

显示本地图片文件 :

// 本地文件处理(如果文件名中有空格需要用+来替代)  
WebView.loadUrl("file:///android_asset/icon.png");  

显示本地网页文件 :

// 本地文件处理(如果文件名中有空格需要用+来替代)  
WebView.loadUrl("file:///android_asset/test.html");  

注意:
webview.loadurl 的加载是在另一个线程中执行必须要在webview加载完毕执行。
比如:

webview.loadUrl("file:///android_asset/test.html");    
webview.loadUrl("javascript:alert(test)");   

第二句可能看不到或后发先至,因为loadUrl是异步执行的。
此时,避免出现这种问题的方式是:将第二句放在handle中,当第一句执行成功再执行第二句。

②中文显示:loadData()

如果是加载的html文本很简单,对排版的要求不高,那么使用loadData比较好,毕竟只是很省流量的方式,而且还可以将数据保存在本地,做离线缓存。
如果排版比较复杂,还是用loadUrl吧,虽然loadUrl需要消耗更多的流量,也不好做离线缓存,但是loadUrl充分发挥了webview的优势。
loadData不能加载图片内容,如果要加载图片内容或者获得更强大的Web支持请使用loadDataWithBaseURL。

String data = " 测试含有空格的Html 数据";  
// 不对空格做处理  
WebView.loadData(URLEncoder.encode(data, "utf-8"), "text/html", "utf-8" );
// 对空格做处理(在SDK1.5 版本中)  
WebView.loadData(URLEncoder.encode(data, encoding).replaceAll(  
"\+", " "), "text/html", "utf-8");  

③loadDataWithBaseURL()

显示本地图片和文字混合的Html 内容 :

String data = "测试本地图片和文字混合显示,这是APK 里的图片";  
WebView.loadDataWithBaseURL("about:blank", data, "text/html", "utf-8" "");  

4)获取webview配置管理器:getSettings()

①支持javascript:

webView.getSettings().setJavaScriptEnabled(true);// 支持javascript

②控制JS跨域访问能力

setAllowFileAccess(true);//webview是否允许JS访问本地文件系统,默认允许
setAllowFileAccessFromFileURLs(true);//WebView是否运行运行在本地文件中的JS访问其它的本地文件。Android4.1之前默认允许,之后默认为禁止的。
setAllowUniversalAccessFromFileURLs(true);//WebView是否运行运行在本地文件中的JS访问其它的本地或远程文件。Android4.1之前默认运行,之后默认为禁止的。

为了避免文件跨域脚本漏洞,在不需要的情况下,将以上3个设置全部设置为false。

③其它

WebSettings ws = webView_guarantee.getSettings();  
        /**支持javascript*/  
        ws.setJavaScriptEnabled(true);  
        /** 
         * 允许访问文件  
         * 若html是一个文件框的话,就可以浏览本地文件 
         */  
        ws.setAllowFileAccess(true);  
        /**设置显示缩放按钮*/  
        ws.setBuiltInZoomControls(false);   
        /**支持缩放*/  
        ws.setSupportZoom(false);  
        /** 
         * 开启DOM Storage 
         * 应该是Html 5中的localStorage 
         * (可以使用Android4.4手机和Chrome Inspcet Device联调), 
         * 用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的, 
         * 绝大多数的浏览器都是支持 localStorage 的, 
         * 但是鉴于它的安全特性(任何人都能读取到它,尽管有相应的限制,将敏感数据存储在这里依然不是明智之举), 
         * Android 默认是关闭该功能的。 
         */  
        ws.setDomStorageEnabled(true);  
        /**开启缓存*/  
        ws.setAppCacheEnabled(true);  
        /**设置WebView是否使用其内置的变焦机制, 
         * 该机制结合屏幕缩放控件使用,默认是false, 
         * 不使用内置变焦机制。 
         */  
        ws.setAllowContentAccess(true);  
        /** 
         * 标示可以通过javaScript访问file文件 
         */  
        ws.setAllowFileAccessFromFileURLs(true);  
        /** 
         *  在使用webview显示网页或者图片、flash的时候,总会出现没有充满webview的现象,左右滑动,就可以使之充满全屏。 
         */  
        ws.setLoadWithOverviewMode(true);  
        /** 
         * 设定支持viewport 
         */  
        ws.setUseWideViewPort(true);  

5)setWebChromeClient()

主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等。

webView.setWebChromeClient(new WebChromeClient() {  
    /** 
     * 获取网页title  
     */  
    @Override  
    public void onReceivedTitle(WebView view, String title) {  
        super.onReceivedTitle(view, title);  
        mTitleBarView.setTitleText(title);  
    }  
    /** 
     * 回调函数:当加载的数据有所改变的时候,就会通知给你。 
     */  
    @Override  
    public void onProgressChanged(WebView view, int newProgress) {  

        super.onProgressChanged(view, newProgress);  
        setTitleBarChange(view);  
    }  
});  

6)设置事件处理器:setWebViewClient()

主要帮助WebView处理各种通知、请求事.

①WebView导航能力控制:shouldOverrideUrlLoading(WebView view, String url)

决定当用户点击WebView加载的网页内的超链接时,Android应用是继续采用当前的WebView去加载超链接所指向的目标网页,还是启动用户设备的默认浏览器来显示新网页。
用法如下demo.

默认使用设备浏览器来加载网页。
如果WebView没有添加WebViewClient,那么这个WebView不可导航;否则,这个WebView的导航能力取决于所添加的WebViewClient的shouldOverrideUrlLoading方法。该方法没有重载,或重载的方法返回默认的super()或false值,或使用loadUrl()方法来显示新的url,那么该WebView是可导航的。

当WebView可导航的时候,可能存在跨站点脚本漏洞

②onReceivedError

如下demo.

/** 
             * 加载网页 
             */  
            webView.setWebViewClient(new WebViewClient() {  

                /** 
                 * 一般在onPageStarted之前开始调用 
                 * 返回true:点击网页里面的链接在当前的webview里跳转; 
                 * 返回false:调用系统浏览器或第三方浏览器打开链接。 
                 */  
                @Override  
                public boolean shouldOverrideUrlLoading(WebView view, String url) {  

                    view.loadUrl(url);  
                    return true;  
                }  

                /** 
                 * 捕获http error 
                 */  
                @Override  
                public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {  
                    /** 
                     * 用javascript隐藏系统定义的404页面信息,防止IP泄露 
                     */  
                    String data = "当前网页无法访问,请检查网络状态!";  
                    view.loadUrl("javascript:document.body.innerHTML=\"" + data + "\"");  
                }  
            });  

③onPageFinished()

网页加载完成后执行。
注意:
当前正在加载的网页产生跳转的时候这个方法可能会被多次调用。所以当WebView需要加载各种各样的网页并且需要在页面加载完成时采取一些操作的话,可能WebChromeClient.onProgressChanged()比WebViewClient.onPageFinished()都要靠谱一些。

7)webView退出

当程序调用了WebView加载网页,WebView会自己开启一些线程,如果你没有正确地将WebView销毁的话,这些残余的线程会一直在后台运行,由此导致你的应用程序耗电量居高不下。

①destroy()

推荐的方法。
即使webView依赖的activity被销毁了,gc也不会立即回收webview内存,等回收时会自动调用destroy.

webView_guarantee.destroy();

②System.exit(0);

暴力方法(不推荐):Activity.onDestroy()中直接调用System.exit(0),使得应用程序完全被移出虚拟机。

@Override  
protected void onDestroy() {  

    super.onDestroy();  
    /** 
     * 使Webview应用程序完全被移出虚拟机,节省耗电 
     */  
    System.exit(0);  
}  

8)WebView加载带有Input的输入框时点击无法弹出软键盘

添加代码:

webView.requestFocusFromTouch() ;

9)支持JS-java交互

addJavascriptInterface()

10)java8中加入了javascript引擎,有时间试试。

http://www.tuicool.com/articles/ne6b6fI

3,demo

1)简单demo

<!-- 允许应用程序完全使用网络 -->  
<uses-permission android:name="android.permission.INTERNET" /> 
public class MainActivity extends Activity {  

    private static String Tag = "MainActivity";  
    private WebView webView;  

    private final String url_WebView =   
            "https://www.baidu.com";  

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

        findView();  
        init();  
    }  

    @SuppressLint("SetJavaScriptEnabled")  
    private void findView() {  

        try {  

            webView = (WebView) findViewById(R.id.webView);  
            webView.getSettings().setJavaScriptEnabled(true);// 支持javascript  

        } catch (Exception e) {  

            System.out.println(Tag + "findView()");  
        }  
    }  

    /** 
     * 弹窗消息提示 
     *  
     * @param s 
     */  
    private void showMessage(String s) {  

        Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();  
    }  

    /** 
     * 获取网页内容 
     */  
    private void getwebView() {  
        try {  
            showMessage("正在刷新,请稍后");  
            webView.loadUrl(url_WebView);  

            /** 
             * 加载网页 
             */  
            webView.setWebViewClient(new WebViewClient() {  

                @Override  
                public boolean shouldOverrideUrlLoading(WebView view, String url) {  
                    // 返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器  
                    view.loadUrl(url);  
                    return true;  
                }  

                @Override  
                public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {  
                    // 用javascript隐藏系统定义的404页面信息  
                    String data = "当前网页无法访问,请检查网络状态!";  
                    view.loadUrl("javascript:document.body.innerHTML=\"" + data + "\"");  
                }  
            });  
        } catch (Exception e) {  

            System.out.println(Tag + "getwebView()");  
        }  

    }  
    private void init() {  

        try {  
            getwebView();  
        } catch (Exception e) {  

            System.out.println(Tag + "init()");  
        }  
    }  

    @Override  
    protected void onDestroy() {  

        super.onDestroy();  
        /** 
         * 使Webview应用程序完全被移出虚拟机,节省耗电 
         */  
        System.exit(0);  
    }  
}  
<WebView  
    android:id="@+id/webView"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" />  

2)改进

问题:
因为添加了回退按钮、刷新按钮。title根据网页加载的不同而不同,产生了一系列问题:
1)点击进入新的链接后,回退之后,title未及时更新。
2)点击刷新后,会有一瞬间闪过网址(title获取结果为网址)

解决方案:
1)建立一个栈来存储每次获得的标题,每次点击回退时读取栈内信息。

private List<String> List_titles = new ArrayList<String>();  

2)回退按钮点击事件处理:

if (webView_guarantee.canGoBack()) {  

    webView_guarantee.goBack();  
    if (null != List_titles && !List_titles.isEmpty()) {  
        mTitleBarView.setTitleText(List_titles.get(List_titles.size() - 2));  
        List_titles.remove(List_titles.size() - 1);  
    }  
}  

3)每次获得新标题入栈

webView_guarantee.setWebChromeClient(new WebChromeClient() {  
    /** 
     * 获取网页title 
     */  
    @Override  
    public void onReceivedTitle(WebView view, String title) {  
        super.onReceivedTitle(view, title);  

        /** 
         * 去掉title为网址的情况 
         * 去掉title相同的情况 
         */  
         if (null != title && !title.contains(".html")) {  

             if (List_titles.isEmpty() || !List_titles.get(List_titles.size()-1).equals(title)) {  

                    List_titles.add(title);  
                    mTitleBarView.setTitleText(title);  
            }  
         }  
    }  

3)基于2的修改

再次修改后又遇到一些问题:
1)通过onReceivedError可以处理网页出错(如下),但是无法处理404错误。

/** 
                 * 捕获http error 
                 */  
                @Override  
                public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {  
                    /** 
                     * 用javascript隐藏系统定义的404页面信息,防止IP泄露 
                     */  
                    super.onReceivedError(view, errorCode, description, failingUrl);  
                    String Str_data = "当前网页无法访问,请检查网络状态!";  
                    view.loadUrl("javascript:document.body.innerHTML=\"" + Str_data + "\"");  

                }  

2)因为加了title,当出现404错误时,会获取title为“找不到网页”。这样并不好。

解决方案:
1)通过getRespStatus(String url)方法,检测网址返回的IIS状态码,主要目的是检测出404进行处理。因为是耗时操作,所以开一个线程,在线程里面检测IIS状态。
2)添加网络出错标志:htmlError。只要触发onReceivedTitle方法,就将htmlError置为true。每次onReceivedTitle的时候,httmError为true不获取标题,并再次把htmlError置为false.
demo(只添加新加入的部分):

/** 
     * 判断网络是否出错,出错为true,则不更新title 
     */  
    private boolean htmlError = false;  

    private Handler handler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            if (msg.what == 404) {// 主页不存在  
                /** 
                 * 移除最近title:找不到网页 
                 */  
                setHtmlError(webView_guarantee);  
            } else {  
                webView_guarantee.loadUrl(Str_url);  
            }  
        }  
    }; 

刷新:

@Override  
            public void onClick(View v) {  
                // getWebview_Guarantee();  
                if (null != webView_guarantee) {  
                    showMessage("正在刷新,请稍后");  
//                  webView_guarantee.reload();  
                    load(webView_guarantee.getUrl());  
                }  
            }  
private String Str_url = "";  
private void load(String url) {  
    Str_url = url;  

    new Thread(new Runnable() {  
        @Override  
        public void run() {  
            Message msg = new Message();  
            // 此处判断主页是否存在,因为主页是通过loadUrl加载的,  
            // 此时不会执行shouldOverrideUrlLoading进行页面是否存在的判断  
            // 进入主页后,点主页里面的链接,链接到其他页面就一定会执行shouldOverrideUrlLoading方法了  
            if (getRespStatus(url_WebView) == 404) {  
                msg.what = 404;  
            }  
            handler.sendMessage(msg);  
        }  
    }).start();  
}  

在oncreate中初次加载网页:

load(url_WebView);
webView_guarantee.setWebChromeClient(new WebChromeClient() {  
                /** 
                 * 获取网页title 
                 */  
                @Override  
                public void onReceivedTitle(WebView view, String title) {  
                    super.onReceivedTitle(view, title);  

                    /** 
                     * 去掉title为网址的情况 去掉title相同的情况 
                     */  
                    if (null != title && !title.contains(".html") && !htmlError) {  

                        set_titles.add(title);  
                        mTitleBarView.setTitleText(title);  
                    }  
                    htmlError = false;  
                }  
            }); 
/** 
             * 加载网页 
             */  
            webView_guarantee.setWebViewClient(new WebViewClient() {  

                /** 
                 * 一般在onPageStarted之前开始调用 返回true:点击网页里面的链接在当前的webview里跳转; 
                 * 返回false:调用系统浏览器或第三方浏览器打开链接。 
                 */  
                @Override  
                public boolean shouldOverrideUrlLoading(WebView view, String url) {  

                    load(url);  
                    return true;  
                }  

                /** 
                 * 捕获http error 
                 */  
                @Override  
                public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {  
                    /** 
                     * 用javascript隐藏系统定义的404页面信息,防止IP泄露 
                     */  
                    super.onReceivedError(view, errorCode, description, failingUrl);  
                    /** 
                     * 移除最近title:找不到网页 
                     */  
                    htmlError = true;  
                    setHtmlError(view);  
                }  
            });  
private void setHtmlError(WebView view) {  
    String Str_data = "当前网页无法访问,请检查网络状态!";  
    view.loadUrl("javascript:document.body.innerHTML=\"" + Str_data + "\"");  
}  
private int getRespStatus(String url) {  
    int status = -1;  
    try {  
        HttpHead head = new HttpHead(url);  
        HttpClient client = new DefaultHttpClient();  
        HttpResponse resp = client.execute(head);  
        status = resp.getStatusLine().getStatusCode();  
    } catch (IOException e) {  
        status = 404;  
    }  
    return status;  
}  

4,交互性能

业界衡量web app交互性能的优劣主要通过监测webView渲染页面的四个指标:

1)白屏时间( firstPaint)

指浏览器开始显示内容的时间,即开始解析DOM耗时,用户在没有滚动时候看到的内容渲染完成并且可以交互的时间。

如:在低网速的环境中,页面由上至下缓慢显示完、或者先显示文本内容后再重绘成有格式的页面内容。

2)DOM树构建时间(domReady Time)

指浏览器开始对基础页文本内容进行解析到文本中构建出一个内部数据结构(DOM树)的时间。DOM树构建后,CSS等才开始渲染。
DOM树加载后,用户才可以渲染。

3)整页时间(FirstScreen Time)

指用户看到第一屏(整个网页顶部大小未当前窗口)时的时间。

4)首屏时间(Page Load Time)

指页面完成整个加载过此的时间。

5,WebView加载慢优化

1)原因

这是因为在客户端中,加载H5页面之前,需要先初始化WebView,在WebView完全初始化完成之前,后续的界面加载过程都是被阻塞的。

2)方案

预加载WebView。
加载WebView的同时,请求H5页面数据。

3)方法

①全局WebView。
②客户端代理页面请求。WebView初始化完成后向客户端请求数据。
③asset存放离线包。
④脚本执行慢,可以让脚本最后运行,不阻塞页面解析。
⑤DNS与链接慢,可以让客户端复用使用的域名与链接。
⑥React框架代码执行慢,可以将这部分代码拆分出来,提前进行解析。

6,Java和JS交互优化

对协议进行统一的封装和处理。
一个开源框架:jockeyjs

1)开启权限

        // 设置与Js交互的权限
        webSettings.setJavaScriptEnabled(true);
        // 设置允许JS弹窗
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

2)android原生调用JS代码

① 通过WebView的loadUrl()

/**
*android原生调用JS
*①在加载H5页面结束后,可以调用JS中的无参数方法 
*/
webView.loadUrl("javascript:setRed()");  
/**②调用JS中的有参数方法*/ 
webView.loadUrl("javascript:setColor('#00f')");  
webView.loadUrl("javascript:setList(‘" + result + "’)");

②通过WebView的evaluateJavascript()

Android 4.4 后才可使用

// 只需要将第一种方法的loadUrl()换成下面该方法即可
    mWebView.evaluateJavascript(""javascript:setRed()", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            //此处为 js 返回的结果
        }
    });
}

优点:
1,该方法比第一种方法效率更高、使用更简洁:
因为该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会。
2,容易获得JS返回值
缺点:
向下兼容性差,Android4.4开始才可以使用。

3)JS调用android原生代码

注意:点击查看具体安全报告

①通过WebView的addJavascriptInterface()进行对象映射

i)在项目中创建一个文件夹assets,把写好的H5页面放入该文件夹中。
H5页面中有一个script写的setRed()方法。这个方法用于给android原生调用。
ii)在android中,添加webview控件。
设置属性:

//加载assets文件中的H5页面
webView.loadUrl("file:///android_asset/demo.html");   
/**
*JS调用android原生:     
*① android原生方法需要标记:@JavascriptInterface。这个标记在android 4.2之前不需要添加,在4.2之后需要添加。如ClickButton类中的toString()方法。
*②添加JS的交互事件。addJavascriptInterface这个方法,前一个参数是触发的对象,后一个参数是这个对象的一个标志,我们在JS中使用它来调用android方法。
*所以我们需要申明一个内部类,ButtonClick,它的@JavascriptInterface标记的setClick方法供JS调用。如下代码:
*/
webView.addJavascriptInterface(new JsUseAndroid(),"MainActivity");  

/**另一种写法*/
mWebView.addJavascriptInterface(new Object() {

            public void callJavaMethod() {  
               Toast.makeText(getApplicationContext(), "JS调用Android原生", Toast.LENGTH_LONG).show();
            }  

        }, "demo");  
/**
*注意:
*①这是一个内部类,用来注册addJavascriptInterface,以让JS调用android。
*②这个类的权限无所谓,但是标记@JavascriptInterface的方法即:JS要调用的方法,一定要设置为public才可以调用。
*③同一个webView不能跨进程调用,所以不能使用非内部类的webView.
*/
private class JsUseAndroid{

        @JavascriptInterface
        public void setClick(){
            try{

                Message message = new Message();
                message.what = 0;
                handler.sendMessage(message);
            }catch(Exception e){
                LogUtil.i(Tag, "JsUseAndroid" + e.getMessage());
            }

        }
    }

然后在JS中这样调用android中的方法:

 //可以这样:onclick="javascript:MainActivity.setClick()"
 //也可以直接在click事件中这样写:
  $(".close").on('click', function () {
        MainActivity.setClick();
        //或者写成这样:javascript:MainActivity.setClick()
    }) 
优缺点

仅将Android对象和JS对象映射即可,使用简单。
但存在严重的漏洞问题:

android4.2之前的版本,此方式可能产生JS-to-Java接口注入漏洞(过度授权漏洞)。即:Android应用给JS授权过度,使得JS脚本可以调用应用的内部Java代码,以滥用反射机制进行攻击。
解决方式:
a.对apk中的Dex文件进行加密,隐藏可用的WebView接口,防止逆向工程暴露接口。
b.尽量少调用重要的、不安全的系统API。

4.2版本之后,出于安全考虑,为了防止Java层的函数被随便调用,Google在4.2版本之后,规定允许被调用的函数必须以@JavascriptInterface进行注解,所以如果某应用依赖的API Level为17或者以上,就不会受该问题的影响(注:Android 4.2中API Level小于17的应用也会受影响)。

建议不要使用addJavascriptInterface接口,以免带来不必要的安全隐患,如果一定要使用addJavascriptInterface接口:
a) 如果使用HTTPS协议加载URL,应进行证书校验防止访问的页面被篡改挂马;
b) 如果使用HTTP协议加载URL,应进行白名单过滤、完整性校验等防止访问的页面被篡改;
c) 如果加载本地Html,应将html文件内置在APK中,以及进行对html页面完整性的校验;

在Android 3.0以下,系统自己添加了一个叫 searchBoxJavaBridge_的Js接口,要解决这个安全问题,我们也需要把这个接口删除。

webView.removeJavascriptInterface("searchBoxJavaBridge_");

②通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url

  1. Android通过 WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截
  2. url 解析该url 的协议
  3. 如果检测到是预先约定好的协议,就调用相应方法
  @Override
  public boolean shouldOverrideUrlLoading(WebView view, String url) {

      // 步骤2:根据协议的参数,判断是否是所需要的url
      // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
      //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
      Uri uri = Uri.parse(url);                                 
      // 如果url的协议 = 预先约定的 js 协议
      // 就解析往下解析参数
      if ( uri.getScheme().equals("js")) {
           // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
           // 所以拦截url,下面JS开始调用Android需要的方法
           if (uri.getAuthority().equals("webview")) {
               //  步骤3:
               // 执行JS所需要调用的逻辑
               System.out.println("js调用了Android的方法");
               // 可以在协议上带有参数并传递到Android上
               HashMap<String, String> params = new HashMap<>();
               Set<String> collection = uri.getQueryParameterNames();
           }
           return true;
      }
      return super.shouldOverrideUrlLoading(view, url);
  }

如果JS想要得到Android方法的返回值,只能通过 WebView 的 loadUrl ()去执行 JS 方法把返回值传递回去,相关的代码如下:

// Android:MainActivity.java
mWebView.loadUrl("javascript:returnResult(" + result + ")");

// JS:javascript.html
function returnResult(result){
    alert("result is" + result);
}
优缺点

优点:不存在方式1的漏洞;
缺点:JS获取Android方法的返回值复杂。

③通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

在JS中,有三个常用的对话框方法:
这里写图片描述
原理:Android通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调分别拦截JS对话框(即上述三个方法),得到他们的消息内容,然后解析即可。
举例:
拦截 JS的输入框(即prompt()方法)
常用方式,因为只有prompt()可以返回任意类型的值,操作最全面方便、更加灵活;而alert()对话框没有返回值;confirm()对话框只能返回两种状态(确定 / 取消)两个值

//JS
function clickprompt(){
    // 调用prompt()
    var result=prompt("js://demo?arg1=111&arg2=222");
    alert("demo " + result);
}
//Android加载上述JS文件,会触发回调onJsPrompt()
mWebView.loadUrl("file:///android_asset/javascript.html")
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        // 根据协议的参数,判断是否是所需要的url(原理同方式2)
        // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
        //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
        Uri uri = Uri.parse(message);
        // 如果url的协议 = 预先约定的 js 协议
        // 就解析往下解析参数
        if ( uri.getScheme().equals("js")) {
            // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
            // 所以拦截url,下面JS开始调用Android需要的方法
            if (uri.getAuthority().equals("webview")) {
                 // 执行JS所需要调用的逻辑
                 System.out.println("js调用了Android的方法");
                 // 可以在协议上带有参数并传递到Android上
                 HashMap<String, String> params = new HashMap<>();
                 Set<String> collection = uri.getQueryParameterNames();
                 //参数result:代表消息框的返回值(输入值)
                 result.confirm("js调用了Android的方法成功啦");
            }
            return true;
        }
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }

    // 通过alert()和confirm()拦截的原理相同,此处不作过多讲述
    // 拦截JS的警告框
    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
        return super.onJsAlert(view, url, message, result);
    }
    // 拦截JS的确认框
    @Override
    public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
        return super.onJsConfirm(view, url, message, result);
    }

④比较

这里写图片描述

4)注意

①路径

对于同一个文件夹中的html和css,他们地址是相对地址就可以。需要注意的是,网页开发中,路径不区分大小写,但是在android中对大小写区分尤为严格。需要一一校对路径,不然会发生网页在PC端可以访问,但是在android 的webview中无法加载或者失效的情况。

②线程

当采用线程的时候,需要注意,同一个webview不可以在2个线程中调用,否则会报错。

4,demo

1)网上demo

android原生和H5交互

2)推荐文章

webView详解
微信、网易云音乐的JS与android互相调用

3)demo-android调用JS,点击android按钮,修改div内容。(入门练手)

<!DOCTYPE html> 
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></meta>  
        <title></title>
        <script type="text/javascript" language="javascript">
            function  callJavaScriptMethod()
            {
                document.getElementById("content").innerHTML = "Android 调用  Javascript";
            }
        </script>
    </head>

    <body>
        <div style="margin:50px 50px 50px 50px;" id="content"></div>
    </body>
</html> 

android端:按钮点击监听写:

webView.loadUrl("javascript:callJavaScriptMethod()");

此时一个demo完了,下面为其它练习。
甚至我们可以将JS写成外部文件:

function  callJavaScriptMethod()
{
     document.getElementById("content").innerHTML = "Android 调用  Javascript";
}

然后再html中这样引用它:

<script  src="style/js/test.js"></script>

在android中调用方法一样。依然可以实现。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值