For content-oriented Android application, how and where to store the content to display is a issue every developer will be concerned with. The Android framework provided a comprehensive solution with
ContentProvider (and a lot more) which suits quite well with a SQLite database; but in a lot of other scenarios, the app only need to have some information cached, while the majority of content is directly retrieved from network, thus eliminating the need for a database (and a complicated content scheme).
But when we look into the core of this issue, we will soon find out that it is in fact a problem of whether to have a central storage, and how to notify different components about a change.
Let’s look at the naive solution first, retrieving content from network and only keeping them in memory. In this case, no central storage is needed, and every component fetches its content independently, simple enough. But this approach immediately becomes flawed when it comes to content updating such as user modification. For instance, we have a list activity and a detail activity, and the user can often modified something in the detail activity, say, they clicked ‘like’, and the like button shall look activated not only in the detail activity, but also in the list activity now in background.
Content is fetched independently, so every component have no knowledge of whether someone else is holding the same content as itself, and that content will become stale when shown to user if the content this component newly retrieved is an updated version. Ideally speaking, any piece content is a single resource that should have only one state at point of time, and keeping it represented by one entity in memory is the best way to achieve this — yes, a central storage (whether in memory or also backed by disk), and the DRY (don’t repeat yourself) principle, a different story of the solution framework provided.
Anyway, we always need a notifying mechanism, instead of having some holder of a unique entity shared among clients, because clients may need to transform the content into something else instead of using the unique one directly, or they need notify others when content is changed.
The idea behind
ContentProvider is that,
ContentProvider is the only central storage and all its content is the only real entity that will always be up-to-date, while the content its clients hold is only a copy that will easily get out-of-date. So when a change happened, the central storage is refreshed, and
ContentObservers are notified of the change and then they will query the central
ContentProvider for an updated copy of content.
In this case, only the
ContentProvider (or a delegated
SyncAdapter) can fetch data from network; the clients can only request a fetch and wait for the change callback just as any other client. The central
ContentProvider acts as a middle man between clients and network, to ensure the uniqueness of any content entity.
But a principally-correct and comprehensive solution may not be the best solution for a specific type of scenario. We will face at least the following difficulties:
Identifier: In order to ensure only one central entity per resource, We need URIs to uniquely identify resources just as its name suggests, so we must defining a content URI scheme. But things go complex quickly considering the complex logical hierarchy an interlinked content system will need.
Action: We’ll lose the flexibility on handling changes for special cases. Think of infinite loading, liking and post deleting, we’ll need a generic mechanism to notify all the observers on an URI if some items are newly loaded, modified or removed, while framework’s
ContentObserver only offers an
onChanged() method. However if we handle this only for specific cases, it can be much more easier to implement.
Releasing memory: Because most of the content is dynamic, there is little point in data persistence on disk. So we’ll store retrieved items only in memory, and then, we need to release them once nobody needs them. This can be tricky when, for instance, we have an observer on a collection, and another observer on a detail of this collection is removed, now whether the detailed content should be released depends on whether the collection observer actually observes on this detail, which cannot be inferred by the central content manager from URI scheme.
JSON interoperability: The framework
ContentProvider mechanism uses
Cursor, where only basic types and blob are valid column type, making it uninteroperable with the widely used JSON approach. We’ll have to roll our own.
Implementing and using such a framework is a complex and heavy task, and complexity is error-prone, while it brings little advantage over the decentralized solution we are going to talk about.
So we want to stay on the track of not having any central storage, and allow duplicated (so possibly inconsistent) entities of content in memory.
This way, we are to do a sync among components once we any content is newly fetched from network in this case. The solution is much more specific to each scenario in this case: we can utilize some already present event bus system to get notified of updates, listen to specific event code of content change, and then respond accordingly.
On the first sight, this solution may seem not generic enough, lacking in the beauty of unity. However, considering the overall cost of using a centralized storage with URI scheme, I believe this decentralized-and-syncing mechanism is the way to go for applications in this scenario.
Different mechanisms apply to different scenarios. Android’s centralized
ContentProvider mechanism fits for content that should be persisted and synced, while the decentralized mechanism works well with application with highly-dynamic content.