Gradle系列(3)——Gradle extension(gradle扩展),如何自定义gradle扩展,AppPlugin,AppExtension原理

project.extensions.getByType Extension of type ‘AndroidComponentsExtension‘ does not exist 解决 使用Project.pluginManager.withPlugin("com.android.application")过滤模块,获取到ApplicationPlugin,然后进行transform的注册。 阅读详情


系列: Gradle系列(2)——如何自定以Gradle 插件

在上一篇Gradle系列(2)——如何自定以Gradle 插件 中,我们了解了什么是 Gradle Plugin以及如何用Gradle Plugin在项目构建中实现更多强力而有趣的功能。尽管在demo中我们已经实现了在整个公司的不同团队通过本地json文件实现平台化的配置,但是仍然存在某个项目需要个性化的配置,或者在构建过程中需要一些灵活便捷的自定义的构建,那么上一篇实现的功能就不能满足我们的需求,于是本篇就引入了如何自定义Gradle Extensions


1.什么是Extensions

  • 我们其实经常接触Gradle Extension

Gradle Extensions 是Gradle Plugin 的一种扩展机制,通过实现自定义的Extension,我们可以在Gradle 脚本(例如build.gradle)中增加自己的配置,例如android(其实例为com.android.build.gradle.internal.dsl.BaseAppModuleExtension或com.android.build.gradle.LibraryExtension),
Gradle在configuration阶段可以读取这些配置里面的内容。

2.如何自定义Extension

关于这点,百度搜索有许多好多大量海量千篇一律毫无区别的帖子告诉你怎么定义一个自定义扩展并注册,我不再展开,就按照他们写的简单写个小demo

  • 定义一个扩展

public class TestExtension {

    public String anyString;
}

  • 在Plugin实现类注册这个扩展

public class RocketPlugin implements Plugin<Project>  {

    @Override
    public void apply(Project project) {
        super.apply(project);
        project.getExtensions().create("anyTest", TestExtension.class);
    }
}


我们给build.gradle内注册了一个TestExtension的实例,名字为anyTest

  • 使用这个扩展
    在app/library build.gradle内
plugins {
    id("com.android.application")
    id("gradle-rocket")
}

anyTest{
    topic = "extension test"
}

  • 获取插件的配置信息

在Plugin实现类内:


public class RocketPlugin implements Plugin<Project>  {

    @Override
    public void apply(Project project) {
        super.apply(project);
        TestExtension anyTest = project.getExtensions().create("anyTest", TestExtension.class);
          project.afterEvaluate(new Action<Project>() {
            @Override
            public void execute(Project project) {
                System.out.println("    afterEvaluate :anyTest:"+anyTest.topic);
            }
        });
    }
}


然后同步就可以看到输出:

!请添加图片描述

按照大家的说法,到这一步我们实现了一个简单的扩展。

3.问题来了——如何通过自定义Extension给Plugin传递数据

到上一节,我们实现了一个扩展,简单说明如何定义注册使用扩展,但,这只是一个简陋的扩展,甚至不能称之为扩展,我称之为扩展都感觉十分脸红惭愧甚至觉得丢人。

回想看过的十数篇帖子,不由得内心怒骂好几次:

这TM是扩展?!!这TM是扩展?!!这TM是扩展?!!

你们这些灌水的人给我翻译翻译,什么TM的叫TM的扩展?!!

为什么这个东西不能叫扩展?

我们回到开头的“什么是Extensions”,扩展的核心诉求是在gradle构建脚本(比如build.gradle)中增加我们自己的配置应用到我们的自定义gradle plugin中。而在以上篇章我们的扩展在哪里应用了,可以看到一行代码:

   estExtension anyTest = project.getExtensions().create("anyTest", TestExtension.class);

   project.afterEvaluate(new Action<Project>() {
            @Override
            public void execute(Project project) {
                System.out.println("    afterEvaluate :anyTest:"+anyTest.topic);
            }
        });
        //外部直接打印
      System.out.println("    afterEvaluate :anyTest:"+anyTest.topic);


我们注意project.afterEvaluate这个方法,在我的上一篇Gradle系列(2)——如何自定以Gradle 插件 中我简单讲了gradle的生命周期以及监听,afterEvaluate表示,gradle 的configuration阶段已经完成了。而上述代码表示,在项目配置完成后,输出一下我们扩展配置的值。

  • 实际问题

由于我们做的是平台化项目,android和harmony双平台打包的,两个平台应用的三方库,签名机制也不一样,所以我需要知道编译的是哪个平台,是否需要支持双平台,所以我添加了一个扩展来配置这个信息,然而我在上述afterEvaluate生命周期去做签名的时候,发现报错(当时忘了截图,大概意思是说buildType已经创建好了,此时修改太晚了)

我们尝试在外部直接打印,你会发现,根本没有值!

那么问题来了——我们这里实现的扩展还有什么用呢?

答案是,没卵用!

你去搜索相关的帖子,千篇一律,都是这样,甚至你看官网,他们的demo也是这样!我请问一下,这样一个不能给插件传送数据,只能看没有实际用处的花架子,它叫扩展吗?

4.BaseAppModuleExtension和AppPlugin部分原理

那么到底在构建脚本怎么通过我们的扩展给插件传递数据呢?我想,我们可以看Android的AppExtension和AppPlugin是如何实现的呀。

注意:

  • com.android.application,com.android.library 实现类分别为com.android.build.gradle.internal.plugins.AppPlugin 和com.android.build.gradle.internal.plugins.LibraryPlugin;

  • build.gradle中声明的“android”扩展的实例根据module是应用的插件不同,其实例可能是com.android.build.gradle.internal.dsl.BaseAppModuleExtension或com.android.build.gradle.LibraryExtension,

BuildTypes是如何创建并传递数据给AppPlugin的?

我们以build.gradle.kts中buildTypes为例:


plugins {
    id("com.android.application")
    id("gradle-rocket")
}
android {
	//。。。

        create("test") {
            isMinifyEnabled = false
            isDebuggable = true
            signingConfigs { }
        }

	//。。。
}

其实build.gradle的dsl写法我们可以理解为一种极致的lambda,buildTypes完整写法应该是


    buildTypes {

        create("test", object : Action<ApplicationBuildType> {

            override fun execute(t: ApplicationBuildType) {
                t.isMinifyEnabled = false
                t.isDebuggable = true
            }
        })
    }

我们点进去这个create方法,
在这里插入图片描述

可以看到,这是一个抽象类,点击左侧图标查看源码:

在这里插入图片描述

看得出来这个类应用还是比较广泛的,一时难以确定实现类是哪个。我们转变思路看AppPlugin:

AppPlugin是如何接收数据的?

查看AppPlugin源码


public class AppPlugin
        extends AbstractAppPlugin<
                com.android.build.api.dsl.ApplicationExtension,
                ApplicationAndroidComponentsExtension,
                ApplicationVariantBuilderImpl,
                ApplicationVariantImpl> {
    @Inject
    public AppPlugin(
            ToolingModelBuilderRegistry registry,
            SoftwareComponentFactory componentFactory,
            BuildEventsListenerRegistry listenerRegistry) {
        super(registry, componentFactory, listenerRegistry);
    }

    @NonNull
    @Override
    protected BaseExtension createExtension(
            @NonNull DslServices dslServices,
            @NonNull GlobalScope globalScope,
            @NonNull
                    DslContainerProvider<DefaultConfig, BuildType, ProductFlavor, SigningConfig>
                            dslContainers,
            @NonNull NamedDomainObjectContainer<BaseVariantOutput> buildOutputs,
            @NonNull ExtraModelInfo extraModelInfo) {
        ApplicationExtensionImpl applicationExtension =
                dslServices.newDecoratedInstance(ApplicationExtensionImpl.class, dslServices, 

          //..忽略一万行代码

          //创建“android”,注入到所有Extensions中
        return project.getExtensions()
                .create(
                        "android",
                        BaseAppModuleExtension.class,
                        dslServices,
                        globalScope,
                        buildOutputs,
                        dslContainers.getSourceSetManager(),
                        extraModelInfo,
                        applicationExtension);
    }

}

AppPlugin和LibraryPlugin代码都相当简单,主要区别在各自的泛型以及createExtension时android的实现类不同。逻辑都在其父类com.android.build.gradle.internal.plugins.BasePlugin,这个类也实现了Plugin接口,我们主要看apply方法


    @Override
    public final void apply(@NonNull Project project) {
        CrashReporting.runAction(
                () -> {
                    basePluginApply(project);
                    pluginSpecificApply(project);
                    project.getPluginManager().apply(AndroidBasePlugin.class);
                });
    }


接着进入basePluginApply


    private void basePluginApply(@NonNull Project project) {
        // We run by default in headless mode, so the JVM doesn't steal focus.
        System.setProperty("java.awt.headless", "true");

        this.project = project;

        //...
        
        configuratorService.recordBlock(
                ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
                project.getPath(),
                null,
                this::configureProject);

        configuratorService.recordBlock(
                ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
                project.getPath(),
                null,
                this::configureExtension);

        configuratorService.recordBlock(
                ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
                project.getPath(),
                null,
                this::createTasks);
    }


这3方法都比较重要,但我们要看configureExtension:


    private void configureExtension() {
        DslServices dslServices = globalScope.getDslServices();

        final NamedDomainObjectContainer<BaseVariantOutput> buildOutputs =
                project.container(BaseVariantOutput.class);

        project.getExtensions().add("buildOutputs", buildOutputs);

        //1.创建BuildType 工厂类
        variantFactory = createVariantFactory(projectServices, globalScope);

        variantInputModel =
                new LegacyVariantInputManager(
                        dslServices,
                        variantFactory.getVariantType(),
                        new SourceSetManager(
                                project,
                                isPackagePublished(),
                                dslServices,
                                new DelayedActionsExecutor()));
         //2.子类中重载的方法                       	
        extension =
                createExtension(
                        dslServices, globalScope, variantInputModel, buildOutputs, extraModelInfo);
        //3.把扩展添加到全局作用域       
        globalScope.setExtension(extension);

        androidComponentsExtension = createComponentExtension(dslServices, variantApiOperations);

        variantManager =
                new VariantManager(
                        globalScope,
                        project,
                        projectServices.getProjectOptions(),
                        extension,
                        variantApiOperations,
                        variantFactory,
                        variantInputModel,
                        projectServices);

        registerModels(
                registry,
                globalScope,
                variantInputModel,
                extension,
                extraModelInfo);

        // create default Objects, signingConfig first as its used by the BuildTypes.
        //创建默认的buildType和签名
        variantFactory.createDefaultComponents(variantInputModel);

        createAndroidTestUtilConfiguration();
    }


这段代码我们需要了解的有三处:

  • createVariantFactory创建了我们的buildTypes工厂
  • 调用熟悉的createExtension创建我们的扩展实例,就是在上面提到子类中重载的方法。
  • 把扩展实例添加到全局作用域
  • 创建默认的buildType和签名

那么我们接下来createVariantFactory是怎么创建的,继而看我们的BuildType到底怎么创建的。

    protected abstract VariantFactory<VariantBuilderT, VariantT> createVariantFactory(
            @NonNull ProjectServices projectServices, @NonNull GlobalScope globalScope);

这是个抽象方法,我们看实现类

在这里插入图片描述

能看到AppPlugin,这个类是BaseAppModuleExtension的父类


    @NonNull
    @Override
    protected ApplicationVariantFactory createVariantFactory(
            @NonNull ProjectServices projectServices, @NonNull GlobalScope globalScope) {
        return new ApplicationVariantFactory(projectServices, globalScope);
    }

最终到了ApplicationVariantFactory


class ApplicationVariantFactory(
    projectServices: ProjectServices,
    globalScope: GlobalScope
) : AbstractAppVariantFactory<ApplicationVariantBuilderImpl, ApplicationVariantImpl>(
    projectServices,
    globalScope
) {
	//。。。
}

这个类的父类是AbstractAppVariantFactory,我们先记住这个类,返回到configureExtension,查看variantFactory.createDefaultComponents(variantInputModel);

    fun createDefaultComponents(
            dslContainers: DslContainerProvider<DefaultConfig, BuildType, ProductFlavor, SigningConfig>)

这是VariantFactory接口的抽象方法,接着看实现类AbstractAppVariantFactory

    override fun createDefaultComponents(
            dslContainers: DslContainerProvider<DefaultConfig, BuildType, ProductFlavor, SigningConfig>) {
        // must create signing config first so that build type 'debug' can be initialized
        // with the debug signing config.
        //创建默认的debug签名
        dslContainers.signingConfigContainer.create(BuilderConstants.DEBUG)
        //创建默认的debug和release buildType
        dslContainers.buildTypeContainer.create(BuilderConstants.DEBUG)
        dslContainers.buildTypeContainer.create(BuilderConstants.RELEASE)
    }


这个方法里做了两件事:

  • 创建默认的debug签名
  • 创建默认的debug和releae buildType

这就解释了为什么我们项目为什么不配置签名也能安装,以及两个默认的buidType从何而来。(这段代码救了我的命,在做平台化Flavor和签名的时候要配置我们的签名密钥,创建SigningConfig遇到问题,就是看这段代码找到了解决方法)

好,我们接着往下看dslContainers.buildTypeContainer.create(BuilderConstants.DEBUG):

在这里插入图片描述

是不是回到了远点,又是NamedDomainObjectContainer。

buildTypeContainer

那么我们的buildTypeContainer从何而来呢?

我们要看dslContainers;

从createDefaultComponents(),从方法形参可以看到它的类型是DslContainerProvider;

往回退到configureExtension(),可以看到dslContainers就是variantInputModel;在configureExtension开始的地方创建的:


        variantInputModel =
                new LegacyVariantInputManager(
                        dslServices,
                        variantFactory.getVariantType(),
                        new SourceSetManager(
                                project,
                                isPackagePublished(),
                                dslServices,
                                new DelayedActionsExecutor()));



我们再看这个LegacyVariantInputManager的代码:


class LegacyVariantInputManager(
    dslServices: DslServices,
    variantType: VariantType,
    sourceSetManager: SourceSetManager
) : AbstractVariantInputManager<DefaultConfig, BuildType, ProductFlavor, SigningConfig>(
    dslServices,
    variantType,
    sourceSetManager
) {

    override val buildTypeContainer: NamedDomainObjectContainer<BuildType> =
        dslServices.domainObjectContainer(
            BuildType::class.java, BuildTypeFactory(dslServices)
        )
    override val productFlavorContainer: NamedDomainObjectContainer<ProductFlavor> =
        dslServices.domainObjectContainer(
            ProductFlavor::class.java,
            ProductFlavorFactory(dslServices)
        )
    override val signingConfigContainer: NamedDomainObjectContainer<SigningConfig> =
        dslServices.domainObjectContainer(
            SigningConfig::class.java,
            SigningConfigFactory(
                dslServices,
                getBuildService(
                    dslServices.buildServiceRegistry,
                    AndroidLocationsBuildService::class.java
                ).get().getDefaultDebugKeystoreLocation()
            )
        )

    override val defaultConfig: DefaultConfig = dslServices.newInstance(
        DefaultConfig::class.java,
        BuilderConstants.MAIN,
        dslServices
    )
}

这里我特意多粘贴了点代码,是不是看着很熟悉,有productFlavor和signingConfigContainer, defaultConfig。

我们接着看buildTypeContainer = dslServices.domainObjectContainer,它是接口DslServices的方法,唯一实现类DslServicesImpl


class DslServicesImpl constructor(
    projectServices: ProjectServices,
    override val sdkComponents: Provider<SdkComponentsBuildService>
) : BaseServicesImpl(projectServices), DslServices {



    override fun <T> domainObjectContainer(
        type: Class<T>,
        factory: NamedDomainObjectFactory<T>
    ): NamedDomainObjectContainer<T> =
        projectServices.objectFactory.domainObjectContainer(type, factory)

}

继续点进去domainObjectContainer,他是接口ObjectFactory的方法,它的有效实现类是DefaultObjectFactory,查看它的方法:


    @Override
    public <T> NamedDomainObjectContainer<T> domainObjectContainer(Class<T> elementType, NamedDomainObjectFactory<T> factory) {
        return domainObjectCollectionFactory.newNamedDomainObjectContainer(elementType, factory);
    }

继续点进去仍然是个接口DomainObjectCollectionFactory:

public interface DomainObjectCollectionFactory {
    <T> NamedDomainObjectContainer<T> newNamedDomainObjectContainer(Class<T> elementType, NamedDomainObjectFactory<T> factory);
}

接着看它唯一实现类DefaultDomainObjectCollectionFactory:

是不是很绕啊,确实这么绕,山高水远,道阻且长,说实话

BaePlugin


public class DefaultDomainObjectCollectionFactory implements DomainObjectCollectionFactory {

    @Override
    public <T> NamedDomainObjectContainer<T> newNamedDomainObjectContainer(Class<T> elementType, NamedDomainObjectFactory<T> factory) {
        Instantiator instantiator = instantiatorFactory.decorateLenient();
        return Cast.uncheckedCast(instantiator.newInstance(FactoryNamedDomainObjectContainer.class, elementType, instantiator, new DynamicPropertyNamer(), factory, mutationGuard, collectionCallbackActionDecorator));
    }


}

看到这里就没有必要继续往下看了,其实已经能看出来了,这里其实就是反射创建了FactoryNamedDomainObjectContainer的实例,那我们接着看它


public class FactoryNamedDomainObjectContainer<T> extends AbstractNamedDomainObjectContainer<T> {
    
    public FactoryNamedDomainObjectContainer(Class<T> type, Instantiator instantiator, Namer<? super T> namer, NamedDomainObjectFactory<T> factory, MutationGuard crossProjectConfiguratorMutationGuard, CollectionCallbackActionDecorator collectionCallbackActionDecorator) {
        super(type, instantiator, namer, collectionCallbackActionDecorator);
        this.factory = factory;
        this.crossProjectConfiguratorMutationGuard = crossProjectConfiguratorMutationGuard;
    }

    @Override
    protected T doCreate(String name) {
        return factory.create(name);
    }
}

最终,我们找到了dslContainers.buildTypeContainer的实现类NamedDomainObjectContainer

(这里我特意贴了构造函数和一个方法,先记住,然后往后看)

它并没有实现create方法,我们接着看它的父类


public abstract class AbstractNamedDomainObjectContainer<T> extends DefaultNamedDomainObjectSet<T> implements NamedDomainObjectContainer<T>, HasPublicType {
   
    @Override
    public T create(String name) {
        assertMutable("create(String)");
        return create(name, Actions.doNothing());
    }

        @Override
    public T create(String name, Closure configureClosure) {
        assertMutable("create(String, Closure)");
        return create(name, ConfigureUtil.configureUsing(configureClosure));
    }

    @Override
    public T create(String name, Action<? super T> configureAction) throws InvalidUserDataException {
        assertMutable("create(String, Action)");
        assertCanAdd(name);
        //创建BuildType实例
        T object = doCreate(name);
        //添加到集合中
        add(object);
        //传递给我们的回调,让我们自己配置
        configureAction.execute(object);
        return object;
    }

}

可以看到实现了NamedDomainObjectContainer的3个create方法,最主要的是第三个方法,我们再次回顾一下build.gradle里面


    buildTypes {

        create("test", object : Action<ApplicationBuildType> {

            override fun execute(t: ApplicationBuildType) {
                t.isMinifyEnabled = false
                t.isDebuggable = true
            }
        })
    }

对比一下上面的方法,是不是觉得柳暗花明了😎😎😎

我们知道,create创建的就是BuildType实例(它实现了ApplicationBuildType),这里主要做了3件事:

  • 创建BuildType实例
  • 添加到缓存集合中
  • 传递给我们的回调,由我们自己去配置

看到希望了,我们接着往下看,我们的BuildType是怎么创建出来的,doCreate是个抽象方法,FactoryNamedDomainObjectContainer:


    @Override
    protected T doCreate(String name) {
        return factory.create(name);
    }

看到这个方法,我震惊了🙃🙃🙃我费尽心思追了这么久想看factory是什么终于找到了FactoryNamedDomainObjectContainer,你告诉我创建BuildType的还不是它?🤦‍♂️可是我能有什么办法啊,为了维护世界和平,为了沉没成本,只能继续看啊!

那我们看factory是哪里来的,记得我说前面特意贴的构造函数吗,竟然是构造函数传进来的,那我就返回往前看,发现我们来时的路上每一个方法的参数都是它的身影,直到LegacyVariantInputManager:

    override val buildTypeContainer: NamedDomainObjectContainer<BuildType> =
        dslServices.domainObjectContainer(
            BuildType::class.java, BuildTypeFactory(dslServices)
        )

看它的第二个参数点进去:


public class BuildTypeFactory implements NamedDomainObjectFactory<BuildType> {

    @NonNull private DslServices dslServices;

    public BuildTypeFactory(@NonNull DslServices dslServices) {
        this.dslServices = dslServices;
    }

    @NonNull
    @Override
    public BuildType create(@NonNull String name) {
        return dslServices.newInstance(BuildType.class, name, dslServices);
    }
}


诺,就是这里了,后面我实在不想浪费时间继续贴了,相信大家已经看腻了,总结六个字——反射创建实例


流程总结

ok,到这一步,我们就明白了:

  • BuildType是由FactoryNamedDomainObjectContainer创建的;
  • 实例是由BuildTypeFactory反射创建的;
  • BuildType实例创建后添加到了FactoryNamedDomainObjectContainer,然后又给到我们的回调由我们自己配置

AppPlugin创建默认BuildType和我们自己在build.gradle 添加方式是一样的。

5. 回归初心——扩展是如何传递数据给插件的呢?

我们回溯上篇章的内容,找到各个类的从属关系:AppExtension持有ApplicationVariantFactory,后者在前者实例化时通过构造函数注入,它又持有DslServiceImpl… :

  • AppExtension -> ApplicationVariantFactory ->DslServiceImpl->FactoryNamedDomainObjectContainer->BuildType,
  • 而 ApplicationVariantFactory 和 DslServiceImpl 是BasePlugin的成员变量

简单的结构图:

在这里插入图片描述

总结:

所以我们可以直观的理解为,ApplicationVariantFactory 就是注入A品牌Extension的一个callback,build.gralde里面看似调用AppExtension的函数,实则调用AppPlugin的成员。

经过千难万险,坎坷曲折,一路不停的追踪代码,最终我们得出这样一个很简单额结论,是不是很开心呢😂

6 应用

好,既然我们已经明白了这个原理,那么我们就可以在我们插件中应用。

改造我们的扩展:


public class RocketExtensions {

    //多平台支持。如若支持,会默认配置多个flavor
    private RocketConfigs mExtensionContainer;


    public RocketExtensions(BoosterConfigs container) {
        mExtensionContainer = container;
    }

    public void setMultiFlavor(boolean multi) {
        mExtensionContainer.setMultiFlavor(multi);
    }
}


新增一个配置类RocketConfigs:


public class RocketConfigs {

    public boolean isMultiFlavor;

    public void setMultiFlavor(boolean multi) {
        System.out.println("setMultiFlavor:" + multi);
        this.isMultiFlavor = multi;
        //doSth
    }
}

插件类:


public class BoosterPlugin implements Plugin {

    @Override
    public void apply(Project project) {
        super.apply(project);

        //初始化配置类
        RocketConfigs container = new BoosterConfigs();
        //注入扩展
        project.getExtensions().create("rocket", RocketExtensions.class, container);
    }
}

然后发布,在应用的build.gradle里使用:


plugins {
    id("com.android.application")
    id("gradle-rocket")
}


rocket {
    setMultiFlavor(true)
}
android{
	//...
}

同步一下就可以看到build输出

在这里插入图片描述


为了看明白这块儿代码真的费尽心思,弯弯绕绕,脑袋都晕了,不过最终搞懂这些东西还是非常开心的。

好了,本篇到此结束

Android Gradle扩展「Android Gradle Extension」-crx插件 在developer.android.com中为类引用添加Gradle依赖文本。 一个Chrome扩展程序,添加了Gradle依赖项以用于Android API类参考页。 然后,用户可以复制Gradle依赖项文本并在其代码中使用它。 Android是Google Inc.的商标。Gradle是Gradle,Inc.的商标。图标是从Android Asset Studio生成的。 支持语言:English 立即下载

相关推荐

基于STM32的3D打印机控制

通过本项目,我们成功搭建了一个基础的3D打印机控制系统,能够控制步进电机和加热元件。这为使用STM32开发功能丰富的3D打印机提供了基础,您可以在此基础上继续扩展功能,如增加LCD显示、SD卡存储、联网控制等功能。在这个项目中,我们将使用STM32微控制器开发一个基础的3D打印机控制系统。通过控制步进电机、加热元件和温度传感器,我们将实现一个简单的FDM(熔融沉积制造)3D打印机控制。- 检查PWM信号的生成功能,确认加热元件的控制逻辑是否正常。- 增加适当的滤波和校准算法,以提高温度读取的准确性。

2401_88938661的博客 1503

Gradle | Extension扩展详解

我们先来看一段Android应用的Gradleandroid {release {相信做Android应用开发的同学,对这段代码都快看吐了吧。记得当初刚从eclipse转到的时候,看这些配置就像看天书一样,只知道按规定配置就可以了。但是为什么要这样配置?除此外还支持哪些配置?为什么一定要在android这个命名空间下配置呢?可以不可以定义自己的特殊配置呢?上面这个android打包配置,就是Gradle的Extension,翻译成中文意思就叫扩展。它的作用就是通过实现自定义的Extension。

小马快跑的博客 1669

头歌工程实训答案-机器学习篇之手写体识别篇

第2关:激活函数'''x:负无穷到正无穷的实数'''if x<=0:return 0else:return x#***第3关:反向传播算法import os。

m0_71537260的博客 2518

Gradle ExtenionContainer 创建和使用扩展参数(extensions)详解

Gradle ExtenionContainer 创建和使用扩展参数(extensions)详解 我们在开发 Gradle 插件时,大多数插件都需要从构建脚本中获取一些配置,这样就可以根据项目的不同,对 Gradle 插件传递不同的配置,而不需要修改插件内的代码。我们可以使用 ExtensionContainer 来实现 Gradle 的扩展(参数传递能力)。 ExtenionContainer 每个 Gradle 的 Project 都维护了一个 ExtenionContainer,我们可以通过 pro

卜大爷的博客 3763

【03】海康MVS V4.3.0 ——安装教程、查看示例、库、头文件、开发指南

海康威视MVS机器视觉软件安装与使用指南:首先从官网下载MVS 4.3.0安装包,默认设置完成安装。安装后可在Development目录查看示例代码、开发文档及头文件,支持C++/C#/Python等多语言开发。软件内置虚拟相机功能,通过工具菜单添加虚拟设备后即可模拟真实相机操作,支持中英文界面切换、参数调节(曝光/增益等)及图像采集功能。虚拟相机虽仅能显示预设图像,但完整保留了参数调节功能,适合开发者进行功能测试。

程序员,他们想的是什么?他们想的永远都是技术,他们崇尚的也永远都是技术。 3306

2024年全国大学生信息安全竞赛安徽省赛网络系统建设与运维赛项-网络构建真题

该网络工程方案主要包含基础配置、有线网络、无线网络、出口网络、运维配置和SDN网络六个部分。基础配置包括设备接口设置、SSH服务开启和SNMP功能配置。有线网络部分重点部署VLAN、MSTP、VRRP和OSPF协议,实现网络冗余和负载均衡。无线网络规划了AP点位、信号覆盖和用户接入策略,包括SSID配置、频段优化和安全认证。出口网络配置了NAT转换、VPN隧道和流量审计功能。运维方面搭建了监控平台实时监测设备状态。SDN网络部分通过控制器实现流量管控。整个方案实现了多业务隔离、高可用性网络架构和

weixin_42221934的博客 1174

Android Gradle之自定义Extension

就是 Gradle 的 Extension,翻译成中文意思就叫扩展。它的作用就是通过实现自定义的 Extension,可以在 Gradle 脚本中增加类似 android 这样命名空间的配置,Gradle 可以识别这种配置,并读取里面的配置内容。

star_nwe的博客 731

从电商平台到微信私域:构建合规跳转链路的技术实现

在互联网营销与用户增长领域,链接跳转是连接不同平台、引导用户流量的基础技术。其核心原理是通过服务器端路由与参数解析,实现用户请求的接收、处理与重定向。这项技术的价值在于能够突破平台间的生态壁垒,实现流量的合规、可控迁移,尤其适用于跨平台营销、私域沉淀等场景。在实际应用中,常需处理微信环境限制、参数传递与动态内容生成等挑战。本文聚焦于电商平台(如淘宝)到微信生态的跳转需求,详细阐述了如何利用Node.js、Express框架及二维码生成技术,构建一个包含环境检测、参数传递与动态落地页的完整跳转系统,以解决微信

weixin_33889665的博客 323

【Android Gradle 插件】Gradle 自定义 Plugin 插件 ③ ( 自定义插件作用 | Android Gradle 插件的扩展 | 自定义 Extension 扩展 )

一、自定义插件作用、 二、Android Gradle 插件的 AppExtension 扩展、 三、自定义 Extension 扩展

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 1706

基于YOLOv26的智能农业无人机巡检系统

本文提出了一种基于YOLOv26的智能农业无人机巡检系统。系统采用模块化设计,包含数据采集、预处理、目标检测、图像拼接、分析决策和部署控制六大核心模块。无人机搭载多光谱相机、RGB相机和热红外相机进行农田数据采集,通过YOLOv26实现作物病虫害、杂草及灌溉设备的检测。系统采用PyTorch、OpenCV等技术栈,支持无人机自动路径规划和多传感器数据采集,并包含数据增强和预处理功能。该系统可有效提升农业巡检效率,为精准农业提供智能化解决方案。

一键难忘的博客 807

Android 12.0 SystemUI下拉状态栏定制化之隐藏下拉通知栏布局功能实现(二)

在12.0的系统定制化开发中,由于从12.0开始SystemUI下拉状态栏和11.0的变化比较大,所以可以说需要从新分析相关的SystemUI的 布局,然后做分析来实现不同的功能,今天就开始实现关于隐藏SystemUI下拉状态栏中的通知栏布局系列二,去掉下拉状态栏中 通知栏部分 白色的圆角背景

安卓兼职framework和app工程师的博客 1634

android gradle配置_【Android 修炼手册】Android Gradle Plugin 插件主要流程

预备知识理解 gradle 的基本开发了解 gradle task 和 plugin 使用及开发了解 android gradle plugin 的使用看完本文可以达到什么程度了解 android gradle plugin 的构建流程了解 android gradle plugin 的主要 task 的实现学会 hook android 构建流程,添加自己想要的功能阅读前准备工作1.项目添加 a...

weixin_31931045的博客 667

手把手教程 | 使用 apktool 修改已发布 APK 的版本号与版本名称

摘要:本文介绍如何使用apktool工具直接修改APK版本信息,适用于版本回滚、多版本测试等场景。详细步骤包括反编译APK、修改版本号、重打包、对齐优化和重新签名,并分享了反编译相关实用知识。该方法无需源代码参与,但需注意修改后的APK必须重新签名才能安装。最后强调应在合法范围内使用反编译技术。

学无止境 826

QGroundControl Radio Setup 详解:遥控器校准流程、固件专属选项与源码级实现原理

Radio Setup 是 QGroundControl(QGC)中用于配置遥控系统(RC)的核心页面:它负责把主发射机的姿态控制摇杆(roll、pitch、yaw、throttle)映射到具体通道,并为其余所有遥控器控制量校准最小值、最大值、trim 与反向设置。本篇以 [中文用户指南中的 Radio Setup 文档](https://link.gitcode.com/i/e927ec36ea

gitblog_00366的博客 460

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Android Gradle学习(五) Extension详解

前面我们已经详细讲解了 Gradle 的 Task、Project 等基本用法,现在我们还要学习一个很重要的概念 Extension,它在 Gradle 中几乎随处可见,特别是在 Android 打包配置中。 1. 什么是Extension 我们先来看一段 Android 应用的 Gradle 配置代码: android { compileSdkVersion 26 default...

假装你是大灰狼的专栏 3177

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

【Android Gradle 插件】Module 目录下 build.gradle 配置文件 ( android 闭包块配置 | AppExtension 扩展类型参考文档 )

该 android 方法定义在 AppExtension 扩展类型中 , 下面简单介绍该扩展类型;android 方法中的配置参考。

浪漫之圣 537

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

找不到 org.gradle:gradle-tooling-api:jar:7.1.1

gradle-tooling-api:jar:7.1.1 找不到

gs80140的专栏 2060

Gradle插件-extensions

简单实例,在工程的build.gradle文件中添加如下代码 class CustomParamsExtensions{ def happyDes = 'very happy' def happyLevel = 3 } class CustomParamsNestExtensions{ def dataParams = 'NestParam' } project.

zlcjssds的博客 2796

Gradle系列(四)-扩展属性

另外我们还可以在gradle.properties下直接定义全局属性.如上所示,我们定义test属性.这里定义的属性我们是可以直接调用的根目录的build.gradle中调用println "根目录build.gradle:" + testandroid {...

赵航的博客 5982

Gradle插件探究

Gradle插件基础知识

mayday_sanshui的博客 2578

【Android Gradle 插件】Extension 扩展类型 ( Module 引入插件类型 | application 插件 | library 插件 | Variants 变体列表 )

一、Module 引入插件类型、 1、com.android.application 插件、 2、com.android.library 插件、 二、Extension 扩展类型、 三、applicationVariants 变体与 libraryVariants 变体、

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 3014

gradle自定义插件

学习资料 Writing Custom Plugins 总体来说思路和自定义任务很类似,也是三种方式 概述 创建一个Gradle插件,需要编写一个实现Plugin接口的类。当插件被应用到一个项目时,Gradle创建了一个插件类的实例,并调用了实例的plugin.apply(T)方法。项目对象作为一个参数传递,插件可以使用它来配置项目,但是它需要这样做。 直接写在构建文件中 简单插...

约会远行的专栏 5444

Google Cloud Platform App Gradle Plugin 使用教程

Google Cloud Platform App Gradle Plugin 使用教程 项目介绍 Google Cloud Platform App Gradle Plugin 是一个用于构建和部署 Google App Engine 应用的 Gradle 插件。该插件提供了多种任务,帮助开发者简化应用的构建和部署流程。自 2024 年 2 月起,该库已迁移至 appengine-plugins...

gitblog_00368的博客 942

AGP7.0|kts 搞一个加固插件

每次都要手动使用工具去手动加固,非常麻烦,所以自己搞一个加固插件来提高生产力 开发环境使用的AGP7.0.2 ,相比较之前的版本,改动还是蛮大的,自己也踩了不少坑。 更多AGP7.0的内容可以关注一下虾哥的文章 掘金:https://juejin.cn/post/7056395437544046606 我们分为以下几步去完成: 获取apk产物 获取签名 获取加固工具 进行加固 获取APK AGP7获取apk 的方式跟以前也大不相同了 ,我们需要借助Variant API apk 来进行获取 Var

WangYilei0318的博客 1933

暴力突破 Gradle 自动化项目构建(七)- 其他模块及自定义 Gradle 插件

Setting SourceSet

lerendan的博客 825
上一篇: Android插件化原理(三)——加载插件资源
下一篇: Android截屏录屏MediaProjection分享
cry kid
博客等级 码龄10年 144粉丝 24原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值