1. 程式人生 > >EventBus原始碼詳解(一):基本使用

EventBus原始碼詳解(一):基本使用

寫在前面

對於Android程式設計師來說,相信大家都聽過EventBus的大名。EventBus是一個Android平臺上基於事件釋出和訂閱的輕量級框架,可以對釋出者和訂閱者解耦,並簡化Android的事件傳遞。正如官方介紹其優勢:

  • 簡化了元件之間的通訊
    • 解耦事件的傳送者和接收者
    • 在Activity、Fragment和後臺執行緒表現良好
    • 避免複雜和易出錯的依賴性和生命週期問題
  • 使你的程式碼更加簡潔
  • 快速和輕量

圖來自EventBus GitHub.png

接下來,讓我們一起從EventBus的使用到原始碼解讀,全方位地解讀這個輕量但功能強大的開源框架。

正文

本文先從EventBus的簡單使用開始,先介紹EventBus

的基本配置和簡單使用,然後用一個Demo來舉例EventBus的使用及其優勢。

配置和簡單使用

在app下的build.gradle配置:

compile 'org.greenrobot:eventbus:3.0.0'

是的,就新增這句就可以了,非常簡單。當然還可以配置生成索引,這部分在進階使用文章裡介紹。

定義一個事件類:

public static class MessageEvent { /* Additional fields if needed */ }

在訂閱者類中定義一個用註解“@Subscribe”標記並引數為事件類的方法:

@Subscribe
public
void onMessageEvent(MessageEvent event) {/* Do something */};

然後在訂閱者類中註冊和登出,在Android上,通常在Activity或Fragment的生命週期裡處理:

@Override
 public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this); // 註冊
 }

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this
); // 登出,當訂閱者不再訂閱事件時,必須登出,否則可能發生記憶體洩漏 }

最後,傳送事件,每個訂閱者註冊的事件都將收到訊息(會呼叫“@Subscribe”標記的方法):

EventBus.getDefault().post(new MessageEvent());

舉例

現在來舉個栗子來展示EventBus的魅力!想想一個閱讀App看書的場景:左邊是書籍選擇列表,右邊是書籍的內容。這時我們大多數情況都是用Fragment來處理,也即左右兩邊都是Fragment,這就涉及兩個Fragment的通訊問題。我們通常的處理方式是在Fragment新增介面回撥,然後Activitty作為兩個Fragment的通訊橋樑。

或許有人說直接在Activity獲取Fragment裡的控制元件,互動更加簡單。這樣雖然簡單,但Activity的職責變得很重,而Fragment的作用之一就是分擔Activity的職責,所以這是不可取的。

好,我們先來看不用EventBus時用回撥的方式來實現Fragment的通訊的例子。

首先先定義兩個Fragment,左邊的Fragment1,佈局只有一個ListView(佈局程式碼不貼,後面有原始碼),在Fragment1裡定義一個介面,當點選ListView的item時,就把所點選的書籍回撥給外部:

public class Fragment1 extends Fragment {

    private final static String[] BOOKS = new String[] {
            "第一行程式碼",
            "Java程式設計思想",
            "Android開發藝術探索",
            "Android原始碼設計模式",
            "演算法",
            "研磨設計模式"
    };

    ListView listView;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);
        listView = (ListView) view.findViewById(R.id.lv_catalogue);
        ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_1, BOOKS);
        listView.setAdapter(arrayAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                // 回撥
                if (listener != null) {
                    listener.onCLickBook(BOOKS[position]);
                }
            }
        });
        return view;
    }

    private OnClickBookListener listener;

    public void setOnClickBookListener(OnClickBookListener listener) {
        this.listener = listener;
    }

    /**
     * 點選書籍監聽介面
     */
    public interface OnClickBookListener {
        void onCLickBook(String book);
    }
}

右邊的Fragment2就更加簡單了,佈局只有一個TextView來展示書籍的內容:

public class Fragment2 extends Fragment {

    TextView textView;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment2, container, false);
        textView = (TextView) view.findViewById(R.id.tv_content);
        return view;
    }

    /**
     * 需要暴露介面給外部來互動
     */
    public void readBook(String book) {
        textView.setText(book);
    }
}

最後,MainActivity作為兩個Fragment的通訊橋樑,來實現兩個Fragment的通訊:

public class MainActivity extends AppCompatActivity {

    Fragment1 fragment1;
    Fragment2 fragment2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        fragment1 = (Fragment1) getSupportFragmentManager().findFragmentById(R.id.fragment1);
        // 設定監聽
        fragment1.setOnClickBookListener(new Fragment1.OnClickBookListener() {
            @Override
            public void onCLickBook(String book) {
                // 更新fragment2的閱讀書籍
                fragment2.readBook(String.format("我正在閱讀%s", book));
            }
        });
        fragment2 = (Fragment2) getSupportFragmentManager().findFragmentById(R.id.fragment2);
    }
}

從上面程式碼可以看到,兩個Fragment的通訊也非常簡單,而Activity的職責也明顯降低,只需作為橋樑,那麼我們還有必要用EventBus嗎?答案是有必要的!這只是一個例子,而實際開發中兩個Fragment通訊互動幾乎不可能這麼簡單,往往是縱橫交錯的,這時難道在Fragment上定義多個監聽介面,然後在Activity上處理這些邏輯?這就又導致了Activity職責過大,而耦合度也顯著增加了。所以,使用回撥的通訊方式似乎適合互動較少的兩個Fragment,但一旦互動過多,就無能為力了。下面再來看看EventBus怎麼實現Fragment的通訊。

首先,先定義一個閱讀書籍的事件類(注意,最好用public修飾,否則有可能在生成索引時失敗,這些內容在後續文章會講解,現在先記著):

public class ReadBookEvent {

    private String book;

    ReadBookEvent(String book) {
        this.book = book;
    }

    public String getBook() {
        return book;
    }
}

顯然,因為Fragment2是監聽Fragment1的點選事件作出更新響應,所以,這裡Fragment2是訂閱者(注意:與事件類一樣,訂閱者最好宣告為public,否則有可能在生成索引失敗):

public class Fragment2 extends Fragment {

    TextView textView;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment2, container, false);
        textView = (TextView) view.findViewById(R.id.tv_content);
        return view;
    }

    @Override
    public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this); // 註冊訂閱者
    }

    @Override
    public void onStop() {
        super.onStop();
        EventBus.getDefault().unregister(this); 登出訂閱者
    }

    /**
     * 監聽閱讀事件
     */
    @Subscribe
    public void onReadBook(ReadBookEvent event) {
        textView.setText(String.format("我正在閱讀%s", event.getBook()));
    }
}

而Fragment1不用定義回撥介面了,只需在點選書籍時傳送事件:

public class Fragment1 extends Fragment {

    private final static String[] BOOKS = new String[] {
            "第一行程式碼",
            "Java程式設計思想",
            "Android開發藝術探索",
            "Android原始碼設計模式",
            "演算法",
            "研磨設計模式"
    };

    ListView listView;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);
        listView = (ListView) view.findViewById(R.id.lv_catalogue);
        ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_1, BOOKS);
        listView.setAdapter(arrayAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                EventBus.getDefault().post(new ReadBookEvent(BOOKS[position])); // 傳送事件
            }
        });
        return view;
    }
}

另外在MainActivity裡,不用再處理Fragment的互動了,甚至不用建立Fragment的例項,直接解耦:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

從上面程式碼可以看到,EventBus很好地使兩個Fragment解耦,並優雅地實現了兩者的通訊,同時也減少了Fragment和Activity的耦合,真正的一舉多得!再想想兩個Activity之間的通訊,我們通常使用Intent作為載體,來傳遞資料,使用EventBus就可以拋開Intent了,直接實現Activity間的通訊!這裡就不再舉例了,相信聰明的你能舉一反三。

至此,相信大家對EventBus有了基礎的認知了。

寫在最後

EventBus所帶來的便利和簡潔有沒有使你震驚?!別滿足的太早,EventBus如此受人待見,當然有她的厲害之處。後續文章將會介紹使用編譯期處理生成索引來提升事件傳送的效率,還有EventBus的高階配置,敬請期待!

demo