android進階4step3:Android常用框架——OTTO事件匯流排
具體定義:之前有mark過一篇Otto事件框架匯流排的文章:android進階3step1:Android元件通訊——事件框架匯流排Otto
Otto 官方網站:http://square.github.io/otto/
Github上的原始碼:https://github.com/square/otto
學習步驟
- OTTO簡介
- OTTO的使用方法
- OTTO的實際應用
使用場景
-
Fragment和Activity之間的資料傳遞: Interface -Android Fragment(碎片)基本使用
-
Activity
-
Fragment和Fragment之間的資料傳遞: Interface
PS:OTTO事件匯流排的出現將簡化這一切。
Activity和Fragment
主要流程:
主要的方法:
OTTO主要方法
-
register(Object o):註冊,註冊以後可以訂閱事件
-
unregister(Object o):登出,放棄對之前的訂閱的所有事件
-
post(Object o):釋出事件,會被有Subscribe註解的方法獲取到
註解:
- @Subscribe:
- - 呼叫了register後有效
- - 表示訂閱了一個事件,並且方法用 public 修飾的 - 方法名可以隨意取,重點是引數
- @Produce:
- - 通知Bus該函式是一個事件產生者,產生的事件型別為該函式的返回值。
初始化BUS
Bus bus = new Bus();
Bus bus2 = new Bus(ThreadEnforcer.MAIN);//主執行緒
可以指定@Subscribe和@Produce標註的回撥方法所執行的執行緒,預設是MainThread。也可以使用ThreadEnforcer.ANY 。
訂閱事件
@Subscribe
public void answerAvailable(AnswerAvailableEvent event)
{
// TODO: React to the event somehow!
}
注意:subscribe方法接收的引數型別需要和post引數的型別一致 或者是post引數型別的父類。
釋出事件
bus.post(new AnswerAvailableEvent(42));
bus.register(this)
-
register方法呼叫後,Otto會尋找所有帶有@Subscribe或者 @Produce註解的方法,並將這些方法快取下來。
-
只有在呼叫了register之後,該類裡面標註了@Subscribe或者 @Produce的方法才會在適當的時候被呼叫。
-
當不需要訂閱事件的時候,可以呼叫unregister來取消訂閱。
生產者
有時候當訂閱某個事件的時候,希望能夠獲取當前的一個值,比如訂閱位置變化事件的時候,希望能拿到當前的位置資訊。
Otto中@Produce正是 扮演了這麼一個生產者的角色。@Produce也是用於方法,並且這個方法 的引數必須為空,返回值是你要訂閱的事件的型別。
@Produce
public AnswerAvailableEvent produceAnswer() {
// Assuming ‘lastAnswer’exists,
return new AnswerAvailableEvent(this.lastAnswer);
}
- 使用標籤@Produce之後,也需要呼叫bus.register()
- 呼叫了register方法之後,所有之前訂閱AnswerAvailableEvent事件的方法都會被執行一次,引數就是produceAnswer方法的返回值
- 之後任何新的訂閱了AnswerAvailableEvent事件的方法,也都會立即呼叫produceAnswer方法
解讀Sample Code
一、實現Activity與Activity之間進行資料傳遞
A跳到B 點選B中的按鈕將資料傳遞給A
使用:
第一步:在模組中新增依賴
implementation 'com.squareup:otto:1.3.8'
BusProvider.java 從頭到尾都是一個Bus 所以寫出單例
/**
* 單例提供bus物件
*/
public class BusProvider {
private static final Bus mBus = new Bus();
private BusProvider(){}
public static Bus getInstance() {
return mBus;
}
}
注意:如果想在子執行緒post資料 改成:
private static Bus mBus = new Bus(ThreadEnforcer.ANY);
事件的封裝:這裡簡單的輸出一個整型數
ActivityEvent.java
public class ActivityEvent {
private int Event;
public ActivityEvent(int event) {
this.Event = event;
}
public int getEvent() {
return Event;
}
}
MainAcitivity.java
public class MainActivity extends AppCompatActivity {
private Button mBtn2Second;
private int i;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtn2Second = findViewById(R.id.id_btn_main);
mBtn2Second.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
});
//註冊
BusProvider.getInstance().register(this);
}
/**
* 訂閱者
* 每次post都會拿到傳來的資料
*
* @param event
*/
@Subscribe
public void onActivityEvent(ActivityEvent event) {
Log.e("TAG", "這是" + (++i) + "次,接收到Post傳來的事件 event=" + event.getEvent());
}
@Override
protected void onDestroy() {
super.onDestroy();
//登出
BusProvider.getInstance().unregister(this);
}
}
SecondActivity.java
public class SecondActivity extends AppCompatActivity {
private Button mBtnSend2Main;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
mBtnSend2Main = findViewById(R.id.id_btn_send);
mBtnSend2Main.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//post event to main activity
//釋出事件,會被有Subscribe註解的方法獲取到
BusProvider.getInstance().post(new ActivityEvent(40));
}
});
//註冊
BusProvider.getInstance().register(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
//註冊
BusProvider.getInstance().unregister(this);
}
}
結果:
Log輸出:
E/TAG: 我是MainActivity,這是第1次,接收到Post傳來的事件 event=40
E/TAG: 我是MainActivity,這是第2次,接收到Post傳來的事件 event=40
E/TAG: 我是MainActivity,這是第3次,接收到Post傳來的事件 event=40
E/TAG: 我是MainActivity,這是第4次,接收到Post傳來的事件 event=40
E/TAG: 我是MainActivity,這是第5次,接收到Post傳來的事件 event=40
二、實現同一個Activity中兩個Fragment之間傳遞資料:
佈局檔案:fragment_up.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_up"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#e68a8a"
android:orientation="vertical">
<Button
android:id="@+id/id_btn_up"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Fragment Up" />
<TextView
android:id="@+id/id_tv_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是Fragment Up"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
fragment_down.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_down"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#8fe6e7"
android:orientation="vertical">
<Button
android:id="@+id/id_btn_down"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Fragment Down" />
<TextView
android:id="@+id/id_tv_down"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="20sp"
android:text="我是Fragment Down"/>
</LinearLayout>
裝fragment的activity的佈局 activity.xml
<?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">
<!--裝fragment 的viewgroup-->
<FrameLayout
android:id="@+id/layout_up"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"></FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#000000" />
<FrameLayout
android:id="@+id/layout_down"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"></FrameLayout>
</LinearLayout>
java 程式碼:
FragmentUp.java
public class FragmentUp extends Fragment {
private Button mBtnUp;
private TextView mTvUp;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frament_up, container, false);
mBtnUp = view.findViewById(R.id.id_btn_up);
mTvUp = view.findViewById(R.id.id_tv_up);
mBtnUp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//傳送資料
BusProvider.getInstance().post(new myEvent(myEvent.FROM_FRAGMENT_UP, "from fragment up"));
}
});
//註冊
BusProvider.getInstance().register(this);
return view;
}
@Subscribe
public void setOnDownListener(myEvent event) {
if (event.mEvent == myEvent.FROM_FRAGMENT_DOWN) {
mTvUp.setText(mTvUp.getText() + "\n" + event.mMsg);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
//登出
BusProvider.getInstance().unregister(this);
}
}
FragmentDown.java
public class FragmentDown extends Fragment {
private Button mBtnDown;
private TextView mTvDown;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frament_down, container, false);
mBtnDown = view.findViewById(R.id.id_btn_down);
mTvDown = view.findViewById(R.id.id_tv_down);
mBtnDown.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//傳送資料
BusProvider.getInstance().post(new myEvent(myEvent.FROM_FRAGMENT_DOWN, "from fragment down"));
}
});
//註冊
BusProvider.getInstance().register(this);
return view;
}
/**
* 訂閱者
*
* @param event
*/
@Subscribe
public void setOnUpLinstener(myEvent event) {
//如果是來自FragmentUp的則預設顯示一個數據
if (event.mEvent == myEvent.FROM_FRAGMENT_UP)
mTvDown.setText(mTvDown.getText() + "\n" + event.mMsg);
}
@Override
public void onDestroyView() {
super.onDestroyView();
//登出
BusProvider.getInstance().unregister(this);
}
}
myEvent.java 封裝的資料類
public class myEvent {
//識別符號
public static final int FROM_FRAGMENT_UP = 1001;
public static final int FROM_FRAGMENT_DOWN = 1002;
//資料
public int mEvent;
public String mMsg;
//構造
public myEvent(int event, String msg) {
this.mEvent = event;
this.mMsg = msg;
}
}
Bus的單例類
/**
* 單例提供bus物件
*/
public class BusProvider {
private static final Bus mBus = new Bus();
private BusProvider(){}
public static Bus getInstance() {
return mBus;
}
}
Acitivity.java 將兩個自定義fragment 裝載並顯示
public class Activity extends AppCompatActivity {
private Fragment mUp;
private Fragment mDown;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
mUp = new FragmentUp();
mDown = new FragmentDown();
FragmentManager fm = getSupportFragmentManager();
//通過fragmentManager物件去開啟一個事務
FragmentTransaction transaction = fm.beginTransaction();
//要將fragment新增到哪個容器ViewGroup中去,裝你要替換的新的fragment的具體物件
transaction.add(R.id.layout_up, mUp);
transaction.add(R.id.layout_down, mDown);
transaction.addToBackStack(null);
transaction.commit();
}
}
GitHub的Otto案例 :
/*
* 1. 新增OTTO框架到自己的專案中:build.gradle 新增OTTO的依賴
* 2. 建立一個單例,來給APP中所有的Activity或者Fragment提供一個Bus
* 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 'com.squareup:otto:1.3.8'
佈局檔案:
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
BusProvider.getInstance().register(this);
}
@Override
public void onStop() {
super.onStop();
//登出Bus
BusProvider.getInstance().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
import android.app.Fragment;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.squareup.otto.Subscribe;
import java.net.URL;
/**
* 通過傳來的經緯度在地圖的api中查詢返回一張照片顯示
*/
public class LocationMapFragment extends Fragment {
private ImageView mImageView;
// %s 可以用來格式化
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 onResume() {
super.onResume();
BusProvider.getInstance().register(this);
}
@Override
public void onStop() {
super.onStop();
BusProvider.getInstance().unregister(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mImageView = new ImageView(getActivity());
return mImageView;
}
private class ImageAvailableEvent {
public Drawable image;
public ImageAvailableEvent(Drawable img) {
image = img;
}
}
@Subscribe
public void onLocationMoveEvent(LocationMoveEvent event) {
mTask = new DownloadTask();
//格式化Url
String downloadUrl = String.format(URL, event.longitude, event.latitude);
//將這個Url 傳到doInBackground 中
mTask.execute(downloadUrl);
}
@Subscribe
public void onImageAvailableEvent(ImageAvailableEvent event) {
if (event.image != null) {
mImageView.setImageDrawable(event.image);
}
}
private class DownloadTask extends AsyncTask<String, Void, Drawable> {
@Override
protected Drawable doInBackground(String... params) {
String downloadUrl = params[0];
try {
//返回下載好的drawable
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);
//show drawable
//mImageView.setImageDrawable(drawable);
//post Image available event
//將下載好的drawable post 通知這個fragment顯示圖片
BusProvider.getInstance().post(new ImageAvailableEvent(drawable));
}
}
}
MainActivity.java 裝兩個Fragment
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);
initViews();
initEvent();
}
private void initEvent() {
mClearButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//post data 讓list清除
BusProvider.getInstance().post(new LocationClearEvent());
}
});
mMoveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//在預設的座標下 偏移隨機量 產生新的 經緯度
longitude = DEFAULT_LONGITUDE + OFFSET * random.nextFloat();
latitude = DEFAULT_LATITUDE + OFFSET * random.nextFloat();
//將這個資料post 給 訂閱者
BusProvider.getInstance().post(new LocationMoveEvent(longitude, latitude));
}
});
}
private void initViews() {
mClearButton = findViewById(R.id.bt_clear_location);
mMoveButton = findViewById(R.id.bt_move_location);
}
@Override
protected void onResume() {
super.onResume();
BusProvider.getInstance().register(this);
}
@Override
protected void onStop() {
super.onStop();
BusProvider.getInstance().unregister(this);
}
}
BusProvider.java 匯流排單例物件
import com.squareup.otto.Bus;
/*
* 1. 新增OTTO框架到自己的專案中:build.gradle 新增OTTO的依賴
* 2. 建立一個單例,來給APP中所有的Activity或者Fragment提供一個Bus
* 3. register,unregister 事件匯流排
* 4. 在要傳送事件的地方呼叫bus.post(Event)
* 5. 接收的地方定義訂閱的函式@Subscribe ...
* */
public class BusProvider {
private final static Bus bus = new Bus();
public static Bus getInstance() {
return bus;
}
private BusProvider()
{}
}
事件:
移動座標的事件(封裝的實體類,用來傳遞資料的)
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 是一個非常優秀和強大的框架.
- BusProvider.getInstance().post(object)
- Subscribe訂閱事件就一定要register這個類了
- otto 傳遞事件的時候,引數最後用一個實體類包裹