Android组件化开发实践(四):组件间通信问题

简介: 记得第一次实施项目组件化时,遇到的最大困扰就是,组件之间的通信问题。例如:怎么从这个组件跳转到另一个组件的页面;组件之间怎么传递数据;怎么获取其他组件的数据或服务;组件怎么通知其他组件响应某个事件;1. 页面跳转统一采用路由在Android中,页面跳转都是通过startActivity来实现的。

记得第一次实施项目组件化时,遇到的最大困扰就是,组件之间的通信问题。例如:

  • 怎么从这个组件跳转到另一个组件的页面;
  • 组件之间怎么传递数据;
  • 怎么获取其他组件的数据或服务;
  • 组件怎么通知其他组件响应某个事件;

1. 页面跳转统一采用路由

在Android中,页面跳转都是通过startActivity来实现的。但是我们组件化之后,上层的业务组件之间是不能相互依赖的,也就是说现在无法通过startActivity来进行页面跳转了。

组件化之后,所有页面跳转都必须采用路由来实现。现在已经有很多成熟的路由框架了,具体什么是路由、路由的作用都讲的很清楚,我这里不再赘述了,比较成熟的有:

路由框架的核心原理都是一样,这里我来说说我自己的路由框架,以及这样设计的原因何在。

1.1 路由URI格式

路由实质上都是将一个URI映射到某一个具体的界面,通过URI跳转时,路由框架内部找到该URI对应的Activity页面,进而实现页面跳转。首先我们来看一张图,明白一个完整的URI是怎么定义的。


img_e06db4e3c9edfdabe62a7b20eec99f0d.png
图片引用自美团

是不是特别复杂,但实际上我们并不需要这么复杂,以我的一个路由uri为例:

hmiou://www.54jietiao.com/webview/index?title=*&url=*

这是一个打开WebView页面的路由地址定义,具体来说只采用了URI的几个部分:

  • scheme
    这个是必须的,我定义为hmiou,这个根据项目来自定义即可。
  • host
    www.54jietiao.com,通常定义为你项目的主站域名。
  • path
    /webview/index,也就是路径,根据你的业务来区分即可。
  • query
    title=*&url=*,查询参数

我的路由定义里面,只采用了scheme、host、path、query这4部分,能满足我的需求即可。

1.2 路由映射文件

我们没有采用注解,而是定义了一份全局的路由映射json文件,应用启动时读取配置文件进行路由初始化。

{
  "test": [
    {
      "url": "hmiou://www.54jietiao.com/test/test1?title=*",
      "iclass": "Test1ViewController",
      "aclass": "com.hm.iou.router.demo.TestActivity1"
    },
    {
      "url": "hmiou://www.54jietiao.com/test/test2",
      "iclass": "Test2ViewController",
      "aclass": "com.bwton.router.demo.MainActivity"
    }
  ],
  "main": [
    {
      "url": "hmiou://www.54jietiao.com/main/index?url=*",
      "iclass": "MainViewController",
      "aclass": "com.hm.iou.router.demo.MainActivity"
    }
  ]
}

每个配置项都包含“url、iclass、aclass”3个选项,url就是路由定义,iclass是对应的iOS里面该页面的类名,aclass是对应的Android里面该页面的类名,这么做的目的是为了保持2个平台的路由统一。

我根据功能将路由进行了分组,从上面配置文件中可以看到有2个分组:test、main,然后每个路由url的path都以该分组名开头,所以每个路由url至少应该包含2级路径。

以路由“hmiou://www.54jietiao.com/test/test1?title=标题”为例,来看看内部是怎样实现页面跳转的。
1.路由框架首先解析出这个url的scheme、host、path、query这4部分;
2.检查scheme是否应用能支持的scheme,如果不是则不允许跳转或跳转失败;
3.检查host是否应用支持;
4.前面检查通过后,取出path的第一级路径,这里为“test”,然后框架查找路由配置表,找到“test”这个分组;
5.在“test”分组下的路由配置里遍历匹配,找到与当前url一致的路由配置项数据来;
6.找到对应的配置项之后,找到该url对应的aclss,这里为“com.hm.iou.router.demo.TestActivity1”;
7.框架通过反射调用startActivity来进行页面跳转;
8.如果第1步解析出的查询参数里有值,则将参数放到Intent里面传递过去,这里我们会传递一个key为“title”,value为“标题”的数据传递过去,类似于intent.putExtra("title", "标题")。
9.路由表里的查询参数都定义成类似于title=*,这里*只是一个占位符,仅仅是为了便于开发人员理解,知道该路由接收一个参数,名为“title”。

这里对路由进行分组,是因为做url匹配时,需要遍历整个路由表,分组可以提高查找匹配url的速度。配置文件里的url甚至可以设置一些自定义的正则匹配规则,你可以设置一些通配符,让若干个不同的url都能跳转到同一个页面。

当然还有很多细节需要处理,比如:

  • 支持页面间跳转动画;
  • 支持startActivityForResult;
  • 支持设置intent的flag;
  • 使用路由url进行跳转时,查询参数的值必须进行encode,否则会导致解析失败;
  • 通过Intent传递参数时,不能知道查询参数里的数据类型,统一定义为字符串类型;

还有些功能可能实现不了,比如说页面之间怎么传递对象,Android里可以传递Parceable、Serializable对象,在我这里就不能支持。不过我并不推荐页面间传递对象,这样会带来比较高的耦合度,同时不利于组件化开发。

1.3 动态更新路由文件

通常安装包里会包含一份初始的配置文件,但是当应用发布之后,某个页面出现严重bug,或者我们想改变某个入口点击后的跳转目标页面,这时可以通过动态更新路由配置文件来实现。

新的配置文件里,只需要把原本配置里的aclass、iclass替换成新的目标页面类名即可,而不用重新发布app版本。

1.4 外部路由分发器

现在很多应用有这么个功能:在外部第三方应用里,或者H5网页里,直接通过路由url能打开我们的应用,并跳转到指定的目标页面。

public class RouteDispatchActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onResume() {
        super.onResume();
        Intent data = getIntent();
        if(data != null && data.getData() != null) {
            Uri uri = data.getData();
            //-------通过路由来跳转-------
            1.判断uri是否合法;
            2.判断uri是否在白名单内;
            3.判断通过,则采用路由跳转;
            4.不通过或跳转失败,则仅仅打开应用而已;
        }
        finish();
    }
}

在AndroidManifest.xml里配置:

<activity
    android:name=".RouteDispatchActivity"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:screenOrientation="portrait"
    android:theme="@android:style/Theme.Translucent.NoTitleBar"
    >
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:scheme="hmiou" android:host="www.54jietiao.com" />
    </intent-filter>
</activity>

该Activity没有任何业务逻辑,它只是一个接收外部uri跳转的入口Activity。注意这里的 <intent-filter />配置,这样该Activity就能响应所有 hmiou://www.54jietiao.com 这种格式的uri跳转了。

该Activity被设置成透明的样式,用户感知不到,它的作用就是一个外部路由的分发器,这样我们就能在外部应用里跳转到任意页面了。这里有个路由白名单是个什么鬼,且继续看下去。

1.5 路由白名单配置

前面讲到可以从外部跳转到任意指定页面,这显然是极度危险的操作,如果你的应用里有钱包的话,这意味着任何应用都可以打开你的钱包页面进行付钱。所以对外部应用的路由跳转,我们必须设置白名单,在白名单内的路由url,能跳转到指定的目标页面,不在其内的仅仅只是打开应用进入首页而已。

[
  "hmiou://www.54jietiao.com/main/index"
]

2. 数据服务共享

像美团的WMRouter框架,主要提供了URI分发、ServiceLoader两大功能。ServiceLoader通俗点说就是组件间服务共享、数据共享,我在路由框架里没有实现,而是换了个方式来实现这些需求。

2.1 维护好全局共享数据

一般应用里都需要用户登录,登录之后我们会本地保存用户信息,而用户信息可能在所有的组件都会使用。例如注册登录组件服务里,用户登录后需要保存登录信息到本地;用户在个人中心组件服务里,需要读取用户登录信息进行展示。

通常这类数据我称之为全局共享数据,我通常的做法是,将这类数据下沉到底层模块里,所有业务组件可依赖,这样就解决了组件之间数据共享的问题。

不要盲目的将共享数据下沉到底层组件里,否则随着业务的扩张,会造成难以维护的地步。一旦数据下沉之后,以后想从底层组件里剥离出,将会是一件非常困难的事情。

2.2 采用EventBus

除了数据共享之外,还有一个是服务调用,例如A组件想调用B组件的某个操作。还是以登录为例,当用户登录成功之后,在个人中心这个组件里,需要及时展示用户的个人信息。

我引入了EventBus库,通过EventBus发送事件通知,其他组件接收自己感兴趣的事件,通过订阅通知的模式,来实现组件之间的通信。

public void post(Object event)

采用EventBus有个问题,它发送的事件必须是一个对象,但我们不可能在底层模块定义很多event class,所以我定义了一个通用的事件。

public class CommBizEvent {
    public String key;
    public String content;

    public CommBizEvent(String key, String content) {
        this.key = key;
        this.content = content;
    }
}

通过key来区分事件,然后组件文档维护好这些事件名。

3. 小结

组件之间通信是组件化开发首先要解决的问题,我们必须先解决该问题,后面才能实施下去。在资源紧张、时间紧迫的情况下,可以借鉴成熟的方案,没必要重新造轮子。我的方案,前期实施比较容易,也很容易理解,但是问题其实还很多。

系列文章
Android组件化开发实践(一):为什么要进行组件化开发?
Android组件化开发实践(二):组件化架构设计
Android组件化开发实践(三):组件开发规范
Android组件化开发实践(四):组件间通信问题
Android组件化开发实践(五):组件生命周期管理
Android组件化开发实践(六):老项目实施组件化
Android组件化开发实践(七):开发常见问题及解决方案
Android组件化开发实践(八):组件生命周期如何实现自动注册管理
Android组件化开发实践(九):自定义Gradle插件

目录
相关文章
|
1月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
123 4
|
1月前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
2月前
|
缓存 搜索推荐 Android开发
安卓开发中的自定义控件实践
【10月更文挑战第4天】在安卓开发的海洋中,自定义控件是那片璀璨的星辰。它不仅让应用界面设计变得丰富多彩,还提升了用户体验。本文将带你探索自定义控件的核心概念、实现过程以及优化技巧,让你的应用在众多竞争者中脱颖而出。
|
2月前
|
存储 Android开发 开发者
深入理解安卓应用开发的核心组件
【10月更文挑战第8天】探索Android应用开发的精髓,本文带你了解安卓核心组件的奥秘,包括Activity、Service、BroadcastReceiver和ContentProvider。我们将通过代码示例,揭示这些组件如何协同工作,构建出功能强大且响应迅速的应用程序。无论你是初学者还是资深开发者,这篇文章都将为你提供新的视角和深度知识。
|
2月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
96 0
|
19天前
|
XML 搜索推荐 前端开发
安卓开发中的自定义视图:打造个性化UI组件
在安卓应用开发中,自定义视图是一种强大的工具,它允许开发者创造独一无二的用户界面元素,从而提升应用的外观和用户体验。本文将通过一个简单的自定义视图示例,引导你了解如何在安卓项目中实现自定义组件,并探讨其背后的技术原理。我们将从基础的View类讲起,逐步深入到绘图、事件处理以及性能优化等方面。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
19天前
|
搜索推荐 Android开发 开发者
安卓应用开发中的自定义控件实践
在安卓应用开发的广阔天地中,自定义控件如同璀璨的星辰,点亮了用户界面设计的夜空。它们不仅丰富了交互体验,更赋予了应用独特的个性。本文将带你领略自定义控件的魅力,从基础概念到实际应用,一步步揭示其背后的原理与技术细节。我们将通过一个简单的例子——打造一个具有独特动画效果的按钮,来展现自定义控件的强大功能和灵活性。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇通往更高阶UI设计的大门。
|
1月前
|
前端开发 Android开发 UED
安卓应用开发中的自定义控件实践
【10月更文挑战第35天】在移动应用开发中,自定义控件是提升用户体验、增强界面表现力的重要手段。本文将通过一个安卓自定义控件的创建过程,展示如何从零开始构建一个具有交互功能的自定义视图。我们将探索关键概念和步骤,包括继承View类、处理测量与布局、绘制以及事件处理。最终,我们将实现一个简单的圆形进度条,并分析其性能优化。
|
2月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。
|
2月前
|
前端开发 搜索推荐 Android开发
安卓开发中的自定义控件实践
【10月更文挑战第4天】在安卓开发的世界里,自定义控件如同画家的画笔,能够绘制出独一无二的界面。通过掌握自定义控件的绘制技巧,开发者可以突破系统提供的界面元素限制,创造出既符合品牌形象又提供卓越用户体验的应用。本文将引导你了解自定义控件的核心概念,并通过一个简单的例子展示如何实现一个基本的自定义控件,让你的安卓应用在视觉和交互上与众不同。