Android 项目组件化

随着App越来越大,越来越复杂,我们会面临一些问题:团队多人开发协作不顺畅;项目越来越大,编译运行越来越慢甚至超过十分钟;渠道特殊要求版本维护花费大量时间精力;

 

组件化能解决以上所有问题.

组件化:对App做拆分,按照业务拆分成多个子模块,之间完全解耦,通过打包编译流程控制App功能;

(组件化还有个孪生兄弟,插件化,两者的区别有一个很形象的图)

 

 

两者的核心都是各个模块都完全独立,也就是整体可以缺了任何一个模块运行,任何一个模块也可以脱离整体独立运行.区别在于组件化是编译打包时添加删除,插件化可以再运行时动态添加删除模块.这么一看,还搞毛的组件化,直接插件化啊?然而插件化目前还并没有一个完全成熟完美的技术,套用任何一个插件化解决方案都存在大量的工作量和风险.所以,稳一点,还是先从组件化去重构代码.

 

开始组件化拆分,比如拆成这样子:

 

 

这样拆分很容易理解,积分墙,分享等功能模块都独立自成一个模块,可以选择性得集成到主项目中去,每个组件都依赖各自需要的依赖包,比如网络库,图片库...

依赖包这会出现一个问题导致编译失败,不同组件可能会依赖了不同版本的库(比如google的support包),面对错综复杂的依赖,靠脑子逻辑是很难理清楚,我们需要

列出项目的依赖树.

打开android studio的命令行终端,输入:gradlew app:dependencies >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;
    }

}

 

 

Activity的反射实现:

 

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

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值