Android 开发之Gradle《四》Configuration

本文详细探讨了Gradle中Configuration的概念及其作用,解释了如何通过dependencies块添加不同类型的依赖。从源码层面解析了`implementation`等配置的实现原理,包括依赖解析、配置目标查找以及依赖添加的过程。通过示例展示了配置与依赖的添加方法,揭示了最终如何通过Configuration获取到构建产物Task。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

在往下面讲解前,我们先了解一下Configuration的一些知识。
按照官网所说:

Every dependency declared for a Gradle project applies to a specific scope. For example some dependencies should be used for compiling source code whereas others only need to be available at runtime. Gradle represents the scope of a dependency with the help of a Configuration. Every configuration can be identified by a unique name.

单纯的翻译过来之后也是云里雾里的,不知所云。按照我的理解说一下。

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.2.0'
    api 'org.gradle:gradle-plugins:0.9-rc-1'
}

implementation 与api 实际都是一个Configuration 类型的对象,这两个Configuration 对象是andorid 插件创建的。Configuration 可以下载关系各种依赖,同时andorid 插件 有规定了每一个Configuration 的作用范围。例如implementation 引入的依赖仅仅可以在当前project 使用,这也就是它的作用域。
在这里插入图片描述

一 查找方法

dependencies {
    implementation 'androidx.appcompat:appcompat:1.2.0'
}

下面我们跟着源码看看这段代码是怎么工作的。

DefaultProject.java

    @Override
    public void dependencies(Closure configureClosure) {
        ConfigureUtil.configure(configureClosure, getDependencies());
    }

调用了ConfigureUtil.configure 方法

    public static <T> T configure(@Nullable Closure configureClosure, T target) {
        if (configureClosure == null) {
            return target;
        }

        if (target instanceof Configurable) {
            ((Configurable) target).configure(configureClosure);
        } else {
        	//走这里
            configureTarget(configureClosure, target, new ConfigureDelegate(configureClosure, target));
        }

        return target;
    }

target 这里为DependencyHandler,而DependencyHandler没有实现Configurable接口,因此这里都后面的分支。

这里有一个关键的类ConfigureDelegate,这个类可以看成是DependencyHandler的代理类,其会拦截方法的路由,我们稍后分析这个类。

    private static <T> void configureTarget(Closure configureClosure, T target, ConfigureDelegate closureDelegate) {
    	//代码有删减。
        Closure withNewOwner = configureClosure.rehydrate(target, closureDelegate, configureClosure.getThisObject());
        //执行配置
        new ClosureBackedAction<T>(withNewOwner, Closure.OWNER_ONLY, false).execute(target);
    }

rehydrate 是配置Closure 内部调用的方法或属性的查找范围。
例如

implementation 'androidx.appcompat:appcompat:1.2.0'

implementation 会被当成一个方法,系统会在target, closureDelegate, configureClosure.getThisObject() 这三个对象里面查找是否存在这个方法,换句话说我们可以在Closure 闭包里面调用这三个对象的方法或者是属性。
查找顺序为Closure.OWNER_ONLY,表示优先在closureDelegate 里面查找。关于这一块大家可以查阅一下groovy 的Closure这一块,这里不在赘述。

我们这里着重分析ConfigureDelegate 类,这里这个类可以看成是DependencyHandler代理类。

public class ConfigureDelegate extends GroovyObjectSupport {
    protected final DynamicObject _owner;
    protected final DynamicObject _delegate;
    private final ThreadLocal<Boolean> _configuring = new ThreadLocal<Boolean>() {
        @Override
        protected Boolean initialValue() {
            return false;
        }
    };

    public ConfigureDelegate(Closure configureClosure, Object delegate) {
        _owner = DynamicObjectUtil.asDynamicObject(configureClosure.getOwner());
        //_delegate 为BeanDynamicObject(delegate)
        _delegate = DynamicObjectUtil.asDynamicObject(delegate);
    }
    @Override
    public Object invokeMethod(String name, Object paramsObj) {
        Object[] params = (Object[])paramsObj;

        boolean isAlreadyConfiguring = _configuring.get();
        _configuring.set(true);
        try {
          //通过_delegate的tryInvokeMethod 查找对应方法,注意这里是能找到一个方法的,我们放到后面介绍。
            DynamicInvokeResult result = _delegate.tryInvokeMethod(name, params);
            if (result.isFound()) {
                return result.getValue();
            }

            MissingMethodException failure = null;
            if (!isAlreadyConfiguring) {
                try {
                    result = _configure(name, params);
                } catch (MissingMethodException e) {
                    failure = e;
                }
                if (result.isFound()) {
                    return result.getValue();
                }
            }

            // try the owner
            result = _owner.tryInvokeMethod(name, params);
            if (result.isFound()) {
                return result.getValue();
            }

            if (failure != null) {
                throw failure;
            }
			//抛出异常
            throw _delegate.methodMissingException(name, params);
        } finally {
            _configuring.set(isAlreadyConfiguring);
        }
    }
  }

由于ConfigureDelegate 没有implementation方法,根据前一章介绍的groovy方法路由过程,此时会调用ConfigureDelegate 的invokeMethod 方法,第一个参数是调用的方法名字也就是implementation,后面的参数是方法的参数,这里就是字符串’androidx.appcompat:appcompat:1.2.0’。
这里调用_delegate.tryInvokeMethod(name, params) 方法查找对应的方法,_delegate为BeanDynamicObject。

        public DynamicInvokeResult invokeMethod(String name, Object... arguments) {
        	//通过MetaClass 在DependencyHandler 查找对应的方法
            MetaClass metaClass = getMetaClass();
            MetaMethod metaMethod = lookupMethod(metaClass, name, inferTypes(arguments));
            if (metaMethod != null) {
                return DynamicInvokeResult.found(metaMethod.doMethodInvoke(bean, arguments));
            }
            //如果没有找到对应的方法,判断是不是实现了MethodMixIn接口
            if (bean instanceof MethodMixIn) {
                // If implements MethodMixIn, do not attempt to locate opaque method, as this is expensive
                MethodMixIn methodMixIn = (MethodMixIn) bean;
                return methodMixIn.getAdditionalMethods().tryInvokeMethod(name, arguments);
            }

            if (!implementsMissing) {
                return DynamicInvokeResult.notFound();
            }

            return invokeOpaqueMethod(metaClass, name, arguments);
        }

BeanDynamicObject 这个了我们这里前一章已经介绍过了。

BeanDynamicObject 会首先通过MetaClass 在被代理类(此处就是DependencyHandler)里面查找对应的方法是否存在,由于DependencyHandler 并没有implementation这样一个方法,因此后面会判断DependencyHandler 是否实现了MethodMixIn接口,如果是,则调用getAdditionalMethods 的tryInvokeMethod 查找对应的方法。这里DependencyHandler的实际类型是DefaultDependencyHandler而DefaultDependencyHandler 实现了MethodMixIn接口
在这里插入图片描述
下面我们看看DefaultDependencyHandler 的

 private final DynamicMethods dynamicMethods;
    @Override
    public MethodAccess getAdditionalMethods() {
        return dynamicMethods;
    }

DynamicMethods 是DefaultDependencyHandler 的一个内部类。

        @Override
        public DynamicInvokeResult tryInvokeMethod(String name, Object... arguments) {
        	//判断参数的个数
            if (arguments.length == 0) {
                return DynamicInvokeResult.notFound();
            }
            //查找对应的Configuration 
            Configuration configuration = configurationContainer.findByName(name);
            if (configuration == null) {
                return DynamicInvokeResult.notFound();
            }
            List<?> normalizedArgs = CollectionUtils.flattenCollections(arguments);
            
            if (normalizedArgs.size() == 2 && normalizedArgs.get(1) instanceof Closure) {
            //执行doAdd 方法
                return DynamicInvokeResult.found(doAdd(configuration, normalizedArgs.get(0), (Closure) normalizedArgs.get(1)));
            } else if (normalizedArgs.size() == 1) {
                return DynamicInvokeResult.found(doAdd(configuration, normalizedArgs.get(0), null));
            } else {
                for (Object arg : normalizedArgs) {
                    doAdd(configuration, arg, null);
                }
                return DynamicInvokeResult.found();
            }
        }

DynamicMethods 会根据调用的方法名字查找是否存在对应的Configuration ,如果存在那么调用功能Configuration 的doAdd 方法。
因此表面山我们是想调用一个名为implementation 的方法,但是最终却是获取一个名为implementation 的Configuration 对象,然后调用Configuration 的doAdd 方法。

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

二、添加依赖

dependencies {
    implementation project(":testandroid")
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.2.0'
    }

我们可以看到,一般有三种三方库类型,
第一种是二进制文件,fileTree指向这个文件夹下的所有jar文件,
第二种是三方库的string坐标形式,
第三种是project的形式。

当查找到对应的 Configuration 之后就会调用doAdd 方法添加依赖。

    private Dependency doAdd(Configuration configuration, Object dependencyNotation, Closure configureClosure) {
        if (dependencyNotation instanceof Configuration) {
            Configuration other = (Configuration) dependencyNotation;
            if (!configurationContainer.contains(other)) {
                throw new UnsupportedOperationException("Currently you can only declare dependencies on configurations from the same project.");
            }
            configuration.extendsFrom(other);
            return null;
        }
		//
        Dependency dependency = create(dependencyNotation, configureClosure);
        configuration.getDependencies().add(dependency);
        return dependency;
    }

参数dependencyNotation 表示的就是implementation 后面的参数。
可以看到,这里会先判断dependencyNotation是否是Configuration,如果存在的话,就让当前的configuration继承dependencyNotation,也就是所有添加到dependencyNotation的依赖都会添加到configuration中。

这里可能有些朋友就会疑惑了,为啥还要对dependencyNotation判断呢?这个主要是为了处理嵌套的情况。比如implementation project(path: ‘:projectA’, configuration: ‘configA’)这种类型的引用。有兴趣可以看看上面的CollectionUtils.flattenCollections(arguments)方法。这里不考虑这种情形。
之后调用create 方法创建对应的依赖,我们看看创建的过程

2.2 创建依赖

    @Override
    public Dependency create(Object dependencyNotation, Closure configureClosure) {
        Dependency dependency = dependencyFactory.createDependency(dependencyNotation);
        return ConfigureUtil.configure(configureClosure, dependency);
    }

dependencyFactory 的实际类型是DefaultDependencyFactory

DefaultDependencyFactory.java

    public Dependency createDependency(Object dependencyNotation) {
        return dependencyNotationParser.parseNotation(dependencyNotation);
    }

可以看到最终是调用了dependencyNotationParser来parse这个dependencyNotation。而这里的dependencyNotationParser其实就是DependencyNotationParser这个类。

public class DependencyNotationParser {
    public static NotationParser<Object, Dependency> parser(Instantiator instantiator, DefaultProjectDependencyFactory dependencyFactory, ClassPathRegistry classPathRegistry, FileLookup fileLookup, RuntimeShadedJarFactory runtimeShadedJarFactory, CurrentGradleInstallation currentGradleInstallation) {
        return NotationParserBuilder
            .toType(Dependency.class)
            .fromCharSequence(new DependencyStringNotationConverter<DefaultExternalModuleDependency>(instantiator, DefaultExternalModuleDependency.class))
            .converter(new DependencyMapNotationConverter<DefaultExternalModuleDependency>(instantiator, DefaultExternalModuleDependency.class))
            .fromType(FileCollection.class, new DependencyFilesNotationConverter(instantiator))
            .fromType(Project.class, new DependencyProjectNotationConverter(dependencyFactory))
            .fromType(DependencyFactory.ClassPathNotation.class, new DependencyClassPathNotationConverter(instantiator, classPathRegistry, fileLookup.getFileResolver(), runtimeShadedJarFactory, currentGradleInstallation))
            .invalidNotationMessage("Comprehensive documentation on dependency notations is available in DSL reference for DependencyHandler type.")
            .toComposite();
    }
}

其中DependencyStringNotationConverter 用于 将 ‘androidx.appcompat:appcompat:1.2.0’ 这种形式的依赖转换为DefaultExternalModuleDependency。
DependencyFilesNotationConverter 用于将fileTree(dir: ‘libs’, include: [’*.jar’]) 这种形式的依赖转换为DefaultProjectDependency。
DependencyClassPathNotationConverter 用于将project(":testandroid") 这种形式的依赖转换为DefaultSelfResolvingDependency。

DependencyClassPathNotationConverter 用于将gradleApi 解析为对应的依赖。

dependencies {
    gradleApi()
    localGroovy()
}

我们这里以DependencyProjectNotationConverter 为例子

public class DependencyProjectNotationConverter implements NotationConverter<Project, ProjectDependency> {

    private final DefaultProjectDependencyFactory factory;

    public DependencyProjectNotationConverter(DefaultProjectDependencyFactory factory) {
        this.factory = factory;
    }
    public void convert(Project notation, NotationConvertResult<? super ProjectDependency> result) throws TypeConversionException {
        result.converted(factory.create(notation));
    }
}

这里调用工厂类的创建了一个ProjectDependency,关于这个工厂是怎么工作的这里不在继续深入。

2.3 依赖的实质

看到这里,大家可以已经理解了不同的依赖的解析方式,但是到了这里还没有完,因为依赖最终是依赖的产物,例如依赖一个android module 最终依赖的是DefaultProjectDependency 的产物,这个产物是衣aar 文件,依赖一个java module 依赖的也是DefaultProjectDependency 的产物,但是这个产物是jar文件。也就是最终的产物与具体的平台有关系。所有的产物都是通过Task 完成。

    public TaskDependencyInternal getBuildDependencies() {
        return new TaskDependencyImpl();
    }

getBuildDependencies 是Buildable 接口定义的方法。
Buildable接口。这个接口有啥用呢,用处大得很。先看官网介绍

在这里插入图片描述

Buildable表示很多Task对象生成的产物。它里面只有一个方法。

public interface Buildable {
    TaskDependency getBuildDependencies();

DefaultProjectDependency 的getBuildDependencies 返回的是 TaskDependencyImpl,通过这个TaskDependencyImpl我们可以获取到生产产物对应的Task集合
实验

dependencies {
        implementation project(":testandroid")
}

task testBuild{

}

project.configurations.getByName("implementation").getDependencies().forEach{
    it->
        if (it instanceof  ProjectDependency){
            def p=(ProjectDependency) it
            p.buildDependencies.getDependencies(tasks.findByName("testBuild")).forEach{
                taskName->
                    def  taskName2=(Task) taskName
                    println(" Task name is ${taskName2.toString()}")
                    println(" Task name  des is ${taskName2.description}")
                    taskName2.outputs.getFiles().forEach{fileItem->
                        //输出产物的名字
                        println(" File name  des is ${fileItem.getName()}")
                    }
            }
        }
}

执行 gradlew testBuild 命令
在这里插入图片描述

如上我们通过一个例子获取到了DefaultProjectDependency 生产产物的Task 以及最终的产物Aar 文件。

TDefaultProjectDependency 的getBuildDependencies 返回的是askDependencyImpl

    private class TaskDependencyImpl extends AbstractTaskDependency {
        @Override
        public void visitDependencies(TaskDependencyResolveContext context) {
            if (!buildProjectDependencies) {
                return;
            }
            projectAccessListener.beforeResolvingProjectDependency(dependencyProject);

            Configuration configuration = findProjectConfiguration();
            context.add(configuration);
            context.add(configuration.getAllArtifacts());
        }
    }
public abstract class AbstractTaskDependency implements TaskDependencyInternal {
    public Set<Task> getDependencies(Task task) {
        CachingTaskDependencyResolveContext context = new CachingTaskDependencyResolveContext();
        context.add(this);
        return context.resolve(task);
    }
}
    public void add(Object dependency) {
        Preconditions.checkNotNull(dependency);
        queue.add(dependency);
    }

CachingTaskDependencyResolveContext 可以看成是一个列表。这里将自己加入到列表内部,然后调用resolve 方法

    public Set<Task> resolve(Task task) {
        this.task = task;
        try {
            return doResolve();
        } catch (Exception e) {
            throw new TaskDependencyResolveException(String.format("Could not determine the dependencies of %s.", task), e);
        } finally {
            queue.clear();
            this.task = null;
        }
    }
    private Set<Task> doResolve() {
        walker.add(queue);
        return walker.findValues();
    }

注意此时此时queue 里面仅仅存在一个元素也即是TaskDependencyImpl

    public Set<T> findValues() {
        try {
            return doSearch();
        } finally {
            startNodes.clear();
        }
    }

TaskDependencyImpl 的getDependencies 中

    private Set<T> doSearch() {
        int componentCount = 0;
        Map<N, NodeDetails<N, T>> seenNodes = new HashMap<N, NodeDetails<N, T>>();
        Map<Integer, NodeDetails<N, T>> components = new HashMap<Integer, NodeDetails<N, T>>();
        LinkedList<N> queue = new LinkedList<N>(startNodes);

        while (!queue.isEmpty()) {
            N node = queue.getFirst();
            NodeDetails<N, T> details = seenNodes.get(node);
            if (details == null) {
                // Have not visited this node yet. Push its successors onto the queue in front of this node and visit
                // them

                details = new NodeDetails<N, T>(node, componentCount++);
                seenNodes.put(node, details);
                components.put(details.component, details);

                Set<T> cacheValues = cachedNodeValues.get(node);
                if (cacheValues != null) {
                    // Already visited this node
                    details.values = cacheValues;
                    details.finished = true;
                    queue.removeFirst();
                    continue;
                }
			    //这是关键
                graph.getNodeValues(node, details.values, details.successors);
                for (N connectedNode : details.successors) {
                    NodeDetails<N, T> connectedNodeDetails = seenNodes.get(connectedNode);
                    if (connectedNodeDetails == null) {
                        // Have not visited the successor node, so add to the queue for visiting
                        queue.add(0, connectedNode);
                    } else if (!connectedNodeDetails.finished) {
                        // Currently visiting the successor node - we're in a cycle
                        details.stronglyConnected = true;
                    }
                    // Else, already visited
                }
            } else {
                // Have visited all of this node's successors
                queue.removeFirst();

                if (cachedNodeValues.containsKey(node)) {
                    continue;
                }

                for (N connectedNode : details.successors) {
                    NodeDetails<N, T> connectedNodeDetails = seenNodes.get(connectedNode);
                    if (!connectedNodeDetails.finished) {
                        // part of a cycle : use the 'minimum' component as the root of the cycle
                        int minSeen = Math.min(details.minSeen, connectedNodeDetails.minSeen);
                        details.minSeen = minSeen;
                        connectedNodeDetails.minSeen = minSeen;
                        details.stronglyConnected = true;
                    }
                    details.values.addAll(connectedNodeDetails.values);
                    graph.getEdgeValues(node, connectedNode, details.values);
                }

                if (details.minSeen != details.component) {
                    // Part of a strongly connected component (ie cycle) - move values to root of the component
                    // The root is the first node of the component we encountered
                    NodeDetails<N, T> rootDetails = components.get(details.minSeen);
                    rootDetails.values.addAll(details.values);
                    details.values.clear();
                    rootDetails.componentMembers.addAll(details.componentMembers);
                } else {
                    // Not part of a strongly connected component or the root of a strongly connected component
                    for (NodeDetails<N, T> componentMember : details.componentMembers) {
                        cachedNodeValues.put(componentMember.node, details.values);
                        componentMember.finished = true;
                        components.remove(componentMember.component);
                    }
                    if (details.stronglyConnected) {
                        strongComponents.add(details);
                    }
                }
            }
        }

        Set<T> values = new LinkedHashSet<T>();
        for (N startNode : startNodes) {
            values.addAll(cachedNodeValues.get(startNode));
        }
        return values;
    }

在这里插入图片描述
doSearch 实际是对整个依赖树的处理,例如projectA 依赖projectB ,同时projectB 由依赖projectC。整个doSearch 的过程正是对这个树的遍历过程,queue 在遍历的过程中起到辅助作用,有不了解遍历算法的鹅同学可以百度一个树的非递归遍历算法。

    private class TaskGraphImpl implements DirectedGraph<Object, Task> {
        public void getNodeValues(Object node, Collection<? super Task> values, Collection<? super Object> connectedNodes) {
            if (node instanceof TaskDependencyContainer) {
                TaskDependencyContainer taskDependency = (TaskDependencyContainer) node;
                //这个queue 跟上面那个queue 是同一个对象。
                queue.clear();
                //添加元素
                taskDependency.visitDependencies(CachingTaskDependencyResolveContext.this);
                connectedNodes.addAll(queue);
            } else if (node instanceof Buildable) {
               //
                Buildable buildable = (Buildable) node;
                connectedNodes.add(buildable.getBuildDependencies());
            } else if (node instanceof TaskDependency) {
                TaskDependency dependency = (TaskDependency) node;
                values.addAll(dependency.getDependencies(task));
            } else if (node instanceof Task) {
                values.add((Task) node);
            } else if (node instanceof TaskReference) {
                TaskContainerInternal tasks = (TaskContainerInternal) task.getProject().getTasks();
                Task task = tasks.resolveTask((TaskReference) node);
                values.add(task);
            } else {
                throw new IllegalArgumentException(String.format("Cannot resolve object of unknown type %s to a Task.",
                        node.getClass().getSimpleName()));
            }
        }
    }

初始的时候queue 里面仅有一个元素就是TaskDependencyImpl,而TaskDependencyImpl 实现了TaskDependencyContainer 接口,因此会执行第一个分支。

        public void visitDependencies(TaskDependencyResolveContext context) {
            if (!buildProjectDependencies) {
                return;
            }
            projectAccessListener.beforeResolvingProjectDependency(dependencyProject);

            Configuration configuration = findProjectConfiguration();
             //添加Configuration 
            context.add(configuration);
            //getAllArtifacts 返回PublishArtifactSet,其实现了Buildable
            context.add(configuration.getAllArtifacts());
        }


以 implementation project(":testandroid") 为例子,findProjectConfiguration 返回的实际是testandroid 这个module 的名为default的Configuration 。每一个Configuration 都有自己的产物。Configuration 也实现了Buildable接口。
执行完visitDependencies 之后此时queue多了两个元素,这两个元素在后续也会被遍历,进而执行getNodeValues,遍历到Configuration 的时候会进入到第二个分支,configuration 的getBuildDependencies 返回的是
TaskDependency,后面接着循环的时候会接着掉用器getNodeValues 处理TaskDependency,
对于TaskDependency 会调用getDependencies,getDependencies 返回一系列的task ,而这些task 正是p.getBuildDependencies().getDependencies(task)的返回值,测试发现返回的Task 列表为空也就是此时还没有找到输出产物的Task

configuration.getAllArtifacts() 返回值PublishArtifactSet 实现了Buildable 接口,测试验证最终输出产物的Task 就包含在这里面。
在这里插入图片描述
在这里插入图片描述
到此我们知道了通过configuration.getAllArtifacts()可以获取到将源码编译打包成jar的Task,对于Java modue 而言这个Task为jar,
JavaPlugin
Jar
archives 是JavaPlugin 定义的一个Configuration。

对于andorid 而言这个Task 是BundleAar

LibraryTaskManager.java

    private void createBundleTask(@NonNull VariantScope variantScope) {
        TaskProvider<BundleAar> bundle =
                taskFactory.register(new BundleAar.CreationAction(variantScope));
			xxxx
            project.getArtifacts().add("default", bundle);
        }
    }

BundleAar

总结

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值