| Scott Main | dc277e1 | 2013-10-29 17:56:00 -0700 | [diff] [blame] | 1 | page.title=Migrating to WebView in Android 4.4 |
| 2 | @jd:body |
| 3 | |
| 4 | <div id="qv-wrapper"> |
| 5 | <div id="qv"> |
| 6 | |
| 7 | <h2>In this document</h2> |
| 8 | <ol> |
| 9 | <li><a href="#UserAgent">User Agent Changes</a></li> |
| 10 | <li><a href="#Threads">Multi-threading and Thread Blocking</a></li> |
| 11 | <li><a href="#URLs">Custom URL Handling</a></li> |
| 12 | <li><a href="#Viewport">Viewport Changes</a> |
| 13 | <ol> |
| 14 | <li><a href="#TargetDensity">Viewport target-densitydpi no longer supported</a></li> |
| 15 | <li><a href="#SmallViewport">Viewport zooms in when small</a></li> |
| 16 | <li><a href="#MultiViewport">Multiple viewport tags not supported</a></li> |
| 17 | <li><a href="#Zoom">Default zoom is deprecated</a></li> |
| 18 | </ol> |
| 19 | </li> |
| 20 | <li><a href="#Style">Styling Changes</a> |
| 21 | <ol> |
| 22 | <li><a href="#BackgroundSize">The background CSS shorthand overrides background-size</a></li> |
| 23 | <li><a href="#Pixels">Sizes are in CSS pixels instead of screen pixels</a></li> |
| 24 | <li><a href="#Columns">NARROW_COLUMNS and SINGLE_COLUMN no longer supported</a></li> |
| 25 | </ol> |
| 26 | </li> |
| 27 | <li><a href="#TouchCancel">Handling Touch Events in JavaScript</a></li> |
| 28 | </ol> |
| 29 | |
| 30 | </div> |
| 31 | </div> |
| 32 | |
| 33 | |
| 34 | |
| 35 | |
| 36 | |
| 37 | |
| 38 | |
| 39 | |
| 40 | |
| 41 | |
| 42 | <p>Android 4.4 (API level 19) introduces a new version of {@link android.webkit.WebView} that is |
| 43 | based on <a href="http://www.chromium.org/Home">Chromium</a>. This change upgrades |
| 44 | {@link android.webkit.WebView} performance and standards support for HTML5, CSS3, and JavaScript |
| 45 | to match the latest web browsers. Any apps using {@link android.webkit.WebView} will inherit these |
| 46 | upgrades when running on Android 4.4 and higher.</p> |
| 47 | |
| 48 | <p>This document describes additional changes |
| 49 | to {@link android.webkit.WebView} that you should be aware of if you set your |
| 50 | <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code |
| 51 | targetSdkVersion}</a> to "19" or higher.</p> |
| 52 | |
| 53 | <p class="note"><strong>Note:</strong> |
| 54 | If your <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code |
| 55 | targetSdkVersion}</a> is set to "18" or lower, {@link android.webkit.WebView} operates in |
| 56 | "quirks mode" in order to avoid some of the behavior changes described below, as closely |
| 57 | as possible—while still providing your app the performance and web standards upgrades. |
| 58 | Beware, though, that <a href="#Columns">single and narrow column layouts</a> and <a |
| 59 | href="#Zoom">default zoom levels</a> are |
| 60 | <strong>not supported at all</strong> on Android 4.4, and there may be other behavioral differences |
| 61 | that have not been identified, so be sure to test your app on Android 4.4 |
| 62 | or higher even if you keep your <a |
| 63 | href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code |
| 64 | targetSdkVersion}</a> set to "18" or lower. </p> |
| 65 | |
| 66 | <p>To help you work through any issues you may encounter when migrating your app to |
| 67 | {@link android.webkit.WebView} in Android 4.4, you can enable remote debugging through |
| 68 | Chrome on your desktop by calling |
| 69 | {@link android.webkit.WebView#setWebContentsDebuggingEnabled setWebContentsDebuggingEnabled()}. |
| 70 | This new feature in {@link android.webkit.WebView} allows |
| 71 | you to inspect and analyze your web content, scripts, and network activity while running in |
| 72 | a {@link android.webkit.WebView}. For more information, see <a |
| 73 | href="https://developers.google.com/chrome-developer-tools/docs/remote-debugging">Remote |
| 74 | Debugging on Android</a>.</p> |
| 75 | |
| 76 | |
| 77 | |
| 78 | |
| 79 | <h2 id="UserAgent">User Agent Changes</h2> |
| 80 | |
| 81 | <p>If you serve content to your {@link android.webkit.WebView} based on the user agent, you should |
| 82 | to be aware of the user agent string has changed slightly and now includes the Chrome version:</p> |
| 83 | |
| 84 | <pre class="no-pretty-print"> |
| 85 | Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16H) AppleWebKit/537.36 |
| 86 | (KHTML, like Gecko) Version/4.0 <strong>Chrome/30.0.0.0</strong> Mobile Safari/537.36 |
| 87 | </pre> |
| 88 | |
| 89 | <p>If you need to retrieve the user agent but don't need to store it for your app or |
| 90 | do not want to instantiate {@link android.webkit.WebView}, you should use |
| 91 | the static method, {@link android.webkit.WebSettings#getDefaultUserAgent |
| 92 | getDefaultUserAgent()}. However, if you intend to override the user agent string in your |
| 93 | {@link android.webkit.WebView}, you may instead want to use |
| 94 | {@link android.webkit.WebSettings#getUserAgentString getUserAgentString()}.</p> |
| 95 | |
| 96 | |
| 97 | |
| 98 | |
| 99 | <h2 id="Threads">Multi-threading and Thread Blocking</h2> |
| 100 | |
| 101 | <p>If you call methods on {@link android.webkit.WebView} from any thread other than your app's |
| 102 | UI thread, it can cause unexpected results. For example, if your app uses multiple threads, |
| 103 | you can use the {@link android.app.Activity#runOnUiThread runOnUiThread()} method |
| 104 | to ensure your code executes on the UI thread:</p> |
| 105 | |
| 106 | <pre> |
| 107 | runOnUiThread(new Runnable() { |
| 108 | @Override |
| 109 | public void run() { |
| 110 | // Code for WebView goes here |
| 111 | } |
| 112 | }); |
| 113 | </pre> |
| 114 | |
| 115 | <p>Also be sure that you <a href="{@docRoot}guide/components/processes-and-threads.html#Threads"> |
| 116 | never block the UI thread</a>. A situation in which some apps make this mistake is while waiting for |
| 117 | a JavaScript callback. For example, <strong>do not</strong> use code like this:</p> |
| 118 | |
| 119 | <pre> |
| 120 | // This code is BAD and will block the UI thread |
| 121 | webView.loadUrl("javascript:fn()"); |
| 122 | while(result == null) { |
| 123 | Thread.sleep(100); |
| 124 | } |
| 125 | </pre> |
| 126 | |
| 127 | <p>You can instead use a new method, {@link android.webkit.WebView#evaluateJavascript |
| 128 | evaluateJavascript()}, to run JavaScript asynchronously.</p> |
| 129 | |
| 130 | |
| 131 | |
| 132 | |
| 133 | |
| 134 | <h2 id="URLs">Custom URL Handling</h2> |
| 135 | |
| 136 | <p>The new {@link android.webkit.WebView} applies additional restrictions when requesting resources |
| 137 | and resolving links that use a custom URL scheme. For example, if you implement callbacks such as |
| 138 | {@link android.webkit.WebViewClient#shouldOverrideUrlLoading shouldOverrideUrlLoading()} or |
| 139 | {@link android.webkit.WebViewClient#shouldInterceptRequest shouldInterceptRequest()}, then |
| 140 | {@link android.webkit.WebView} invokes them only for valid URLs.</p> |
| 141 | |
| 142 | <p>If you are using a custom URL scheme or a base URL and |
| 143 | notice that your app is receiving fewer calls to these callbacks or failing |
| 144 | to load resources on Android 4.4, ensure that the requests specify valid URLs that conform to |
| 145 | <a href="http://tools.ietf.org/html/rfc3986#appendix-A">RFC 3986</a>. |
| 146 | |
| 147 | <p>For example, the new {@link android.webkit.WebView} may not call your |
| 148 | {@link android.webkit.WebViewClient#shouldOverrideUrlLoading shouldOverrideUrlLoading()} method |
| 149 | for links like this:</p> |
| 150 | |
| 151 | <pre><a href="showProfile">Show Profile</a></pre> |
| 152 | |
| 153 | <p>The result of the user clicking such a link can vary: |
| 154 | <ul> |
| 155 | <li>If you loaded the page by calling {@link android.webkit.WebView#loadData |
| 156 | loadData()} or {@link android.webkit.WebView#loadDataWithBaseURL |
| 157 | loadDataWithBaseURL()} with an invalid or null base URL, then you will not receive the |
| 158 | {@link android.webkit.WebViewClient#shouldOverrideUrlLoading shouldOverrideUrlLoading()} callback |
| 159 | for this type of link on the page. |
| 160 | <p class="note"><strong>Note:</strong> |
| 161 | When you use {@link android.webkit.WebView#loadDataWithBaseURL |
| 162 | loadDataWithBaseURL()} and the base URL is invalid or set null, all links in the content |
| 163 | you are loading must be absolute.</p> |
| 164 | <li>If you loaded the page by calling {@link android.webkit.WebView#loadUrl |
| 165 | loadUrl()} or provided a valid base URL with {@link android.webkit.WebView#loadDataWithBaseURL |
| 166 | loadDataWithBaseURL()}, then you will receive the |
| 167 | {@link android.webkit.WebViewClient#shouldOverrideUrlLoading shouldOverrideUrlLoading()} callback |
| 168 | for this type of link on the page, but the URL you receive will be absolute, relative |
| 169 | to the current page. For example, the URL you receive will be |
| 170 | <code>"http://www.example.com/showProfile"</code> instead of just <code>"showProfile"</code>. |
| 171 | </ul> |
| 172 | |
| 173 | |
| 174 | <p>Instead of using a simple string in a link as shown above, you can use a custom scheme such |
| 175 | as the following:</p> |
| 176 | |
| 177 | <pre><a href="example-app:showProfile">Show Profile</a></pre> |
| 178 | |
| 179 | <p>You can then handle this URL in your |
| 180 | {@link android.webkit.WebViewClient#shouldOverrideUrlLoading shouldOverrideUrlLoading()} method |
| 181 | like this:</p> |
| 182 | |
| 183 | <pre> |
| 184 | // The URL scheme should be non-hierarchical (no trailing slashes) |
| 185 | private static final String APP_SCHEME = "example-app:"; |
| 186 | |
| 187 | @Override |
| 188 | public boolean shouldOverrideUrlLoading(WebView view, String url) { |
| 189 | if (url.startsWith(APP_SCHEME)) { |
| 190 | urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8"); |
| 191 | respondToData(urlData); |
| 192 | return true; |
| 193 | } |
| 194 | return false; |
| 195 | } |
| 196 | </pre> |
| 197 | |
| 198 | <p>If you can't alter the HTML then you may be able to use |
| 199 | {@link android.webkit.WebView#loadDataWithBaseURL loadDataWithBaseURL()} and set a base URL |
| 200 | consisting of a custom scheme and a valid host, such as |
| 201 | <code>"example-app://<valid_host_name>/"</code>. For example:</p> |
| 202 | |
| 203 | <pre> |
| 204 | webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA, |
| 205 | null, "UTF-8", null); |
| 206 | </pre> |
| 207 | |
| 208 | <p>The valid host name should conform to |
| 209 | <a href="http://tools.ietf.org/html/rfc3986#appendix-A">RFC 3986</a> |
| 210 | and it's important to include the trailing slash at the end, otherwise, any requests from the |
| 211 | loaded page may be dropped.</p> |
| 212 | |
| 213 | |
| 214 | |
| 215 | <h2 id="Viewport">Viewport Changes</h2> |
| 216 | |
| 217 | |
| 218 | <h3 id="TargetDensity">Viewport target-densitydpi no longer supported</h3> |
| 219 | |
| 220 | <p>Previously, {@link android.webkit.WebView} supported a viewport property called |
| 221 | <code>target-densitydpi</code> to help web pages specify their intended screen density. This |
| 222 | property is no longer supported and you should migrate to using standard solutions with |
| 223 | images and CSS as discussed in <a |
| 224 | href="http://developers.google.com/chrome/mobile/docs/webview/pixelperfect">Pixel-Perfect UI in |
| 225 | the WebView</a>.</p> |
| 226 | |
| 227 | |
| 228 | <h3 id="SmallViewport">Viewport zooms in when small</h3> |
| 229 | |
| 230 | <p>Previously, if you set your viewport width to a value less than or equal to "320" |
| 231 | it would be set to "device-width", and if you set the viewport height to a value less than or |
| 232 | equal to the {@link android.webkit.WebView} height, it would be set to "device-height". However, |
| 233 | when running in the new {@link android.webkit.WebView}, the width or height value is adhered and |
| 234 | the {@link android.webkit.WebView} zooms in to fill the screen width.</p> |
| 235 | |
| 236 | |
| 237 | <h3 id="MultiViewport">Multiple viewport tags not supported</h3> |
| 238 | |
| 239 | <p>Previously, if you included multiple viewport tags in a web page, {@link android.webkit.WebView} |
| 240 | would merge the properties from all the tags. |
| 241 | In the new {@link android.webkit.WebView}, only the last viewport is |
| 242 | used and all others are ignored.</p> |
| 243 | |
| 244 | |
| 245 | <h3 id="Zoom">Default zoom is deprecated</h3> |
| 246 | |
| 247 | <p>The methods {@link android.webkit.WebSettings#getDefaultZoom()} and |
| 248 | {@link android.webkit.WebSettings#setDefaultZoom setDefaultZoom()} for getting and setting |
| 249 | the initial zoom level on a page have are no longer supported and you should instead define |
| 250 | the appropriate viewport in the web page.</p> |
| 251 | |
| 252 | <p class="caution"><strong>Caution:</strong> These APIs are not supported on Android 4.4 and higher |
| 253 | at all. Even if your <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target" |
| 254 | >{@code targetSdkVersion}</a> is set to "18" or lower, these APIs have no effect.</p> |
| 255 | |
| 256 | <p>For information about how to define the viewport properties in your HTML, read |
| 257 | <a href="http://developers.google.com/chrome/mobile/docs/webview/pixelperfect" class="external-link" |
| 258 | >Pixel-Perfect UI in the WebView</a>. |
| 259 | |
| 260 | <p>If you cannot set the width of the viewport in the HTML, then you should call |
| 261 | {@link android.webkit.WebSettings#setUseWideViewPort setUseWideViewPort()} to ensure the page |
| 262 | is given a larger viewport. For example:</p> |
| 263 | |
| 264 | <pre> |
| 265 | WebSettings settings = webView.getSettings(); |
| 266 | settings.setUseWideViewPort(true); |
| 267 | settings.setLoadWithOverviewMode(true); |
| 268 | </pre> |
| 269 | |
| 270 | |
| 271 | |
| 272 | |
| 273 | |
| 274 | <h2 id="Style">Styling Changes</h2> |
| 275 | |
| 276 | <h3 id="BackgroundSize">The background CSS shorthand overrides background-size</h3> |
| 277 | |
| 278 | <p>Chrome and other browser have behaved this way for a while, but now |
| 279 | {@link android.webkit.WebView} will also override a CSS setting for {@code background-size} |
| 280 | if you also specify the {@code background} style. For example, the size here will be reset |
| 281 | to a default value:</p> |
| 282 | |
| 283 | <pre class="no-pretty-print"> |
| 284 | .some-class { |
| 285 | background-size: contain; |
| 286 | background: url('images/image.png') no-repeat; |
| 287 | } |
| 288 | </pre> |
| 289 | |
| 290 | <p>The fix is to simply switch the two properties around.</p> |
| 291 | |
| 292 | <pre class="no-pretty-print"> |
| 293 | .some-class { |
| 294 | background: url('images/image.png') no-repeat; |
| 295 | background-size: contain; |
| 296 | } |
| 297 | </pre> |
| 298 | |
| 299 | |
| 300 | <h3 id="Pixels">Sizes are in CSS pixels instead of screen pixels</h3> |
| 301 | |
| 302 | <p>Previously, size parameters such as <a |
| 303 | href="https://developer.mozilla.org/en-US/docs/Web/API/Window.outerWidth" class="external-link"> |
| 304 | <code>window.outerWidth</code></a> and |
| 305 | <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window.outerHeight" |
| 306 | class="external-link"><code>window.outerHeight</code></a> returned a value in actual screen pixels. |
| 307 | In the new {@link android.webkit.WebView}, these return a value based on CSS pixels.</p> |
| 308 | |
| 309 | <p>It's generally bad practice to try and calculate the physical size in pixels for |
| 310 | sizing elements or other calculations. However, if you've disabled zooming and the initial-scale |
| 311 | is set to 1.0, you can use <code>window.devicePixelRatio</code> |
| 312 | to get the scale, then multiply the CSS pixel value by that. Instead, |
| 313 | you can also <a href="{@docRoot}guide/webapps/webview.html#BindingJavaScript">create a |
| 314 | JavaScript binding</a> to query the pixel size from the {@link android.webkit.WebView} itself.</p> |
| 315 | |
| 316 | <p>For more information, see <a class="external-link" |
| 317 | href="http://www.quirksmode.org/blog/archives/2012/03/windowouterwidt.html">quirksmode.org</a>.</p> |
| 318 | |
| 319 | |
| 320 | |
| 321 | <h3 id="Columns">NARROW_COLUMNS and SINGLE_COLUMN no longer supported</h3> |
| 322 | |
| 323 | <p>The {@link android.webkit.WebSettings.LayoutAlgorithm#NARROW_COLUMNS} value for {@link |
| 324 | android.webkit.WebSettings.LayoutAlgorithm} is not be supported in the new {@link |
| 325 | android.webkit.WebView}.</p> |
| 326 | |
| 327 | <p class="caution"><strong>Caution:</strong> These APIs are not supported on Android 4.4 and higher |
| 328 | at all. Even if your <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target" |
| 329 | >{@code targetSdkVersion}</a> is set to "18" or lower, these APIs have no effect.</p> |
| 330 | |
| 331 | <p>You can handle this change in the following ways:</p> |
| 332 | |
| 333 | <ul> |
| 334 | <li>Alter the styles of your application: |
| 335 | <p>If you have control of the HTML and CSS on the page, you may find that altering the design |
| 336 | of your content may be the most reliable approach. For example, for screens where you cite |
| 337 | licenses, you may want wrap text inside of a |
| 338 | <code><pre></code> tag, which you could do with the following styles: |
| 339 | <pre><pre style="word-wrap: break-word; white-space: pre-wrap;"></pre> |
| 340 | <p>This may be especially helpful if you have not defined the viewport properties for |
| 341 | your page.</p> |
| 342 | </li> |
| 343 | <li>Use the new {@link android.webkit.WebSettings.LayoutAlgorithm#TEXT_AUTOSIZING} layout |
| 344 | algorithm: |
| 345 | <p>If you were using narrow columns as a way to make a broad spectrum of desktop |
| 346 | sites more readable on mobile devices and you aren't able to change the HTML content, the new |
| 347 | {@link android.webkit.WebSettings.LayoutAlgorithm#TEXT_AUTOSIZING} algorithm may be a |
| 348 | suitable alternative to {@link android.webkit.WebSettings.LayoutAlgorithm#NARROW_COLUMNS}.</p> |
| 349 | </li> |
| 350 | </ul> |
| 351 | |
| 352 | <p>Additionally, the {@link android.webkit.WebSettings.LayoutAlgorithm#SINGLE_COLUMN} value—which |
| 353 | was previously deprecated—is also not supported in the new {@link |
| 354 | android.webkit.WebView}.</p> |
| 355 | |
| 356 | |
| 357 | |
| 358 | |
| 359 | <h2 id="TouchCancel">Handling Touch Events in JavaScript</h2> |
| 360 | |
| 361 | <p>If your web page is directly handling touch events in a {@link android.webkit.WebView}, |
| 362 | be sure you are also handling the <a |
| 363 | href="https://developer.mozilla.org/en-US/docs/Web/Reference/Events/touchcancel" |
| 364 | class="external-link"><code>touchcancel</code></a> |
| 365 | event. There are a few scenarios where <code>touchcancel</code> will be called, which can |
| 366 | cause problems if not received:</p> |
| 367 | |
| 368 | <ul> |
| 369 | <li>An element is touched (so <code>touchstart</code> and <code>touchmove</code> are called) |
| 370 | and the page is scrolled, causing a <code>touchcancel</code> to be thrown.</li> |
| 371 | <li>An element is touched (<code>touchstart</code> is called) but |
| 372 | <code>event.preventDefault()</code> is not called, resulting earlier enough that |
| 373 | <code>touchcancel</code> is thrown (so |
| 374 | {@link android.webkit.WebView} assumes you don't want to consume the touch events).</li> |
| 375 | </ul> |
| 376 | |
| 377 | |
| 378 | |