Before you know it, Hybird App has become a mainstream development method.
For functions requiring high user experience or interacting with hardware, we generally adopt the Native way to achieve them. However, there is little user interaction, and the function of activity class is usually realized in the way of H5. For example, the detailed display page of news app is usually the PAGE of H5
- On the one hand, the Web has an innate advantage in graphic layout. At the same time, the performance experience of pure display pages on the current mobile devices has made it difficult for users to distinguish between web pages and native pages.
- On the other hand, H5’s cross-platform page makes it easy to share on the native client and has a strong spread. Common activity pages also have such advantages, so the activity pages you see are basically H5, which can be shared to various platforms with a simple click.
- At the same time, the H5 page development reduces the development cost, a set of code, web, Android, ios can be accessed. (In actual development, however, the H5 adaptation was all tears)
Given all the advantages of the Hybird App, how can we embed H5 pages in native projects on Android?
Then we have to mention our WebVew, as the official only used to display the Web component, the display of web pages such tasks can only be entrusted to it.
A View that displays web pages. This class is the basis upon which you can roll your own web browser or simply display some online content within your Activity. It uses the WebKit rendering engine to display web pages and includes methods to navigate forward and backward through a history, zoom in and out, perform text searches and more.
WebView is a view component used to display our web pages in the Activity. It renders and displays our Web pages through the WebKit rendering engine, and includes the web history navigation manipulation, page zooming in and out, text search and other methods.
Let’s first look at the basic usage of WebView:
The basic usage of WebView
As for the basic usage of WebView, most people are also familiar with it. They wrote part of it originally, but accidentally found that the introduction of WebView on a blogger’s blog was too detailed. Lazy people like me would not write better articles by themselves, so I deleted what I wrote and shared the blog of the Blogger. Those who are interested can have a look together:
Android WebView development details (a) Android WebView development details (two) Android WebView development details (three)
Now that you know the basics of WebView usage, let’s summarize some of the problems we’ve encountered with WebView in recent projects
# Problems with using WebView in projects
### Native title Settings for WebView interface
As shown in the figure, under normal circumstances, the interface where our WebView is located consists of the native navigation bar with title at the top and the content part of the WebView, and the interface in WebView may jump to other Web pages after clicking on it (as shown in the figure, clicking to ask for leave will jump to the Web page for asking for leave in the current WebView).
由于点击内容的不确定性,所以通常情况下,最简单的做法就是捕获h5页面的 </a> 标签来进行标题设置。</p> <p>对于捕获 <a href=”” target=”_blank”><title></a> 标签内容的方式,WebView也很好地提供了支持,我们可以通过继承WebChromeClient的onReceivedTitle来进行获取:</p> <pre><code lang=”bash” class=”hljs bash”> private class WebViewChromeClient extends WebChromeClient { @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); mTitleText.setTitle(String.valueOf(view.getTitle())); } } </code></pre><p>然而这样的方式在实际使用中有一个问题:</p> <p>当通过 <a href=”https://developer.android.google.cn/reference/android/webkit/WebView.html#goBack()” target=”_blank”>webView.goBack()</a> 方式返回上一级Web页面的时候不会触发这个方法,因此会导致标题无法跟随历史记录返回上一级页面。</p> <p>所以在项目中, 我们可以通过重写 <a href=”https://developer.android.google.cn/reference/android/webkit/WebViewClient.html” target=”_blank”>WebViewClient</a> 的 [onPageFinished](https://developer.android.google.cn/reference/android/webkit/WebViewClient.html#onPageFinished(android.webkit.WebView, java.lang.String)) 方法,在 [onPageFinished](https://developer.android.google.cn/reference/android/webkit/WebViewClient.html#onPageFinished(android.webkit.WebView, java.lang.String)) 中对界面标题进行设置。 因为不管是历史记录的返回还是点击跳转都会触发页面加载, 当页面加载完成时(不包括js动态创建以及img图片加载完毕)都会触发 [onPageFinished](https://developer.android.google.cn/reference/android/webkit/WebViewClient.html#onPageFinished(android.webkit.WebView, java.lang.String)) 这个方法, 此时我们去获取 <a href=”” target=”_blank”><title></a> 的标题内容不会有任何问题,可以确保在页面返回时能够获取到正确的标题。</p> <pre><code lang=”bash” class=”hljs bash”> mWebView.setWebViewClient(new <span class=”hljs-function”><span class=”hljs-title”>WebViewClient</span></span>(){ //Web页面每次加载并完成时会触发该方法 @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); mToolbar.setTitle(String.valueOf(view.getTitle())); Log.i (LOG_TAG, <span class=”hljs-string”>”onPageFinished”</span>); } }); </code></pre><p><strong>注: 这种做法有一个缺陷,就是返回上一个界面的时候,等页面加载完成的时候标题才会显示出来,为了更好地优化,我们可以创建一个集合用来保存我们的标题,加载url的时候把标题添加进集合,当返回上一级页面的时候,从集合中取出标题进行显示,同时从集合中移除标题。</strong></p> <hr> <p>###WebView中的Web页面存在<input type=’file’>标签时无法打开文件选择器</p> <p>在我们的手机浏览器中,当web页面中有 <a href=”” target=”_blank”><input type=’file’></a> 按钮标签的时候点击会自动打开系统的文件选择器, 然而这个功能在主流系统的WebView中没有被默认实现, 因此,为了让 <a href=”” target=”_blank”><input type=’file’></a> 点击时能够打开系统的文件选择器, 我们必须通过重写 <a href=”https://developer.android.google.cn/reference/android/webkit/WebChromeClient.html” target=”_blank”>WebChromeClient</a> 来实现点击<a href=”” target=”_blank”><input type=’file’></a> 打开系统文件选择器。 代码如下:</p> <pre><code lang=”bash” class=”hljs bash”>public class MainActivity extends AppCompatActivity { /** Android 5.0以下版本的文件选择回调 */ protected ValueCallback<Uri> mFileUploadCallbackFirst; /** Android 5.0及以上版本的文件选择回调 */ protected ValueCallback<Uri[]> mFileUploadCallbackSecond; protected static final int REQUEST_CODE_FILE_PICKER = 51426; protected String mUploadableFileTypes = <span class=”hljs-string”>”image/*”</span>; private WebView mWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); <span class=”hljs-built_in”>set</span>ContentView(R.layout.activity_main); initWebView(); } private void <span class=”hljs-function”><span class=”hljs-title”>initWebView</span></span>() { mWebView = (WebView) findViewById(R.id.my_webview); mWebView.loadUrl(<span class=”hljs-string”>”file:///android_asset/index.html”</span>); mWebView.setWebChromeClient(new OpenFileChromeClient()); } private class OpenFileChromeClient extends WebChromeClient { // Android 2.2 (API level 8)到Android 2.3 (API level 10)版本选择文件时会触发该隐藏方法 @SuppressWarnings(<span class=”hljs-string”>”unused”</span>) public void openFileChooser(ValueCallback<Uri> uploadMsg) { openFileChooser(uploadMsg, null); } // Android 3.0 (API level 11)到 Android 4.0 (API level 15))版本选择文件时会触发,该方法为隐藏方法 public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { openFileChooser(uploadMsg, acceptType, null); } // Android 4.1 (API level 16) — Android 4.3 (API level 18)版本选择文件时会触发,该方法为隐藏方法 @SuppressWarnings(<span class=”hljs-string”>”unused”</span>) public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { openFileInput(uploadMsg, null, <span class=”hljs-literal”>false</span>); } // Android 5.0 (API level 21)以上版本会触发该方法,该方法为公开方法 @SuppressWarnings(<span class=”hljs-string”>”all”</span>) public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { <span class=”hljs-keyword”>if</span> (Build.VERSION.SDK_INT >= 21) { final boolean allowMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;//是否支持多选 openFileInput(null, filePathCallback, allowMultiple); <span class=”hljs-built_in”>return</span> <span class=”hljs-literal”>true</span>; } <span class=”hljs-keyword”>else</span> { <span class=”hljs-built_in”>return</span> <span class=”hljs-literal”>false</span>; } } } @SuppressLint(<span class=”hljs-string”>”NewApi”</span>) protected void openFileInput(final ValueCallback<Uri> fileUploadCallbackFirst, final ValueCallback<Uri[]> fileUploadCallbackSecond, final boolean allowMultiple) { //Android 5.0以下版本 <span class=”hljs-keyword”>if</span> (mFileUploadCallbackFirst != null) { mFileUploadCallbackFirst.onReceiveValue(null); } mFileUploadCallbackFirst = fileUploadCallbackFirst; //Android 5.0及以上版本 <span class=”hljs-keyword”>if</span> (mFileUploadCallbackSecond != null) { mFileUploadCallbackSecond.onReceiveValue(null); } mFileUploadCallbackSecond = fileUploadCallbackSecond; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); <span class=”hljs-keyword”>if</span> (allowMultiple) { <span class=”hljs-keyword”>if</span> (Build.VERSION.SDK_INT >= 18) { i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, <span class=”hljs-literal”>true</span>); } } i.setType(mUploadableFileTypes); startActivityForResult(Intent.createChooser(i, <span class=”hljs-string”>”选择文件”</span>), REQUEST_CODE_FILE_PICKER); } public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { <span class=”hljs-keyword”>if</span> (requestCode == REQUEST_CODE_FILE_PICKER) { <span class=”hljs-keyword”>if</span> (resultCode == Activity.RESULT_OK) { <span class=”hljs-keyword”>if</span> (intent != null) { //Android 5.0以下版本 <span class=”hljs-keyword”>if</span> (mFileUploadCallbackFirst != null) { mFileUploadCallbackFirst.onReceiveValue(intent.getData()); mFileUploadCallbackFirst = null; } <span class=”hljs-keyword”>else</span> <span class=”hljs-keyword”>if</span> (mFileUploadCallbackSecond != null) {//Android 5.0及以上版本 Uri[] dataUris = null; try { <span class=”hljs-keyword”>if</span> (intent.getDataString() != null) { dataUris = new Uri[] { Uri.parse(intent.getDataString()) }; } <span class=”hljs-keyword”>else</span> { <span class=”hljs-keyword”>if</span> (Build.VERSION.SDK_INT >= 16) { <span class=”hljs-keyword”>if</span> (intent.getClipData() != null) { final int numSelectedFiles = intent.getClipData().getItemCount(); dataUris = new Uri[numSelectedFiles]; <span class=”hljs-keyword”>for</span> (int i = 0; i < numSelectedFiles; i++) { dataUris[i] = intent.getClipData().getItemAt(i).getUri(); } } } } } catch (Exception ignored) { } mFileUploadCallbackSecond.onReceiveValue(dataUris); mFileUploadCallbackSecond = null; } } } <span class=”hljs-keyword”>else</span> { //这里mFileUploadCallbackFirst跟mFileUploadCallbackSecond在不同系统版本下分别持有了 //WebView对象,在用户取消文件选择器的情况下,需给onReceiveValue传null返回值 //否则WebView在未收到返回值的情况下,无法进行任何操作,文件选择器会失效 <span class=”hljs-keyword”>if</span> (mFileUploadCallbackFirst != null) { mFileUploadCallbackFirst.onReceiveValue(null); mFileUploadCallbackFirst = null; } <span class=”hljs-keyword”>else</span> <span class=”hljs-keyword”>if</span> (mFileUploadCallbackSecond != null) { mFileUploadCallbackSecond.onReceiveValue(null); mFileUploadCallbackSecond = null; } } } } } </code></pre><p><strong>注:当用户点击input file弹出文件选择器后,点击取消或者返回按钮没有执行选择时,必须在onActivityResult里给valueCallback的onReceiveValue传null,因为valueCallback持有的是WebView,在onReceiveValue没有回传值的情况下,WebView无法进行下一步操作,会导致取消选择文件后,点击input file不会再响应:</strong></p> <pre><code lang=”bash” class=”hljs bash”> <span class=”hljs-keyword”>if</span> (mFileUploadCallbackFirst != null) { mFileUploadCallbackFirst.onReceiveValue(null); mFileUploadCallbackFirst = null; } <span class=”hljs-keyword”>else</span> <span class=”hljs-keyword”>if</span> (mFileUploadCallbackSecond != null) { mFileUploadCallbackSecond.onReceiveValue(null); mFileUploadCallbackSecond = null; } </code></pre><p>示例demo地址: https://github.com/cjpx00008/FileChooser4WebViewDemo</p> <hr> <p>###WebView中的web页面调用系统选择器或者相机导致app进入后台被系统释放</p> <p>众所周知,WebView基于webkit内核来渲染web页面,因此使用起来相当于一个小型浏览器,即使页面内容不复杂,只要使用WebView也会占用大量的内存。</p> <p>而Android的内存回收机制,在系统内存不足的情况下会优先释放内存占用较大的app从而回收内存资源,此时正在使用WebView的运行在后台的app肯定是首当其冲被回收的。</p> <p>因此,当WebView通过input file调用系统文件选择器,或者通过文件选择器调用了相机时,我们的app就进入了后台,在部分低端Android设备(尤其红米这类手机,默认的神隐模式会在app进入后台的时候较大概率的释放app)或者系统内存资源不足的情况下,我们的app就会优先被释放掉,导致文件选择完毕后,回到上一界面时,app的界面重新走了onCreate,web页面也因此重建了。</p> <p>对于部分需要填写大量表单的web页面来说,用户填写的数据会随着界面的销毁重建而丢失,而选择的文件也因为页面的重建而无法回传给input file,这对于用户的体验来说肯定是不友好的。</p> <p>也许你会说,重写onSaveInstance保存数据就是啦。 这也是我一开始考虑的, 我们的WebView也提供了 <a href=”https://developer.android.google.cn/reference/android/webkit/WebView.html#saveState(android.os.Bundle)” target=”_blank”>saveState</a> 以及 <a href=”https://developer.android.google.cn/reference/android/webkit/WebView.html#restoreState(android.os.Bundle)” target=”_blank”>restoreState</a> 来保存状态。</p> <p>然而悲催的是,这两个方法并不会保存web页面内的数据,它只保存了WebView加载的页面,前进后退的历史状态等数据。</p> <p>引用官方文档的描述:</p> <blockquote> <p>Saves the state of this WebView used in <a href=”https://developer.android.google.cn/reference/android/app/Activity.html#onSaveInstanceState(android.os.Bundle)” target=”_blank”>onSaveInstanceState(Bundle)</a> . Please note that this method no longer stores the display data for this WebView. The previous behavior could potentially leak files if <a href=”https://developer.android.google.cn/reference/android/webkit/WebView.html#restoreState(android.os.Bundle)” target=”_blank”>restoreState(Bundle)</a> was never called.</p> </blockquote> <p><strong>Please note that this method no longer stores the display data for this WebView</strong></p> <p>WebView的saveState并不会保存界面的数据。</p> <p>所以,对于表单数据的恢复,我们只能自己想办法了,我们这里采用了两套方案:</p> <ol> <li>通过WebView与JS交互,在onSaveInstance的时候触发界面保存数据,保存数据的方式也大体分为两种, 一种使用H5自带的localStorage来进行数据存储,页面销毁重建的时候H5页面判断本地localStorage数据是否有值,有就将值重新填充到页面表单,提交数据后清除本地localStorage的数据。 这种方式需要给WebView开启对localStorage的支持。</li> </ol> <pre><code lang=”bash” class=”hljs bash”>WebSettings settings = mWebView.getSettings(); settings.setDomStorageEnabled(<span class=”hljs-literal”>true</span>); </code></pre><p>另一种则提供JS接口将数据传递给原生,通过原生代码将数据保存到本地,在页面重建渲染完成时,web页面通过JS接口调用原生方法拉取数据判断是否有值,有则填充表单,无则不做操作,提交数据后调用JS接口调用原生方法清空本地数据。</p> <ol start=”2″> <li>由web端自己处理,在表单页面文本输入失去焦点时自动保存数据,页面销毁重建时,自己拉取数据进行判断。 这种方式对原生的依赖较低,个人更倾向这种方式,当然最终由于项目的特殊情况,我们还是采用了第一种方式。</li> </ol> <p>以上是表单数据的恢复方案,</p> <p>而对于从系统文件选择器选择的文件web页面是无法直接接收并处理了,这里我们提供了一个JS接口在web页面加载完成时,进行触发,并将数据传递给web页面。</p> <p>说到这里,不得不提另外一个问题</p> <hr> <p>###WebView调用服务端页面如何访问本地文件</p> <p>上面我们提到了通过JS接口将选择的文件数据传递给web页面,</p> <p>然而由于安全原因,WebView限制了远程url页面访问本地文件, 如果我们加载的url是服务端的页面,那我们没有任何办法直接通过文件地址来访问客户端本地的文件</p> <p>我们知道,WebView用来加载网页的方式主要有三种:</p> <pre><code lang=”bash” class=”hljs bash”>loadUrl(String url) loadUrl(String url, Map<String, String> additionalHttpHeaders) </code></pre><pre><code lang=”bash” class=”hljs bash”>loadData(String data, String mimeType, String encoding) </code></pre><pre><code lang=”bash” class=”hljs bash”>loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String <span class=”hljs-built_in”>history</span>Url) </code></pre><p>[loadData()](https://developer.android.google.cn/reference/android/webkit/WebView.html#loadData(java.lang.String, java.lang.String, java.lang.String)) 和 [loadDataWithBaseURL()](https://developer.android.google.cn/reference/android/webkit/WebView.html#loadDataWithBaseURL(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)) 都是直接将数据加载进WebView中,相当于显示的一个本地Web</p> <p>loadUrl也可以通过访问本地的文件地址(例如本地asset目录下的存放了index.html页面,可以通过<code>loadUrl(“file:///android_asset/index.html”)</code>来显示web页面)</p> <p>对于这样的三种加载本地内容的方式,我们可以使用多种方式来传递路径供web页面传递,这里以图片为例(相册目录下test/IMG_20170105_093405.jpg):</p> <ol> <li>直接通过文件的绝对地址来提供给页面显示:</li> </ol> <pre><code lang=”bash” class=”hljs bash”><img src = <span class=”hljs-string”>’file:///storage/emulated/0/dcim/test/IMG_20170105_093405.jpg'</span> /> </code></pre><ol start=”2″> <li>通过媒体库查询出来的content uri地址展示</li> </ol> <pre><code lang=”bash” class=”hljs bash”><img src = <span class=”hljs-string”>’content://media/external/images/media/102610′</span> /> </code></pre><ol start=”3″> <li>通过FileProvider转换的content uri地址展示</li> </ol> <pre><code lang=”bash” class=”hljs bash”><img src = <span class=”hljs-string”>’content://com.test.myfileprovider/dcim/test/IMG_20170105_093405.jpg'</span>/> </code></pre><p>可当你使用loadUrl(String url)加载服务端的http地址时,以上三种方法将均无法使用,经过各种尝试,目前找到两种方案来提供给web端进行图片显示:</p> <ol> <li> <p>由原生代码处理,将文件流转换为Base64之后通过JS接口回传给web;</p> </li> <li> <p>重写WebViewClient里的shouldInterceptRequest方法,每当页面发生资源请求的时候就会触发这个方法,我们可以过滤请求,判断请求是否为本地文件,通过拦截请求转换为二进制流回传回去, 示例代码如下:</p> </li> </ol> <pre><code lang=”bash” class=”hljs bash”>mWebView.setWebViewClient(new <span class=”hljs-function”><span class=”hljs-title”>WebViewClient</span></span>(){ @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { <span class=”hljs-keyword”>if</span> (url.startsWith(<span class=”hljs-string”>”http://”</span>)&&url.endWith(<span class=”hljs-string”>”.jpg”</span>) { <span class=”hljs-built_in”>return</span> getWebResourceResponse(<span class=”hljs-string”>”/storage/emulated/0/dcim/trinaic/IMG_20170105_093405.jpg”</span>, <span class=”hljs-string”>”image/jpeg”</span>, <span class=”hljs-string”>”.jpg”</span>); } <span class=”hljs-built_in”>return</span> super.shouldInterceptRequest(view, url); } } private WebResourceResponse getWebResourceResponse(String url, String mime, String style) { WebResourceResponse response = null; try { response = new WebResourceResponse(mime, <span class=”hljs-string”>”UTF-8″</span>, new FileInputStream(new File(url))); } catch (FileNotFoundException e) { e.printStackTrace(); } <span class=”hljs-built_in”>return</span> response; } </code></pre><hr> <p>###WebView JS注入漏洞</p> <p>要想让原生跟JS进行交互,按照官方提供的方法就得使用addJavaScriptInterface</p> <pre><code lang=”bash” class=”hljs bash”>class JsObject { @JavascriptInterface public String <span class=”hljs-function”><span class=”hljs-title”>toString</span></span>() { <span class=”hljs-built_in”>return</span> <span class=”hljs-string”>”injectedObject”</span>; } } webView.addJavascriptInterface(new JsObject(), <span class=”hljs-string”>”injectedObject”</span>); webView.loadData(<span class=”hljs-string”>””</span>, <span class=”hljs-string”>”text/html”</span>, null); webView.loadUrl(<span class=”hljs-string”>”javascript:alert(injectedObject.toString())”</span>); </code></pre><blockquote> <p>Injects the supplied Java object into this WebView. The object is injected into the JavaScript context of the main frame, using the supplied name. This allows the Java object’s methods to be accessed from JavaScript. For applications targeted to API level <a href=”https://developer.android.google.cn/reference/android/os/Build.VERSION_CODES.html#JELLY_BEAN_MR1″ target=”_blank”>JELLY_BEAN_MR1</a> and above, only public methods that are annotated with <a href=”https://developer.android.google.cn/reference/android/webkit/JavascriptInterface.html” target=”_blank”>JavascriptInterface</a> can be accessed from JavaScript. For applications targeted to API level <a href=”https://developer.android.google.cn/reference/android/os/Build.VERSION_CODES.html#JELLY_BEAN” target=”_blank”>JELLY_BEAN</a> or below, all public methods (including the inherited ones) can be accessed, see the important security note below for implications.</p> </blockquote> <p>引用官方api的说明,在Android 4.2以下,会有被注入的风险,4.2以上版本可以通过<code>@JavascriptInterface</code>的注解来处理这个问题。 具体的注入方式,我找了篇博客,如果有不清楚的同学可以了解下: <a href=”http://blog.csdn.net/leehong2005/article/details/11808557/” target=”_blank”>Android WebView的Js对象注入漏洞解决方案</a></p> <p>在之前乌云平台报出的漏洞中, android/webkit/webview中默认内置的一个searchBoxJavaBridge_ 接口同时存在远程代码执行漏洞</p> <p>在于android/webkit/AccessibilityInjector.java中,调用了此组件的应用在开启辅助功能选项中第三方服务的安卓系统中会造成远程代码执行漏洞。这两个接口分别是”accessibility” 和”accessibilityTraversal” ,此漏洞原理与searchBoxJavaBridge_接口远程代码执行相似,均为未移除不安全的默认接口,不过此漏洞需要用户启动系统设置中的第三方辅助服务,利用条件较复杂。</p> <p>因此,一般情况下我们通过removeJavaScripteInterface来移除这几个接口</p> <pre><code lang=”bash” class=”hljs bash”> <span class=”hljs-keyword”>if</span> (Build.VERSION.SDK_INT < 17) { mAdvanceWebView.removeJavascriptInterface(<span class=”hljs-string”>”searchBoxJavaBridge_”</span>); mAdvanceWebView.removeJavascriptInterface(<span class=”hljs-string”>”accessibility”</span>); mAdvanceWebView.removeJavascriptInterface(<span class=”hljs-string”>”accessibilityTraversal”</span>); } </code></pre><p>除此之外也有通过onJsPrompt的方式来实现WebView原生跟JS交互功能的,github上的开源项目JSBridge就是采用这种方法: https://github.com/lzyzsd/JsBridge</p> <p>之前拜读过大名鼎鼎的cordova的源码,它内部的原生JS交互也是采用onJsPrompt的方式,不过在此基础上做了更强大的封装。</p> <hr> <p>###WebView后台耗电问题</p> <p>当我们的WebView的web页面在解析或者播放视频再或者有js定时器在执行的时, 如果我们把应用退到后台,不做任何处理的情况下,以上的操作还会在后台继续执行,导致WebView在后台持续耗电,因此一般我们会做以下处理</p> <pre><code lang=”bash” class=”hljs bash”> @Override protected void <span class=”hljs-function”><span class=”hljs-title”>onPause</span></span>() { super.onPause(); mWebView.onPause();//暂停部分可安全处理的操作,如动画,定位,视频播放等 mWebView.pauseTimers();//暂停所有WebView的页面布局、解析以及JavaScript的定时器操作 } @Override protected void <span class=”hljs-function”><span class=”hljs-title”>onResume</span></span>() { super.onResume(); mWebView.onResume(); mWebView.resumeTimers(); } </code></pre><hr> <p>对于WebView的使用,在处理问题的过程中发现一个不错的开源库: https://github.com/delight-im/Android-AdvancedWebView</p> <p>基本上上面我提到的或者没提到的问题它都做了一定的封装处理,并且考虑了一些版本适配的问题,可以直接拿来使用,也可以拿来参考学习。</p> <p>如果你觉得问题还是太多的话也可以考虑使用<a href=”http://x5.tencent.com/” target=”_blank”>腾讯浏览服务</a>,基于QQ浏览器X5内核,适配了Android全部主流平台,可以在所有Android手机上使用Blink的技术能力,具有更好的H5/CSS3支持和性能,目前微信、qq都在使用它。</p> <p>唯一的缺陷就是它不提供打包内核版的SDK,第一次使用时,它会自动到腾讯服务端去下载内核,下载完毕后会弹窗提示用户是否重启app,重启之后就能正常使用x5浏览服务了,如果你不介意这样的用户体验,可以考虑直接使用腾讯浏览服务。</p> <hr> <p>(补充) ###WebView混淆问题</p> <p>如果app打包混淆之后发现提供给web页面的JS接口失效了,记得检查是否添加了JavaScriptInterface的混淆配置:</p> <pre><code lang=”bash” class=”hljs bash”>-keepclassmembers class * { @android.webkit.JavascriptInterface <methods>; } </code></pre><p>###红米WebView内部Web页面的div自身滚动条问题 红米上WebView内部的Web页面的div由于内容高度大于div,产生了基于div的滚动条(WebView滚动条已禁用的情况下),通过设置div的css样式来禁用div滚动条</p> <pre><code lang=”bash” class=”hljs bash”>Html dom元素ID或class:: -webkit-scrollbar {display:none} </code></pre><p>###WebView内部web页面px跟dp的关系 经测试发现,WebView内部web页面的px值会在内部自动转换为dp,且1px=1dp,跟ppi值无关,这点跟原生开发中的1dp = 设备ppi/160 * px换算关系nveou</p>