续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第18天,点击查看活动详情
Activity与Fragment的通信方式
Activity与Fragment的通信属实是老生常谈的话题了,还能整出什么花活。其实网上的都是比较老的方法了,在2022年的今天,我们看看到目前为止有哪些通信方式。在单Activity与多Fragment的框架下,又有哪些通信方式呢?
一、接口通信
这应该是最常见的方式了,属于网上最多的推荐方式了,简单的过一下。
定义2个接口,给Activity实现,方便Fragment调用
interface IOneFragmentCallback {
fun callActOne(str: String)
}
interface ITwoFragmentCallback {
fun callActTwo(str: String)
}
定义Activity实现2个接口
class Demo11Activity : BaseVMActivity(), IOneFragmentCallback, ITwoFragmentCallback {
...
override fun callActOne(str: String) {
YYLogUtils.w("str:$str")
}
override fun callActTwo(str: String) {
YYLogUtils.w("str:$str")
}
}
在Fragment中拿到对应的接口实例对象,直接调用 伪代码如下:
class Demo11Fragment1(private val test: String) : BaseFragment(){
...
private lateinit var mCallback: IOneFragmentCallback
override fun onAttach(context: Context) {
super.onAttach(context)
mCallback = context as IOneFragmentCallback
}
@Click
fun callback() {
mCallback.callActOne("message come from one page")
}
}
class Demo11Fragment2(private val test: String) : BaseFragment(){
...
private lateinit var mCallback: ITwoFragmentCallback
override fun onAttach(context: Context) {
super.onAttach(context)
mCallback = context as ITwoFragmentCallback
}
@Click
fun callback() {
mCallback.callActTwo("message come from two page")
}
}
我们通过Fragment通知到了Activity:
没错,那么Activity怎么通知Fragment呢?那还不简单,Activity本身就拿到了Fragment的实例,直接调用函数就行了,如果是接口直接强转为接口对象调用即可!
什么?你用的Navigation?Fragment都是xml自动管理的,Activity中没有Fragment的实例?这如何调用? 这。。。
其实有办法的,看看我们Activity中的xml是 FragmentContainerView
就算直接通过 supportFragmentManager
拿到Fragment也是不对的。拿到的是一个Fragment容器,我们的真正fragment是由这个容器 NavHostFragment
管理的。
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
app:defaultNavHost="true" />
我们先通过supportFragmentManager
拿到 NavHostFragment
容器,然后通过此容器NavHostFragment
的 childFragmentManager
拿到真正的容器,扩展方法如下:
//获取全部的Fragment
fun NavHostFragment.getAllFragments(): List<Fragment> {
val list = arrayListOf<Fragment>()
navController.mBackStack.forEachIndexed { index, entry ->
entry.destination.let { des ->
if (des is FragmentNavigator.Destination) {
val frag = childFragmentManager.fragments[index - 1]
list.add((frag as NavContainerFragment).mRealFragment)
}
}
}
return list
}
//根据Activity获取到Fragments
fun FragmentActivity.getAllNavFragments(navHostRes: Int): List<Fragment> {
val navHostFragment = supportFragmentManager.findFragmentById(navHostRes) as NavHostFragment
return navHostFragment.getAllFragments()
}
我们添加2个Activity调用Fragment的接口,并在Fragment实现它们:
interface IOneActivityCallback {
fun callOneFragment(string: String)
}
interface ITwoActivityCallback {
fun callTwoFragment(string: String)
}
Fragment实现接口,给Activity调用:
class Demo11Fragment1(private val test: String) : BaseFragment(){
...
private lateinit var mCallback: IOneFragmentCallback
override fun onAttach(context: Context) {
super.onAttach(context)
mCallback = context as IOneFragmentCallback
}
@Click
fun callback() {
mCallback.callActOne("message come from one page")
}
override fun callOneFragment(string: String) {
YYLogUtils.w("啊,我在Fragment1中被调用了!msg:$string")
}
}
class Demo11Fragment2(private val test: String) : BaseFragment(){
...
private lateinit var mCallback: ITwoFragmentCallback
override fun onAttach(context: Context) {
super.onAttach(context)
mCallback = context as ITwoFragmentCallback
}
@Click
fun callback() {
mCallback.callActTwo("message come from two page")
}
override fun callTwoFragment(string: String) {
YYLogUtils.w("啊,我在Fragment2中被调用了!msg:$string")
}
}
我们实现的功能为:fragment2点击callback,回调给Activity,Activity再拿到fragment1的实例回调给fragment1的方法。
修改Activity的回调函数如下:
class Demo11Activity : BaseVMActivity(), IOneFragmentCallback, ITwoFragmentCallback {
...
override fun callActOne(str: String) {
YYLogUtils.w("str:$str")
getAllNavFragments(R.id.nav_host).firstOrNull {
it is ITwoActivityCallback
}?.let {
(it as ITwoActivityCallback).callTwoFragment("yeye")
}
}
override fun callActTwo(str: String) {
YYLogUtils.w("str:$str")
getAllNavFragments(R.id.nav_host).firstOrNull {
it is IOneActivityCallback
}?.let {
(it as IOneActivityCallback).callOneFragment("hehe")
}
}
}
这样就可以实现Navigation下单Activity+多Fragment的以接口的方式回调了。
效果:
二、Function的缓存方式
就是把需要调用的方法使用Function的方式缓存起来,在使用的时候取出来执行不就Ok了吗?都不限制于Fragment与Activity,简直是黑科技,有没有一点EventBus的味道了。
已有实现 ActivityCommWithFragment。和 鸿洋的FABridge。都是基于此思路实现。
下面是简单的实现:
把方法对象集合放入Map中 在内存中存储
public abstract class Function {
public String functionName;//名称
public Function(String functionName) {
this.functionName=functionName;
}
}
定义四种类型的方法,是否带参数和带返回值
public abstract class FunctionNoParamNoResult extends Function {
public FunctionNoParamNoResult(String functionName) {
super(functionName);
}
protected abstract void function();
}
public abstract class FunctionNoParamWithResult<Result> extends Function {
public FunctionNoParamWithResult(String functionName) {
super(functionName);
}
protected abstract Result function();
}
public abstract class FunctionWithParamNoResult<Param> extends Function {
public FunctionWithParamNoResult(String functionName) {
super(functionName);
}
protected abstract void function(Param param);
}
public abstract class FunctionWithParamWithResult<Result,Param> extends Function {
public FunctionWithParamWithResult(String functionName) {
super(functionName);
}
protected abstract Result function(Param param);
}
我们通过一个管理类来添加方法执行方法:
public class FunctionManager {
private static FunctionManager functionManager;
public FunctionManager() {
mFunctionNoParamNoResult=new HashMap<>();
mFunctionWithParamWithResult=new HashMap<>();
mFunctionWithParamNoResult=new HashMap<>();
mFunctionNoParamWithResult=new HashMap<>();
}
public static FunctionManager getInstance(){
if(functionManager==null){
functionManager=new FunctionManager();
}
return functionManager;
}
private static HashMap<String,FunctionNoParamNoResult> mFunctionNoParamNoResult;
private static HashMap<String,FunctionWithParamWithResult> mFunctionWithParamWithResult;
private static HashMap<String,FunctionWithParamNoResult> mFunctionWithParamNoResult;
private static HashMap<String,FunctionNoParamWithResult> mFunctionNoParamWithResult;
/**
* 添加无参数无返回值
* @param function
* @return
*/
public FunctionManager addFunction(FunctionNoParamNoResult function){
if(mFunctionNoParamNoResult!=null){
mFunctionNoParamNoResult.put(function.functionName,function);
}
return this;
}
/**
* 添加有参数有返回值
* @param function
* @return
*/
public FunctionManager addFunction(FunctionWithParamWithResult function){
if(mFunctionWithParamWithResult!=null){
mFunctionWithParamWithResult.put(function.functionName,function);
}
return this;
}
/**
* 添加有参数无返回值
* @param function
* @return
*/
public FunctionManager addFunction(FunctionWithParamNoResult function){
if(mFunctionWithParamNoResult!=null){
mFunctionWithParamNoResult.put(function.functionName,function);
}
return this;
}
/**
* 添加吴参数有返回值
* @param function
* @return
*/
public FunctionManager addFunction(FunctionNoParamWithResult function){
if(mFunctionNoParamWithResult!=null){
mFunctionNoParamWithResult.put(function.functionName,function);
}
return this;
}
/**
* 执行没参数没返回值的
* @param key
*/
public void invokeFunction(String key){
if(TextUtils.isEmpty(key)){
return;
}
if(mFunctionNoParamNoResult!=null){
FunctionNoParamNoResult functionNoParamNoResult=mFunctionNoParamNoResult.get(key);
if(functionNoParamNoResult!=null){
functionNoParamNoResult.function();
}else {
try {
throw new FunctionException("function not found");
} catch (FunctionException e) {
e.printStackTrace();
}
}
}
}
/**
* 有参数有返回值
* @param key
* @param param
* @param result
* @param <Result>
* @param <Param>
* @return
*/
public <Result,Param>Result invokeFunction(String key,Param param,Class<Result> result){
if(TextUtils.isEmpty(key)){
return null;
}
if(mFunctionWithParamWithResult!=null){
FunctionWithParamWithResult f=mFunctionWithParamWithResult.get(key);
if(f!=null){
if(result==null){
return (Result) f.function(param);
}else {
return result.cast(f.function(param));
}
}else {
try {
throw new FunctionException("function not found");
} catch (FunctionException e) {
e.printStackTrace();
}
}
}
return null;
}
}
使用的时候也是很简单,例如在Fragment初始化的的是我们通过管理类添加需要调用的方法。然后再Activity就可以通过管理类直接调用这个方法。如果没有remove那么这个方法的map会越来越大,所以我们需要在Fragment Destory的时候移除掉。
其实这一种思路不止可以用于通信,还能用与其他地方,这里我没有详细的展开,是因为虽说是黑科技,但是我们还有更便捷的方式,所以这种方案是用的比较少的。
三、消息的方式
终于到大家喜欢的发消息的方式了,使用接口需要定义的类太多,太麻烦。使用方法缓存的方式,还需要额外的内存开销。搞不好还内存泄露,更便捷的方式肯定是消息总线的方式了。
实现的方式可以分很多种EventBus ,广播,LiveDataBus等。只要是消息总线的方式都可以实现功能。具体的分析与实现可以看我的这一篇。
这里我们以最简单的FlowBus来演示:
object FlowBus {
private val busMap = mutableMapOf<String, EventBus<*>>()
private val busStickMap = mutableMapOf<String, StickEventBus<*>>()
@Synchronized
fun <T> with(key: String): EventBus<T> {
var eventBus = busMap[key]
if (eventBus == null) {
eventBus = EventBus<T>(key)
busMap[key] = eventBus
}
return eventBus as EventBus<T>
}
@Synchronized
fun <T> withStick(key: String): StickEventBus<T> {
var eventBus = busStickMap[key]
if (eventBus == null) {
eventBus = StickEventBus<T>(key)
busStickMap[key] = eventBus
}
return eventBus as StickEventBus<T>
}
//真正实现类
open class EventBus<T>(private val key: String) : LifecycleObserver {
//私有对象用于发送消息
private val _events: MutableSharedFlow<T> by lazy {
obtainEvent()
}
//暴露的公有对象用于接收消息
val events = _events.asSharedFlow()
open fun obtainEvent(): MutableSharedFlow<T> = MutableSharedFlow(0, 1, BufferOverflow.DROP_OLDEST)
//主线程接收数据
fun register(lifecycleOwner: LifecycleOwner, action: (t: T) -> Unit) {
lifecycleOwner.lifecycle.addObserver(this)
lifecycleOwner.lifecycleScope.launch {
events.collect {
try {
action(it)
} catch (e: Exception) {
e.printStackTrace()
YYLogUtils.e("FlowBus - Error:$e")
}
}
}
}
//协程中发送数据
suspend fun post(event: T) {
_events.emit(event)
}
//主线程发送数据
fun post(scope: CoroutineScope, event: T) {
scope.launch {
_events.emit(event)
}
}
//自动销毁
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
YYLogUtils.w("FlowBus - 自动onDestroy")
val subscriptCount = _events.subscriptionCount.value
if (subscriptCount <= 0)
busMap.remove(key)
}
}
class StickEventBus<T>(key: String) : EventBus<T>(key) {
override fun obtainEvent(): MutableSharedFlow<T> = MutableSharedFlow(1, 1, BufferOverflow.DROP_OLDEST)
}
}
我们使用FlowBus发送消息:
class Demo11Fragment2(private val test: String) : BaseFragment(){
...
@Click
fun callback() {
FlowBus.with<String>("test-key-01").post(lifecycleScope, "Test Flow Bus Message")
}
}
Fragment1来接收消息:
class Demo11Fragment1(private val test: String) : BaseFragment(){
...
override fun startObserve() {
FlowBus.with<String>("test-key-01").register(this) {
YYLogUtils.w("收到FlowBus消息 - $it")
}
}
}
结果:
可以说是非常简单,相信大家日常开发中使用最多的应该就是此种方法了!
讲到这里有了结果,就要完结了吗? 到2022年的今天还有没有其他的方式。我们继续探讨一下。
四、ViewModel管理
ViewModel 我们都熟悉了,我们在 Activity+Fragment 的框架中,我们可以通过 Fragment 的扩展方法 activityViewModels()
来获取到父Activity的 ViewModel 对象,由此我们就能拿到 ViewModel 中定义的 LiveData 或者 Flow 对象。间接的实现 Activity 与 Fragment 的通信或 Fragment 与 Fragment 的通信。
我们分别定义读写分类的LiveData与StateFlow
class Demo11ViewModel : BaseViewModel() {
private val _backOneLiveData = MutableLiveData<String>()
val mBackOneLD: LiveData<String> get() = _backOneLiveData
private val _msgFlow = MutableStateFlow("")
val mMsgFlow: StateFlow<String> = _msgFlow.asStateFlow()
fun setCallbackValue() {
_backOneLiveData.value = "test value"
_msgFlow.value = "test flow value"
}
我们使用Fragment2发送消息:
class Demo11Fragment2(private val test: String) : BaseFragment(){
private val activityViewModel: Demo11ViewModel by activityViewModels()
...
@Click
fun callback() {
activityViewModel.setCallbackValue()
}
}
Fragment1来接收消息:
class Demo11Fragment1(private val test: String) : BaseFragment(){
private val activityViewModel: Demo11ViewModel by activityViewModels()
...
override fun startObserve() {
activityViewModel.mBackOneLD.observe(this) {
YYLogUtils.w("收到LD消息 - $it")
}
lifecycleScope.launch {
activityViewModel.mMsgFlow.collect {
YYLogUtils.w("收到Flow消息 - $it")
}
}
}
}
打印日志如下:
既然ViewMoel都可以,那么依赖注入容器是不是也可以?
五、Hilt依赖注入管理
Hilt是Android场景化的一个依赖注入容器,都是一些固定的用法,如果不是很明白可以看我之前的文章。
Hilt其实和ViewModel一样的逻辑,我们只需要保证LiveData或Flow是一个对象不就行了吗?
我们尝试一下,看看到底行不行?先定义一个DI容器
@Module
@InstallIn(SingletonComponent::class)
class Demo11Module {
@Provides
fun provideMsg(): String {
return "di提供的消息"
}
@Singleton
@Provides
fun provideLD(): MutableLiveData<String> {
return MutableLiveData()
}
}
然后在Activity与Fragment中注入这个对象
@AndroidEntryPoint
class Demo11Activity : BaseVMActivity(), IOneFragmentCallback, ITwoFragmentCallback {
@Inject
lateinit var mMsgLD: MutableLiveData<String>
...
override fun startObserve() {
mMsgLD.observe(this) {
YYLogUtils.w("收到消息:" + it)
}
}
}
@AndroidEntryPoint
class Demo11Fragment2(private val test: String) : BaseFragment(){
@Inject
lateinit var mMsgLD: MutableLiveData<String>
...
@Click
fun callback() {
mMsgLD.value = "hilt注入对象发出的消息"
}
}
调用的方法和ViewModel类似的,一边设置值,另一边接收值,可以看出同样的效果:
总结
其实依赖注入的运行方式和ViewModel类似的,只是不局限于一个Activity下面了,因为ViewModel是ActivityScope内的。这样我们通过依赖注入可以实现在OneACtivity下面的OneFragment直接通知到TwoACtivity下面的TwoFragment。
当然跨页面之间的通信我们可以用消息总线FlowBus来实现,更加方便。在实际开发中我们一般也是用消息总线+ViewModel管理这两种方法。
Ok,到这里就差不多了,还有其他非主流的Handle Bundle等其他不常用的方式就不一一介绍了。
如果觉得不错还请点赞支持,如需源码,可以看这里。
到处完结