Oct 26

GreenRobot’s EventBus has a number of benefits compared to local broadcasts. Like broadcasts, events are a tool for decoupling your application architecture, and they’re especially useful when handling responses to asynchronous requests. EventBus makes it possible to receive results on specific threads and is also easier on the keyboard because it requires you to write less boilerplate code.

Note that discussion here only concerns broadcasts sent using LocalBroadcastManager, i.e. broadcasts that are sent and received only within the same process. System-wide broadcasts sent using Context.sendBroadcast() are not covered.

There’s an accompanying example app on GitHub. The app explores only the performance of EventBus in relation to local broadcasts when sending multiple events/broadcasts in succession (i.e. in a for-loop). For single events/broadcasts sent only sporadically, performance of the event/broadcast delivery itself is a non-issue. Furthermore, you can usually rearrange your event delivery to happen in bigger batches if performance, CPU and battery usage is an issue (which is basically always :) Therefore, you should not base your decision whether to use the EventBus library on the example code alone.

Event delivery on specific threads

You can select whether to receive EventBus events on different threads based on the threadMode setting you (optionally) specify in the @Subscribe annotation, as described in the EventBus documentation). Make sure you check out the documentation, since threadMode behavior may not be immediately obvious based on the name alone. Here are some scenarios where you might want to choose a specific threadMode over others:

  • @Subscribe(threadMode = ThreadMode.POSTING) The only mode that’s always guaranteed to be synchronous. You may want to use this e.g. to update the UI if you know you’re already on the main thread, or continue some background work on a worker thread. This is the default setting if you don’t specify the mode at all.
  • @Subscribe(threadMode = ThreadMode.MAIN) You can use this to update the UI when work completes on a background thread.
  • @Subscribe(threadMode = ThreadMode.BACKGROUND) For handling events on a dedicated worker thread one-by-one. When posting subsequent events from outside the worker thread while work is still ongoing, further requests will be queued. Note that if you’re already on the worker thread, any further events posted using this mode will be posted synchronously. If you need to post an event asynchronously from the event handler, consider using the ASYNC mode instead.
  • @Subscribe(threadMode = ThreadMode.ASYNC) This option is useful when you need to handle multiple callbacks concurrently, like when executing multiple network requests. The calling thread is from a separate ThreadPool of worker threads, and never the posting thread. Remember to be careful with this in your event handler, since multiple threads may be executing the code asynchronously.

By contrast, BroadcastReceiver’s onReceive() is always called on the process’ main thread, unless you call sendBroadcastSync() from some other thread which, as the named implies, is executed directly.

Callback registration

To register callbacks that you want EventBus to call when certain types of event objects are posted, you specify the posted object type as parameter to your callback methods. You also need to register the object of the class where your callbacks are implemented. This is roughly equivalent to registering a broadcast receiver with a set of intent filters, as shown in this example:

public class MyActivity extends Activity {  // ...

    private MyReceiver myReceiver = new MyReceiver();
    private static final BROADCAST_ACTION = "BROADCAST_ACTION";

    @Override public void onStart() {
        super.onStart();
        LocalBroadcastManager.getInstance(this)
            .registerReceiver(myReceiver, new IntentFilter(BROADCAST_ACTION));
        EventBus.getDefault().register(this);
    }
    
    @Override public void onStop() {
        EventBus.getDefault().unregister(this);                  
        LocalBroadcastManager.getInstance(this).unregisterReceiver(myReceiver);
        super.onStop();
    }

    @Subscribe public void onEvent(MyEvent event) {
        // Do stuff with received event ...
    }

    private static class MyReceiver extends BroadcastReceiver {
        @Override public void onReceive(Context context, Intent intent) {
            // Do stuff with received broadcast ...
        }
    }
}

As you can see, writing a broadcast receiver is somewhat more cumbersome since you need to extend the BroadcastReceiver class and supply an intent filter, whereas you can just annotate your event handling methods with @Subscribe when using EventBus.

Posting events and broadcasts

This is an area where LocalBroadcastManager and EventBus are very similar to each other, without much boilerplate even with LocalBroadcastManager:

public void postEvent() {
    MyEvent event = new MyEvent();
    EventBus.getDefault().post(event);
}

public void sendBroadcast() {
    Intent intent = new Intent("my_action");
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

Delivering content

With broadcasts, you need to package your payload using intent extras, which can often require quite a bit of boilerplate code, especially when using Parcelables. With EventBus you can place your content within the event object itself. This requires no cumbersome packaging, no parcelables, etc. The content of the event you post is delivered as-is to the callback handler. Let’s expand the methods in the previous section to supply some payload as well:

class MyObject {
    public void doStuff() {
        // ...
    }
}

class MyEvent {
    int value;
    String text;
    MyObject myObject;

    public MyEvent(int value, String text, MyObject myObject) {
        this.value = value;
        this.text = text;
        this.myObject = myObject;
    }
}

public void postEvent() {
    MyEvent event = new MyEvent(123, "abcdef", new MyObject());
    EventBus.getDefault().post(event);
}

public void sendBroadcast() {
    Intent intent = new Intent("my_action");
    intent.putExtra("int_extra", 123);
    intent.putExtra("text_extra", "abcdef);
    intent.putExtra("myobject_extra", new MyObject());
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

Passing the example instance of MyObject to a broadcast intent is not as simple as it looks, since the MyObject class needs to implement the Parcelable interface as specified in Android documentation. (Note that you should really use constant definitions for names of extras, they’re literals above only for conciseness and clarity.)

With EventBus, you can use the posted event directly in your callback handler, whereas with broadcasts you first need to unpack the payload from extras in your broadcast receiver:

@Subscribe
public void onEvent(MyEvent event) {
    Log.d(TAG, "value: " + event.value);
    Log.d(TAG, "text: " + event.text);
    Log.d(TAG, "myObject.doStuff(): " + event.myObject.doStuff());
}

// ...

private static class MyReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
    int value = intent.getIntExtra("int_extra", -1);
    String text = intent.getStringExtra("text_extra");
    MyObject myObject = (MyObject) intent.getParcelable("myobject_extra");
    Log.d(TAG, "value: " + value);
    Log.d(TAG, "text: " + text);
    Log.d(TAG, "myObject.doStuff(): " + myObject.doStuff());
}

Conclusion

EventBus is a great library for decoupling your application architecture, delivering results on specific threads, and having to write no unnecessary boilerplate code when handling event payloads. While very convenient compared to simple local broadcasts, EventBus’ tradeoff comes in the lack of performance, although it’s still quite performant for most tasks. All things considered, for most intents and purposes EventBus is a very good choice due to its versatility.

This article was also published at my employer’s blog at https://bitfactor.fi/en/2016/11/01/comparison-of-eventbus-and-androids-local-broadcasts/

Apr 24

Perhaps not broken, but documented inadequately. In http://developer.android.com/training/implementing-navigation/temporal.html there’s a simple guide on how to set up back stack navigation when opening an app e.g. from a notification using a “deep link” to an activity that is not the topmost one in the app, so that the user can navigate upwards in the stack by pressing the back key.

Having wasted a couple of hours trying to make this work in LoanShark, I finally noticed a warning in LogCat:

W/ActivityManager﹕ Permission Denial: starting Intent [...] not exported from uid 10131

What the documentation forgot to mention is that you need to set the activity you want to jump to from e.g. a notification as exported. I added the attribute and encountered no more problems. The weird thing is that it did actually sometimes work even without the exported attribute… strange indeed. But now it works 100% of the time, fortunately.

Apr 03

TextView with italics text gets cropped at the rightmost edge when the view is set to wrap_content. Reportedly this affects when the content is set to have gravity=”right” as well.

http://stackoverflow.com/q/4353836

I haven’t plunged into the source code on this one, but it seems that Android’s layout engine evaluates the TextView’s width based on the text baseline, and does not take into account the fact that skewing the text may result in the projected baseline at the top (whatever it could be called) may not be the same. The solution is to simply add a space character at the end of the string.

I ran into this problem when designing the card layout for LoanShark loans. I wonder how many applications’ layouts would break if there was to be an actual fix to this problem…

Dec 06

If you’ve been dealing with Android fragments, odds are you’ve used the FragmentTransaction.addToBackStack() method. Often this method is called with a null argument, and that’s fine for most uses. Afterwards, you can pop the top of the back stack by calling FragmentManager.popBackStack() and you’re done.

You might be wondering what use is the String parameter in the addToBackStack() method. It can be quite useful in cases where you need to pop a number of items from the FragmentManager back stack at once. For example, you may want to wipe the slate clean of all fragments that you’ve been adding/replacing. You can do this as follows:

    FragmentManager fragMan = getFragmentManager();
    for (int i = 0; i < fragMan.getBackStackEntryCount(); ++i) {
        fragMan.popBackStack();
    }

Now, in case you want to leave one or more items on the back stack instead of popping the whole lot, you could try to keep track of the number of back stack items to pop, but this is tedious and error-prone to say the least. Here’s where addToBackStack()’s String argument comes to play. The String argument is the name of a back stack “state” you want to keep track of.

Let’s say that you have a two-pane layout, and on the right side you have a fragment that you’ve replace()d to a layout element. You’d like to keep this fragment visible at all times. You then proceed to utilize other fragments as well in the same layout spot, calling FragmentTransaction.replace() in the process, along with addToBackStack(). If you then want to pop all other back stack items but the first one, you can do this by adding a predefined state name to the first fragment’s addToBackStack() method:

    private static final String INITIAL_FRAGMENT_STATE = "initial_fragment_state";
    // ...
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    ft.replace(...);
    ft.addToBackStack(INITIAL_FRAGMENT_STATE);

Then, when you need to wipe the slate clean and lose all but the initial state from the back stack:

    getFragmentManager().popBackStack(INITIAL_FRAGMENT_STATE);

And there you have it. No need to keep track of the number of fragments in the back stack in each valid state of the application or any such nonsense. Just make use of named back stack states.

Nov 06

LoanShark_1

A while back I released a new, completely-rewritten version of LoanShark. The current version is 2.0.6 which contains a number of

bugfixes, and I consider it to be quite stable. I think writing this version took considerably more effort than the first one, or maybe I have just forgotten how much effort went into the first attempt.

There were problems with database migration code from the previous version. I’ve done away with some tables altogether to make things simpler, which necessitated writing quite a bit of migration code. Migration is both engaging and frustrating at the same time. On one side there’s the need to evaluate the old and new database structures exactly and trying to figure out the simplest way to transfer old data to the new format. On the other side there’s no way around writing migration code unless you want to make your app crash on your users’ faces when they update to the latest version.

I thought I had ironed out the potential migration issues before releasing the first update, but there were several bug reports from users. I guess most of the users don’t bother to send an error report when an app crashes on startup; they’ll just uninstall it and be on their merry way. Fortunately it also seems that they don’t even bother to write those nasty 1-star reviews either.

During last week I was contacted by two users who had crashing problems, one of which wasn’t related to database migration. I’m very thankful for all feedback, even if it’s crash reports. It gives me the extra kick in the backside that keeps me motivated to continue development.

Sep 21

To force the garbage collector to run from adb shell, first find out the target process’ id (using e.g. ps), and then execute this in the shell (as root):

kill -10 <pid>

You should see GC_EXPLICIT in logcat:

I/dalvikvm﹕ threadid=3: reacting to signal 10
I/dalvikvm﹕ SIGUSR1 forcing GC (no HPROF)
D/dalvikvm﹕ GC_EXPLICIT freed 1K, 9% free 9250K/10112K, paused 12ms+2ms, total 60ms

Before Android 2.3, the afore would also dump the heap. On modern platforms (since Android 3.0), you can use the following, which doesn’t require root:

am dumpheap <pid> <output_file>
May 21

Last Sunday I released a new version (1.05) of LoanShark. The next morning I checked, as usual, whether anyone had had problems with the new version. Sure enough, there was one crash report waiting for me. I tried to figure the problem out before leaving off to work, but I wasn’t able to do so in the short amount of time I had available. I figured that maybe it was a single incident, and that I would later check if any error reports pour in, that would contain a better indication as to what might be the problem.

In the Android Market control panel, the link that goes to the error reports hadn’t changed for a few days (I eventually found out that it displays the number of releases with errors), so I figured there had been no new crashes and didn’t pay attention to the matter any further. As the days went by, I began a little worried about the installations not increasing at their usual rate.

I had received a report from a user to LoanShark’s own page on my site, so I figured there must be something seriously wrong. I decided to check out the error report page at the Android Market control panel, and lo behold, there were 19 error reports in total. Whew!

I think the link to the error reports page should display the total number of errors in the latest release, not the number of releases with errors. Now it just displayed the same number for five days, so I didn’t bother checking the reports behind the link at all.

The reason behind the crash was that I didn’t check for a null pointer in a section of code where the database schema is being upgraded to the latest version. If there were loans that had been returned or repaid in full, the upgrade would fail.

I’ve uploaded a new version (v1.05b) to the Android Market, which fixes the problem. Now existing users are also able to enjoy the plethora of enhancements in the latest version.

Incidentally, when loans are returned or repaid in full, they are not actually deleted from the database, but just marked as being inactive. This is because I’m planning to write a feature which would allow viewing (as read-only) the loan history. Such old loans could then be used, for example, as a template for creating new loans.

If you come up with suggestions for new features, please feel free to leave a comment!

Edit – Fixed one more bug that caused a crash when the quantity of lent/borrowed items was unset. The fix is included in the latest version, v1.05c. I found another bug, although this one is not so fatal: when sending SMS from an overdue loan’s management view, if the contact has more than one phone number set and you select a number, it tries to open the email editor instead of the SMS editor. Haven’t got yet time to fix this but it shouldn’t be a biggie.

preload preload preload