Skip to content
This repository has been archived by the owner on Sep 25, 2021. It is now read-only.

Turbolinks Android Adapter 1.x Deprecation

Dan Kim edited this page Dec 21, 2018 · 1 revision

Summary

Following a breaking change in Chrome 64, this Turbolinks 1.x adapter for Android hit an architectural dead end. Existing projects built with this version of the adapter will continue to work, although there may be some slow memory leaks and other minor issues.

Still, the general architecture and approach continues to work well enough to power the Basecamp 3 Android application. We run a forked version that bypasses the breaking change in Chrome 64 (but at the same time creates a different set of UI challenges with native dialogs). We considered releasing this slightly modified version, but eventually realized this modified library carried too many of its own drawbacks. In the end we felt that it would only cause confusion to release a version that didn't fix the root issue and simply presented a different set of issues.

So to that end, we're currently working on a brand new Turbolinks 2.0 adapter for Android that will solve the root cause of the Chrome 64 breaking change and get us in line with the latest architectural guidelines from Google.

We don't have this new adapter ready just yet, though. It's under active development, and we'll share the code as soon as we have something solid that we'd build our own future applications upon. But until that point, we're stuck in a bit of a transition period. The old Turbolinks adapter can do the job for existing applications that were built on it, but it's hard for us to recommend that new applications be started on an older architectural model when Google is actively recommending a new, single-activity approach.

So at this time you should probably just build a vanilla WebView application that follows the single-activity approach without Turbolinks for Android. And then when we have a Turbolinks adapter that's compatible ready, you can upgrade to use that.

We will not be maintaining this adapter any further beyond whatever emergency fixes we might need for Basecamp 3's own usage.

Background: How Turbolinks works

Turbolinks is based on the premise that slow-changing, heavy weight assets – typically Javascript and CSS – shouldn’t be retrieved or compiled more than absolutely necessary. Loading and compiling those assets is what we refer to as a cold boot – an expensive process whereby the entirety of a webpage is downloaded, computed, and rendered.

Turbolinks ensures that every subsequent page visit after cold boot will bypass downloading/computing of existing assets (Javascript, CSS) and retrieve and render only the <body> tag of the document. This significantly speeds up the work needed to render the page since all the Javascript and CSS are not reloaded.

In order to achieve this, the Javascript and CSS must be maintained in the browser session. On desktop this would be a browser window/tab, and on Android this would be a single instance of WebView.

Android’s Turbolinks 1.x adapter implementation To maintain the downloaded/computed CSS and Javascript in memory, a single WebView instance is shared with the entire application. This is an anti-pattern that runs counter to the typical 1 to 1 relationship that an activity has with a view (usually when an activity is created the view is created, and when the activity is destroyed the view is destroyed).

In the Android Turbolinks adapter, the single shared WebView instance is never destroyed. Instead, the WebView is moved between activities. When a new WebView activity is instantiated, we detach the shared WebView from the previous activity and reattach it to the newest foreground activity. This same process happens in reverse when going back/an activity is destroyed – it is removed from its current parent and re-attached to the foreground activity.

In order to achieve moving a shared view around, we leveraged public Android APIs to move the WebView between View hierarchies as well as to switch its context (MutableContextWrapper) to whichever activity was in the foreground.

What changed and why the deprecation?

This system worked well and without issue for 2 years.

But in January 2018 Chromium 64 was released. There was a small but significant change to its accessibility implementation that required that the original Context object be maintained and accessible. But because we were changing the context of the WebView each time a new activity was loaded, any user with accessibility services enabled would potentially run into an unexpected state (the original context could be missing), and therefore crash.

On 2/5/18 we released a patch to work around this issue by catching and swallowing the exception. This is an admittedly leaky patch, but one that slows the bleeding (there is a small memory leak that results for users with accessibility services on, but there’s no way around this for now).

We tried to make our case with the Chromium team that this change was a regression and should be reconsidered. But although MutableContextWrapper is a valid public Android API, the Chromium team consistently reiterated that relying on that wasn’t a good idea and that swapping contexts would always be a risky proposition. In the end, the Chromium team was unwilling to make any changes to their implementation and advised us to change our approach or live with this issue.

Context swapping is a core concept for the Turbolinks Android Adapter, so both Chrome 64 and the Chromium organization’s continued recommendation to not alter the WebView’s context gave us pause. It soon became clear to us that constantly working around context swapping wasn’t going to be viable in the long term, and that we needed to focus our efforts on a far more stable, modern approach to WebView performance and navigation.

The future

We’ve already begun investigating other options and techniques that would give us the performance benefits of Turbolinks but without the risk of context swapping. The intial prototypes are based on Kotlin and Android Jetpack (specifically its Navigation Architecture Components) and it looks promising. Kotlin gives us so many modern language features to work with, while Google’s new opinionated single activity approach bypasses any context swapping issues we ran into previously.

We are very early in the prototype stage, so we still don’t know the exact shape or even a rough timeline of when Turbolinks Android 2.0 might be available, but we plan to have something available in 2019.

Thank you for supporting Turbolinks Android 1.0, and we’re excited to share 2.0 when the time comes.