So, say we have a piece of rich text, most probably in the form of HTML (e.g. a simplified HTML made for mobile reading), what kind of widget is best used to display it? How do we achieve the flexible yet rich reading experience from popular mobile readers like Readability, Pocket, or Materialistic (!)?
In this article, we will explore two very popular and powerful widgets that has been available since API 1: TextView
and WebView
, for the purpose of rich text rendering. The article does not aim to provide a comprehensive comparison, but rather touches on several critical desicion making points.
For the sake of comparison, let’s assume that we are given the task of displaying the following image-intensive article, styled to specific background color, text size and color. We will first try to use available APIs in TextView
and WebView
to render the given HTML in the same, comparable way, then analyze their performance: memory, GPU and CPU consumption. Example code can be found here.
The basics
First let’s quickly get through the basics. By the way they are named, it is quite obvious that TextView
is meant for text rendering, while WebView
is for webpage rendering. With proper use of their APIs however, we can quickly turn both of them into flexible widgets that can render rich text with images.
- Use
Html.fromHtml(String, ImageGetter, TagHandler)
to parse HTML string intoTextView
- Use
WebView.loadDataWithBaseURL()
to load HTML string intoWebView
TextView
TextVew
, with default support for Spanned
, an interface for markup text, allows very fine-grained format options over any range of text. It also exposes a bunch of styling attributes, e.g. textAppearance
, and APIs for controlling text appearance. Check out Flavien Laurent1’s and Chiu-Ki Chan2’s excellent materials on advanced uses of TextView
and Spanned
.
When it comes to rich text however, TextView
shows certain limitations that we may want to weigh up before considering using it: it only handles a limited set of HTML tags, which should be sufficient in most cases; and we have to handle fetching embedded remote images by ourselves, via an ImageGetter
instance; and intercept hyperlinks by using LinkMovementMethod
. Whew!
WebView
Meant for HTML display, WebView
supports most HTML tags out of the box. We already know how to use a WebView
to load a remote webpage with WebView.loadUrl()
, but it can also load a local webpage as well: by wrapping HTML string inside a <body>
block and loading it via WebView.loadDataWithBaseURL()
, where base URL is null
. WebView
supports zooming (need to enable), and handles images and hyperlinks by default (of course!).
Styling
Now let’s try to style TextView
and WebView
to render the example article on a teal theme, with some standard paddings around it. We can see that both of them are capable of rendering the page in pretty much the same way, with the exception of TextView
ignoring the <hr />
tag it cannot handle.
While TextView
provides many attributes and APIs out of the box for styling, WebView
does not provide public APIs for styling its HTML content. However with some basic CSS knowledge, one can instrument given HTML with CSS styles and achieve desired styling as above. We need to be careful on the conversion from CSS metrics to Android metrics though.
Performance
Using the above techniques to style TextView
and WebView
to display the sample HTML content yields the following performance statistics.
As seen from the performance monitor screenshots, TextView
consumes significantly more memory than WebView
, as it needs to hold bitmaps from all loaded images once they are fetched, regardless of whether they are visible on screen.
The example article has 7 images of various sizes with a combined file sizes of 2MB which would become bitmaps in memory. The amount of memory needed depends on how we sample or resize the fetched images, but all of them will need to be in memory at the same time regardless. If we have an article which holds an arbitrary number of images, we may run into the dreaded OutOfMemoryException
very quickly. Thus for this use case, TextView
is a clear no go.
On the other hand, WebView
historically has been optimized to be memory efficient. Under the hood3 (highly recommended read!), it loads content into tiles visible on screen and recycles them as we scroll, resulting in incredibly low memory profile. However, it needs to use more GPU and CPU to process and draw those tiles into pixels on the fly, probably explaining why it consumes more GPU and CPU than TextView
.
So the trade-off here is between memory versus CPU & GPU consumption.
Rendering
Applying above techniques to style WebView
or handle images in TextView
does not come for free. By ‘preprocessing’ our content for rendering, we inherently add certain inital delay to our user experience. A video is best demonstrates this point:
This initial delay may vary depending on devices. On high end devices it may not be noticeable, but we all know how many low end Android devices are around! Using a progress indicator when applicable would surely smoothen the experience.
Another side effect of WebView
is that users may see some pixelated effects while scrolling as the tiles are recycled to render new content.
Bottom line and gotchas
Both widgets have its highs and lows when it comes to rich text rendering. For arbitrary HTML, my choice would be to favor WebView
over TextView
for its low memory consumption and native support for HTML content. Basic HTML and CSS knowledge would be needed, but knowing them will benefit you anyway. If the HTML content is known to be limited to certain tags without images (e.g. forum posts), TextView
would be a more sensible choice.
The article explores a simple layout where both widgets occupy the whole screen. Things may change in a much more complicated way when we place them in a layout hierarchy together with other widgets. Try it out and profile to see what works for you!