1. 程式人生 > >android進階4step3:Android常用框架——OTTO事件匯流排

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的實際應用

使用場景

  • FragmentActivity之間的資料傳遞: Interface -Android Fragment(碎片)基本使用

  • Activity

    Activity之間的資料傳遞: startActivityForResult

  • FragmentFragment之間的資料傳遞: 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&center=%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 傳遞事件的時候,引數最後用一個實體類包裹