Skip to content

模块化

将一个大项目拆分成互相独立的模块,降低各个部分之间的耦合性。 能大大提高代码复用能力,也能缕清项目的架构。便于团队协同开发。 模块化后,便于进行单模块调试。

对于常用功能,可抽出来作为基础模块。

重构工程,模块化思路和步骤

  • 新建模块
  • 移动枚举类和接口
  • 移动工具类
  • 减少全局变量,尽可能消除对Application的直接引用
  • 移动执行最小操作的类
  • 增加回调接口,增加通知方法,移动其他类
  • 设计对外统一访问管理的类

Android 蓝牙管理模块

Android中需要调用蓝牙设备。

一开始是在Service中管理蓝牙设备的连接和业务。随着业务代码的增长,蓝牙的连接部分日趋复杂和混乱。 整个Service的代码在变大。业务要求针对同一款蓝牙硬件设备开发出不同的App。各个App要实现的功能不同。 为避免大量的重复代码,需要将这个蓝牙设备管理模块化。

单例化一个BtDevice,相关信息都存储在单例中,可以直接调用。参照Google示例,在子线程中进行蓝牙连接。 BtDevice模块与Service解耦。添加回调接口,发送蓝牙设备发回的数据。 App中有多个activity,各个页面生命周期不同。app主工程中增设一个消息管理Service,专门用于接受BtService的数据, 然后再用别的方法发送出去(EventBus,Broadcast等)。

在Android Studio中新建一个module,把蓝牙管理相关的代码放进模块,调整好API。编译后会有aar文件。 新的工程只需要引入aar文件即可。

模块化后,代码层级和逻辑更清晰,耦合度下降,更易维护。

使用git submodule管理模块

git子模块功能允许往git管理的项目中添加另一个由git管理的工程。

使用Android Studio创建Android App工程并由git进行管理。工程里可以用git submodule添加子模块。

可以在as中新建模块,然后把整个模块复制出去,模块目录下git init,提交到服务器。 回到工程中,git submodule add 模块地址。添加后修改gradle即可。

问题与方法

xml中找不到module中的自定义view

  • Android Studio 2.2.3

模块的build和调用方的build的配置要一致,最好全部配置成一样的。

比如compileSdkVersion等等。然后重新make module和rebuild project

使用Android Studio打包module得到aar文件

Android App的模块可以用aar文件来表示。

在as的Gradle侧边栏直接运行:mylib-Tasks-build-assembleRelease

可以在module目录下project\mylib\build\outputs\aar找到release版的aar文件

可以在模块的Gradle中加入重命名的任务

android {
    //...
    libraryVariants.all { variant ->
        variant.outputs.each { output ->
            def outputFile = output.outputFile
            if (outputFile != null && outputFile.name.endsWith('.aar')) {
                def fileName = "${archivesBaseName}-${defaultConfig.versionName}-${variant.buildType.name}.aar"
                output.outputFile = new File(outputFile.parent, fileName)
            }
        }
    }
}

gradle 3以后,不能使用outputFile属性,报错:

Cannot set the value of read-only property 'outputFile' for object of type com.android.build.gradle.internal.api.LibraryVariantOutputImpl

修改gradle方法,添加一个getTime方法

// 获取当前时间
static def getTime() {
    String timeNow = new Date().format('YYYYMMdd-HHmmss')
    return timeNow
}

android {
    // ...
    libraryVariants.all { variant ->
        variant.outputs.all { output ->
            def outputFile = output.outputFile
            if (outputFile != null && outputFile.name.endsWith('.aar')) {
                outputFileName = "${archivesBaseName}-${defaultConfig.versionName}-${variant.buildType.name}_${getTime()}.aar"
            }
        }
    }
}

执行assembleRelease后得到aar文件,mylib-1.0.0.0-release_20180914-091348.aar

gradle重复引用

子模块mylib和主app工程中引用了相同的jar包。假设是commons-net-3.0.1.jar。 编译时报错Multiple dex files define Lxxx/xxx/xxx;

app gradle引用子模块时申明transitive false

    implementation (project(':mylib')) {
        transitive false
    }

弊端是,当app主工程中没有子模块需要的依赖包时,子模块会找不到依赖项而报错。

排除一些传递性依赖中的某个模块,此时便不能靠单纯的关闭依赖传递特性来解决了。

子模块中的FileProvider问题

  • gradle 3.1.4
  • compileSdkVersion 27

假设有子模块mylibapp模块。

子模块中要使用FileProvider,步骤如下:

  • 新建类继承android.support.v4.content.FileProvider
  • 编写path信息
  • 模块AndroidManifest.xml中声明provider
  • 定义能修改authorities的方法

新建类RustFileProvider,不复写其他方法。

import android.support.v4.content.FileProvider;

public class RustFileProvider extends FileProvider {

}

模块中新建文件res/xml/rust_provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="external_files"
        path="." />
</paths>

模块AndroidManifest.xml中声明provider

    <application>

        <provider
            android:name=".RustFileProvider"
            android:authorities="${applicationId}.file_provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/rust_provider_paths" />
        </provider>

    </application>

定义一个模块的配置文件

/**
 * 全局配置文件
 */
public class DFConfig {

    // 文件提供器名字前缀  要根据appID而改变
    private static String fileProviderAuthoritiesPrefix = "applicationId";

    public static String getFileProviderAuthoritiesPrefix() {
        return fileProviderAuthoritiesPrefix;
    }

    /**
     * @param fileProviderAuthoritiesPrefix 传入app的applicationId
     */
    public static void setFileProviderAuthoritiesPrefix(String fileProviderAuthoritiesPrefix) {
        DFConfig.fileProviderAuthoritiesPrefix = fileProviderAuthoritiesPrefix;
    }
}

app模块代码中要预先将自己的applicationId设置进去。

DFConfig.setFileProviderAuthoritiesPrefix(BuildConfig.APPLICATION_ID); // 设置fileProvider权限

在模块中的Activity调用分享功能。

    private void shareOneFile(File sFile, String title) {
        if (null != sFile && sFile.exists()) {
            String sPath = sFile.getPath().toLowerCase();
            Intent shareIntent = new Intent();
            shareIntent.setAction(Intent.ACTION_SEND);
            Uri uri = RustFileProvider.getUriForFile(this,
                    DFConfig.getFileProviderAuthoritiesPrefix() + ".file_provider", sFile);
            shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
            if (sPath.endsWith("jpg") || sPath.endsWith("jpeg")) {
                shareIntent.setType("image/jpeg");
            } else if (sPath.endsWith("mp4")) {
                shareIntent.setType("video/*");
            } else {
                shareIntent.setType("*/*");
            }
            startActivity(Intent.createChooser(shareIntent, title));
        }
    }