Android 项目组件化
随着App越来越大,越来越复杂,我们会面临一些问题:团队多人开发协作不顺畅;项目越来越大,编译运行越来越慢甚至超过十分钟;渠道特殊要求版本维护花费大量时间精力;
组件化能解决以上所有问题.
组件化:对App做拆分,按照业务拆分成多个子模块,之间完全解耦,通过打包编译流程控制App功能;
(组件化还有个孪生兄弟,插件化,两者的区别有一个很形象的图)
两者的核心都是各个模块都完全独立,也就是整体可以缺了任何一个模块运行,任何一个模块也可以脱离整体独立运行.区别在于组件化是编译打包时添加删除,插件化可以再运行时动态添加删除模块.这么一看,还搞毛的组件化,直接插件化啊?然而插件化目前还并没有一个完全成熟完美的技术,套用任何一个插件化解决方案都存在大量的工作量和风险.所以,稳一点,还是先从组件化去重构代码.
开始组件化拆分,比如拆成这样子:
这样拆分很容易理解,积分墙,分享等功能模块都独立自成一个模块,可以选择性得集成到主项目中去,每个组件都依赖各自需要的依赖包,比如网络库,图片库...
依赖包这会出现一个问题导致编译失败,不同组件可能会依赖了不同版本的库(比如google的support包),面对错综复杂的依赖,靠脑子逻辑是很难理清楚,我们需要
列出项目的依赖树.
打开android studio的命令行终端,输入:gradlew app:dependencies --configuration compile >dependencies.txt
运行后,项目编译的依赖书就会在项目根目录的dependencies.txt文件中.形如:
Incremental java compilation is an incubating feature.
:app:dependencies
------------------------------------------------------------
Project :app
------------------------------------------------------------
compile - Classpath for compiling the main sources.
+--- project :common
| +--- com.squareup.okhttp:okhttp:2.4.0
| | \--- com.squareup.okio:okio:1.4.0
| +--- com.google.code.gson:gson:2.3.1
| +--- io.reactivex:rxandroid:1.2.1
| | \--- io.reactivex:rxjava:1.1.6
| +--- io.reactivex:rxjava:1.1.6
| +--- com.umeng.analytics:analytics:latest.integration -> 6.1.2
| +--- com.android.support:appcompat-v7:24.+ -> 24.2.1
| | +--- com.android.support:support-v4:24.2.1 -> 25.2.0
| | | +--- com.android.support:support-compat:25.2.0
| | | | \--- com.android.support:support-annotations:25.2.0
| | | +--- com.android.support:support-media-compat:25.2.0
| | | | +--- com.android.support:support-annotations:25.2.0
| | | | \--- com.android.support:support-compat:25.2.0 (*)
| | | +--- com.android.support:support-core-utils:25.2.0
| | | | +--- com.android.support:support-annotations:25.2.0
| | | | \--- com.android.support:support-compat:25.2.0 (*)
| | | +--- com.android.support:support-core-ui:25.2.0
| | | | +--- com.android.support:support-annotations:25.2.0
| | | | \--- com.android.support:support-compat:25.2.0 (*)
| | | \--- com.android.support:support-fragment:25.2.0
| | | +--- com.android.support:support-compat:25.2.0 (*)
| | | +--- com.android.support:support-media-compat:25.2.0 (*)
| | | +--- com.android.support:support-core-ui:25.2.0 (*)
| | | \--- com.android.support:support-core-utils:25.2.0 (*)
| | +--- com.android.support:support-vector-drawable:24.2.1
| | | \--- com.android.support:support-compat:24.2.1 -> 25.2.0 (*)
| | \--- com.android.support:animated-vector-drawable:24.2.1
| | \--- com.android.support:support-vector-drawable:24.2.1 (*)
| \--- com.alibaba:arouter-api:1.2.4
| +--- com.alibaba:arouter-annotation:1.0.4
| \--- com.android.support:support-v4:25.2.0 (*)
+--- project :subprocess
+--- project :qrcode
| \--- com.android.support:appcompat-v7:24.+ -> 24.2.1 (*)
+--- project :image_zoom_drag_vp
| +--- com.android.support:appcompat-v7:24.+ -> 24.2.1 (*)
| \--- project :imgloader
| +--- com.android.support:appcompat-v7:24.+ -> 24.2.1 (*)
| +--- project :common (*)
| +--- project :qrcode (*)
| \--- com.squareup.okhttp:okhttp:2.4.0 (*)
+--- project :imgloader (*)
+--- project :hook
| \--- project :common (*)
+--- project :td
+--- project :social_lib
| +--- project :common (*)
| +--- project :imgloader (*)
| \--- com.android.support:support-annotations:24.2.1 -> 25.2.0
+--- com.android.support:multidex:1.0.1
+--- com.android.support:recyclerview-v7:24.2.1
| +--- com.android.support:support-annotations:24.2.1 -> 25.2.0
| +--- com.android.support:support-compat:24.2.1 -> 25.2.0 (*)
| \--- com.android.support:support-core-ui:24.2.1 -> 25.2.0 (*)
+--- com.jakewharton:butterknife:8.4.+ -> 8.4.0
| +--- com.jakewharton:butterknife-annotations:8.4.0
| | \--- com.android.support:support-annotations:24.1.0 -> 25.2.0
| \--- com.android.support:support-annotations:24.1.0 -> 25.2.0
+--- com.android.support:support-v4:24.+ -> 25.2.0 (*)
+--- com.umeng.analytics:analytics:6.1.2
+--- com.squareup.okhttp:okhttp:2.4.0 (*)
+--- com.alibaba:arouter-annotation:1.0.4
+--- com.alibaba:arouter-api:1.2.4 (*)
+--- com.google.code.gson:gson:2.3.1
+--- io.reactivex:rxjava:1.1.6
\--- io.reactivex:rxandroid:1.2.1 (*)
(*) - dependencies omitted (listed previously)
BUILD SUCCESSFUL
Total time: 9.932 secs
+, -, | 和 \ 都不用在意,没有其他意思,只是花出树的形状,
重点是(*) 和 ->;
(*) 代表已经有重复的依赖在了,
-> 代表在重复的依赖中,胜出的版本(依赖同一个包,不同的版本).
比如上述分析文件中显示,arouter中依赖的supportv4包是25.2.0的,导致了所有其他v4包的版本都从24.2.1变成了25.2.0
我们可以指定去掉某个依赖包的依赖包:
compile('com.alibaba:arouter-api:1.2.4') {
exclude module: 'support-v4'
}
另外一个问题,主项目与组件之间如何调用,如果直接导包引用类肯定是不行的,解耦的目标就是要做到组件间的完全隔离,直接引用类的话就不是我们要的解耦程度.
解决这个问题有蛮多种方法:
方法一:反射.
使用反射是万能的,形如:
try {
Class<?> aClass = Class.forName("com.ddfun.social_lib.wxutils.WXUtil");
Method method = aClass.getMethod("shareUrlType",Activity.class, int.class, String.class, String.class, String.class);
method.invoke(null,activity,1,share_msg,null,"WebViewUtil");
} catch (Exception e) {
}
import android.content.Context;
import android.content.Intent;
/**
* Created by Ace on 2017/11/14.
*/
public class ReflectNavigation {
public static void navigation(Context context, final String destination, final Intent intent){
if(context == null || destination == null || intent == null){
throw new NullPointerException("context == null || destination == null || intent == null");
}
Class<?> clazz = null;
try {
clazz = Class.forName(destination);
if(clazz != null){
intent.setClass(context, clazz);
context.startActivity(intent);
}
}catch (ClassNotFoundException e){
e.printStackTrace();
}
}
public static Intent getNavigationIntent(Context context, final String destination){
if(context == null || destination == null ){
throw new NullPointerException("context == null || destination == null ");
}
Class<?> clazz = null;
try {
clazz = Class.forName(destination);
if(clazz != null){
Intent intent = new Intent();
intent.setClass(context, clazz);
return intent;
}
}catch (ClassNotFoundException e){
e.printStackTrace();
}
return null;
}
}
Intent navigationIntent = ReflectNavigation.getNavigationIntent(this, "com.dd.third_party_task_sdks.activities.BDMSSPActivity");
但在Android环境下使用反射得考虑到代码混淆得问题,使用到的类,方法切忌配置了防混淆,
##reflect start
#-keep class com.ddfun.social_lib.wxutils.TencentUtil{
# public static void shareUrlType;
#}
-keep class com.tencent.**{*;}
##reflect end
对症配置可以,但是太繁琐,安利个5秒配置完的东东:
-dontskipnonpubliclibraryclassmembers
-printconfiguration
-keep,allowobfuscation @interface android.support.annotation.Keep
-keep @android.support.annotation.Keep class *
-keepclassmembers class * {
@android.support.annotation.Keep *;
}
混淆配置文件中添加这个,然后在需要防混淆的地方注释@Keep就没事了.
方法二: 使用设计模式(代理模式).
抽取公共方法为接口,这些接口可以自成一个依赖包,这样就能暴露自己的方法.这个方法跟系统的各种service类似,接口+实现的C/S结构.
方法三:阿里的ARouter框架.
https://github.com/alibaba/ARouter
ARouter框架可以很方便得统一管理页面路由,一般正常按文档来做没问题,不过组件化情况下使用还是踩到了点坑.
1.路由路径 groupName不能一样,例如@Route(path = "/app/MyWebview") 一级路径app是groupName.
2.不同集成版本,例如A版本集成所有组件,B版本没有社交分享组件,用户安装了A版本再更新装B版本,应用就会崩溃,无法使用.
原因是ARouter 初始化的时候扫描了所有ARouter产生的类,扫描完后会缓存,在versionCode或者versionName都没有变化
的时候,不会再去重新扫一遍,所以,不同集成版本,versionCode或者versionName一定要有变更.
如果需要单独运行某一个组件,
if(isComponent.toBoolean()){
apply plugin: 'com.android.application'}else{
apply plugin: 'com.android.library'}
再提供一个manifest文件
sourceSets {
main { if (isComponent.toBoolean()) {
manifest.srcFile 'src/main/component/AndroidManifest.xml'
java.srcDirs = ['src/main/java','src/main/component/java']
res.srcDirs = ['src/main/res','src/main/component/res']
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
转载请注明出处:http://blog.csdn.net/lazyer_dog/article/details/78558422
推荐阅读
-
Android 项目组件化
-
使用Maven构建Android项目 博客分类: Android android-maven-pluginjarsignerMaven archetypeproguard
-
基于业务模块组件的系统架构 博客分类: 架构乱弹 OSGI设计模式项目管理框架数据结构
-
用“看板图”实现敏捷项目的可视化 博客分类: Software Process 敏捷 项目管理 agile
-
适用于入门学习的综合性Android项目---获取全国各地实时天气预报
-
Java 开源企业信息化建设平台 | 如何隐藏系统内置应用? 博客分类: O2OA二次开发手册 O2OA开源项目企业信息化协同办公企业OA
-
开源视频会议bigbluebutton开发(1)——初始化安装以及配置 博客分类: 开源项目 ActiveMQAsteriskNginxTomcat 视频会议
-
WEB前端:vuejs全家桶(35):模块化开发:vue-router模块化、自定义组件添加事件
-
Android六大官方构架组件的基础使用(Lifecycle,LiveData,ViewModel,Room,Paging,Navigation)
-
电商数仓-(项目经验之Flume组件,日志采集Flume配置,Flume的ETL和分类型拦截器)