【Android组件内核面试题】Activity A跳转B,B跳转C,A不能直接跳转到C,A如何传递消息给C?(美团)

214 阅读7分钟

Activity A跳转B,B跳转C,A不能直接跳转到C,A如何传递消息给C?(美团)

这道题想考察什么?
  1. 是否了解Activity间信息传递?
考察的知识点
  1. 消息传递的机制
  2. 事件总线
  3. 本地数据存储
考生应该如何回答
消息通信机制

Android 开发之中我们常常需要应用到消息传递,消息的传递有多种方式。消息传递的作用不必多说,主要是在不同的组件之间传播消息或者共享某些状态等,以下是几种常用的消息传递机制:

  • 静态变量
  • 全局变量 及Application
  • Android系统剪切板
  • 本地化存储方式
  • Android组件
  • EventBus
  • LiveDataBus
静态变量 和 全局变量、Application && Android 系统剪切板

这三种方式其实非常类似,静态变量和全局变量都可以采用static的方式来声明,如果采用这种方式还是推荐用一个专门的类结合单体模式进行管理,尽量减少对内存的消耗。 而使用系统剪切板的方式一般也很少用,比较多限制,容易丢失数据,几乎没有看到有这样用的。

Application: 可以通过在Application 中的全局静态变量来实现

这里还有利用Application进行共享Handler来消息传递,方法很简单,就是在Application中定义一个全局的Handler,虽然这种方法可以实现,但是却保留了在整个App中保留了全局的Handler,如果在Handler的设置中引用了某个Activity,就容易造成内存泄露了。

本地化存储方式

本地存储方式有如下三种:

SharedPreference SQLite File

这三种方式的好处就是他们是持久存储的,只要不卸载APP或者不删除文件就可以一直保存下去,而且也几乎没有大小的限制,可以做一些统计。不过缺点也比较明显,这三种方式最好是采用多线程来进行读写,尤其是数据量大的时候,我们知道,IO的操作是非常耗费时间的,所以尽量不要在UI线程中使用这三种方式读写。

示例代码:

SharedPreference:

// 发送消息
SharedPreferences.Editor editor = MainActivity.this.getSharedPreferences("SEND", Context.MODE_PRIVATE).edit();
editor.putString("SEND","SharedPreferences的消息");
editor.apply();
startActivity(new Intent(MainActivity.this,ReceiveActivity.class));
// 接收消息
SharedPreferences sharedPreferences = getSharedPreferences("SEND", MODE_PRIVATE);
textView.setText(sharedPreferences.getString("SEND", ""));
附支持的数据类型如下: 

SQLite

SQLite需要先创建数据库,后面向数据库中插入和读取数据实现信息共享。 首先要继承SQLiteOpenHelper并在onCreate方法中创建数据库:

sqLiteDatabase.execSQL("CREATE TABLE Teacher(teacherId INTEGER PRIMARY KEY" +
           " AUTOINCREMENT,userId VARCHAR(20) UNIQUE,name VARCHAR(20),password VARCHAR(20))");
           

向数据库中插入:

String type = "Teacher";
sqLiteDatabase.execSQL("INSERT INTO " + type + "(userId,name,password) values (?,?,?)",
                    new String[]{userId, userName, userPassword});

从数据库中读取信息:

String type = "Teacher";
Cursor cursor = sqLiteDatabase.rawQuery("SELECT * FROM " + type + " WHERE userId = ?",
         new String[]{userId.getText().toString()});
if(cursor.moveToFirst()){
         String userId = cursor.getString(cursor.getColumnIndex("userId"));
         String name = cursor.getString(cursor.getColumnIndex("name"));
}
cursor.close();

File

文件方式要记得申请权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

写入数据:

try {
    File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/data/temp.txt");
    FileOutputStream out = new FileOutputStream(file);
    out.write("message".getBytes(Charset.forName("UTF-8")));
    out.flush();
    out.close();
} catch (IOException e) {
    e.printStackTrace();
}

读取数据:

try {
    FileInputStream in = new FileInputStream(Environment.getExternalStorageDirectory().getAbsolutePath() + "/temp.txt");
    byte[] reader = new byte[256];
    int read = in.read(reader);
    String content = "";
    if (read > 0)
        content = new String(reader, 0, read, Charset.forName("UTF-8"));
    Toast.makeText(this, content, Toast.LENGTH_SHORT).show();
} catch (IOException e) {
    e.printStackTrace();
}
Broadcast方式

使用组件也就是说利用Broadcast进行消息传递。 优选LocalBroadcast:

LocalBroadcastManager.getInstance(context).registerReceiver(@NonNull BroadcastReceiver receiver, @NonNull IntentFilter filter) LocalBroadcastManager.getInstance(context).sendBroadcast(intent); 如果说使用Android 进行消息传递的话,Broadcast是最好的了,顾名思义的我们清楚,广播就是有一个发送消息和接受消息的过程,所以可以用于消息传递。 示例:

注册广播和接受消息
        // 定义广播
        final Button broadButton = findViewById(R.id.broadcast);
        final BroadcastReceiver receiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                broadButton.setText("" + intent.getStringExtra("data"));
            }
        };
        broadButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                registerReceiver(receiver,new IntentFilter("broadsend.action"));
                startActivity(new Intent(MainActivity.this,ReceiveActivity.class));
            }
        });
发送广播:
    Intent intent = new Intent("broadsend.action");
    intent.putExtra("data","send");
    sendBroadcast(intent);

这种方式有一些限制,因为接收事件要比发送事件先定义好,所以只能在当前Activity中注册广播,在跳转的Activity发送,所以严格说这不能算是消息传递,因为是单向传递的。

Service方式

Service可以结合Broadcast进行消息传递,不过这样子就不能算是Service了。 使用Service进行消息传递,我们可以定义接口,并利用接口进行消息传递。 定义消息接收的接口:

    public static MessageCallback messageCallback = new MessageCallback() {
        @Override
        public void onMessage(String message) {
            Log.d("tag","" + message);
        }
    };
    public interface MessageCallback{
        public void onMessage(String message);
    }

进行消息发送:

MainActivity.messageCallback.onMessage("message"); 有的人讲这种方式不久和共享变量一样了吗,不不不,这是完全不一样的,如果是共享变量的话,当变量被改变了是不是还得程序员或者用户去响应这种改变呢,这就很不好了,而采用这种静态接口的方法,只要函数被调用,就立刻可以进行响应并处理,不是很方便吗。当然也可以想办法将接口的对象进行传递,例如利用Broadcast来进行传递。

EventBus

EventBus 是一款针对Android的发布以及订阅事件总线,使用它可以很方便的进行信息传递,而且使用起来很方便。 首先是定义一个消息:

public class Event {
    private String message;
    public Event(){
        message = "EventBus message";
    }

    public void setMessage(String message){
        this.message = message;
    }
     
    public String getMessage(){
        return message;
    }
}
发送消息:

这里使用了postSticky,这是发送的粘性广播,使用这个发送就可以先发送信息再进行注册,后注册的也能接收到前面发送的广播。当然还有其他的使用方式。

EventBus.getDefault().postSticky(new Event());
startActivity(new Intent(MainActivity.this,ReceiveActivity.class));
注册事件的订阅者:

EventBus.getDefault().register(this); 接受粘性广播:

@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventThread(Event event) {
    textView.setText(event.getMessage());
}
LiveDataBus

LiveDataBus是通过包装LiveData实现的消息总线组件,如果同学对LiveData不熟悉的同学,可以找到相对应的章节先学习一下。

LiveDataBus 的封装
  • 通过 map 维护一个消息事件和 MutableLiveData 的映射关系,MutableLiveData 的类型默认为 Object,接收任意类型,实现总线通信将 LiveDataBus 封装为一个单例类。
  • 消息注册时,如果当前 map 中不存在,则先将消息和对应的 MutableLiveData 对象放入维护的 map 中,添加映射关系,返回当前 map 中缓存的 MutableLiveData 对象
LiveDataBus 的组成
  • 消息 消息可以是任何的Object,可以定义不同类型的消息,如 Boolean、String。也可以定义自定义类型的消息。

  • 消息总线 消息总线通过单例实现,不同的消息通道存放在一个 HashMap中。

  • 订阅 订阅者通过 with 方式获取消息通道,然后调用 observe 订阅这个通道的消息。

  • 发布 发布者通过 with 获取消息通道,然后调用 setValue 或者 postValue 发布消息。

LiveDataBus 的实现
public final class LiveDataBus {

    private final Map<String, MutableLiveData<Object>> bus;

    private LiveDataBus() {
        bus = new HashMap<>();
    }

    private static class SingletonHolder {
        private static final LiveDataBus DATA_BUS = new LiveDataBus();
    }

    public static LiveDataBus get() {
        return SingletonHolder.DATA_BUS;
    }

    public <T> MutableLiveData<T> with(String target, Class<T> type) {
        if (!bus.containsKey(target)) {
            bus.put(target, new MutableLiveData<>());
        }
        return (MutableLiveData<T>) bus.get(target);
    }

    public MutableLiveData<Object> with(String target) {
        return with(target, Object.class);
    }
}
注册订阅
LiveDataBus.get().with("key_test", Boolean.class)
    .observe(this, new Observer<Boolean>() {
        @Override
        public void onChanged(@Nullable Boolean aBoolean) {
        }
 });
发送消息
LiveDataBus.get().with("key_test").setValue(true);

详细关注公众号:Android老皮
还能解锁  《Android十大板块文档》 ,让学习更贴近未来实战。已形成PDF版

内容如下

1.Android车载应用开发系统学习指南(附项目实战)
2.Android Framework学习指南,助力成为系统级开发高手
3.2023最新Android中高级面试题汇总+解析,告别零offer
4.企业级Android音视频开发学习路线+项目实战(附源码)
5.Android Jetpack从入门到精通,构建高质量UI界面
6.Flutter技术解析与实战,跨平台首要之选
7.Kotlin从入门到实战,全方面提升架构基础
8.高级Android插件化与组件化(含实战教程和源码)
9.Android 性能优化实战+360°全方面性能调优
10.Android零基础入门到精通,高手进阶之路