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是一個Android和Java的開源庫,使用釋出者/訂閱者模式進行鬆散耦合。EventBus只需幾行程式碼即可實現與分離類的集中通訊 - 簡化程式碼,消除依賴關係,並加速應用程式開發。
綜合版:
EventBus是一個Android端優化的publish/subscribe訊息匯流排,簡化了應用程式內各元件間、元件與後臺執行緒間的通訊。比如請求網路,等網路返回時通過Handler或Broadcast通知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個
- onEvent
- onEventMainThread
- onEventBackgroundThread
- onEventSync
這四個,這個和ThreadMode有關。
ThreadMode
ThreadMode制定了會呼叫的函式
3.0之 前有以下四個ThreadMode:
@Subscribe(threadMode = ThreadMode.ASYNC)
- PostThread(傳送和接收都在同一個執行緒內)
- MainThread(傳送和接收都是在主執行緒)
- BackgroundThread (同步的,接收是在單獨的執行緒內,如果同時執行多個 依次執行)
- 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¢er=%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不做任何檢查的事情。
- EventBus在3.0以前,還需要根據四種執行緒模式分別對應固定接收方法,而OTTO則可以通過註解的方法自定義方法,比較方便,但是EventBus在3.0也實現了通過註解自定義方法了。而Otto介紹上不管是訂閱者還是傳送者都需要註冊事件,但是我發現現在傳送者不用註冊也可以傳送了。
- 每個框架都有自己的特點,我們開發者必須明白每個框架的出發點才能更好的使用,沒有哪個框架好不好的問題,只要開發者自己使用哪個舒服,哪個就是最好的。適合自己的才是最好的。
- 最後我想說,可能EventBus和Otto很早以前就有了,現在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())