具体定义:之前有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和Activity之间的数据传递: startActivityForResult
-
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 传递事件的时候,参数最后用一个实体类包裹