Rewriting Android Priority JobQueue – Lessons Learned

Earlier this year (2016) I’ve decided to rewrite the internals of the Android Priority JobQueue, which is a task queue that I’ve written when I was working at Path to provide a decent offline experience in the app.

Over the 4 years, it has grown organically from a simple task manager to a complex one that allows fine grained control over how background tasks are managed. There were also changes in the Android scene especially the addition of the JobScheduler.

It was getting really hard to improve the JobManager to adapt these changes due to some bad decisions that I made in its earlier development. So I thought it is a good idea to share these as I’ve recently completed the rewrite and feeling much better for the future of the project.

Do not communicate by sharing memory; instead, share memory by communicating

This is the single most important thing in the rewrite and also was the most wrong thing I’ve done in v1. The JobManager is multithreaded by nature since it has to run multiple jobs in parallel. It is intuitive to put shared resources behind locks so that only 1 thread accesses them at a given time. Even though this works well at the beginning, it is very hard to manage especially if you have APIs that would trigger access to these resources. JobManager V1 had some thread lock bugs that were almost impossible to solve. You also need to be careful with memory barriers or mark all necessary fields volatile which is very hard to track.

In V2, I’ve changed this communication entirely to make JobManager single threaded. JobManager gets its own thread which is the only thread that can access shared resources. Jobs that runs on other threads can communicate with the JobManager only via message passing. They cannot access resources. Indeed, they don’t have a way to grab a reference to the shared resource anyways so mistakes cannot be made in the future. Any public API also goes through this message passing, just like Job consumer threads.

This solution is not without its downsides either. There was a bug during development which created a message passing loop between the JobManager and Job consumers which meant JobManager is constantly running. This is a much easier problem to solve by making one item (JobManager) as the master to initiate any conversation and much easier to keep under control compared to deadlocks.

Also, read the Awesome Go Article on Share Memory By Communicating as a background.

If you code needs to do clock related stuff, abstract it out

JobManager provides the ability to delay execution of jobs. Java has built in functionality to defer things or get current time. They work great until you run your tests on a CI server. Your “solid” tests become flaky. Or in other words, they were always flaky, you just didn’t know that. It is only half the story. You also have no way of testing race conditions because you don’t control time, you cannot.

In V2, I’ve abstracted all time related tasks into a helper class. I even wrote lint checks to ensure that the real time is never accessed. This abstraction does not just abstract System.nanotime calls. It also abstracts message queue delay timing or any timer that is used across the codebase.

Of course this introduce the risk of not using real time in tests but this relatively simple abstraction can itself be tested with real time to mitigate this risk.

Think twice and then think again before adding a new API

This is something I learned while working on the Android Framework, seeing all these functionality which we need to support going forward but does not make sense or not well designed for flexibility. JobManager had APIs like this, the worst one being the addJob method which returns a long id that is only unique when composed with the persistent property of the Job. So a persistent and non-persistent job could have the same id. It worked this way because ids were provided by the Job queues which had default implementations but could be swapped by the developer.

API breaking V2 gave the opportunity to clean these and I had fun :). I’ve moved long ids to UUIDs that are assigned when Job is created so that it is easier for the developer to tie things to the job lifecycle w/o relying on a response. The worst part of this story is that I knew it was a bad API when added but added anyways. So don’t do it. No matter what the use case it. Just wait until you have a better solution before implemented whatever functionality requested by the user.

Overall, writing V2 was a fun experience for me to try new things. There are more things that I would like to change but didn’t as there is a balance between providing the most desired API vs backward compatibility. I’m happy with I have right now as I release V2 and I hope you enjoy using it and develop the most responsive apps that work offline.

Go check it out!

RecyclerView Animations Part 2 – Behind The Scenes

This is the second part of a 2 articles series. Please read Part 1 first if you’ve not read it yet.

In the first article, I’ve covered the main idea on how predictive animations run in RecyclerView. There is actually a lot more going on to achieve this simplicity (for the LayoutManager). Here are some important points that you should know about.

RecyclerView keeps some children attached although they have been removed by the LayoutManager. How does it work? Does it invalidate the contract between the LayoutManager and RecyclerView?

Yes it does ‘kind of’ violate the contract with LayoutManager, but:

RecyclerView does keep the View as a child of the ViewGroup but hides it from the LayoutManager. Each time LayoutManager calls a method to access its children, RecyclerView takes into account the hidden Views. Lets look at the example at Part 1 where ‘C’ was being removed from the adapter.

Predictive Animation

While ‘C’ is fading out, if LayoutManager calls getChildCount(), RecyclerView returns 6 although it has 7 children. If LayoutManager calls getChildAt(int), RecyclerView offsets that call properly to skip child ‘C’ (or any hidden children). If LayoutManager calls addView(View, position), RecyclerView offsets the index properly before calling ViewGroup#addView.

When the animation ends, RecyclerView will remove the View and recycle it.

For more details, you can check ChildHelper internal class.

How does RecyclerView handle item positions in the preLayout pass since they don’t match Adapter contents?

This is doable thanks to the specific notify events added to the Adapter. When Adapter dispatches notify** events, RecyclerView records them and requests a layout to apply them. Any events that arrives before the next layout pass will be applied together.

When onLayout is called by the system, RecyclerView does the following:

  1. Reorder update events such that move events are pushed to the end of the list of update ops. Moving move events to the end of the list is a simplification step so I’ll not go into details here. You can check OpReorderer class for details if you are interested.
  2. Process events one by one and update existing ViewHolders’ positions with respect to the update. If a ViewHolder is removed, it is also marked as removed. While doing this, RecyclerView also decides whether the adapter change should be dispatched to the LayoutManager before or after the preLayout step. This decision process is as follows:
    • If it is an add operation, it is deferred because item should not exist in preLayout.
    • If it is an update or remove operation and if it affects existing ViewHolders, it is postponed. If it does not effect existing ViewHolders, it is dispatched to the LayoutManager because RecyclerView cannot resurrect the previous state of the item (because it does not have a ViewHolder that represents the previous state of that Item).
    • If it is a move operation, it is deferred because RecyclerView can fake its location in the pre-layout pass. For example, if item at position 3 moved to position 5, RecyclerView can return the View for position 5 in pre-layout when View for position 3 is asked.
    • RecyclerView rewrites update operations as necessary. For example, if an update or delete operation affects some of the ViewHolders, RecyclerView divides that operation. If an operation should be dispatched to LayoutManager but a deferred operation may affect it, RecyclerView re-orders these operations so that they are still consistent.

    For example, if there is an Add 1 at 3 operation which is deferred followed by a Remove 1 at 5 operation which cannot be deferred, RecyclerView dispatches it to the LayoutManager as Remove 1 at 4. This is done because the original Remove 1 at 5 was notified by the Adapter after Add 1 at 3 so it includes that item. Since RecyclerView did not tell LayoutManager about the Add 1 at 3, it rewrites the remove operation to be consistent.

    This approach makes tracking items dead simple for a LayoutManager. The abstraction between the Adapter and the LayoutManager makes all of this possible, which is why RecyclerView never passes the Adapter to the LayoutManager, instead, provides methods to access Adapter via State and Recycler.

    ViewHolders also have their old position, pre layout position and final adapter positions. When ViewHolder#getPosition is called, they return either preLayout position or final adapter position depending on the current layout state (pre or post). LayoutManager doesn’t need to know about this because it will always be consistent with the previous events that were dispatched to the LayoutManager.

  3. After Adapter updates are processed, RecyclerView saves positions and dimensions of existing Views which will later be used for animations.
  4. RecyclerView calls LayoutManager#onLayoutChildren for the preLayout step. As I’ve mentioned in the first article, LayoutManager runs its regular layout logic. All it has to do is to layout more items for those which are being deleted or changed (LayoutParams#isItemRemoved , LayoutParams#isItemChanged). As a reminder, the deleted or changed item still ‘appears’ in the Adapter API given to the LayoutManager. This way, LayoutManager simply treats it as any other View (adds, measures, positions etc).
  5. After preLayout is complete, RecyclerView records the positions of the Views again and dispatches the remaining Adapter updates to the LayoutManager.
  6. RecyclerView calls LayoutManager’s onLayout again (postLayout). This time, all item positions match the current contents of the Adapter. LayoutManager runs its regular layout logic again.
  7. After post layout is complete, RecyclerView checks positions of Views again and decides which items are added, removed, changed and moved. It ‘hides’ removed Views and for views not added by the LayoutManager, adds them to the RecyclerView (because they should be animated).
  8. Items which require an animation are passed to the ItemAnimator to start their animations. After the animation is complete, Item Animator calls a callback in RecyclerView which removes and recycles the View if it is no longer necessary.

What happens if LayoutManager keeps some internal data structure using item positions?

Everything works… kind of :). Thanks to the re-writing of Adapter updates by the RecyclerView, all LayoutManager has to do is to update its own bookkeeping when one of its adapter data changed callbacks is called due to Adapter changes. RecyclerView ensures that these updates are called at the appropriate time and order.

At any time during a layout, if LayoutManager needs to access the adapter for additional data (some custom API), it can call Recycler#convertPreLayoutPositionToPostLayout to get the item’s Adapter position. For example, GridLayoutManager uses this API to get the span size of items.

What happens if notifyDataSetChanged is called? How do predictive animations run?

They don’t, which is why notifyDataSetChanged should be your last resort. When notifyDataSetChanged is called on the adapter, RecyclerView does not know where items moved so it cannot properly fake getViewForPosition calls. It simply runs animations as a LayoutTransition would do.

I hope this two part series helped you understand how animations work in RecyclerView and why they work this way. Feel free to ask more questions in the comments and I’ll try to update the article with answers.

Disclaimer: After joining the Android Framework Team on March 2014, I spent fair amount of my time working on RecyclerView.

  • Special thanks to Chet for reviewing early versions of this blog post!

RecyclerView Animations Part 1 – How Animations Work

ListView is one of the most popular widgets of the Android Framework. It has many features, yet it is fairly complex and hard to modify. As the UX paradigms evolved and phones got faster, its limitations started to overshadow its feature set.

With Lollipop, the Android team decided to release a new widget that will make writing different collection views much easier with a pluggable architecture. Many different behaviors can be controlled easily by implementing simple contracts to change:

  • how items are laid out
  • animations!
  • item decorations
  • recycling strategy

This great flexibility comes with the additional complexity of a bigger architecture. Also, there are more things to learn.

In this post, I want to deep dive into RecyclerView internals, particularly on how animations work.

On Honeycomb, the Android Framework introduced LayoutTransition, which was a very easy way to animate changes inside a ViewGroup. It works by taking a snapshot of the ViewGroup before and after the layout changes, then creating Animators to move between these two states. This process is fairly similar to what RecyclerView needs to do to animate changes in the adapter.

LayoutTransition example:

LayoutTransirion example

Unfortunately, lists have one major difference which makes LayoutTransitions a bad fit for their animations. Specifically, items in lists are not the same as views in a ViewGroup. This is an important distinction that needs to be understood to handle animations on “items” while using mechanisms that animate the “views” that show the item contents.

In a normal ViewGroup, if a View is newly added to the View hierarchy, it can be treated like a newly added View and thus it can be animated accordingly (e.g. fade in). For collections, it is a bit different. For example, a View for an item may become visible just because an item before it has been removed from the Adapter. In this case, running a fade in animation for the new item would be misleading because it was already in the list though the view is new because the item just entered the viewport. RecyclerView knows if the item is new or not but it does not know where it was if the item is not new. The same case is valid for disappearing Views, RecyclerView does not know where the view went if it is not removed from the Adapter.

LayoutTransition failure for a list:

LayoutTransirion bad example

To overcome this problem, RecyclerView could ask LayoutManager for the previous location of the new View. Although this would work, it would require some bookkeeping on the LayoutManager end and may not be trivial to calculate for more complex LayoutManagers.

The way that RecyclerView handles animating appearing and disappearing items (that is, animating the appearance and disappearance of views that refer to items that were and are still in the list) is by relying on the LayoutManager to handle predictive layout logic. One one hand, the RecyclerView wants to know where views would have been had they been laid out prior to this change. On the other hand, the RecyclerView wants to know where views would be laid out after this change if the LayoutManager went to the trouble of laying out items that are not currently visible.

To make it easy for the LayoutManager to provide this information, RecyclerView uses a two step layout process when there are adapter changes which should be animated. The mechanisms for handling these predictive layout passes are described below.

  • In the first layout pass (preLayout), RecyclerView asks LayoutManager to layout the previous state with the knowledge of the additional information. For the example above, it would be like requesting “Layout items again, btw, ‘C’ has been removed”. The LayoutManager runs its usual layout step but knowing that ‘C’ will be removed, it lays out View(s) to fill the space left by ‘C’.
    The cool part of this contract is that RecyclerView still behaves as if ‘C’ is still in the backing Adapter. For example, when LayoutManager asks for the View for position 2, RecyclerView returns ‘C’ (getViewForPosition(2) == View('C')) and if LayoutManager asks for position 4, RecyclerView returns the View for ‘E’ (although ‘D’ is the 4th item in the Adapter). LayoutParams of the returned View has an isItemRemoved method which LayoutManager can use to check if this is a disappearing item.

  • In the second layout pass (postLayout), RecyclerView asks LayoutManager to re-layout its items. This time, ‘C’ is not in the Adapter anymore. getViewForPosition(2) will return ‘D’ and getViewForPosition(4) will return ‘F’.
    Keep in mind that the backing item for ‘C’ was already removed from the Adapter, but since RecyclerView has the View representation of it, it can behave as if ‘C’ is still there. In other words, RecyclerView does the bookkeeping for the LayoutManager.

Every time onLayoutChildren is called on the LayoutManager, it temporarily detaches all views and lays them out from scratch again. Unchanged Views are returned from the scrap cache so their measurements stay valid, making this relayout fairly cheap and simple.

LinearLayoutManager pre layout result: (pink rectangle marks the area visible to the user)

LinearLayoutManager pre layout

LinearLayoutManager post layout result:

LinearLayoutManager post layout

After these two layout passes, RecyclerView knows where the Views came from so it can run the correct animation.

Predictive Animation

You might ask: The View ‘C’ was not laid out by the LayoutManager, how come it is still visible?

To be clear, ‘C’ was laid out by the LayoutManager in the pre-layout pass because it looked like it was in the Adapter. It is true that ‘C’ was not laid out by the LayoutManager in the post-layout pass because it does not exist in the Adapter anymore. It is also true for the LayoutManager that ‘C’ is not its child anymore but not true for the RecyclerView. When a View is removed by the LayoutManager, if ItemAnimator wants to animate it, RecyclerView keeps it as a child (so that animations can run properly). More details on this in Part2.

Disappearing Items

With these two layout passes, RecyclerView is able to animate new Views properly. But now, there is another problem with Views that are disappearing. Consider the following case where a new item is added to the list, pushing some other items outside the visible area. This is how it would look with LayoutTransitions:

Add Animation Failure

When ‘X’ was added after ‘A’, it pushed ‘F’ outside the screen. Since LayoutManager will not layout ‘F’, LayoutTransition thinks it has been removed from the UI and runs a fade out animation for it. In reality, ‘F’ is still in the adapter but has been pushed out of bounds.

To solve this issue, RecyclerView provides an additional API to LayoutManager to get this information. At the end of a postLayout pass, LayoutManager can call getScrapList to get list of Views which are in this situation (not laid out by the LayoutManager but still present in the Adapter). Then, it lays out these views as well, as if the size of RecyclerView was big enough to show them.

LinearLayoutManager post layout result: (pink rectangle marks the area visible to the user)

LinearLayoutManager post layout

One important detail is that, since these Views are not necessary after their animations are complete, LayoutManager calls addDisappearingView instead of addView. This gives the clue to the RecyclerView that this View should be removed after its animations is complete. RecyclerView also adds the View to the list of hidden views so that it will disappear from LayoutManager’s children list as soon as postLayout method returns. This way, LayoutManager can forget about it.

Predictive Add Animation

Initially, at least for a LinearLayoutManager, you might think that it can calculate where the Views came from or where they went (if disappeared) and thus won’t need a two pass layout calculation. Unfortunately, there are many edge cases when multiple types of adapter changes happen in the same layout pass. In addition to that, for a more complex LayoutManager, it is not always trivial to calculate where an item would be placed (e.g. StaggeredGridLayout). This approach removes all burden from the LayoutManager and it can support proper animations with little effort.

So far, I’ve covered the main idea on how predictive animations run in RecyclerView. There is actually a lot more going on to achieve this simplicity (for the LayoutManager). You can read about how all this works in Part 2 – Behind The Scenes.

Disclaimer: After joining the Android Framework Team on March 2014, I spent fair amount of my time working on RecyclerView.

  • Special thanks to Chet for reviewing early versions of this blog post!

A Recipe for writing responsive REST clients on Android

Android development is easy but making the app resilient is hard.

Apps are developed under perfect conditions. Test phones with few applications running on top of them with a reliable network conditions. When the app is handed off to end users, environment change dramatically. People use their apps everywhere and network connection is very unreliable.

When developers don’t take care of such cases, it results in a sh*tty user experience. In this blog post, I will share my personal architectural choices with library references.
Continue reading

How to Solve LinearAlloc Problem

skip drama

Imagine you’ve been working on your Android app for a while adding new big features. You’ve been developing it on your beautiful ICS phone. New features are almost ready so it is now time to test  them on older devices.

The first time you try installing it on a Gingerbread device, you realize that it does not work. bummer! It does not even install! How the hack did that happen? You run adb logcat and try to reinstall. You see a buch of errors and on top a messages saying

E/dalvikvm( 7815): LinearAlloc exceeded capacity (5242880), last=3906240

You start googling and realize that Android has a 8MB linear alloc limit on Gingerbread devices. You would not think hitting that limit anytime soon but Android has a bug that makes you hit that limit a lot faster if you are using a complex library or your code tree is complex & well(over) abstracted.
Continue reading

Don’t Dress Your Android App with iPhone Clothes

Many companies go with iPhone when they develop the first version of their app and I personally support this decision for various reasons.

  • Developing for 3-4 similar hardwares is a lot more easier than developing for X inconsistent hardware sets.
  • iOS is a far better OS compared to Android 2.x (FYI I think ICS is better than iOS).
  • App Store is known to have better distribution. Users are richer, spend more money etc…
  • Developing for Android requires more resources and takes more time to test.
    Continue reading

The Real Problem with Android Fragmentation

Many people complain about the Android fragmentation, mostly referring to different screen dimensions, densities and android versions. In this post, I will try to mention them and share my biggest complaint.

It is true that writing an app that can handle multiple screen dimensions/densities is harder than supporting 1-2 screen sizes. Yet I don’t think this is very important. first of all, dp is quite a good unit that makes your design easy to manage. In addition to that, c’mon, we’ve been developing websites for various screens for many years. I believe we have developed enough many design guidelines to handle different screen sizes. So, please stop bringing this argument in any other conversation.
Continue reading