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