如何优雅的实现自己的Android组件化改造?

本篇文章的主要目的:

  • 帮助正在对项目进行组件化改造或者想建立组件化项目架构的小伙伴,更好的认识组件化本质。
  • 目前组件化的框架众多,说的天花乱坠的,其本质来说其实都差不多,阅读本文以后,读者甚至可以摒弃这些开源框架,根据自己的项目特点,轻松构建自己的组件化框架。
  • 帮助想学习和了解组件化框架,并尝试动手写自己的开源框架的小伙伴们

什么是组件化?

在平时的开发过程中,随着项目需求的增加,app支持功能越来越多,如果没有组件化的思想,代码会越来越臃肿。导致的问题也会越来越明显,比如一个很小的需求变化,可能会导致代码的“牵一发动全身”问题,甚至会出现很多隐藏的bug。还会导致维护成本越来越高,团队协作低效,问题边界不清晰。

于是,很多团队开始有了代码解耦的想法,但是面对如此复杂的项目,又不敢轻易变更其中的代码结构,如何顺利的解耦就成了很多团队难以入手的问题。甚至由于业务的不断迭代,导致代码解耦的问题遥遥无期。

当然,作为一位合格的App架构师,遇到再难的问题也会迎难而上。于是便开始对APP整体项目结构进行分析,制定解耦方案。通常情况下,解耦的最佳思路就是根据业务边界对代码结构进行划分。比如说某APP里面包含了IM、直播、内容展示等等业务场景,于是从架构的角度来说,整个APP的架构应该是如下图所示:

图1

这种架构思路上很清晰,对应到我们Android代码结构,就是根据这些业务边界,拆分成不同的module,module之间没有直接的引用和依赖,代码完全解耦。作为团队开发成员也有很清晰的业务边界,代码维护成本大大降低,开发效率也会明显提高,应该是一个很不错的方案。

所谓的组件化其实就是根据业务边界对代码进行解耦,不同的业务对应不同的module,这些module相互独立,可以独自作为一个app进行开发并独立运行,合并时可以打包到一个整体的app中,实现完整的app功能。

如何优雅的进行组件化?

那么问题来了,以上的架构的确是非常不错的选择,但是实际的业务中,很难有个清晰的边界,并且业务与业务直接总会有衔接的地方。如果使用以上的架构,那么这些不同的module之间又该如何进行调用呢?

在我们Android系统中,进程是一个独立程序,每个进程都具有自己的虚拟机 (VM),应用代码是在与其他应用隔离的环境中运行,进程直接的通信主要是基于Binder机制。为什么要提Binder,首先Binder是Android系统的中非常重要的实现机制,而我们组件化代码耦合的问题也可以借鉴其实现原理。接下来我简单的介绍一下Binder机制,先总体看一下Binder架构图:

Binder进程间通信机制.png

可以看出Binder是一个典型的CS架构,进程间的通信基于ServiceManager这个大管家,Client进程从ServiceManager中获取Server进程的一个远程代理,进行通信。为了让大家更直接的理解,我从代码层面上来简单描述一下这个过程。比如我们启动一个Activity时,需要ActivityManagerService(AMS)这个服务来进行管理,而AMS运行在SystemServer中,那么如何获取这AMS呢,我们从源码来分析(以下源码android-28中):

public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}

private static final Singleton<IActivityManager> IActivityManagerSingleton =
    new Singleton<IActivityManager>() {
         @Override
         protected IActivityManager create() {
         //通过ServiceManager.getService获取到AMS的代理IActivityManager
         final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
         final IActivityManager am = IActivityManager.Stub.asInterface(b);
         return am;
         }
};

app中通过ServiceManager.getService获取到远程进程中服务的代理,只需要指定服务的name就可以。

Binder机制就不展开讲解,如果想了解更多的同学,可以加入到ARetrofit的QQ群中@我进行交流。接下来我们再回到“如何优雅的进行组件化”这个问题上。

在了解Binder机制后,对于我们组件化过程中解耦代码如何通信,其实也可以采用类似的机制。当然我们的module之间并没有跨进程,不会有跨进程通信的问题。

我们可以将图1中的每一个module想象成一个服务,那么module之间的通信其实就类似于服务之间的通信。而服务之间的通信其实也不需要直接进行类的引用,只需要拿到另一个module的服务代理,通过这个代理进行通信。这里又出了一个新的问题,如何实现提供代理服务的代理管家呢,其实这也类似于Binder里面的ServiceManager,用以下这张架构图来说明:

module通信架构

上图中IM module注册自己的IM服务到ServiceManager中,直播 module想要和IM module只需要获取IM的服务代理就可以进行操作。注册的过程,对应到实际的代码中,其实就是中ServiceManager这个管家的Module中声明自己的服务接口,并在自己的module中实现这一接口。其他module只需要在运行时拿到接口实现类的实例对象就可以完成这一过程了,这一部分其实可以参考ARetrofit README中 四 高阶用法中登录服务接口的声明与注册过程就可以了解。

当然,这篇文章不仅仅让大家了解别人已经开源好的框架,其实类似的框架很多,如ARetrofit 、ARouter、CC等开源,无关star量,这都是开源早晚和推广的问题,其实本质上都是基于以上的原理,区别就是上层的封装的问题,最终呈现的API是否简洁,是否符合自己的要求,能否直观的进行代码维护。

这里我将带着大家动手一起实现自己的ServiceManager管家。

第一步,我们在ServiceManager中注册不同module的服务接口,如下:

public interface ILoginManager {
    void login();
    User getUser();
}

第二步,在Login Module中实现该接口,如下:

@Inject //需要自动注入服务的声明
public class LoginManagerService implement ILoginManager {
    @Override
    void login(Activity activity) {
       Intent intent = new Intent(activity, LoginActivity.class);
       activity.startActivity(intent);
    }

    @Override
    User getUser(CallBack callback) {
        //...网络或者  或者  本地数据库 等回去异步返回或者同步返回结果
        
    }
}

第三步, 在直播 Module中跨Module获取ILoginManager服务实例对象,这里需要通过Android Studio自动注入框架AInject,完成跨Module自动注入流程,可参考AInject用法,具体实现如下:

 public class ServiceManager implements InjectContract {

  private static class ServiceManager {
      private static final ServiceManager instance = new ServiceManager();
  }

  static ServiceManager getInstance() {
      return InstanceHolder.instance;
  }
  
  /**
    * @Fixme 这里建议使用实现LRU算法的列表存储
    */
  List<Object> services = new ArrayList();

 /**
   *
   * "@Inject" 注解标示的class 最终都会注入到该"@IMethod"注解标示过的方法中
   *  注:"@IMethod"注解标示过的方法将由编译器自动注入实现代码,注入最终的代码如下如:
   *
   * @IMethod
   * public void iMethodName() {
   *       injectClass("injectClassName1")
   *       injectClass("injectClassName2")
   *       injectClass("injectClassName3")
   *       injectClass("injectClassName4")
   * }
   *
   * 用户可以在该方法中通过反射完成自己的业务需求
   * @param className class name
   */
  @IMethod
  public void startInject() {
     

  }

  @Override
  public void injectClass(String serviceClassName) {
       services.clear()
       services.add(className);
  }

 /**
  * 获取登录服务,可在任意Module直接获取该服务的实例化对象
  */
  public static ILoginManager getILoginManager() {
        if (getInstance().service.size() == 0) {
               getInstance().startInject();
        }
        for (String className: getInstance().services) {
           try {
                Class<?> clazz = Class.forName(className);
                Object obj = clazz.getConstructor().newInstance();
                if (obj instanceof ILoginManager) { 
                     return obj;
                } else {
                     obj = null;
                }
            } catch (Exception e) {
              e.printStackTrace();
           }    
        }
        return null;
  }
}

其实就是这么简单,一个组件化的框架就完成了。

看到这里的小伙伴们,大概已经理解了如何进行解耦Module直接的通信工作了吧。想想平时有没有遇到其他关于高耦合的代码需要解耦的,都可以参考这种思路哦。

组件化并不需要一蹴而就的

前面教大家如何进行组件化,已经如何实现组件化,其实还忽略了一个非常重要的问题,就是如何对现有的项目进行组件化。现有的项目一般都已经累计了很多代码量,如果一次性根据业务进行拆解处理,解耦合显然是不合实际的。那么到底该怎么做呢?

其实有了以上自定义的组件化框架(当然推荐建议使用作者新开源的ARetrofit框架,API使用非常简洁),其实组件化并不是一个版本就需要完成的。组件化的第一步当然还是根据业务边界来架构自己的APP框架,不同的Module中声明好自己的服务。而组件化的工作可以拆分到不同的迭代版本中,对于新增的业务明确到对应的业务Module中开发,对应老的业务代码如果耦合度比较高的,不建议直接修改逻辑,可以先将这部分代码耦合的地方抽象到服务接口中,通过服务调用来实现调用过程。并在未来的版本中逐步进行剥离解耦最终实现真正的代码隔离,以服务的形式完成业务间的交集部分。

组件化最佳实践

小结

前面讲了很多,我们再回到主题,相信大家对于“如何实现自己的Android组件化改造”应该有了很清晰的步骤和流程来吧。此外仅代表个人的一些拙见,如果意见或者建议欢迎进ARetrofit QQ群赐教。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343