Android模块化学习

参考:Android模块化探索与实践

强烈建议先看上面博客,写的十分优秀,为我理解模块化,和真正实践起到了很大的帮助。我的这篇文章仅是我自己的学习笔记,一个知识点如果只是看过那很快就会从大脑中遗忘,我更喜欢自己去写demo,做笔记,这样不仅对我理解这个知识点有很大的帮助,同时在脑中的记忆也更深刻,即便一段时间我有所遗忘,只要翻开自己的笔记也很快可以回忆起。

一、模块化分层设计

模块化的好处:

  • 多团队并行开发测试;
  • 模块间解耦、重用;
  • 可单独编译打包某一模块,提升开发效率。

模块化和组件化之间的区别定义:

  • 组件:指的是单一的功能组件,如地图组件(MapSDK)、支付组件(AnjukePay)、路由组件(Router)等等;

  • 模块:指的是独立的业务模块,如新房模块(NewHouseModule)、二手房模块(SecondHouseModule)、即时通讯模块(InstantMessagingModule)等等;模块相对于组件来说粒度更大。

具体的设计方案:


整个项目分为三层,从下至上分别是:

  • Basic Component Layer: 基础组件层,顾名思义就是一些基础组件,包含了各种开源库以及和业务无关的各种自研工具库;
  • Business Component Layer: 业务组件层,这一层的所有组件都是业务相关的,例如上图中的支付组件 AnjukePay、数据模拟组件 DataSimulator 等等;

  • Business Module Layer: 业务 Module 层,在 Android Studio 中每块业务对应一个单独的 Module。例如安居客用户 App 我们就可以拆分成新房 Module、二手房 Module、IM Module 等等,每个单独的 Business Module 都必须准遵守我们自己的 MVP 架构。

上面都是Android模块化探索与实践总结出的一些观点,这里还是强烈建议直接浏览这篇博客。

二、模块化基本实现

下面是我自己在实现模块化时的整个流程,包括具体的代码编译过程。

(1)各个模块的开发

就如同上文定义的一般,每个模块是可以独自开发编译,打包调试的。同时也可以作为一个module导入到最终的项目里使用。所以每个模块不仅是“application”还可以是“library”,可以在gradle.properties文件中设置一个开关变量用于判断当前模块的状态。

isBuildModule=false

然后在项目的build.gradle文件中增加判断

if (isBuildModule.toBoolean()){
    apply plugin: 'com.android.library'
}else{
    apply plugin: 'com.android.application'
}

还需要注意以下几点:

① 作为一个application进行开发是需要applicationId的,作为一个library是不需要的,所以还需要加上判断:

if (!isBuildModule.toBoolean()){
            applicationId "com.example.francis.amoudle"
        }

② 如果作为一个library开发是不需要应用入口的,即要把AndroidManifest文件中的LAUNCHER关闭,所以需要两套的AndroidManifest文件,可以使用sourceSets进行设置。(build.gradle中的很多配置还是需要学习一下的)

sourceSets {
        main {
            if (isBuildModule.toBoolean()) {
                manifest.srcFile 'src/main/release/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            }
            java {
                //release 时 debug 目录下文件不需要合并到主工程
                exclude 'debug/**'
            }
        }
    }

这里完整的build.gradle文件如下:

if (isBuildModule.toBoolean()){
    apply plugin: 'com.android.library'
}else{
    apply plugin: 'com.android.application'
}

android {
    compileSdkVersion 26
    defaultConfig {
        if (!isBuildModule.toBoolean()){
            applicationId "com.example.francis.amoudle"
        }

        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        resourcePrefix "a_"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
        main {
            if (isBuildModule.toBoolean()) {
                manifest.srcFile 'src/main/release/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            }
            java {
                //release 时 debug 目录下文件不需要合并到主工程
                exclude 'debug/**'
            }
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

这里还是比较简陋的写法,比如说android的一些基本配置,sdk的版本,依赖第三方库的版本都可以统一起来,一方面保证不同的module不会出现版本冲突的问题,另一方面也方便整理。

(2)导入到壳APP中

直接new -> import module -> 选择需要导入的module,修改导入module的名字就可以了,还是是比较简单的。


导入成功后,setting.gradle文件中会显示所有的application和library

除了导入module还需要将添加依赖,主框架才可以使用module中的代码,右键项目打开Open Module Settings,


添加依赖


这样app就可以调用它所依赖的module了

(3)模块间跳转通信

对业务进行模块化拆分后,为了使各业务模块间解耦,因此各个 Bussiness Module 都是独立的模块,它们之间是没有依赖关系。那么各个模块间的跳转通讯如何实现呢?

比如业务上要求从新房的列表页跳转到二手房的列表页,那么由于是 NewHouseModule 和 SecondHouseModule 之间并不相互依赖,我们通过想如下这种显式跳转的方式来实现 Activity 跳转显然是不可能的实现的。

 
    
1
2
 
    
Intent intent = new Intent(NewHouseListActivity. this, SecondHouseListActivity.class);
startActivity(intent);

可能会想到用隐式跳转,通过 Intent 匹配规则来实现:

 
    
1
2
 
    
Intent intent = new Intent(Intent.ACTION_VIEW, "<scheme>://<host>:<port>/<path>");
startActivity(intent);

但是这种代码写起来比较繁琐,且容易出错,出错也不太容易定位问题。因此一个简单易用、解放开发的路由框架是必须的了。


Android模块化探索与实践的作者提供了 路由框架分为 路由(Router)  和 参数注入器(Injector)  两部分:

这里我目前只学了路由方面,注入这方面还没有尝试。学习这块内容最好对注解,反射,动态代理,android隐式跳转这几个稍微有些概念,代码看起来也会轻松一些,这里推荐几篇博客注解动态代理
学习过Android的同学应该都不会对Retrofit感觉到陌生,它里面使用的就是动态代理,简单来说,就是增强现有接口的功能
Retrofit是将接口通过动态代理转换为网络请求,而路由是将接口转换为Android的隐式请求。

这里代码都是从Android模块化探索与实践获取的,感兴趣的朋友可以直接查看他项目的github地址

定义了一些基本注解:

用于定义跳转 URI 的注解 FullUri:

 
    
1
2
3
4
5
 
    
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FullUri {
String value();
}

用于定义跳转传参的 UriParam( UriParam 注解的参数用于拼接到 URI 后面):

 
    
1
2
3
4
5
 
    
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface UriParam {
String value();
}

用于定义跳转传参的 IntentExtrasParam( IntentExtrasParam 注解的参数最终通过 Intent 来传递):

 
    
1
2
3
4
5
 
    
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface IntentExtrasParam {
String value();
}

主要方法:

public <T> T create(final Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                FullUri fullUri = method.getAnnotation(FullUri.class);//通过反射获取注解信息
                StringBuilder urlBuilder = new StringBuilder();
                urlBuilder.append(fullUri.value());
                //获取注解参数
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                HashMap<String, Object> serializedParams = new HashMap<>();
                //拼接跳转 URI
                int position = 0;
                for (int i = 0; i < parameterAnnotations.length; i++) {
                    Annotation[] annotations = parameterAnnotations[i];
                    if (annotations == null || annotations.length == 0)
                        break;
                    Annotation annotation = annotations[0];
                    if (annotation instanceof UriParam) {
                        urlBuilder.append(position == 0 ? "?" : "&");
                        position++;
                        UriParam uriParam = (UriParam) annotation;
                        urlBuilder.append(uriParam.value()).append("=").append(args[i]);
                    } else if (annotation instanceof IntentExtrasParam) {
                        IntentExtrasParam intentExtrasParam = (IntentExtrasParam) annotation;
                        serializedParams.put(intentExtrasParam.value(), args[i]);
                    }
                }
                //执行Activity跳转操作
                performJump(urlBuilder.toString(), serializedParams);
                return null;
            }
        });
    }

/**
     * 执行Activity跳转操作
     *
     * @param routerUri Intent跳转URI
     */
    private void performJump(String routerUri, HashMap<String, Object> serializedParams) {

        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(routerUri));//使用隐式跳转
        Bundle bundle = new Bundle();
        for (Map.Entry<String, Object> entry : serializedParams.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            if (value instanceof Integer) {
                bundle.putInt(key, Integer.parseInt(value.toString()));
            } else if (value instanceof Long) {
                bundle.putLong(key, Long.parseLong(value.toString()));
            } else if (value instanceof Double) {
                bundle.putDouble(key, Double.parseDouble(value.toString()));
            } else if (value instanceof Short) {
                bundle.putShort(key, Short.parseShort(value.toString()));
            } else if (value instanceof Float) {
                bundle.putFloat(key, Float.parseFloat(value.toString()));
            } else if (value instanceof String) {
                bundle.putString(key, (String) value);
            } else if (value instanceof CharSequence) {
                bundle.putCharSequence(key, (CharSequence) value);
            } else if (value.getClass().isArray()) {
                if (int[].class.isInstance(value)) {
                    bundle.putIntArray(key, (int[]) value);
                } else if (long[].class.isInstance(value)) {
                    bundle.putLongArray(key, (long[]) value);
                } else if (double[].class.isInstance(value)) {
                    bundle.putDoubleArray(key, (double[]) value);
                } else if (short[].class.isInstance(value)) {
                    bundle.putShortArray(key, (short[]) value);
                } else if (float[].class.isInstance(value)) {
                    bundle.putFloatArray(key, (float[]) value);
                } else if (String[].class.isInstance(value)) {
                    bundle.putStringArray(key, (String[]) value);
                } else if (CharSequence[].class.isInstance(value)) {
                    bundle.putCharSequenceArray(key, (CharSequence[]) value);
                } else if (Parcelable[].class.isInstance(value)) {
                    bundle.putParcelableArray(key, (Parcelable[]) value);
                }
            } else if (value instanceof ArrayList && !((ArrayList) value).isEmpty()) {
                ArrayList list = (ArrayList) value;
                if (list.get(0) instanceof Integer) {
                    bundle.putIntegerArrayList(key, (ArrayList<Integer>) value);
                } else if (list.get(0) instanceof String) {
                    bundle.putStringArrayList(key, (ArrayList<String>) value);
                } else if (list.get(0) instanceof CharSequence) {
                    bundle.putCharSequenceArrayList(key, (ArrayList<CharSequence>) value);
                } else if (list.get(0) instanceof Parcelable) {
                    bundle.putParcelableArrayList(key, (ArrayList<? extends Parcelable>) value);
                }
            } else if (value instanceof Parcelable) {
                bundle.putParcelable(key, (Parcelable) value);
            } else if (value instanceof Serializable) {
                bundle.putSerializable(key, (Serializable) value);
            } else {
                throw new IllegalArgumentException("不支持的参数类型!");
            }
        }
        intent.putExtras(bundle);
        PackageManager packageManager = context.getPackageManager();
        List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
        if (!activities.isEmpty()) {
            context.startActivity(intent);
        }
    }

在使用 Router 来跳转的时候,首先需要定义一个 Interface(类似于 Retrofit 的使用方式):

 
    
1
2
3
4
5
6
7
 
    
public interface RouterService {
@FullUri( "router://com.baronzhang.android.router.FourthActivity")
void startUserActivity(@UriParam("cityName")
String cityName, @ IntentExtrasParam ("user") User user);
}

接下来我们就可以通过如下方式实现 Activity 的跳转传参了:

 
    
1
2
3
4
 
    
RouterService routerService = new Router( this).create(RouterService.class);
User user = new User( "张三", 17, 165, 88);
routerService.startUserActivity( "上海", user);

主要代码都在上面了,这里我们可以来分析下:

前提:代码最终使用的是Intent的Uri隐式跳转,所以需要为组件声明跳转链接。


定义注解,是为了给每一个跳转方法贴上标签包括跳转Activity的链接和需要传递的一些参数信息。然后在代理方法中通过反射获取这些信息,包括路径,传递的值等等,


FullUri fullUri = method.getAnnotation(FullUri.class);
                StringBuilder urlBuilder = new StringBuilder();
                urlBuilder.append(fullUri.value());

最后拼接成一条完整的Intent请求。

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(routerUri));

完整代码可以参考点这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值