Communication Patterns For Android Components

Delhi mobile development meetup

29.11.2014

Achin Kumar

@infernus321

Agenda

  • Dependency, Tight and Loose Coupling
  • Interfaces
  • MessageBus
  • Implementations of MessageBus
    • Implicit Intent MB: FluffyEvents
    • EventBus: Otto and GreenRobot's EB
  • Idea of a better EventBus
  • Recommendations

Nodes of communication

Any android specific classes (Activities, Services, Fragments, etc) or

other java classes (Models, Helper classes, Threads) inside an application

Dependency

Class A uses another class or interface B,

then A depends on B. A cannot carry out it's work without B

 

A is dependent

B is dependency

 

Dependency affects reusability, development speed, code quality, code readability.

Degree of dependency

 

 

 

In order to reuse a Class, how much other code needs to be reused along with it?

 

It's neither binary nor discrete, but linear.

That is, you cannot entirely get rid of dependencies but reduce the degree of it.

Tight Coupling

Degree of dependency among components is relatively high.

 

Components keep references of each other and call methods on them directly. 

 

Inflexible, error-prone, modularization not possible, debugging can be painful.

class SongsListFragment extends Fragment {
    private void onSongSelected(int trackId) {
	MusicPlayerActivity mpAct = 
            (MusicPlayerActivity) getActivity();
	mpAct.playSong(trackId);
    }
}

SongsListFragment tightly coupled with MusicPlayerActivity

With above degree of coupling, SongsListFragment cannot be reused without MusicPlayerActivity

Loose Coupling

Components have very little knowledge about other significant components. Relatively low degree of dependency.

 

Adaptable, modular and plug-n-playable, less error-prone, easier to debug.

Our goals while laying down Communication channel are

 

      1. Reduce degree of dependency 

      2. To keep code efficient

      3. Less pain while development

Interfaces

A component implements an interface and other components interact rather with the interface.

 

Standard and efficient but requires lot of boiler plate code and isn't perfectly decoupled as interface still needs to be shared among nodes.

Interface example

public interface TrackStateListener {
    public void onTimeUpdate(long ms);
    public void onTrackComplete();
}

public class MediaPlayer implements TrackStateListener {
    ...
    public void onTimeUpdate(long ms) {
        // Update progress bar
    }

    public void onTrackComplete() {
        // Play next track and notify SongsList
    }
    ...
}

public class MusicPlayerActivity {
    ...
    musicPlayerService.setTrackStateListener(mediaPlayer);
    ...
}
    

Using Interface to pass message from MusicPLayerService to MediaPlayer

All of the above for reverse communication. Lot of boilerplate!!

Interface chaining

 

When nodes of communication are multiple layers apart, it would involve multiple interfaces.

MediaPlayer

PlayerActivity

Interface1

ListController

Interface2

Interface3

SongsList

Message Bus

Pubsub communication.

 

Components can subscribe on MessageBus for specific action or event.

 

Publishers can post actions or events on MessageBus and subscribers would get notified.

MessageBus Flow

MessageBus implementations

 

  • Implicit Intent based MessageBus
  • Event based MessageBus: EventBus

Implicit Intent MessageBus

Based on BroadcastReceiver.

 

Data is bundled in order to pass, thus involves the overhead of making custom data parcelable or serializable.

 

Highest degree of decoupling.

Example of Implicit Intent MessageBus:

FluffyEvents

Data flow is painful

Data has to be bundled in order to pass, thus involves the overhead of making custom data parcelable or serializable.

EventBus

An event can be any java class.

 

Events can be posted on Bus, subscribed to and notified of.

 

Highly decoupled, simple and less boiler plate code.

 

But not standard and not as efficient as Interface

Sample Event class

class DownloadProgressEvent {
    private float progress;
    public DownloadProgressEvent(float progress) {
	this.progress = progress;
    }

    public float getProgress() {
	return progress;
    }
}

EventBus in 4 steps

1. Define an Event

2. Register Subscriber

3. Post an Event

4. Receive the Event

EventBus in 4 steps

1. Define an Event

public class MyEvent { }

2. Register Subscriber

EventBus.getInstance().register(this)

3. Post an Event

EventBus.getInstance().postEvent(event)

4. Receive the Event

public void onEvent(MyEvent event) { }

Sticky Events

Some events carry imformation that is of interest even after the event is posted.

 

For example, 'last known location'.

 

A subscriber gets notified of such events during registration and these events can be queried anytime.

Examples of EventBus

Square's Otto: Annotation based, light-weight and easy to use. Guava library's EventBus optimized for Android. Provides limited threading options.

 

GreenRobot's EventBus: Convention based. Better performance than any other EventBus for Android.

Behind the scene

EventBus keeps track of events and subscriber methods of active classes in map data-structures, such as following used by Otto.

private static final Map<Class<?>, Map<EventClass, Set<Method>>> SUBSCRIBERS_CACHE = 
    new HashMap<Class<?>, Map<EventClass, Set<Method>>>();

Whenever an Event is posted on Bus, this map is looped through and all the methods registered for the Event are called synchronously.

Mapping is Done in following two steps

1. Loop over methods of a class using reflection.

 

2. If it's an annotation-based EventBus, loop over annotations and fill the map. In convention based EventBus, use the convention to fill the map.

Did you spot the monster here that feeds on performance?

Reflection: the monster

Reflection is a processor intensive task.

 

Looping over methods of a class using reflection scans through it's superclasses as well.

 

 

Class.getMethods()

App startup time is a rare resource

There could be many things taking slice of this start up time. And ideally, an aggregate lag of ~300 ms can be sad. So saving every bit of it is worth an effort.

 

If multiple classes register on EventBus during startup, reflection is going to eat up ~50-100 ms

Idea of a better EventBus

Give another option to replace reflection for subscriber discovery.

Let the developer take the pain of documenting subscribers for EventBus.

Possible usage pattern

class MyActivity extends Activity {
    
    onCreate() {
        ...
        ArrayList<String> subscriberNames = new ArrayList<String>();
        subscriberNames.add("subscriberMethod1");
        subscriberNames.add("subscriberMethod2");
        subscriberNames.add("subscriberMethod3");

        EventBus.getInstance().registerWithSubscribers(this, subscribers);
    }
}

Classes that would register on Bus using this special register method will skip reflection and make use of following method while subscribers mapping.

Class.getMethod("methodA");

Recommendations

As a rule of thumb, use Interfaces when components are directly in touch.

 

And prefer MessageBus when components are multiple layers apart or multiple components are interested in an event.

 

Further, use EventBus when custom data is to be passed or else prefer Implicit Intent MB.

Contact Me

@infernus321

https://github.com/Infernus666

talk.to.achin@gmail.com

www.vinsol.com

MusicPlayer Activity

ListFragments 

(songs, albums)

MediaPlayer

Fragment

MusicPlayerService

A Situation

ListController

Communication cases

  • ListController switches between lists (songs, albums)
  • SongsList triggers change of song in MediaPlayer
  • MediaPlayer notifies SongsList about automatic song change
  • MediaPlayer plays/pauses/ffwds track in MusicPlayerService
  • MusicPlayerService notifies MediaPlayer of track time update
  • MusicPlayerService notifies MediaPlayer of track completion, which is further communicated to SongsList / AlbumsList.

                            .... and many more

Communication Patterns in Android

By Achin Kumar

Communication Patterns in Android

  • 2,305