Android Activity通讯方式
Android Activity通讯方式
Activity 之间传递信息是很常见的方式,比如页面的跳转需要携带信息,比如第一个页面的参数需要到第二个页面显示,Android中对这种传值通讯提供了多种方式,这些方式又有什么异同呢。
一、Bundle传递
含义:把数据封装在Bundle
对象中,通过 Intent
跳转时携带。
伪代码
传递基本数据类型和String类型
// 传递
Bundle bundle = new Bundle();
bundle.putString("name", "Jack");
bundle.putInt("id", 1);
Intent intent = new Intent(this, MainActivity2.class);
intent.putExtras(bundle);
startActivity(intent);
// 接收
Bundle bundle = getIntent().getExtras();
String name = bundle.getString("name");
int id = bundle.getInt("id");
Log.d("===", "name:" + name + " _ id:" + id);
传递对象
对象传递前需要对对象进行序列化,否则会报错。
需要注意的是
通过序列化传递的对象,传输和接收的对象虽然内容相同,但是引用地址是不同的。 也就是说改了接收的对象改了值,原始传递页面的对象不受影响
- 何为序列化?
序列化是为了将对象数据转换成字节流的形式,方便进行传输。 所以在传递的对象的时候我们需要进行序列化,在接收端再进行反序列化就可以恢复对象。
- 如何实现序列化
Serializable
和 Parcelable
Serializable: 实体类直接实现Serializable
接口
import java.io.Serializable;
public class Student implements Serializable {
private int id;
private String name;
public Student() {
}
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 发送
Student student = new Student(1, "Jack");
Bundle bundle = new Bundle();bundle.putSerializable("student", student);
Intent intent = new Intent(this, MainActivity2.class);
intent.putExtras(bundle);startActivity(intent);
// 接收
Bundle bundle = getIntent().getExtras();
Student student = (Student) bundle.getSerializable("student");
Log.d("===", "person:"+student.getName());
Parcelable: 实现接口并且实现方法
import android.os.Parcel;
import android.os.Parcelable;
public class Student implements Parcelable {
private int id;
private String name;
public Student() {
}
public Student(int id, String name) {
this.id = id;
this.name = name;
}
protected Student(Parcel in) {
id = in.readInt();
name = in.readString();
}
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel in) {
return new Student(in);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
}
}
// 发送
Student student = new Student(1, "Jack");
Bundle bundle = new Bundle();
bundle.putParcelable("student", student);
Intent intent = new Intent(this, MainActivity2.class);
intent.putExtras(bundle);
startActivity(intent);
// 接收
Bundle bundle = getIntent().getExtras();
Student student = (Student) bundle.getParcelable("student");
Log.d("===", "person:"+student.getName());
从代码看来,Serializable
来自Java,而Parcelable
来自Android,Parcelable
是Android优化过后的产物,在相同条件下,Parcelable
可以减少很大的内存占用。
二、广播传递
广播是Android 中的四大组件之一,相当于一个全局监听器,可以监听其它App或者系统的广播消息。
在Activity之间虽然也可以传递数据,但是有点大材小用。
三、外部存储
如果有大量的数据,在 A Activity 中可以将数据临时保存在存储卡中,跳转到B Activity 后再从存储卡中取出。
四、静态变量
将数据保存在静态变量中,在 A Activity 中对静态变量进行赋值,跳转到B Activity 后从静态变量获取数据,然后回收静态变量。
五、Application 中转
自定义的application类,临时保存变量,为了代码整洁,一般不用。
六、ARouter
Arouter 也有一些传递消息的方法。比如 withObject、withString 等
看看它们的内部方法
/**
* Set object value, the value will be convert to string by 'Fastjson'
*
* @param key a String, or null
* @param value a Object, or null
* @return current
*/
public Postcard withObject(@Nullable String key, @Nullable Object value) {
serializationService = ARouter.getInstance().navigation(SerializationService.class);
mBundle.putString(key, serializationService.object2Json(value));
return this;
}
/**
* Inserts a String value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
* @param key a String, or null
* @param value a String, or null
* @return current
*/
public Postcard withString(@Nullable String key, @Nullable String value) {
mBundle.putString(key, value);
return this;
}
同样是用Bundle传输,原理和Bundle一致。
七、EventBus
在使用Bundle
传递数据时,当数据量过大(超过1M时),就会抛出 TransactionTooLargeException
异常。
使用 EventBus
可以很轻松的解决这个问题。
1. EventBus简介
EventBus是一种用于Android的事件发布-订阅总线,由GreenRobot开发,可以很方便的进行数据传递。
2. EventBus 三个组成部分
- Event:事件,它可以是任意类型,EventBus会根据事件类型进行全局的通知。
- Subscriber:事件订阅者,在EventBus 3.0之前我们必须定义以onEvent开头的那几个方法,分别是onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe,并且指定线程模型,默认是POSTING。
- Publisher:事件的发布者,可以在任意线程里发布事件。一般情况下,使用EventBus.getDefault()就可以得到一个EventBus对象,然后再调用post(Object)方法即可。
3. EventBus 四种线程模型
- POSTING:默认,表示事件处理函数的线程跟发布事件的线程在同一个线程。
- MAIN:表示事件处理函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作。
- BACKGROUND:表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程。
- ASYNC:表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。
4. EventBus 实战
4.1 引入依赖
implementation 'org.greenrobot:eventbus:3.1.1'
4.2 新建一个实体类,作为传递的对象
public class MessageInfo {
private String message;
public MessageInfo(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
4.3 定义接收事件
Activity:
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv);
EventBus.getDefault().register(this); //初始化EventBus
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this); //释放
}
// 定义接收的事件
@Subscribe(threadMode = ThreadMode.MAIN)
public void getMessage(MessageInfo messageInfo) {
tv.setText(messageInfo.getMessage());
Toast.makeText(this, "接收到的消息为:" + messageInfo.getMessage(), Toast.LENGTH_SHORT).show();
}
public void GoMain2Activity(View view) {
Intent intent = new Intent(this, Main2Activity.class);
startActivity(intent);
}
}
layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="GoMain2Activity"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""/>
</android.support.constraint.ConstraintLayout>
4.4 定义发送事件
Activity:
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
/**
* 发送消息
*
* @param view
*/
public void publishMessage(View view) {
EventBus.getDefault().post(new MessageInfo("小李子"));
}
}
layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Main2Activity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:onClick="publishMessage"/>
</android.support.constraint.ConstraintLayout>
4.5 粘性事件
所谓的黏性事件,就是指发送了该事件之后再订阅者依然能够接收到的事件。使用黏性事件的时候有两个地方需要做些修改。一个是订阅事件的地方,这里我们在先打开的Activity中注册监听黏性事件: 添加 sticky = true 属性
// 定义接收的事件
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void getMessage(MessageInfo messageInfo) {
tv.setText(messageInfo.getMessage());
Toast.makeText(this, "接收到的消息为:" + messageInfo.getMessage(), Toast.LENGTH_SHORT).show();
}
在发送事件时使用postSticky
来发送:
/**
* 发送消息
*
* @param view
*/
public void publishMessage(View view) {
EventBus.getDefault().postSticky(new MessageInfo("小李子"));
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
}
4.6 优先级
在Subscribe注解中总共有3个参数,上面我们用到了其中的两个,这里我们使用以下第三个参数,即priority。它用来指定订阅方法的优先级,是一个整数类型的值,默认是0,值越大表示优先级越大。在某个事件被发布出来的时候,优先级较高的订阅方法会首先接受到事件。 这里有几个地方需要注意:
- 只有当两个订阅方法使用相同的ThreadMode参数的时候,它们的优先级才会与priority指定的值一致;
- 只有当某个订阅方法的ThreadMode参数为POSTING的时候,它才能停止该事件的继续分发。
八、EventBus 问题汇总(持续更新...)
问题一、EventBus 粘性事件接收不到的问题
1、起因
由于EventBus发送的是对象,我们经常构建一个共用的对象,在共用对象中添加tag,用于方便在接收中区分作用。
例如用EventBusHelper工具类发送EventBusMessage对象。
2、遇到的问题
在发送粘性事件时,在A场景发送了一次,然后还没有接收,然后在B场景又发送了一次,这时B发送的粘性事件可以收到,而A场景的粘性事件被替换掉了。
这时因为在EventBus的源码中,粘性事件使用Map集合存储,key为 object.getClass(), 当我们自定义EventBusMessage的时候,导致object.getClass()一直是相同的,以至于会替换前一次的key。
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
post(event);
}
3、解决方法
创造多个单独的对象,用于发送粘性服务。
上面的方法比较Low,还没有想到比较优雅的方法,有的话分享出来吧。
问题二、Post/postSticky 不能传递基本类型。
eg: 使用post/postSticky直接传递,在Subscribe处接收int,此时收不到消息。 将int tag改为 Integer tag 就能收到。
这时因为EventBus中要求传递的为Object,Object属于类类型,也就是复合数据类型,int属于简单数据类型。
EventBus.getDefault().post(111);
@Subscribe(threadMode = ThreadMode.MAIN)
public void getMessage(int tag) {
if (tag == 111)
Log.d(TAG, "received");
}