1. 程式人生 > >android進階4step3:Android常用框架——EventBus框架

android進階4step3:Android常用框架——EventBus框架

Android Event Bus

學習步驟

  • EventBus簡介
  • EventBus方法介紹
  • EventBus的實際應用
  • 總結

EventBus簡介


以下來自:EventBus主頁

開源專案地址:
https://github.com/greenrobot/EventBus

EventBus主頁:

http://greenrobot.org/eventbus/

簡介:

Introduction

EventBus is an open-source library for Android usingthe publisher/subscriber pattern for loose coupling. EventBus enables central communication to decoupled classes with just a few lines of code – simplifying the code, removing dependencies, and speeding up app development.

EventBus是一個AndroidJava的開源庫,使用釋出者/訂閱者模式進行鬆散耦合。EventBus只需幾行程式碼即可實現與分離類的集中通訊 - 簡化程式碼,消除依賴關係,並加速應用程式開發。

綜合版:

EventBus是一個Android端優化的publish/subscribe訊息匯流排,簡化了應用程式內各元件間元件與後臺執行緒間的通訊。比如請求網路,等網路返回時通過HandlerBroadcast通知UI,兩個Fragment之間需要通過Listener通訊,這些需求都可以通過EventBus實現。

使用EventBus的好處:它......


  • 簡化了元件之間的通訊
  • 將事件傳送者和接收者分離
  • 在UI工件(例如,活動,片段)和後臺執行緒中表現良好
  • 避免複雜且容易出錯的依賴關係和生命週期問題
  • 很快; 專門針對高效能進行了優化
  • 很小(<50k jar
  • 在實踐中被證明通過應用與100,000,000+安裝
  • 具有交付執行緒,使用者優先順序等高階功能。

進一步的EventBus功能


  • 方便的基於註釋的API:基於便捷註釋的API:只需將@Subscribe註釋放入訂閱者方法即可。由於註釋的構建時間索引,EventBus不需要在應用程式的執行時進行註釋反射。

  • Android主執行緒交付:
    當與UI互動時,無論事件如何釋出,
    EventBus都可以在主執行緒中傳遞事件。
  • 後臺執行緒傳遞:如果您的訂閱者執行長時間執行的任務EventBus也可以使用後臺執行緒來避免UI阻塞。
  • 事件和訂閱者繼承:EventBus中,面向物件的範例適用於事件和訂閱者類。假設事件類A是B的超類。型別B的已釋出事件也將釋出給對A感興趣的訂閱者。同樣考慮訂閱者類的繼承。
  • 快速入門您可以立即開始使用 - 無需配置任何內容 - 使用程式碼中任何位置提供的預設EventBus例項。
  • 可配置:  要根據您的要求調整EventBus,您可以使用構建器模式調整其行為。

開始使用EventBus


有關EventBus的第一步,請檢視文件/教程,尤其是入門指南

 

 EventBus架構


作為一個訊息匯流排,有三個主要的元素:

Event:事件
Subscriber:事件訂閱者,接收特定的事件
Publisher:事件釋出者,用於通知Subscriber有事件發生

Event

Event 可以是任意物件,用來描述傳遞的資料和事件型別。

 Publisher


可以在任意執行緒任意位置傳送事件,直接呼叫EventBus的post(Object)方法 ,可以自己例項化EventBus物件,但一般使用預設的單例就好了: EventBus.getDefault(),根據post函式引數的型別,會自動呼叫訂閱相應型別事件的函式。

Subscriber

EventBus中,使用約定來制定事件訂閱者以簡化使用。

3.0之 Subscriber函式的名字只能是以下4個

  1. onEvent
  2.  onEventMainThread
  3.  onEventBackgroundThread
  4. onEventSync

這四個,這個和ThreadMode有關。

ThreadMode

ThreadMode制定了會呼叫的函式

3.0之 前有以下四個ThreadMode:

@Subscribe(threadMode = ThreadMode.ASYNC)
  1. PostThread(傳送和接都在同一個執行緒內)
  2. MainThread(傳送和接都是在主執行緒)
  3. BackgroundThread  (同步的,接收是在單獨的執行緒內,如果同時執行多個 依次執行)
  4. Async (非同步的處理耗時操作,接收是非同步的,獨佔一個執行緒池的執行緒來處理)

3.0之後加上@Subscriber後,函式的名字不固定。但需要指明ThreadMode

有以下四個ThreadMode:

  • POSTING(預設的)
  • MAIN
  • BACKGROUND
  • ASYNC

一、ThreadMode-PostThread

  • - 事件的處理在和事件的傳送在相同的程序,所以事件的處理時間不應太長, 不然影響事件的傳送執行緒,而這個執行緒可能是UI執行緒
  • - 對應的函式名是onEvent

簡單案例:兩個Activity間傳遞資料

佈局檔案省略

新增依賴

implementation 'org.greenrobot:eventbus:3.1.1'

第一個Activity程式碼:

public class MainActivity extends AppCompatActivity {

    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = findViewById(R.id.id_btn_main);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //跳轉到第二個Activity中
                startActivity(new Intent(MainActivity.this, SecondActivity.class));
            }
        });
        //註冊 必須有@Subscriber 訂閱者
        EventBus.getDefault().register(this);
    }

    @Subscribe
    public void onEvent(MyEvent event) {
        Toast.makeText(this, "我是第一個Activity,收到Event:"+event.msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //登出
        EventBus.getDefault().unregister(this);

    }
}

第二個Activity程式碼:


public class SecondActivity extends AppCompatActivity {
    private Button mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        mButton = findViewById(R.id.id_btn_second);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyEvent event = new MyEvent();
                event.msg = " hello !";
                //直接post不需要註冊
                EventBus.getDefault().post(event);
            }
        });
    }
    
}

 

二、ThreadMode-BackgroundThread


事件的處理會在一個後臺執行緒中執行,對應的函式名是 onEventBackgroundThread,雖然名字是BackgroundThread,事件處理是在後臺執行緒,但事件處理時間還是不應該太長,因為如果傳送事件的執行緒是後臺執行緒,會直接執行事件,如果當前執行緒是UI執行緒,事件會被加到一個佇列中,由一個執行緒依次處理這些事件,如果某個事件處理時間太長,會阻塞後面的事件的派發或處理。

三、ThreadMode-Async

事件處理會在單獨的執行緒中執行,主要用於在後臺執行緒中執行耗時操作 ,每個事件會開啟一個執行緒(有執行緒池),但最好限制執行緒的數目。

 EventBus 簡單案例 :

  • * 1. 新增BusEvent框架到自己的專案中:build.gradle 新增BusEvent的依賴
  • * 2.獲取Bus例項,來給APP中所有的Activity或者Fragment提供
  • * 3. register,unregister 事件匯流排
  • * 4. 在要傳送事件的地方呼叫bus.post(Event)
  • * 5. 接收的地方定義訂閱的函式@Subscribe ...

在MainAcitivity中有兩個fragment

一個HistoryFragment顯示經緯度的listview
一個MapFragment顯示通過百度API通過經緯度下載的靜態圖片的Image
當點選MOVELOCATION時傳送一個隨機經緯度

  • HistoryFragment 訂閱者:接收到經緯度的資料,更新adapter
  • MapFragment 訂閱者: 也接受到經緯度通過aysncTask在doInBackground中使用百度的API下載
  • onPostExcute 中 返回下載好的drawable影象 此時post給自己(本身的fragment)顯示圖片

注意新增網路許可權

    <uses-permission android:name="android.permission.INTERNET"></uses-permission>

注意:android 9.0 使用這個百度API 會報錯

解決方法: android高版本聯網失敗報錯:Cleartext HTTP traffic to xxx not permitted解決方法

完整程式碼:

模組的build.gradle中新增otto框架的依賴

implementation 'org.greenrobot:eventbus:3.1.1'

佈局檔案:

activity_main.xml

這裡的fragment是靜態新增的  注意修改為自己的包名

        class="包名.LocationMapFragment"

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
 
        <Button
            android:id="@+id/bt_clear_location"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Clear Location" />
 
        <Button
            android:id="@+id/bt_move_location"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Move Location" />
    </LinearLayout>
 
    <!--靜態載入fragment-->
    <fragment
        android:id="@+id/fragment_map"
        class="com.demo.ottosample.LocationMapFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
 
    <fragment
        android:id="@+id/fragment_history"
        class="com.demo.ottosample.LocationHistoryFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
 
 
</LinearLayout>

Fragment:

LocationHistroyFragment.java

顯示歷史經緯度的Fragment

import android.app.ListFragment;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
 
import com.squareup.otto.Subscribe;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * 顯示歷史座標的fragment
 */
public class LocationHistoryFragment extends ListFragment {
    private final List<String> locationEvents = new ArrayList<>();
    private ArrayAdapter<String> adapter;
 
 
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        adapter = new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_1, locationEvents);
        setListAdapter(adapter);
    }
 
    @Override
    public void onResume() {
        super.onResume();
        //註冊Bus
        EventBus.getDefault().register(this);
    }
 
    @Override
    public void onStop() {
        super.onStop();
        //登出Bus
        EventBus.getDefault().unregister(this);
    }
 
    /**
     * 訂閱 座標移動的事件 更新 listview
     *
     * @param event
     */
    @Subscribe
    public void onLocationMoveEvent(LocationMoveEvent event) {
        float lng = event.longitude;
        float lat = event.latitude;
        locationEvents.add(String.format("[%s, %s]", lng, lat));
        adapter.notifyDataSetChanged();
    }
 
    /**
     * 訂閱 座標 清除的事件 清除list資料
     *LocationClearEvent 暫時用不上 所以為空類
     * @param event
     */
    @Subscribe
    public void onLocationClearEvent(LocationClearEvent event) {
        locationEvents.clear();
        adapter.notifyDataSetChanged();
    }
}

 LocationMapFragment.java

顯示地圖的靜態圖片的Fragment

public class LocationMapFragment extends Fragment {
    private ImageView mImageView;
    private final String URL = "http://api.map.baidu.com/staticimage?width=1000&height=1000&center=%s,%s&zoom=15";
    private DownloadTask mTask;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

    }

    @Override
    public void onResume() {
        super.onResume();
        EventBus.getDefault().register(this);
    }

    @Override
    public void onStop() {
        super.onStop();
        EventBus.getDefault().unregister(this);
    }

    //訂閱  非同步下載圖片
    @Subscribe(threadMode =ThreadMode.ASYNC)
    public void onEvent(LocationEvent event) {
        float longitude = event.longitude;
        float latitude = event.latitude;
        String url = String.format(URL, longitude, latitude);
        mTask = new DownloadTask();
        mTask.execute(url);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        mImageView = new ImageView(getActivity());

        return mImageView;
    }


    private class DownloadTask extends AsyncTask<String, Void, Drawable> {
        @Override
        protected Drawable doInBackground(String... params) {
            String downloadUrl = params[0];
            try {
                return BitmapDrawable.createFromStream(new URL(downloadUrl).openStream(), "bitmap.jpg");
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }

        @Override
        protected void onPostExecute(Drawable drawable) {
            super.onPostExecute(drawable);
           mImageView.setImageDrawable(drawable);
        }
    }
}

MainAcitivity.java  展示兩個fragment  清除list和隨機產生一個經緯度的post

public class MainActivity extends AppCompatActivity {

    private Button mClearButton;
    private Button mMoveButton;

    private float DEFAULT_LONGITUDE = 116.413554F;
    private float DEFAULT_LATITUDE = 39.911013f;

    private float longitude;
    private float latitude;

    private float OFFSET = 0.1f;
    private Random random = new Random();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mClearButton = (Button)findViewById(R.id.bt_clear_location);
        mMoveButton = (Button)findViewById(R.id.bt_move_location);

        mClearButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                EventBus.getDefault().post(new ClearLocationEvent());
            }
        });

        mMoveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                longitude = DEFAULT_LONGITUDE + OFFSET * random.nextFloat();
                latitude = DEFAULT_LATITUDE + OFFSET * random.nextFloat();
                EventBus.getDefault().post(new LocationEvent(longitude, latitude));
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

}

事件:

移動座標的事件(封裝的實體類,用來傳遞資料的)

LocationMoveEvent.java

public class LocationMoveEvent {
    public float longitude;
    public float latitude;
 
    public LocationMoveEvent(float lng, float lat) {
        this.longitude = lng;
        this.latitude = lat;
    }
}

LocationClearEvent.java 清除list的資料 這裡比較簡單,可以定義為空 

 
/**
 * 清除座標的事件
 */
public class LocationClearEvent {
}

完成 

  • 與OTTO事件匯流排的對比(涉及複雜的推薦EventBus,簡單的UI元件推薦Otto)

  • 轉:淺析Otto框架,並與EventBus對比
  • 從事件訂閱的處理差別來看:
  • EventBus是採用反射的方式對整個註冊的類的所有方法進行掃描來完成註冊;
  • Otto採用了註解的方式完成註冊;
  • 共同的地方快取所有註冊並有可用性的檢測。同時可以移除註冊;
  • 註冊的共同點都是採用method方法進行一個整合。
  • EventBus. 事件響應有更多的執行緒選擇

  • EventBus. 支援Sticky(黏貼型) Event 和 處理事件的優先順序

  • Otto更多使用場景應該就是在主執行緒中,因為它內部沒有非同步執行緒的場景。(也許是它自身的定位不一樣,它就是為了解決UI的通訊機制。所以出發點就是輕量級)在程式碼中主要體現這一特色的地方就是在介面ThreadEnforcer以及內部的實現域ANY和MAIN。在MAIN內部有一個是否是主執行緒的檢查,而ANY不做任何檢查的事情。
  • EventBus3.0以前,還需要根據四種執行緒模式分別對應固定接收方法,而OTTO則可以通過註解的方法自定義方法,比較方便,但是EventBus3.0也實現了通過註解自定義方法了。而Otto介紹上不管是訂閱者還是傳送者都需要註冊事件,但是我發現現在傳送者不用註冊也可以傳送了。
  • 每個框架都有自己的特點,我們開發者必須明白每個框架的出發點才能更好的使用,沒有哪個框架好不好的問題,只要開發者自己使用哪個舒服,哪個就是最好的。適合自己的才是最好的。
  • 最後我想說,可能EventBusOtto很早以前就有了,現在RxJava就能實現這樣的功能,但是對於不瞭解Rx技術的人來說,這些還是非常有用的,Rx技術雖好,雖然很新,如果沒有搞懂的情況下,貿然使用估計會給你帶來很大的困難。最好在有一個比較懂Rx技術的人的前提下,開始使用,提高自己。

總結:

簡單的使用

基本的使用步驟就是如下4步:

在模組build.gradle中新增EventBus框架的依賴:

implementation 'org.greenrobot:eventbus:3.1.1'
  • 1. 定義事件型別:
public class MyEvent {} 
  • 2. 定義事件處理方法:
public void onEventMainThread
  • 3. 註冊訂閱者: 註冊之後該類必須要有@Subscriber 否則報錯
EventBus.getDefault().register(this)
  • 4. 傳送事件:  如果只需要傳送資料,直接post 不需要註冊
EventBus.getDefault().post(new MyEvent())