注册

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");
}



0 个评论

要回复文章请先登录注册