Android组件化架构实现(一)

什么是组件化?
组件化开发就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。
为什么要用组件化开发?
对于大型app,业务逻辑随着app迭代越来越复杂,代码量越来越多就会存在如下问题。
1.代码耦合度严重,改一个细微的地方有可能牵扯很多文件,牵一发而动全身。
2.修改bug编译调试非常慢,对于大项目来说rebuild一次app需要5分 10分的很常见,假如只修改一个很小的问题编译却要等上10分钟效率太低。
3.功能动态插拔,例如个推,Umeng都是单独的module,如果想用其他推送平台来代替个推,只需要将个推的module删除那么所有个推的依赖库、资源文件、代码、权限等都一并删除了,不会留在项目中一些垃圾资源。
4.在多团队开发的时候代码的冲突也会非常严重,每次提交都需要解决代码冲突和沟通占用了大量的时间,影响开发效率。
5.修改工程就需要整体的回归测试,由于代码耦合度严重,修改一个小bug有可能牵扯很多代码,所以每次都需要回归测试。
基于以上问题,组件化是最好的选择。(组件化不是所有项目都适合,还是要根据公司自身来选用)

本篇文章是组件化实现的第一步。抽取公共组件、开源项目。

Android组件化架构实现(一)_第1张图片
Library.png

一、抽取build.gradle中的dependencies

将每个module中dependencies下面依赖的aar抽取出来放在公共的位置;
这样做的好处是需要依赖的地方只要写公共的标志就可以实现依赖;
如果后期需要修改依赖或者每次升级版本都比较方便只需要更改公共位置标志的Value就可以;
由于aar可以多次依赖所以不用担心重复依赖的问题。
如下图文讲解:
在工程中增加dependencies.gradle文件

Android组件化架构实现(一)_第2张图片
增加dependencies.gradle文件.png

在project的build.gradle中最顶端增加如下代码,将这个文件引入

//引入全局的设置
apply from: 'dependencies.gradle'
                ......

这个文件用来定义applicationIdversionCodeversionNamecompileSdktargetSdkminSdkbuildTools、aar依赖等。
如下代码

ext.versions = [
        applicationId       : "com.component.demo",

        code                : 1,
        name                : '1.0.0',

        minSdk              : 11,
        targetSdk           : 25,
        compileSdk          : 25,
        buildTools          : '25.0.2',

        supportLibs         : '25.3.1',
        supportConstrain    : '1.0.2',

        fastJson            : '1.2.33',
        universalImageLoader: '1.9.5',
]

ext.libraries = [

        supportAnnotations : "com.android.support:support-annotations:$versions.supportLibs",
        supportAppCompat   : "com.android.support:appcompat-v7:$versions.supportLibs",
        supportDesign      : "com.android.support:design:$versions.supportLibs",
        supportRecyclerView: "com.android.support:recyclerview-v7:$versions.supportLibs",
        supportCardView    : "com.android.support:cardview-v7:$versions.supportLibs",
        supportFragment    : "com.android.support:support-fragment:$versions.supportLibs",
        constraintLayout   : "com.android.support.constraint:constraint-layout:$versions.supportConstrain",

        fastJson           : "com.alibaba:fastjson:$versions.fastJson",
        imageLoader        : "com.nostra13.universalimageloader:universal-image-loader:$versions.universalImageLoader",
]

分别在每个module(很可能是多个module)的build.gradle文件中依赖dependencies.gradle文件中的versionslibraries,如下代码:

apply plugin: 'com.android.application'

android {
//    compileSdkVersion 25
//    buildToolsVersion "25.0.2"

    compileSdkVersion versions.compileSdk
    buildToolsVersion versions.buildTools

    defaultConfig {
//        applicationId "com.component.demo"
//        minSdkVersion 11
//        targetSdkVersion 25
//        versionCode 1
//        versionName "1.0"

        applicationId versions.applicationId
        minSdkVersion versions.minSdk
        targetSdkVersion versions.targetSdk
        versionCode versions.code
        versionName versions.name
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

//    compile 'com.android.support:appcompat-v7:25.3.1'
//    compile 'com.android.support.constraint:constraint-layout:1.0.2'

    compile libraries.supportAppCompat
    compile libraries.constraintLayout

    compile libraries.fastJson
    compile libraries.constraintLayout
    compile libraries.imageLoader
}

假如需要升级imageLoader的版本,直接在dependencies.gradle修改版本号,这样所有依赖libraries.imageLoader的module都得到了修改,不用每个module都去修改。
如下代码:

ext.versions = [
       ...
       //修改这里
        universalImageLoader: '1.9.5',
]
ext.libraries = [
        ...
        imageLoader        : "com.nostra13.universalimageloader:universal-image-loader:$versions.universalImageLoader",
]

二、Library分模块

library根据功能、来源分为如下模块

open-source-library :

开源库,或者自己封装的库都是aar和jar文件,不允许有代码的存在。
目的是让依赖的库都保存在一起,方便以后升级修改。
如下图:


Android组件化架构实现(一)_第3张图片
open-source-library
open-source-code :

开源代码,或者自己写的开源代码,一般不需要修改或者少量修改,可以依赖aar或者jar。
每个开源代码都是一个module,他们之前也存在相互依赖关系,
如下图:


Android组件化架构实现(一)_第4张图片
open-source-code.png
third-party-plugin :

第三方插件,个推、Umeng等都是第三方插件。
如果app需要个推,直接添加依赖,那么就可以引入个推,如果想更换推送平台,直接删除个推依赖,那么个推相关的代码、资源、权限等都会被删除,不会对app产生垃圾文件。
如下图:


Android组件化架构实现(一)_第5张图片
third-party-plugin-getui.png
encapsulation-isolation-code :
1.封装:

对依赖库的封装,Utils工具类、SharedPrefercens封装、数据库的封装、统计代码的封装、网络库的封装等等。

2.隔离:

对开源库的隔离非常重要,由于开源库有可能停止维护,Android系统升级对开源库的影响,所以开发期间要求更换开源库是很常见的事。如果对开源库进行隔离,更换库只会影响隔离代码,如果不隔离那么开源库会分布在项目的每个角落,还会参与业务逻辑,这样改起来的成本会非常大,而且极其容易出问题。
下面就拿ImageLoader作为例子吧,
如果不做代码隔离,那么在使用的时候都会调用和引入如下代码,

import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;

ImageLoader.getInstance().displayImage(String uri, ImageView imageView, DisplayImageOptions options,
                ImageLoadingListener listener, ImageLoadingProgressListener progressListener);

像这样的代码,有可能会分布在很多文件中,因为加载图片是个常用的操作,如果我们想替换加载图片的库,那可就麻烦了需要修改N多个文件,这是绝对不允许的。
为了解决这个问题我们做了代码隔离(只是简单的展示隔离的原理,真正的隔离代码要比这个复杂的多),如下代码:


Android组件化架构实现(一)_第6张图片
ImageLoader隔离.png
//我们定义自己的图片加载接口
public interface ImageLoaderProxy {

    void initImageLoader(Application application);

    void displayImage(String uri, ImageView imageView,
                      ImageLoadingListener listener, ImageLoadingProgressListener progressListener);

}
//我们定义自己的图片加载监听
public interface ImageLoadingListener {

    void onLoadingStarted(String imageUri, View view);


    void onLoadingFailed(String imageUri, View view, String failReason);


    void onLoadingComplete(String imageUri, View view, Bitmap loadedImage);


    void onLoadingCancelled(String imageUri, View view);
}
//我们定义自己的图片加载进度
public interface ImageLoadingProgressListener {

    void onProgressUpdate(String imageUri, View view, int current, int total);
}
//用universalimageloader来实现ImageLoaderProxy
public class ImageLoaderUtil implements ImageLoaderProxy {

    private DisplayImageOptions mDefaultOptions = null;

    private static final ImageLoaderUtil IMAGE_LOADING = new ImageLoaderUtil();

    public static final ImageLoaderProxy getInstance(){
        return IMAGE_LOADING;
    }

    @Override
    public void initImageLoader(Application application) {
        if (ImageLoader.getInstance().isInited()) {
            ImageLoader.getInstance().destroy();
        }
        ImageLoaderConfiguration.Builder builder = new ImageLoaderConfiguration.Builder(application)
                .memoryCacheExtraOptions(480, 800)
                .threadPriority(Thread.NORM_PRIORITY - 2)
                .denyCacheImageMultipleSizesInMemory()
                .diskCacheFileNameGenerator(new Md5FileNameGenerator())
                .tasksProcessingOrder(QueueProcessingType.LIFO)
                .memoryCache(new LRULimitedMemoryCache(10 * 1024 * 1024))
                .diskCacheSize(50 * 1024 * 1024)
                .writeDebugLogs();
        ImageLoaderConfiguration config = builder.build();

        ImageLoader.getInstance().init(config);

        mDefaultOptions = new DisplayImageOptions.Builder()
//                .showImageOnLoading(holderId)
//                .showImageForEmptyUri(holderId)
//                .showImageOnFail(holderId)
                .delayBeforeLoading(100)
                .bitmapConfig(Bitmap.Config.RGB_565)
                .considerExifParams(true)
                .cacheInMemory(false)
                .cacheOnDisk(true)
                .build();
    }

    @Override
    public void displayImage(
            String uri,
            ImageView imageView,
            ImageLoadingListener listener,
            ImageLoadingProgressListener progressListener) {




        ImageLoader.getInstance().displayImage(
                uri,
                imageView,
                mDefaultOptions,
                new ImageLoadingImpl(listener),
                new ImageLoadingProgressImpl(progressListener));
    }

    class ImageLoadingImpl implements com.nostra13.universalimageloader.core.listener.ImageLoadingListener {

        private ImageLoadingListener mImageLoadingListener;

        public ImageLoadingImpl(ImageLoadingListener imageLoadingListener) {
            mImageLoadingListener = imageLoadingListener;
        }

        @Override
        public void onLoadingStarted(String imageUri, View view) {
            if (null != mImageLoadingListener) {
                mImageLoadingListener.onLoadingStarted(imageUri, view);
            }
        }

        @Override
        public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
            if (null != mImageLoadingListener) {
                mImageLoadingListener.onLoadingFailed(imageUri, view, failReason.toString());
            }
        }

        @Override
        public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
            if (null != mImageLoadingListener) {
                mImageLoadingListener.onLoadingComplete(imageUri, view, loadedImage);
            }
        }

        @Override
        public void onLoadingCancelled(String imageUri, View view) {
            if (null != mImageLoadingListener) {
                mImageLoadingListener.onLoadingCancelled(imageUri, view);
            }
        }
    }

    class ImageLoadingProgressImpl implements com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener {

        private ImageLoadingProgressListener mImageLoadingProgressListener;

        public ImageLoadingProgressImpl(ImageLoadingProgressListener imageLoadingProgressListener) {
            mImageLoadingProgressListener = imageLoadingProgressListener;
        }

        @Override
        public void onProgressUpdate(String imageUri, View view, int current, int total) {
            if (null != mImageLoadingProgressListener) {
                mImageLoadingProgressListener.onProgressUpdate(imageUri, view, current, total);
            }
        }
    }

}
//使用自己定义的ImageLoaderProxy,使用的地方和universalimageloader没有任何关系都是工程中的代码,真正的做到了代码隔离,假如想换个图片加载库,只需要更改实现ImageLoaderProxy的代码就可以了
import com.component.demo.imageloader.ImageLoaderUtil;
import com.component.demo.imageloader.ImageLoadingListener;
import com.component.demo.imageloader.ImageLoadingProgressListener;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ImageLoaderUtil.getInstance().initImageLoader(getApplication());

        ImageLoaderUtil.getInstance().displayImage("", new ImageView(this), new ImageLoadingListener() {
                    @Override
                    public void onLoadingStarted(String imageUri, View view) {

                    }

                    @Override
                    public void onLoadingFailed(String imageUri, View view, String failReason) {

                    }

                    @Override
                    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {

                    }

                    @Override
                    public void onLoadingCancelled(String imageUri, View view) {

                    }
                },
                new ImageLoadingProgressListener() {
                    @Override
                    public void onProgressUpdate(String imageUri, View view, int current, int total) {

                    }
                });
    }
}

以上代码就是隔离图片加载框架的代码,在MainActivity中使用的地方和universalimageloader没有任何关系都是工程中的代码,真正的做到了代码隔离,假如想换个图片加载库,只需要更改实现ImageLoaderProxy的代码就可以了。

3.自定义控件:

自定义导航栏,自定义TextView,自定义开关按钮等

4.各种基类代码:

BaseActivityBaseApplicationBaseFragmentBaseAdapter,BaseLayout等。
基类代码处理整个项目公用的配置,如下BaseActivity代码:
1.网络对话框的统一设置
2.Unbinder的统一设置
3.EventBus的统一设置
这里只是简单的展示了Base的作用,实际上根据业务需求会更加复杂

public class BaseActivity extends AppCompatActivity {

    private Unbinder unbinder;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        EventBus.getDefault().register(this);

    }

    @Override
    public void onContentChanged() {
        super.onContentChanged();

        unbinder = ButterKnife.bind(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();

    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        EventBus.getDefault().unregister(this);

        if (unbinder != null) {
            unbinder.unbind();
            unbinder = null;
        }
    }

    protected void showLoadingView() {
//        TODO:显示网络加载对话框
    }

    protected void dismissLoadingView() {
//        TODO:隐藏网络加载对话框
    }
}
common-dependencies-library :

公共依赖库,所有module都可以依赖这个lib;
1.ARouter路由配置,全局的PathActivity传递数据的Key
2.多个module需要的JavaBean,例如UserInfo
3.多个module需要的静态常量

坑:

1.App主项目依赖本地aar库。依赖路径如下图:

Android组件化架构实现(一)_第7张图片
App.png

build.gradle 配置

//encapsulation-isolation-code build.gradle
repositories { flatDir { dirs 'libs' } }

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')

    compile(name: 'pickerview-release', ext: 'aar')
    compile(name: 'mupdf-release', ext: 'aar')
    compile(name: 'CropImage-release', ext: 'aar')
}

//app build.gradle
repositories {
    flatDir { dirs 'libs', '../encapsulation-isolation-code/libs' }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')

    compile project(':encapsulation-isolation-code')
}
2.aar混淆问题

对于apply plugin: 'com.android.library'aar库module混淆的配置用consumerProguardFiles 'proguard-rules.pro'来指定一个混淆文件,这个指定的混淆文件会打包在aar文件中,当主app引入这个aar库这个混淆文件会影响整个工程。

3.annotationProcessor

annotationProcessor会扫描代码对注释进行处理,所以每个module都需要单独设置

4.aar版本冲突报错

All com.android.support libraries must use the exact same version specification (mixing versions can lead to runtime crashes). Found versions 25.3.0, 25.2.0, 24.2.0. Examples include com.android.support:support-compat:25.3.0 and com.android.support:support-core-ui:25.2.0 less... (⌘F1)
There are some combinations of libraries, or tools and libraries, that are incompatible, or can lead to bugs. One such incompatibility is compiling with a version of the Android support libraries that is not the latest version (or in particular, a version lower than your targetSdkVersion.)
项目里(依赖或间接依赖)中包含不同版本包容易编译错误,需统一,support v7 appcompat等包依赖support-v4 ,解决办法添加 compile ‘com.android.support:support-v4:25.3.0’

你可能感兴趣的:(Android组件化架构实现(一))