不可错过的新鲜出炉的通用Android组件化Demo

(本文提出的组件化项目已经开源,参见YouJu。*注:请勿商用,若有违反,责任自负)git

前言(废话)

最近有段空闲期,公司的这个项目一直由我负责,以前一直为了效率为忽略了质量,加上以前项目的功能的不断叠加,因此如今项目体积变得很是庞大且冗杂。可是考虑到往后可能其余人接手,并且本身有点完美主义和强迫症,就想利用这段时间对项目进行重构。重构的目的无非是使项目功能及模块具体划分开来,避免过于耦合,牵一发而动全身,这样后续的功能开发和维护也会更加方便和快速。具体功能模块单独抽离出来,不会影响其余的模块。这样每一个人只须要负责本身的那部分工做就能够了。天然而然想到了组件化。其实关于组件化的文章已经处处都是了。可是始终都是别人的思想转化出来的文字。本身可能看一篇就过了,不会有多么深刻的理解和记忆。因而就想单独写一套而且搭建一套往后能够通用的开发框架。因而就有了这个项目,到如今项目基本已经成型了,而且目前也已经实现了不少功能和内容。github

概述

YouJu是一款集多个平台的资讯内容、复合型优秀资源和实用功能以及多种炫酷UI特效的组件化Demo,打破传统限制,融合多元,发现更多、更优秀、更有趣的东西。 项目是一款组件化综合案例,包含新闻,MONO资讯,开眼视频,知乎日报,垃圾分类搜索,一言,古诗词,智能聊天机器人,语音识别功能,干货集中营,豆瓣影视等等模块。项目利用业余时间开发,时间不固定,暂时只实现了这些功能。后续也会加入更多新的功能。参照该项目少量配置便可适用于任何通用项目开发框架,简单易上手,傻瓜式操做。

当前模块有:base基础模块、network网络请求模块、语音识别模块、工具模块、干货模块、资讯模块。除base和network外,每一个模块均可单独抽离为APP运行,不影响主工程,具体参照config.gradle配置文件

项目采用组件化架构模式以及MVP+Walle+ARouter+AndroidX+Dagger2+Retrofit2+Okhttp3+ RxJava2+EventBus+ Kotlin/Java混编模式开发。

整个项目框架结构bash

流程

如上图所示,base和network模块做为底层公用library提供给中间层,中间层能够单独做为一个APP运行,也可做为library提供给App壳做为功能模块。只须要在config.gradle中配置相应参数,这样极大的简化了复杂度和开发的成本。若是须要开发一个新模块,直接继承依赖base或者network模块便可。

分析

此外,base还提供了ServiceProvider和Interceptor供上层调用,ServiceProvider能够提供一个服务给外部调用,好比A模块中的AServiceProvider依赖BaseServiceProvider,BaseServiceProvider是base中定义的基类提供器。A模块可使用AServiceProvider提供AFragment或者其余A模块中的内容或者方法给B模块使用,只须要AServiceProvider配置一个路径,而后B模块访问这个路径,ARouter会去寻找这个路径,若是找到B模块就能够获得AServiceProvider实例服务,而后执行相应的业务逻辑。找不到也不会崩溃。这样就实现了跨模块服务调用。网络

public interface BaseServiceProvider extends IProvider {
    BaseFragment newInstance(Object... args);
    
    void customFun(Bundle bundle);
}
复制代码
@Route(path = ConfigConstants.PATH_MODULE_PROVIDER)
public class AServiceProvider implements BaseServiceProvider {

    @Override
    public void init(Context context) {

    }

    @Override
    public BaseFragment newInstance(Object... args) {
        return AFragment.getInstance();
    }
    
    @Override
    public void customFun(Bundle bundle) {
         ......
    }
}
复制代码


Interceptor是做为一个拦截器,它做用于整个ARouter页面跳转,也能够配置某个页面跳转不经过拦截器。取决于具体业务场景。常规的就是登陆跳转拦截,A→B,若是已经登陆直接到B,若是没有登陆,先去C登陆,登陆成功后直接到B,B若是返回直接到A。架构

@Interceptor(priority = 5, name = "login")
public class LoginInterceptor implements IInterceptor {
    private Context mContext;

    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        Bundle extras = postcard.getExtras();
        boolean isNeedIntercept = false;
        if (extras != null) {
            isNeedIntercept = extras.getBoolean(ConfigConstants.IS_NEED_INTERCEPT, false);
        }
        if (isNeedIntercept) {//须要执行登陆拦截逻辑
            boolean isNeedLogin = new Random().nextInt(10) % 2 == 0;
            if (isNeedLogin) { //须要登陆
                //主线程跳转登陆页面(走绿色通道,不走拦截器)
                Single.just(postcard)
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new SingleObserver<Postcard>() {
                            @Override
                            public void onSubscribe(Disposable d) {
                            }

                            @Override
                            public void onSuccess(Postcard postcard) {
                                Bundle bundle = null;
                                if (null != postcard) {
                                    String targetPath = postcard.getPath();
                                    bundle = postcard.getExtras();
                                    if (null != bundle && !TextUtils.isEmpty(targetPath) && !TextUtils.equals(ConfigConstants.PATH_LOGIN, targetPath)) {
                                        bundle.putString(ConfigConstants.PATH_TARGET, targetPath);
                                    }
                                }
                                ARouter.getInstance().build(ConfigConstants.PATH_LOGIN)
                                        .with(bundle)
                                        .greenChannel()
                                        .navigation();
                            }

                            @Override
                            public void onError(Throwable e) {
                                e.printStackTrace();
                            }
                        });
                callback.onInterrupt(null);
            } else {//不须要登陆
                callback.onContinue(postcard);
            }
        } else {//不须要拦截
            callback.onContinue(postcard);
        }
    }

    @Override
    public void init(Context context) {
        // 拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次
        mContext = context;
    }
}
复制代码

有时候咱们还须要在Application中初始化一些东西,可是各个模块又是能够单独抽离的,因此不能将全部的初始化操做都放在一个Application中,否则仍是会耦合,这里呢能够采用在base模块中定义一个BaseApplication,用于初始化base模块中须要初始化的操做,而后其余中间层模块若是须要初始化,就能够经过MouleApplication继承BaseApplication,而后执行所须要的初始化。这种状况能够适用于中间层模块做为单独APP运行时,可是当中间层模块做为library提供给壳时,这个时候整个应用只有一个Application,也就是壳的Application,因此中间层模块的MouleApplication这个时候不会被执行。这个时候就须要另一种方式去实现。能够在base模块中定义一个BaseApplicationImpl接口,而后须要初始化的中间模块能够实现BaseApplicationImpl,而后在BaseApplication中分别遍历执行全部BaseApplicationImpl的实现类。剩下的就是把每个实现类的具体路径加入到初始化路径列表ModuleConfig.MODULESLIST中便可。app

public interface BaseApplicationImpl {
    void onCreate(BaseApplication application);
}
复制代码
private void modulesApplicationInit() {
        for (String moduleImpl : ModuleConfig.MODULESLIST) {
            try {
                Class<?> clazz = Class.forName(moduleImpl);
                Object obj = clazz.newInstance();
                if (obj instanceof BaseApplicationImpl) {
                    ((BaseApplicationImpl) obj).onCreate(this);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }
复制代码
public class ModuleConfig {
    public static final String[] MODULESLIST = {
            "com.heyongrui.iflytek.mscv5plusdemo.IflytedApplicationImpl"
    };
}
复制代码
public class IflytedApplicationImpl implements BaseApplicationImpl {

    @Override
    public void onCreate(BaseApplication application) {
        StringBuffer param = new StringBuffer();
        param.append("appid=" + application.getString(R.string.app_id));
        param.append(",");
        param.append(SpeechConstant.ENGINE_MODE + "=" + SpeechConstant.MODE_MSC);
        SpeechUtility.createUtility(application, param.toString());
    }
}
复制代码

若是A模块的某个页面须要跳转到B模块的某个页面,可是App壳只依赖了A模块,这个时候就会跳转失败,并且还会弹出Toast,就会很烦,对于用户体验是很差的;亦或是A模块的某个页面的一个View须要根据App壳是否依赖B模块动态设置显示隐藏,这个时候能够经过checkIsHasPath方法去判断,这样就能够兼容到两种状况并且不会弹出Toast提示。框架

private boolean checkIsHasPath(String path) {
        try {
            String group = path.substring(1, path.indexOf("/", 1));
            LogisticsCenter.completion(new Postcard(path, group));
        } catch (Exception e) {
            return false;
        }
        return true;
    }
复制代码

感悟

以上就是该项目的大体状况经过这个项目,中间也遇到了很多的问题,作起来其实没有提及来那么容易,体会到了抽丝剥茧的艰难。找到一个合适的方案当然重要,更重要的是克服重重困难坚决的实施下去。在整个过程当中,不断去完善才有了如今的样子。总的来讲,它学习成本比较低,能够快速的入手,最主要的是代码结构清晰了不少。若是你也面临着庞大的工程或者在重构的边缘犹豫不决,建议你赶忙行动起来,否则后面你会更难行走。也欢迎使用该方案,以最小的代价尽快开始实施组件化。若是你如今负责的是一个开发初期的项目,代码量还不大,那么更建议尽快进行组件化的规划,不要给将来的本身增长徒劳的工做量。dom

github源码已上传,喜欢的给个star,欢迎小伙伴fork~ide

*注:此项目属于业余时间练手的项目,接口数据来源均来自网络,若是存在侵权状况,请第一时间告知。本项目仅作学习交流使用,API数据内容全部权归原做公司全部,请勿用于其余用途!!

相关文章
相关标签/搜索