Android组件工程怎么优雅的在宿主工程源码调试

1. 背景

在Android开发的过程中,我们经常会遇到组件化的开发方式。
组件化的开发流程中,一般组件需要分别打成aar文件,然后集成到宿主工程中。
在开发的过程中,组件有点时候不具备全部的调试环境,这个时候就需要在宿主工程中进行调试,在这个过程中调试方式一般有如下两种:

  1. 每次修改组件都打成aar包,然后集成到宿主工程进行验证
  2. 在宿主工程中依赖组件工程源码进行调试

上面两种方式会有各种弊端和限制,如:

  1. 利用第一种方式,我们就需要每做一点修改进行验证的时候就要打一个aar文件,过程比较繁琐又低效
  2. 利用第二种方式,我们在有些场景下不能满足。比如我们修改的是一个基础组件,那么就可能会遇到下图中的情况

这个时候我们直接在宿主工程中源码依赖技术组件A,会发现修改源码不生效

2. 问题说明

为了验证这个问题,我们需要新建一个工程,里面包括宿主工程和组件工程,工程结构如下

其中app为宿主工程,mylibrary为业务组件工程,commonlibrary为基础组件工程

组件工程里面代码如下:

[-->CommonLibrary.java]
package com.example.commonlibrary;

/**
 * Author: xuweiyu
 * Date: 2021/10/16
 */
public class CommonLibrary {
    public static String className = "CommonLibrary.java";
}

commonlibrary工程打包成aar,上传至maven

[-->MyLibrary.java]
package com.example.mylibrary;

import com.example.commonlibrary.CommonLibrary;

/**
 * Author: xuweiyu
 * Date: 2021/10/16
 */
public class MyLibrary {
    public static String className = "MyLibrary.java";

    public static String commonClassName = CommonLibrary.className;
}

[-->依赖配置]
dependencies {
    implementation 'com.xwy.test:commonlibrary:1.0.0'
}

mylibrary工程会依赖commonlibraryaar包
mylibrary工程打包成aar,上传至maven
此时就完成了业务组件对基础组件的依赖

app宿主工程如下:

package com.example.testactivitytask

import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.example.mylibrary.MyLibrary
import com.example.testactivitytask.databinding.ActivitySecondBinding

class SecondActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivitySecondBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 引用 library工程下的 MyLibrary
        Log.e("xwy-->", "MyLibrary.className:${MyLibrary.className}")
        Log.e("xwy-->", "MyLibrary.commonClassName:${MyLibrary.commonClassName}")
    }
}

app工程中添加对mylibrary的依赖

dependencies {
    implementation 'com.xwy.test:mylibrary:1.0.0'
    ...
}

运行结果

2021-10-16 13:53:11.019 29302-29302/com.example.testactivitytask E/xwy-->: MyLibrary.className:MyLibrary.java
2021-10-16 13:53:11.019 29302-29302/com.example.testactivitytask E/xwy-->: MyLibrary.commonClassName:CommonLibrary.java

一切正常

调试模式场景

app工程中添加对commonlibrary工程的源码依赖

dependencies {
    implementation project(path: ':commonlibrary')
    // mylibrary aar 会传递依赖 commonlibrary aar
    implementation 'com.xwy.test:mylibrary:1.0.0'
    ...
}

修改commonlibrary工程代码

public class CommonLibrary {
    public static String className = "CommonLibrary.java-->update";
}

运行程序,会发现修改的源码没有生效。
执行gradlew build --refresh-dependencies会显示如下的错误

Type com.example.commonlibrary.BuildConfig is defined multiple times:
/Users/xuweiyu/Work/TestActivityTask/commonlibrary/build/
.transforms/5da046d126b3df77c21768d16a3e3ca0/classes/classes.dex,
/Users/xuweiyu/Work/TestActivityTask/app/build/intermediates/external_libs_dex
/release/mergeExtDexRelease/classes.dex

解释一下这个错误,错误原因为dex文件合并的时候发现有多个com.example.commonlibrary.BuildConfig文件。
原因为我们通过aar依赖和源码依赖,引入了两个相同文件。

3. 解决问题

既然问题出现的原因是引入了两份相同的代码,那么我们在编译的时候去除掉aar依赖不就可以了,一个被大众所熟知的方式就是在引入mylibrary的时候去掉对commonlibrary的依赖,那么我们可以这样做, 在app工程依赖mylibrary的时候采用如下方式:

dependencies {
    implementation project(path: ':commonlibrary')
    // mylibrary aar 会传递依赖 commonlibrary aar, 利用exclude去除依赖
    implementation ('com.xwy.test:mylibrary:1.0.0'){
        transitive = true
        exclude group: 'com.xwy.test', module: 'commonlibrary'
    }
    ...
}

运行结果

2021-10-16 15:16:57.194 31535-31535/com.example.testactivitytask E/xwy-->: MyLibrary.className:MyLibrary.java
2021-10-16 15:16:57.194 31535-31535/com.example.testactivitytask E/xwy-->: MyLibrary.commonClassName:CommonLibrary.java-->update

可以看到修改的代码已经生效,问题可以解决。

弊端

  1. 配置文件修改侵入性高,需要修改依赖配置
  2. 如果有很多个业务组件都依赖了该基础组件,那么每一个业务组件都需要配置排除该基础组件的依赖

4. 终极解决方案

我们可以配置全局项目替换,一个不被大众所熟知的方式。 可以进行如下配置

configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute module("com.xwy.test:commonlibrary") using project(":commonlibrary")
    }
}

dependencies {
    implementation('com.xwy.test:mylibrary:1.0.0')
    ···
}

关于gradle项目替换的语法说明,大家可以参考官方文档
docs.gradle.org/current/use…

Using dependency substitution rules
Dependency substitution rules work similarly to dependency resolve rules. In fact, many capabilities of dependency resolve rules can be implemented with dependency substitution rules. They allow project and module dependencies to be transparently substituted with specified replacements. Unlike dependency resolve rules, dependency substitution rules allow project and module dependencies to be substituted interchangeably.

Adding a dependency substitution rule to a configuration changes the timing of when that configuration is resolved. Instead of being resolved on first use, the configuration is instead resolved when the task graph is being constructed. This can have unexpected consequences if the configuration is being further modified during task execution, or if the configuration relies on modules that are published during execution of another task.

To explain:

A Configuration can be declared as an input to any Task, and that configuration can include project dependencies when it is resolved.

If a project dependency is an input to a Task (via a configuration), then tasks to build the project artifacts must be added to the task dependencies.

In order to determine the project dependencies that are inputs to a task, Gradle needs to resolve the Configuration inputs.

Because the Gradle task graph is fixed once task execution has commenced, Gradle needs to perform this resolution prior to executing any tasks.

In the absence of dependency substitution rules, Gradle knows that an external module dependency will never transitively reference a project dependency. This makes it easy to determine the full set of project dependencies for a configuration through simple graph traversal. With this functionality, Gradle can no longer make this assumption, and must perform a full resolve in order to determine the project dependencies.

Substituting an external module dependency with a project dependency
One use case for dependency substitution is to use a locally developed version of a module in place of one that is downloaded from an external repository. This could be useful for testing a local, patched version of a dependency.

The module to be replaced can be declared with or without a version specified.

中文翻译为

使用依赖替换规则
依赖替换规则的工作方式与依赖解析规则类似。事实上,依赖解析规则的很多能力都可以通过依赖替换规则来实现。它们允许使用指定的替换透明地替换项目和模块依赖项。与依赖解析规则不同,依赖替换规则允许项目和模块依赖被互换替换。

向配置添加依赖项替换规则会更改解析该配置的时间。 不是在第一次使用时解析,而是在构建任务图时解析配置。如果在任务执行期间进一步修改配置,或者如果配置依赖于在另一个任务执行期间发布的模块,这可能会产生意想不到的后果。

解释:

一个Configuration可以声明为任何任务的输入,并且该配置可以在解析时包含项目依赖项。

如果项目依赖项是任务的输入(通过配置),则必须将构建项目工件的任务添加到任务依赖项中。

为了确定作为任务输入的项目依赖项,Gradle 需要解析Configuration输入。

因为一旦任务开始执行,Gradle 任务图就固定了,所以 Gradle 需要在执行任何任务之前执行此解析。

在没有依赖替换规则的情况下,Gradle 知道外部模块依赖永远不会传递引用项目依赖。这使得通过简单的图形遍历确定配置的完整项目依赖项变得容易。有了这个功能,Gradle 就不能再做这个假设了,必须执行一个完整的解析来确定项目的依赖关系。

用项目依赖替换外部模块依赖
依赖替换的一个用例是使用本地开发的模块版本代替从外部存储库下载的模块。这对于测试依赖项的本地修补版本可能很有用。

要替换的模块可以在指定或不指定版本的情况下声明。


至此,我们就可以很方便的在宿主工程中调试我们的组件工程啦,不需要修改宿主工程中`dependencies`原有的依赖结
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值