{"action":"opened","issue":{"url":"https://api.github.com/repos/qmsggg/BlogCollect/issues/140","repository_url":"https://api.github.com/repos/qmsggg/BlogCollect","labels_url":"https://api.github.com/repos/qmsggg/BlogCollect/issues/140/labels{/name}","comments_url":"https://api.github.com/repos/qmsggg/BlogCollect/issues/140/comments","events_url":"https://api.github.com/repos/qmsggg/BlogCollect/issues/140/events","html_url":"https://github.com/qmsggg/BlogCollect/issues/140","id":301902913,"number":140,"title":"Android应用开发实践之其它","user":{"login":"qmsggg","id":28669743,"avatar_url":"https://avatars0.githubusercontent.com/u/28669743?v=4","gravatar_id":"","url":"https://api.github.com/users/qmsggg","html_url":"https://github.com/qmsggg","followers_url":"https://api.github.com/users/qmsggg/followers","following_url":"https://api.github.com/users/qmsggg/following{/other_user}","gists_url":"https://api.github.com/users/qmsggg/gists{/gist_id}","starred_url":"https://api.github.com/users/qmsggg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/qmsggg/subscriptions","organizations_url":"https://api.github.com/users/qmsggg/orgs","repos_url":"https://api.github.com/users/qmsggg/repos","events_url":"https://api.github.com/users/qmsggg/events{/privacy}","received_events_url":"https://api.github.com/users/qmsggg/received_events","type":"User","site_admin":false},"labels":[{"id":718679138,"url":"https://api.github.com/repos/qmsggg/BlogCollect/labels/Android","name":"Android","color":"d93f0b","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-03-02T20:58:20Z","updated_at":"2018-03-02T20:58:20Z","closed_at":null,"author_association":"OWNER","body":"# Android混合编程：WebView实践\r\n\r\n**文章目录**\r\n\r\n- 一 基本用法\r\n- 二 代码交互\r\n- 三 性能优化\r\n\r\n## 一 基本用法\r\n\r\nWebView也是Android View的一种, 我们通常用它来在应用内部展示网页, 和以往一样, 我们先来简单看一下它的基本用法。\r\n\r\n添加网络权限\r\n\r\n```xml\r\n<uses-permission android:name=\"android.permission.INTERNET\" />\r\n```\r\n\r\n在布局中添加WebView\r\n\r\n```xml\r\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<WebView  xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:id=\"@+id/webview\"\r\n    android:layout_width=\"fill_parent\"\r\n    android:layout_height=\"fill_parent\"\r\n/>\r\n```\r\n\r\n使用WebView加载网页\r\n\r\n```\r\nWebView myWebView = (WebView) findViewById(R.id.webview);\r\nmyWebView.loadUrl(\"http://www.example.com\");\r\n```\r\n\r\n以上就是WebView的简单用法, 相比大家已经十分熟悉, 下面我们就来逐一看看WebView的其他特性。\r\n\r\n### WebView基本组件\r\n\r\n了解了基本用法, 我们对WebView就有了大致的印象, 下面我们来看看构建Web应用的三个重要组件。\r\n\r\n#### WebSettings\r\n\r\nWebSettings用来对WebView做各种设置, 你可以这样获取WebSettings:\r\n\r\n```java\r\nWebSettings webSettings = mWebView .getSettings();\r\n```\r\n\r\nWebSettings的常见设置如下所示:\r\n\r\nJS处理\r\n\r\n- setJavaScriptEnabled(true);  //支持js\r\n- setPluginsEnabled(true);  //支持插件 \r\n- setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口 \r\n\r\n缩放处理\r\n\r\n- setUseWideViewPort(true);  //将图片调整到适合webview的大小 \r\n- setLoadWithOverviewMode(true); // 缩放至屏幕的大小\r\n- setSupportZoom(true);  //支持缩放，默认为true。是下面那个的前提。\r\n- setBuiltInZoomControls(true); //设置内置的缩放控件。 这个取决于setSupportZoom(), 若setSupportZoom(false)，则该WebView不可缩放，这个不管设置什么都不能缩放。\r\n- setDisplayZoomControls(false); //隐藏原生的缩放控件\r\n\r\n内容布局\r\n\r\n- setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); //支持内容重新布局\r\n- supportMultipleWindows(); //多窗口 \r\n\r\n文件缓存\r\n\r\n- setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //关闭webview中缓存 \r\n- setAllowFileAccess(true);  //设置可以访问文件 \r\n\r\n其他设置\r\n\r\n- setNeedInitialFocus(true); //当webview调用requestFocus时为webview设置节点\r\n- setLoadsImagesAutomatically(true); //支持自动加载图片\r\n- setDefaultTextEncodingName(\"utf-8\"); //设置编码格式\r\n- setPluginState(PluginState.OFF); //设置是否支持flash插件\r\n- setDefaultFontSize(20); //设置默认字体大小\r\n\r\n#### WebViewClient\r\n\r\nWebViewClient用来帮助WebView处理各种通知, 请求事件。我们通过继承WebViewClient并重载它的方法可以实现不同功能的定制。具体如下所示:\r\n\r\n- shouldOverrideUrlLoading(WebView view, String url) //在网页上的所有加载都经过这个方法,这个函数我们可以做很多操作。比如获取url，查看url.contains(“add”)，进行添加操作\r\n \r\n- shouldOverrideKeyEvent(WebView view, KeyEvent event) //处理在浏览器中的按键事件。 \r\n\r\n- onPageStarted(WebView view, String url, Bitmap favicon) //开始载入页面时调用的，我们可以设定一个loading的页面，告诉用户程序在等待网络响应。 \r\n\r\n- onPageFinished(WebView view, String url) //在页面加载结束时调用, 我们可以关闭loading 条，切换程序动作。 \r\n\r\n- onLoadResource(WebView view, String url) //在加载页面资源时会调用，每一个资源（比如图片）的加载都会调用一次。 \r\n\r\n- onReceivedError(WebView view, int errorCode, String description, String failingUrl) //报告错误信息 \r\n\r\n- doUpdateVisitedHistory(WebView view, String url, boolean isReload) //更新历史记录 \r\n\r\n- onFormResubmission(WebView view, Message dontResend, Message resend) //应用程序重新请求网页数据\r\n\r\n- onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,String realm) //获取返回信息授权请求 \r\n\r\n- onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) //让webview处理https请求。\r\n\r\n- onScaleChanged(WebView view, float oldScale, float newScale) //WebView发生改变时调用\r\n\r\n- onUnhandledKeyEvent(WebView view, KeyEvent event) //Key事件未被加载时调用\r\n\r\n#### WebChromeClient\r\n\r\nWebChromeClient用来帮助WebView处理JS的对话框、网址图标、网址标题和加载进度等。同样地, 通过继承WebChromeClient并重载它的方法也可以实现不同功能的定制, 如下所示:\r\n\r\n- public void onProgressChanged(WebView view, int newProgress); //获得网页的加载进度，显示在右上角的TextView控件中\r\n\r\n- public void onReceivedTitle(WebView view, String title); //获取Web页中的title用来设置自己界面中的title, 当加载出错的时候，比如无网络，这时onReceiveTitle中获取的标题为\"找不到该网页\",\r\n\r\n- public void onReceivedIcon(WebView view, Bitmap icon); //获取Web页中的icon\r\n\r\n- public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg);\r\n\r\n- public void onCloseWindow(WebView window);\r\n\r\n- public boolean onJsAlert(WebView view, String url, String message, JsResult result); //处理alert弹出框，html 弹框的一种方式\r\n\r\n- public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) //处理confirm弹出框\r\n\r\n- public boolean onJsConfirm(WebView view, String url, String message, JsResult result); //处理prompt弹出框\r\n\r\n### WebView生命周期\r\n\r\n#### onResume()  \r\n\r\nWebView为活跃状态时回调，可以正常执行网页的响应。\r\n\r\n#### onPause() \r\n\r\nWebView被切换到后台时回调, 页面被失去焦点, 变成不可见状态，onPause动作通知内核暂停所有的动作，比如DOM的解析、plugin的执行、JavaScript执行。\r\n\r\n#### pauseTimers() \r\n\r\n当应用程序被切换到后台时回调，该方法针对全应用程序的WebView，它会暂停所有webview的layout，parsing，javascripttimer。降低CPU功耗。\r\n\r\n#### resumeTimers()\r\n\r\n恢复pauseTimers时的动作。\r\n\r\n#### destroy() \r\n\r\n关闭了Activity时回调, WebView调用destory时, WebView仍绑定在Activity上.这是由于自定义WebView构建时传入了该Activity的context对象, 因此需要先从父\r\n容器中移除WebView, 然后再销毁webview。\r\n\r\n```java\r\nmRootLayout.removeView(webView);  \r\nmWebView.destroy();\r\n```\r\n\r\n###  WebView页面导航\r\n\r\n#### 页面跳转\r\n\r\n当我们在WebView点击链接时, 默认的WebView会直接跳转到别的浏览器中, 如果想要实现在WebView内跳转就需要设置WebViewClient, 下面我们先来\r\n说说WebView、WebViewClient、WebChromeClient三者的区别。\r\n\r\n- WebView: 主要负责解析和渲染网页\r\n- WebViewClient: 辅助WebView处理各种通知和请求事件\r\n- WebChromeClient: 辅助WebView处理JavaScript中的对话框, 网址图标和标题等\r\n\r\n如果我们想控制不同链接的跳转方式, 我们需要继承WebViewClient重写shouldOverrideUrlLoading()方法\r\n\r\n```java\r\n    static class CustomWebViewClient extends WebViewClient {\r\n\r\n        private Context mContext;\r\n\r\n        public CustomWebViewClient(Context context) {\r\n            mContext = context;\r\n        }\r\n\r\n        @Override\r\n        public boolean shouldOverrideUrlLoading(WebView view, String url) {\r\n            if (Uri.parse(url).getHost().equals(\"github.com/qmsggg\")) {\r\n                //如果是自己站点的链接, 则用本地WebView跳转\r\n                return false;\r\n            }\r\n            //如果不是自己的站点则launch别的Activity来处理\r\n            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));\r\n            mContext.startActivity(intent);\r\n            return true;\r\n        }\r\n    }\r\n```\r\n\r\n关于shouldOverrideUrlLoading()方法的两点说明:\r\n\r\n1 方法返回值\r\n\r\n返回true: Android 系统会处理URL, 一般是唤起系统浏览器。\r\n返回false: 当前 WebView 处理URL。\r\n\r\n由于默认放回false, 如果我们只想在WebView内处理链接跳转只需要设置mWebView.setWebViewClient(new WebViewClient())即可\r\n\r\n```java\r\n/** \r\n     * Give the host application a chance to take over the control when a new \r\n     * url is about to be loaded in the current WebView. If WebViewClient is not \r\n     * provided, by default WebView will ask Activity Manager to choose the \r\n     * proper handler for the url. If WebViewClient is provided, return true \r\n     * means the host application handles the url, while return false means the \r\n     * current WebView handles the url. \r\n     * This method is not called for requests using the POST \"method\". \r\n     * \r\n     * @param view The WebView that is initiating the callback. \r\n     * @param url The url to be loaded. \r\n     * @return True if the host application wants to leave the current WebView \r\n     *         and handle the url itself, otherwise return false. \r\n     */  \r\n    public boolean shouldOverrideUrlLoading(WebView view, String url) {  \r\n        return false;  \r\n    }  \r\n```\r\n\r\n2 方法deprecated问题\r\n\r\nshouldOverrideUrlLoading()方法在API >= 24时被标记deprecated, 它的替代方法是\r\n\r\n```\r\n        @Override\r\n        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {\r\n            view.loadUrl(request.toString());\r\n            return true;\r\n        }\r\n```\r\n\r\n但是public boolean shouldOverrideUrlLoading(WebView view, String url)支持更广泛的API我们在使用的时候还是它, \r\n关于这两个方法的讨论可以参见:\r\n\r\nhttp://stackoverflow.com/questions/36484074/is-shouldoverrideurlloading-really-deprecated-what-can-i-use-instead  \r\nhttp://stackoverflow.com/questions/26651586/difference-between-shouldoverrideurlloading-and-shouldinterceptrequest\r\n\r\n#### 页面回退\r\n\r\nAndroid的返回键, 如果想要实现WebView内网页的回退, 可以重写onKeyEvent()方法。\r\n\r\n```java\r\n@Override\r\npublic boolean onKeyDown(int keyCode, KeyEvent event) {\r\n    // Check if the key event was the Back button and if there's history\r\n    if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {\r\n        myWebView.goBack();\r\n        return true;\r\n    }\r\n    // If it wasn't the Back key or there's no web page history, bubble up to the default\r\n    // system behavior (probably exit the activity)\r\n    return super.onKeyDown(keyCode, event);\r\n}\r\n```\r\n\r\n#### 页面滑动\r\n\r\n关于页面滑动, 我们在做下拉刷新等功能时, 经常会去判断WebView是否滚动到顶部或者滚动到底部。\r\n\r\n我们先来看一看三个判断高度的方法\r\n\r\n```java\r\ngetScrollY();\r\n```\r\n\r\n该方法返回的是当前可见区域的顶端距整个页面顶端的距离,也就是当前内容滚动的距离.\r\n\r\n```java\r\ngetHeight();\r\ngetBottom();\r\n```\r\n\r\n该方法都返回当前WebView这个容器的高度\r\n\r\n```\r\ngetContentHeight(); \r\n```\r\n\r\n返回的是整个html的高度, 但并不等同于当前整个页面的高度, 因为WebView有缩放功能, 所以当前整个页面的高度实际上应该是原始html的高度\r\n再乘上缩放比例. 因此, 判断方法是:\r\n\r\n```java\r\nif (webView.getContentHeight() * webView.getScale() == (webView.getHeight() + webView.getScrollY())) {\r\n    //已经处于底端\r\n}\r\n\r\nif(webView.getScrollY() == 0){\r\n    //处于顶端\r\n}\r\n```\r\n\r\n以上这个方法也是我们常用的方法, 不过从API 17开始, mWebView.getScale()被标记为deprecated\r\n\r\n>This method was deprecated in API level 17. This method is prone to inaccuracy due to race conditions \r\nbetween the web rendering and UI threads; prefer onScaleChanged(WebView, \r\n\r\n因为scale的获取可以用一下方式:\r\n\r\n```java\r\npublic class CustomWebView extends WebView {\r\n\r\npublic CustomWebView(Context context) {\r\n    super(context);\r\n    setWebViewClient(new WebViewClient() {\r\n        @Override\r\n        public void onScaleChanged(WebView view, float oldScale, float newScale) {\r\n            super.onScaleChanged(view, oldScale, newScale);\r\n            mCurrentScale = newScale\r\n        }\r\n    });\r\n}\r\n```\r\n\r\n关于mWebView.getScale()的讨论可以参见:\r\n\r\nhttps://developer.android.com/reference/android/webkit/WebView.html\r\n\r\nhttp://stackoverflow.com/questions/16079863/how-get-webview-scale-in-android-4\r\n\r\n### WebView缓存实现\r\n\r\n在项目中如果使用到WebView控件, 当加载html页面时, 会在/data/data/包名目录下生成database与cache两个文件夹。\r\n请求的url记录是保存在WebViewCache.db, 而url的内容是保存在WebViewCache文件夹下。\r\n\r\n控制缓存行为\r\n\r\n```java\r\nWebSettings webSettings = mWebView.getSettings();\r\n//优先使用缓存\r\nwebSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); \r\n//只在缓存中读取\r\nwebSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);\r\n/不使用缓存\r\nWwebSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);\r\n```\r\n\r\n清除缓存\r\n\r\n```java\r\nclearCache(true); //清除网页访问留下的缓存，由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.\r\nclearHistory (); //清除当前webview访问的历史记录，只会webview访问历史记录里的所有记录除了当前访问记录.\r\nclearFormData () //这个api仅仅清除自动完成填充的表单数据，并不会清除WebView存储到本地的数据。\r\n```\r\n\r\n### WebView Cookies\r\n\r\n添加Cookies\r\n\r\n```java\r\npublic void synCookies() {\r\n    if (!CacheUtils.isLogin(this)) return;\r\n    CookieSyncManager.createInstance(this);\r\n    CookieManager cookieManager = CookieManager.getInstance();\r\n    cookieManager.setAcceptCookie(true);\r\n    cookieManager.removeSessionCookie();//移除\r\n    String cookies = PreferenceHelper.readString(this, AppConfig.COOKIE_KEY, AppConfig.COOKIE_KEY);\r\n    KJLoger.debug(cookies);\r\n    cookieManager.setCookie(url, cookies);\r\n    CookieSyncManager.getInstance().sync();\r\n}\r\n```\r\n\r\n清除Cookies\r\n\r\n```java\r\nCookieManager.getInstance().removeSessionCookie();\r\n```\r\n\r\n### WebView本地资源访问\r\n\r\n当我们在WebView中加载出从web服务器上拿取的内容时，是无法访问本地资源的，如assets目录下的图片资源，因为这样的行为属于跨域行为（Cross-Domain），而WebView是禁止\r\n的。解决这个问题的方案是把html内容先下载到本地，然后使用loadDataWithBaseURL加载html。这样就可以在html中使用 file:///android_asset/xxx.png 的链接来引用包里\r\n面assets下的资源了。\r\n\r\n```java\r\nprivate void loadWithAccessLocal(final String htmlUrl) {\r\n    new Thread(new Runnable() {\r\n        public void run() {\r\n            try {\r\n                final String htmlStr = NetService.fetchHtml(htmlUrl);\r\n                if (htmlStr != null) {\r\n                    TaskExecutor.runTaskOnUiThread(new Runnable() {\r\n                        @Override\r\n                        public void run() {\r\n                            loadDataWithBaseURL(htmlUrl, htmlStr, \"text/html\", \"UTF-8\", \"\");\r\n                        }\r\n                    });\r\n                    return;\r\n                }\r\n            } catch (Exception e) {\r\n                Log.e(\"Exception:\" + e.getMessage());\r\n            }\r\n\r\n            TaskExecutor.runTaskOnUiThread(new Runnable() {\r\n                @Override\r\n                public void run() {\r\n                    onPageLoadedError(-1, \"fetch html failed\");\r\n                }\r\n            });\r\n        }\r\n    }).start();\r\n}\r\n```\r\n\r\n**注意**\r\n\r\n- 从网络上下载html的过程应放在工作线程中\r\n- html下载成功后渲染出html的步骤应放在UI主线程，不然WebView会报错\r\n- html下载失败则可以使用我们前面讲述的方法来显示自定义错误界面\r\n\r\n## 二 代码交互\r\n\r\n### Android原生方案\r\n\r\n关于WebView中Java代码和JS代码的交互实现, Android给了一套原生的方案, 我们先来看看原生的用法。后面我们还会讲到其他的开源方法。\r\n\r\nJavaScript代码和Android代码是通过addJavascriptInterface()来建立连接的, 我们来看下具体的用法。\r\n\r\n1 设置WebView支持JavaScript\r\n\r\n```java\r\nwebView.getSettings().setJavaScriptEnabled(true);\r\n```\r\n\r\n2 在Android工程里定义一个接口\r\n\r\n```java\r\npublic class WebAppInterface {\r\n    Context mContext;\r\n\r\n    /** Instantiate the interface and set the context */\r\n    WebAppInterface(Context c) {\r\n        mContext = c;\r\n    }\r\n\r\n    /** Show a toast from the web page */\r\n    @JavascriptInterface\r\n    public void showToast(String toast) {\r\n        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();\r\n    }\r\n}\r\n```\r\n\r\n**注意**: API >= 17时, 必须在被JavaScript调用的Android方法前添加@JavascriptInterface注解, 否则将无法识别。\r\n\r\n3 在Android代码中将该接口添加到WebView\r\n\r\n```java\r\nWebView webView = (WebView) findViewById(R.id.webview);\r\nwebView.addJavascriptInterface(new WebAppInterface(this), \"Android\");\r\n```\r\n\r\n这个\"Android\"就是我们为这个接口取的别名, 在JavaScript就可以通过Android.showToast(toast)这种方式来调用此方法。\r\n\r\n4 在JavaScript中调用Android方法\r\n\r\n```js\r\n<input type=\"button\" value=\"Say hello\" onClick=\"showAndroidToast('Hello Android!')\" />\r\n\r\n<script type=\"text/javascript\">\r\n    function showAndroidToast(toast) {\r\n        Android.showToast(toast);\r\n    }\r\n</script>\r\n```\r\n\r\n在JavaScript中我们不用再去实例化WebAppInterface接口, WebView会自动帮我们完成这一工作, 使它能够为WebPage所用。\r\n\r\n**注意**:\r\n\r\n由于addJavascriptInterface()给予了JS代码控制应用的能力, 这是一项非常有用的特性, 但同时也带来了安全上的隐患, \r\n\r\n> Using addJavascriptInterface() allows JavaScript to control your Android application. This can be a very useful feature or a dangerous \r\nsecurity issue. When the HTML in the WebView is untrustworthy (for example, part or all of the HTML is provided by an unknown person or \r\nprocess), then an attacker can include HTML that executes your client-side code and possibly any code of the attacker's choosing. As such, \r\nyou should not use addJavascriptInterface() unless you wrote all of the HTML and JavaScript that appears in your WebView. You should also \r\nnot allow the user to navigate to other web pages that are not your own, within your WebView (instead, allow the user's default browser \r\napplication to open foreign links—by default, the user's web browser opens all URL links, so be careful only if you handle page navigation\r\nas described in the following section).\r\n \r\n下面正式引入我们在项目中常用的两套开源的替代方案\r\n\r\n### jockeyjs开源方案\r\n\r\n[jockeyjs](https://github.com/tcoulter/jockeyjs)是一套IOS/Android双平台的Native和JS交互方法, 比较适合用在项目中。\r\n\r\n>Library to facilitate communication between iOS apps and JS apps running inside a UIWebView\r\n\r\njockeyjs对Native和JS的交互做了优美的封装, 事件的发送与接收都可以通过send()和on()来完成。我们先简单的看一下Event的发送与接收。\r\n\r\nSending events from app to JavaScript\r\n\r\n```java\r\n// Send an event to JavaScript, passing a payload\r\njockey.send(\"event-name\", webView, payload);\r\n\r\n//With a callback to execute after all listeners have finished\r\njockey.send(\"event-name\", webView, payload, new JockeyCallback() {\r\n    @Override\r\n    public void call() {\r\n        //Your execution code\r\n    }\r\n});\r\n```\r\n\r\nReceiving events from app in JavaScript\r\n\r\n```java\r\n// Listen for an event from iOS, but don't notify iOS we've completed processing\r\n// until an asynchronous function has finished (in this case a timeout).\r\nJockey.on(\"event-name\", function(payload, complete) {\r\n  // Example of event'ed handler.\r\n  setTimeout(function() {\r\n    alert(\"Timeout over!\");\r\n    complete();\r\n  }, 1000);\r\n});\r\n```\r\n\r\nSending events from JavaScript to app\r\n\r\n```java\r\n// Send an event to iOS.\r\nJockey.send(\"event-name\");\r\n\r\n// Send an event to iOS, passing an optional payload.\r\nJockey.send(\"event-name\", {\r\n  key: \"value\"\r\n});\r\n\r\n// Send an event to iOS, pass an optional payload, and catch the callback when all the\r\n// iOS listeners have finished processing.\r\nJockey.send(\"event-name\", {\r\n  key: \"value\"\r\n}, function() {\r\n  alert(\"iOS has finished processing!\");\r\n});\r\n```\r\n\r\nReceiving events from JavaScript in app\r\n\r\n```java\r\n//Listen for an event from JavaScript and log a message when we have receied it.\r\njockey.on(\"event-name\", new JockeyHandler() {\r\n    @Override\r\n    protected void doPerform(Map<Object, Object> payload) {\r\n        Log.d(\"jockey\", \"Things are happening\");\r\n    }\r\n});\r\n\r\n//Listen for an event from JavaScript, but don't notify the JavaScript that the listener has completed\r\n//until an asynchronous function has finished\r\n//Note: Because this method is executed in the background, if you want the method to interact with the UI thread\r\n//it will need to use something like a android.os.Handler to post to the UI thread.\r\njockey.on(\"event-name\", new JockeyAsyncHandler() {\r\n    @Override\r\n    protected void doPerform(Map<Object, Object> payload) {\r\n        //Do something asynchronously\r\n        //No need to called completed(), Jockey will take care of that for you!\r\n    }\r\n});\r\n\r\n\r\n//We can even chain together several handlers so that they get processed in sequence.\r\n//Here we also see an example of the NativeOS interface which allows us to chain some common\r\n//system handlers to simulate native UI interactions.\r\njockey.on(\"event-name\", nativeOS(this)\r\n            .toast(\"Event occurred!\")\r\n            .vibrate(100), //Don't forget to grant permission\r\n            new JockeyHandler() {\r\n                @Override\r\n                protected void doPerform(Map<Object, Object> payload) {\r\n                }\r\n            }\r\n);\r\n\r\n//...More Handlers\r\n\r\n\r\n//If you would like to stop listening for a specific event\r\njockey.off(\"event-name\");\r\n\r\n//If you would like to stop listening to ALL events\r\njockey.clear();\r\n```\r\n\r\n通过上面的代码, 我们对jockeyjs的使用有了大致的理解, 下面我们具体来看一下在项目中的使用。\r\n\r\n1 依赖配置\r\n\r\n下载代码: https://github.com/tcoulter/jockeyjs, 将JockeyJS.Android导入到工程中。\r\n\r\n2 jockeyjs配置\r\n\r\njockeyjs有两种使用方式\r\n\r\n方式一:\r\n\r\n只在一个Activity中使用jockey或者多Activity共享一个jockey实例\r\n\r\n```java\r\n//Declare an instance of Jockey\r\nJockey jockey;\r\n\r\n//The WebView that we will be using, assumed to be instantiated either through findViewById or some method of injection.\r\nWebView webView;\r\n\r\nWebViewClient myWebViewClient;\r\n\r\n@Override\r\nprotected void onStart() {\r\n    super.onStart();\r\n\r\n    //Get the default JockeyImpl\r\n    jockey = JockeyImpl.getDefault();\r\n\r\n    //Configure your webView to be used with Jockey\r\n    jockey.configure(webView);\r\n\r\n    //Pass Jockey your custom WebViewClient\r\n    //Notice we can do this even after our webView has been configured.\r\n    jockey.setWebViewClient(myWebViewClient)\r\n\r\n    //Set some event handlers\r\n    setJockeyEvents();\r\n\r\n    //Load your webPage\r\n    webView.loadUrl(\"file:///your.url.com\");\r\n}\r\n```\r\n\r\n方式二:\r\n\r\n另一种就是把jockey当成一种全局的Service来用, 这种方式下我们可以在多个Activity之间甚至整个应用内共享handler. 当然我们同样需要\r\n把jockey的生命周期和应用的生命周期绑定在一起。\r\n\r\n```java\r\n//First we declare the members involved in using Jockey\r\n\r\n//A WebView to interact with\r\nprivate WebView webView;\r\n\r\n//Our instance of the Jockey interface\r\nprivate Jockey jockey;\r\n\r\n//A helper for binding services\r\nprivate boolean _bound;\r\n\r\n//A service connection for making use of the JockeyService\r\nprivate ServiceConnection _connection = new ServiceConnection() {\r\n    @Override\r\n    public void onServiceDisconnected(ComponentName name) {\r\n        _bound = false;\r\n    }\r\n\r\n    @Override\r\n    public void onServiceConnected(ComponentName name, IBinder service) {\r\n        JockeyBinder binder = (JockeyBinder) service;\r\n\r\n        //Retrieves the instance of the JockeyService from the binder\r\n        jockey = binder.getService();\r\n\r\n        //This will setup the WebView to enable JavaScript execution and provide a custom JockeyWebViewClient\r\n        jockey.configure(webView);\r\n\r\n        //Make Jockey start listening for events\r\n        setJockeyEvents();\r\n\r\n        _bound = true;\r\n\r\n        //Redirect the WebView to your webpage.\r\n        webView.loadUrl(\"file:///android_assets/index.html\");\r\n    }\r\n\r\n}\r\n\r\n///....Other member variables....////\r\n\r\n\r\n//Then we bind the JockeyService to our activity through a helper function in our onStart method\r\n@Override\r\nprotected void onStart() {\r\n    super.onStart();\r\n    JockeyService.bind(this, _connection);\r\n}\r\n\r\n//In order to bind this with the Android lifecycle we need to make sure that the service also shuts down at the appropriate time.\r\n@Override\r\nprotected void onStop() {\r\n    super.onStop();\r\n    if (_bound) {\r\n        JockeyService.unbind(this, _connection);\r\n    }\r\n}\r\n```\r\n\r\n\r\n以上便是jockeyjs的大致用法.\r\n\r\n\r\n## 三 性能优化\r\n\r\n### 优化网页加载速度\r\n\r\n默认情况html代码下载到WebView后，webkit开始解析网页各个节点，发现有外部样式文件或者外部脚本文件时，会异步发起网络请求下载文件，但如果\r\n在这之前也有解析到image节点，那势必也会发起网络请求下载相应的图片。在网络情况较差的情况下，过多的网络请求就会造成带宽紧张，影响到css或\r\njs文件加载完成的时间，造成页面空白loading过久。解决的方法就是告诉WebView先不要自动加载图片，等页面finish后再发起图片加载。\r\n\r\n设置WebView, 先禁止加载图片\r\n\r\n```java\r\nWebSettings webSettings = mWebView.getSettings();\r\n\r\n//图片加载\r\nif(Build.VERSION.SDK_INT >= 19){\r\n    webSettings.setLoadsImagesAutomatically(true);\r\n}else {\r\n    webSettings.setLoadsImagesAutomatically(false);\r\n}\r\n```\r\n\r\n覆写WebViewClient的onPageFinished()方法, 页面加载结束后再加载图片\r\n\r\n```java\r\n@Override\r\npublic void onPageFinished(WebView view, String url) {\r\n    super.onPageFinished(view, url);\r\n    if (!view.getSettings().getLoadsImagesAutomatically()) {\r\n        view.getSettings().setLoadsImagesAutomatically(true);\r\n    }\r\n}\r\n```\r\n\r\n**注意**: 4.4以上系统在onPageFinished时再恢复图片加载时,如果存在多张图片引用的是相同的src时，会只有一个image标签得到加载，因而对于这样的系统我们就先直接加载。\r\n\r\n### 硬件加速页面闪烁问题\r\n\r\n4.0以上的系统我们开启硬件加速后，WebView渲染页面更加快速，拖动也更加顺滑。但有个副作用就是，当WebView视图被整体遮住一块，然后突然恢复时（比如使用SlideMenu将WebView从侧边\r\n滑出来时），这个过渡期会出现白块同时界面闪烁。解决这个问题的方法是在过渡期前将WebView的硬件加速临时关闭，过渡期后再开启，如下所示:\r\n\r\n过度前关闭硬件加速\r\n\r\n```java\r\nif(Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB){\r\n    mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);\r\n}\r\n```\r\n\r\n过度前开启硬件加速\r\n\r\n```java\r\nif(Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB){\r\n    mWebView.setLayerType(View.LAYER_TYPE_HARDWARE, null);\r\n}\r\n```\r\n\r\n以上就是本篇文章的全部内容, 大致就说这么多,  在实际的项目中我们通常会自己去封装一个H5Activity用来统一显示H5页面, 下面就提供了完整的H5Activity, 封装了WebView各种特性与jockeyjs代码交互。\r\n               \r\n该H5Activity提供WebView常用设置、H5页面解析、标题解析、进度条显示、错误页面展示、重新加载等功能。可以拿去稍作改造, 用于自己的项目中。\r\n               \r\n```java\r\npackage com.qmsggg.webview;\r\n\r\nimport android.content.Context;\r\nimport android.graphics.Bitmap;\r\nimport android.net.ConnectivityManager;\r\nimport android.net.NetworkInfo;\r\nimport android.os.Build;\r\nimport android.os.Bundle;\r\nimport android.support.v7.app.AppCompatActivity;\r\nimport android.support.v7.widget.Toolbar;\r\nimport android.text.TextUtils;\r\nimport android.util.Log;\r\nimport android.view.KeyEvent;\r\nimport android.view.View;\r\nimport android.view.Window;\r\nimport android.webkit.JsResult;\r\nimport android.webkit.WebChromeClient;\r\nimport android.webkit.WebResourceError;\r\nimport android.webkit.WebResourceRequest;\r\nimport android.webkit.WebSettings;\r\nimport android.webkit.WebView;\r\nimport android.webkit.WebViewClient;\r\nimport android.widget.ProgressBar;\r\n\r\nimport com.jockeyjs.Jockey;\r\nimport com.jockeyjs.JockeyImpl;\r\n\r\npublic class H5Activity extends AppCompatActivity {\r\n\r\n    public static final String H5_URL = \"H5_URL\";\r\n    private static final String JOCKEY_EVENT_NAME = \"JOCKEY_EVENT_NAME\";\r\n    private static final String TAG = H5Activity.class.getSimpleName();\r\n\r\n    private Toolbar mToolbar;\r\n    private ProgressBar mProgressBar;\r\n\r\n    private Jockey mJockey;\r\n    private WebView mWebView;\r\n    private WebViewClient mWebViewClient;\r\n    private WebChromeClient mWebChromeClient;\r\n\r\n    private String mUrl;\r\n\r\n    @Override\r\n    protected void onCreate(Bundle savedInstanceState) {\r\n        super.onCreate(savedInstanceState);\r\n        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);\r\n        setContentView(R.layout.activity_h5);\r\n        setupView();\r\n        setupSettings();\r\n    }\r\n\r\n    @Override\r\n    protected void onStart() {\r\n        super.onStart();\r\n        setupJockey();\r\n        setupData();\r\n    }\r\n\r\n    private void setupView() {\r\n        mToolbar = (Toolbar) findViewById(R.id.h5_toolbar);\r\n        mProgressBar = (ProgressBar) findViewById(R.id.h5_progressbar);\r\n        mWebView = (WebView) findViewById(R.id.h5_webview);\r\n    }\r\n\r\n    private void setupSettings() {\r\n\r\n        mWebView.setScrollBarStyle(WebView.SCROLLBARS_INSIDE_OVERLAY);\r\n        mWebView.setHorizontalScrollBarEnabled(false);\r\n        mWebView.setOverScrollMode(WebView.OVER_SCROLL_NEVER);\r\n\r\n        WebSettings mWebSettings = mWebView.getSettings();\r\n        mWebSettings.setSupportZoom(true);\r\n        mWebSettings.setLoadWithOverviewMode(true);\r\n        mWebSettings.setUseWideViewPort(true);\r\n        mWebSettings.setDefaultTextEncodingName(\"utf-8\");\r\n        mWebSettings.setLoadsImagesAutomatically(true);\r\n\r\n        //JS\r\n        mWebSettings.setJavaScriptEnabled(true);\r\n        mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);\r\n\r\n        mWebSettings.setAllowFileAccess(true);\r\n        mWebSettings.setUseWideViewPort(true);\r\n        mWebSettings.setDatabaseEnabled(true);\r\n        mWebSettings.setLoadWithOverviewMode(true);\r\n        mWebSettings.setDomStorageEnabled(true);\r\n\r\n\r\n        //缓存\r\n        ConnectivityManager connectivityManager = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);\r\n        NetworkInfo info = connectivityManager.getActiveNetworkInfo();\r\n        if (info != null && info.isConnected()) {\r\n            String wvcc = info.getTypeName();\r\n            Log.d(TAG, \"current network: \" + wvcc);\r\n            mWebSettings.setCacheMode(WebSettings.LOAD_DEFAULT);\r\n        } else {\r\n            Log.d(TAG, \"No network is connected, use cache\");\r\n            mWebSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);\r\n        }\r\n\r\n        if (Build.VERSION.SDK_INT >= 16) {\r\n            mWebSettings.setAllowFileAccessFromFileURLs(true);\r\n            mWebSettings.setAllowUniversalAccessFromFileURLs(true);\r\n        }\r\n\r\n        if (Build.VERSION.SDK_INT >= 12) {\r\n            mWebSettings.setAllowContentAccess(true);\r\n        }\r\n\r\n        setupWebViewClient();\r\n        setupWebChromeClient();\r\n    }\r\n\r\n    private void setupJockey() {\r\n        mJockey = JockeyImpl.getDefault();\r\n        mJockey.configure(mWebView);\r\n        mJockey.setWebViewClient(mWebViewClient);\r\n        mJockey.setOnValidateListener(new Jockey.OnValidateListener() {\r\n            @Override\r\n            public boolean validate(String host) {\r\n                return \"yourdomain.com\".equals(host);\r\n            }\r\n        });\r\n\r\n        //TODO set your event handler\r\n        mJockey.on(JOCKEY_EVENT_NAME, new EventHandler());\r\n    }\r\n\r\n    private void setupData() {\r\n        mUrl = getIntent().getStringExtra(H5_URL);\r\n        if (TextUtils.isEmpty(mUrl)) {\r\n            //TODO show error page\r\n        } else {\r\n            mWebView.loadUrl(mUrl);\r\n        }\r\n    }\r\n\r\n    private void setupWebViewClient() {\r\n        mWebViewClient = new WebViewClient() {\r\n            @Override\r\n            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {\r\n                //TODO 处理URL, 例如对指定的URL做不同的处理等\r\n                return false;\r\n            }\r\n\r\n            @Override\r\n            public void onPageFinished(WebView view, String url) {\r\n                super.onPageFinished(view, url);\r\n            }\r\n\r\n            @Override\r\n            public void onPageStarted(WebView view, String url, Bitmap favicon) {\r\n                super.onPageStarted(view, url, favicon);\r\n            }\r\n\r\n            @Override\r\n            public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {\r\n                super.onReceivedError(view, request, error);\r\n            }\r\n        };\r\n        mWebView.setWebViewClient(mWebViewClient);\r\n    }\r\n\r\n    private void setupWebChromeClient() {\r\n        mWebChromeClient = new WebChromeClient() {\r\n            @Override\r\n            public void onReceivedTitle(WebView view, String title) {\r\n                super.onReceivedTitle(view, title);\r\n                mToolbar.setTitle(title);\r\n\r\n            }\r\n\r\n            @Override\r\n            public void onProgressChanged(WebView view, int newProgress) {\r\n                super.onProgressChanged(view, newProgress);\r\n                mProgressBar.setProgress(newProgress);\r\n                if (newProgress == 100) {\r\n                    mProgressBar.setVisibility(View.GONE);\r\n                } else {\r\n                    mProgressBar.setVisibility(View.VISIBLE);\r\n                }\r\n            }\r\n\r\n            @Override\r\n            public boolean onJsAlert(WebView view, String url, String message, JsResult result) {\r\n                return super.onJsAlert(view, url, message, result);\r\n            }\r\n        };\r\n        mWebView.setWebChromeClient(mWebChromeClient);\r\n    }\r\n\r\n    @Override\r\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\r\n        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {\r\n            mWebView.goBack();\r\n            return true;\r\n        }\r\n        return super.onKeyDown(keyCode, event);\r\n    }\r\n}\r\n```"}}
{"actor":{"display_login":"qmsggg"}}