Android程序架构优化

最近一段时间,看到了一些Android项目组件化相关的文章,同时,因工作需要对Java Spring框架进行了初步的学习,他贯穿整个应用系统的面向接口编程思想来解决业务逻辑和其他各层的松耦合问题的方案让我感受颇深,尤其是其通过控制反转(IoC)技术促进低耦合,面向切面的编程思想等,使程序员能够编写更简洁、易于管理的代码。借此机会,我对当前Android项目的整体架构进行了思考和优化,主要目的是为了解决代码的耦合度高,复用率低,过于脆弱(修改一个地方,会导致看上去没有关系的另一个地方发生故障),从代码架构层面提升代码可扩展性、灵活性、可插入性、可复用性。

一、整体开发框架

首先来看一下当前项目的整体开发框架,主要分为四个部分:持续集成、脚手架、公共Lib以及组件/微服务,如下图所示。

在这里插入图片描述

二、持续集成

持续集成是一种软件开发的实践,即团队开发成员经常集成他们的工作,每次集成都通过自动化的构建(包括编译、发布、自动化测试)来验证,从而尽早地发现问题。

对于Android程序开发来说,首先开发人员在开发工程中每天都要根据最新的代码构建安装包供测试进行评测,或产品人员体验最新的功能等。打包还要区分不同的版本,例如debug、release,线上和测试,混淆和非混淆等,并且对于上线包来说,还要集成不同渠道的安装包。整个集成过称是一个简单、耗时重复的过程,减少这个重复的过程可以节省时间和工作量。通过自动化的持续集成可以将这些重复的动作都变成自动化,让开发人员的时间更多投入到需要动脑经和更有价值的事情上面。

其次,持续集成系统自动检测有新代码进行构建,能够尽早发现集成过程中的问题。如果再将针对于软件的自动化测试与集成系统关联,能够尽可能早发现软件中存在的问题,提升软件质量,降低软件在开发过程中的风险。

一般使用Jenkins系统搭建持续集成的环境,关于持续集成环境的搭建,网上有很多教程,这里就不再细说了。下图是一个项目的持续集成构建环境。

在这里插入图片描述

三、脚手架

在实际的开发过程中,从零开始建立项目的结构时一件让人头疼的事情,所以各种脚手架工具应运而生。通过脚手架,能够快速搭建一个完整的项目结构,开发者只需要在生成的项目结构基础上进行开发即可,还可以定制项目所需要的基础模块,非常简单高效。对于脚手架的使用,可以针对项目的具体需求开展。

四、公共Lib

公共Lib就是公共代码库,就是将软件中多个模块公用的一些基础代码进行抽象封装,其他业务组件会直接引用该公共代码库中的代码文件进行扩展使用,主要目的还是为了提高代码的复用率。例如在我们的代码中,将于界面相关的代码抽取为一个uilib,该lib中实现了ui上面一些公用的代码功能,其他业务组件要实现ui界面时,都会引入该库,在该库封装了Activity、Fragment、Adapter等的基本行为,如果我要实现一个具有列表形式的页面,只需要继承uilib中的基础类,需要很少的代码就能实现需要的功能。公共代码库如下图所示:

在这里插入图片描述

公共代码库的根本目的是为了减少冗余代码,提高代码的复用率。使用公共代码库需要直接引入该库,继承或直接使用公共库中的代码,它会与业务代码产生耦合。如果项目中的某一部分代码模块具有这个特点,就可以把该部分代码抽取为一个公共代码库。

五、组件/微服务

组件/与微服务主要是为了降低代码的耦合度,但他们具有不同的意义。OOP是面向对象编程思想,就是把各功能封装为独立模块,各个模块按照面向对象程序设计思想进行实现。AOP是面向切面编程,它的意思是在各个独立的模块间,在特定的切入点进行hook,将共同的逻辑添加到模块中而不影响原有模块的独立性。Android中,有实现AOP的三大神器:
(1)APT:在代码编译期间生成.java文件;
(2)AspectJ:在.java编译为.class的时候,进行代码注入;
(3)Javassit:修改已经编译好的class文件。
各个工具操作时机如下图所示:

在这里插入图片描述

1、组件
组件我把它定义为不同各个业务模块,这些业务模块之间的功能是相互独立的。例如在一个应用中,有商城(组件A)和社区(组件B),这两个模块都属于功能业务模块,且这两个模块的功能之间没有交互,但是我有可能通过组件A中某个界面C的按钮跳转到组件B中的某一个界面D,这样组件A与组件B需要发生联系,但一般来说开发者不希望在组件A中引用组件B的代码,使它们之间的耦合度最低。此时,我们可以通过注解和APT生成全局路由来实现该功能。

(1)首先,定义路由注解,在每个模块中定义该模块中每个页面的路由地址。

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Router {
    String value();
} 

@Router(RouterConstants.ROUTER_APP_SETTING_PAGE)
public class SettingActivity extends AppBaseActivity {
}

(2)定义处理注解的APT文件,在编译项目的时候,会生成该组件相关的路由表;

public final class TRouter_app implements IRouter {
  /**
   * @此方法由apt自动生成 */
  public HashMap getUrlPageMap() {
    HashMap<String, String> map = new HashMap();;
    map.put("/home/mine/setting", "com.ui.activity.SettingActivity");
    return map;
  }
}

(3)定义全局路由表,在项目启动的时候将每个模块的路由表注册到全局的路由表,通过全局路由表进行页面导航;

private void registerRouter() {
       TRouter.register(new TRouter_app());
}

跳转的时候直接通过该页面对应的URI进行跳转:

TRouter.go(getBaseActivity(), RouterConstants.ROUTER_APP_SETTING_PAGE);

这样在每个组件内部和组件之间都可以通过全局路由方便的进行跳转,组件之间进行跳转时不需要知道其他组件的代码文件,能够降低代码复杂度。通过URI跳转,也增强了程序的灵活性,例如通过服务器下发的通知跳转到特定页面,都可以通过下发URI的方式来实现。

2、服务

服务是对其他组件或服务提供某种功能的组件,例如网络服务,图片加载服务等。说到这里,可能有人会问,服务于公共代码库看起来很像,他们的区别是什么?主要有下面几点区别:
(1)服务的初始化需要Context环境,在项目启动时,需要在Application环境进行初始化,例如分享,图片加载等;公共代码库不需要初始化的过程,它更多是直接调用代码,例如ui的组件库;
(2)服务初始化之后,在整个应用中是以单例的形式存在的,使用的时候直接通过注解实现依赖注入;公共代码库在需要的时候进行初始化或直接继承使用;
(3)服务对外提供统一接口,其他组件对服务的使用都是面向该接口编程,当该服务内部的代码进行变动时不会对使用该服务的地方造成影响;

下面是实现服务的步骤:

(1)定义服务注解,服务对外提供的接口和接口的实现类;

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface IService {
    String value();
}

定义加载图片的服务接口:

public interface IImageLoader extends IBaseService {
    public static final String IMAGE_LOADER = "ImageLoader";
    void loadImage(Object data, ImageView imageView, ImageView.ScaleType scaleType);
}

@IService(IImageLoader.IMAGE_LOADER)
public abstract class ImageWorker implements IImageLoader {

    public ImageWorker() {
    }

    public void initService(Context context) {
        mResources = context.getResources();
    }

    @Override
    public void loadImage(Object data, ImageView imageView, ScaleType scaleType) {
        loadImage(data, imageView, scaleType, mDefaultStop, mLoadingBitmap, mLoadingBitmap, 0);
    }

   ...
}

(2)定义处理注解的APT代码和全局服务控制器;

(3)在应用启动的时候向系统内注册该服务;

private void registerService() {
    TService.register(new TService_imageloader(), this);
}

(4)通过注解使用服务;

@FindService(IImageLoader.IMAGE_LOADER)
IImageLoader mImageLoader;

六、总结

上面就是当前对我们项目架构的梳理和总结。总之,对于项目架构和代码的优化是一个需要持续进行的过程,很多东西现在看来是好的,过段时间再看可能就有问题了。最终的目标还是为了提升代码质量,降低代码风险和维护成本。

发布了71 篇原创文章 · 获赞 34 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/TuGeLe/article/details/83575866