본문 바로가기

6. With IT/6.1 Android

EventBus기법

http://greenrobot.github.io/EventBus/

Activity와 Activity, 혹은 Activity와 Fragment간 데이터 교환시 좀더 심플하고 안정적(?)으로 처리할 수 있게 많들어 놓은 '라이브러리'

*간단 사용법
1. Define an event
    public class MyEvent {}
2. Register subscriber
    EventBus.getDefault().register(this);
3. Post an event
    EventBus.getDefault().post(event);
4. Receive the event
    public void onEvent(MyEvent event);


[Github] 

[Example]

[Example Slide (설명이 잘 되어있음)



The Android development ecosystem has changed tremendously since I posted my first ever Android article almost 5 years ago. A number of tools and frameworks have been released that allow Android programmers to develop, test, and release applications rapidly. The next couple of posts are going to be overviews of some of the most cutting edge tools and approaches at your disposal. They will include simple but actual code examples from consumer facing applications I have developed.

Android provides a number of approaches for communicating between the major Android abstractions (Service, Activity, Application, and Fragment). These include Intents, Binder interfaces, Fragment Arguments, and Broadcast Receivers. Each of these approaches have their own set of gotchas and maintenance problems. Developers end up maintaining a number of references to Fragments, Managers, Service Binders, and Broadcast registrations as well as having to maintain a number of IntentFilter XML elements in AndroidManifest.xml.

Many recent frameworks have attempted to alleviate these communication problems by introducing an approach that is in use by many modern server side architectures. Event or message buses allow for the decoupling of services through a common communication “pipeline” that provides a queue of messages that can be sent, received, and processed by any service in the “network” without the services having to know about each other’s interfaces or even whether they even exist. This allows systems to be more maintainable and scalable.

EventBus

EventBus provides, well, an event bus for Android components to communicate in a decoupled way. Components simply register with the event bus, provide an onEvent* method for a certain type of Event, and will be able to receive any events posted by any other component(with some exceptions).

Simple register and unregister in an Activity. No interface implementation needed!

  1. public void onCreate(Bundle savedInstanceState) {  
  2.         super.onCreate(savedInstanceState);  
  3.                //register  
  4.         EventBus.getDefault().register(this);  
  5.                ...  
  6. }  
  7. //unregister  
  8. protected void onDestroy() {  
  9.         super.onDestroy();  
  10.         EventBus.getDefault().unregister(this);  
  11.     }  

Posting an Event. UpdateUIEvent is just an object, no subclassing or interfaces and can have any number of member variables.

  1. bus.post(new UpdateUIEvent());  

Subscribing to UpdateUIEvents to be received on the main UI Thread.

  1. public void onEventMainThread(UpdateUIThread ev) {  
  2.     .... //perform operations on ev and update the ui  
  3. }  

The onEvent* method naming convention allows developers to specify which thread they would like to receive events on. These are not enforced by interfaces so there is no extra “implements” boiler plate needed. Some other onEvent* methods are: PostThread (thread event was published on, default), MainThread (main UI thread), BackgroundThread (android’s background thread), Async (a newly spawned thread, be careful with these).

Examples
The following are some real implementation examples that provide a pain point and it’s EventBus solution.

Example 1 Activities and Fragments

The filter editing screen in my app 100filters is a combination of an Activity with a content view, an embedded fragment, and a subfragment managed by the embedded fragment. Before EventBus I was managing references to each through standard java member variables as well as the ever-fickle Fragment.getActivity() and FragmentManager.findFragmentBy* methods. The code for this is difficult to maintain and things like device orientation changes can wreak havoc on these types of implementations.



The main problem I had was when a user wants to apply a filter, they press the “check” button which lives inside of a subfragment(FilterSelector) that is dynamically added and removed by a root fragment (GroupFilterSelector). The “check” button uses the following code to call the save() method which is inside of the root FilterActivity.

  1. public void onClick(View v) {  
  2.     if (((FilterActivity) FilterSelector.this.getActivity()).save()) {  
  3.         mAdd.setImageResource(R.drawable.ic_action_add_check_disabled);  
  4.         mSub.setImageResource(R.drawable.ic_action_undo_enabled);  
  5.     }  
  6. }  

The save() method then calls an AsyncTask that applies the filter to the image and saves it. When the AsyncTask is successful I wanted it to do the above mAdd.setImage/mSub.setImage.. code inside of the FilterSelector fragment. The problem is FilterSelector is a subfragment of another Fragment and keeping track of which FilterSelector is currently active inside of FilterActivity(through FragmentManager or member refs), introduced a bunch of bugs that were difficult to debug to the point where I simply used the code above that just assumes that the save is successful.

Another problem is that the FilterActivity can also call save() on it’s own through a menu option and there was no way to perform the above code inside of the subfragment without the buggy referencing code which introduced an inconsistency into the interface that confused users.

The EventBus solution is simple. After registering for events inside of FilterActivity and FilterSelector I simply use the following code in FilterSelector:

  1. public void onClick(View v) {  
  2.     bus.post(new SaveEvent());  
  3. }  
  4. public void onEventMainThread(UpdateUIEvent ev){  
  5.   if(ev.isSuccessful()){  
  6.        mAdd.setImageResource(R.drawable.ic_action_add_check_disabled);  
  7.        mSub.setImageResource(R.drawable.ic_action_undo_enabled);  
  8.   }  
  9. }  

And the following inside of FilterActivity:

  1. //receive the SaveEvent from FilterSelector  
  2. public void onEventMainThread(SaveEvent ev){  
  3.       performSaveTask();  
  4. }  
  5. //inside of SaveTask, post UpdateUIEvent to current FilterSelector  
  6. @Override  
  7. protected void onPostExecute(Boolean result) {  
  8.     EventBus.getDefault().post(new UpdateUIEvent(result));  
  9.   
  10. }  

This solution provides a simple, decoupled approach to performing tasks and updating the user interface(whether it lives in an Activity or Fragment) with minimal amount of code.

Example 2: IntentService to Activity

This example involves updating the UI of a currently active activity from an IntentService performing an operation in the background. This code is from an unreleased video app that would trigger an IntentService to upload a video to a server and then update the UI with a share link to the video on the web. Before EventBus, in order to do this the developer would have to implement a BroadcastReceiver and IntentFilter inside of the Activity and then create and send a broadcast intent from inside of the service.

The code in the Activity would look something like this:

  1. @Override  
  2. protected void onResume() {  
  3.     super.onResume();  
  4.     mBroadcastReceiver = new BroadcastReceiver() {  
  5.   
  6.             @Override  
  7.             public void onReceive(Context context, Intent intent) {  
  8.                 String url = intent.getStringExtra("url");  
  9.                 mShareUrl.setText(url);  
  10.             }  
  11.         };  
  12.     IntentFilter filter = new IntentFilter("com.awc.UPDATEUI");  
  13.     this.registerReceiver(mBroadcastReceiver, filter);  
  14. }  
  15.   
  16. @Override  
  17.     protected void onPause() {  
  18.         super.onPause();  
  19.         this.unregisterReceiver(mBroadcastReceiver);  
  20.     }  

And sending the notification from the Service would look something like this:

  1. Intent updateUi = new Intent("com.awc.UPDATEUI");  
  2. updateUi.putExtra("url", url);  
  3. sendBroadcast(updateUi);  

With EventBus the Activity code is simplified to:

  1. public void onEventMainThread(UpdateUIEvent e){  
  2.         mShareUrl.setText(e.url);  
  3. }  

and the Service is simplified to:

  1. bus.post(new UpdateUIEvent(url));  

This example is really basic but getting rid of managing intent filter string constants, registering broadcast receivers, and building Intents with a large amount of extras contributes immensely to maintainability (using EventBus is also faster than through the Intent Registry).

Example 3: Sticky Events and Activity Results

By now you are familiar with how components in Android can start Activities and receive data results from those activities. In my application HoneyGram the user can use their Instagram credentials to receive an OAuth access token that can then be used to interact with the Instagram API. In HoneyGram this flow involves starting an activity that opens the Instagram OAuth authorization endpoint in a fullscreen WebView. The WebView then parses the access token and passes it back to the Main Activity which then opens a number of Fragment Tabs with content using the token (if the log in was successful of course).

The boilerplate code for starting and receiving data from the Login Activity looks a little something like this:

  1. //start login flow  
  2. Intent itent = new Intent(Main.this, LoginActivity.class);  
  3. Main.this.startActivityForResult(tent, 111);  
  4. //on result  
  5. @Override  
  6. protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
  7. if (requestCode == 111 && resultCode == Activity.RESULT_OK) {  
  8.       String token = arg2.getStringExtra("token");  
  9.        ...//save token and add new tabs  
  10. }  
  11. }  

The code which returns the data in LoginActivity to the Main Activity looks like:

  1. Intent data = new Intent();  
  2. data.putExtra("token", token    );  
  3. LoginActivity.this.setResult(Activity.RESULT_OK, data);  
  4. LoginActivity.this.finish();  

EventBus allows you to create Sticky Events which persist in the message queue until they are removed from it by a component. We can take advantage of these events to simplify the process of receiving data from an activity without having to deal with request codes, result codes, and intent data.

The code from LoginActivity that uses sticky events simply becomes:

  1. bus.postSticky(new LoggedInEvent(token));  
  2. finish();  

And the code to pull the event from the sticky queue becomes: (note this code could also be in onResume)

  1. @Override  
  2.     protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
  3.         LoggedInEvent stickyEvent = (LoggedInEvent) bus  
  4.                 .getStickyEvent(LoggedInEvent.class);  
  5.         if (stickyEvent != null) {  
  6.             bus.removeStickyEvent(stickyEvent);  
  7.             String token = stickyEvent.token;  
  8.                         ...//do stuff  
  9.                 }  
  10. }  

These are just a few simple real world examples for using EventBus in your Android application to streamline code as well as make your app more performant. If you have any examples of any heavy duty uses of EventBus feel free to post them in the comments.

Check out the github page for EventBus for source/jars as well as more documentation and some performance measurements compared to other Android event bus frameworks.




'6. With IT > 6.1 Android' 카테고리의 다른 글

GCM 푸시서버연동 설정 - Client  (3) 2015.04.10
ScalableLayout  (0) 2015.03.06
Meterial Design Example  (0) 2014.12.23
안드로이드 Affinity  (0) 2013.10.17
안드로이드 어플리케이션 기초적인 내용들  (0) 2013.10.17