欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 项目组件化

程序员文章站 2024-03-20 21:48:52
...

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


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

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

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

Android 项目组件化

Android 项目组件化

Android 项目组件化

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


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

Android 项目组件化

Android 项目组件化

Android 项目组件化

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

依赖包这会出现一个问题导致编译失败,不同组件可能会依赖了不同版本的库(比如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

Android 项目组件化

我们可以指定去掉某个依赖包的依赖包:

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