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

文章目录

    • 1.什么是Extensions
    • 2.如何自定义Extension
    • 3.问题来了——如何通过自定义Extension给Plugin传递数据
    • 4.BaseAppModuleExtension和AppPlugin部分原理
      • BuildTypes是如何创建并传递数据给AppPlugin的?
      • AppPlugin是如何接收数据的?
      • buildTypeContainer
      • 流程总结
    • 5. 回归初心——扩展是如何传递数据给插件的呢?
    • 6 应用

系列: 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.BaseAppModuleExtensioncom.android.build.gradle.LibraryExtension),
Gradle在configuration阶段可以读取这些配置里面的内容。

2.如何自定义Extension

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

  • 定义一个扩展

public class TestExtension {public String anyString;
}
  • 在Plugin实现类注册这个扩展

public class RocketPlugin implements Plugin<Project>  {@Overridepublic 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>  {@Overridepublic void apply(Project project) {super.apply(project);TestExtension anyTest = project.getExtensions().create("anyTest", TestExtension.class);project.afterEvaluate(new Action<Project>() {@Overridepublic 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>() {@Overridepublic 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阶段已经完成了。而上述代码表示,在项目配置完成后,输出一下我们扩展配置的值

  • 实际问题

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

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

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

答案是,没卵用!

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

4.BaseAppModuleExtension和AppPlugin部分原理

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

注意:

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

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

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

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


plugins {id("com.android.application")id("gradle-rocket")
}
android {//。。。create("test") {isMinifyEnabled = falseisDebuggable = truesigningConfigs { }}//。。。
}

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

buildTypes {create("test", object : Action<ApplicationBuildType> {override fun execute(t: ApplicationBuildType) {t.isMinifyEnabled = falset.isDebuggable = true}})}

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

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

在这里插入图片描述

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

AppPlugin是如何接收数据的?

查看AppPlugin源码


public class AppPluginextends AbstractAppPlugin<com.android.build.api.dsl.ApplicationExtension,ApplicationAndroidComponentsExtension,ApplicationVariantBuilderImpl,ApplicationVariantImpl> {@Injectpublic AppPlugin(ToolingModelBuilderRegistry registry,SoftwareComponentFactory componentFactory,BuildEventsListenerRegistry listenerRegistry) {super(registry, componentFactory, listenerRegistry);}@NonNull@Overrideprotected BaseExtension createExtension(@NonNull DslServices dslServices,@NonNull GlobalScope globalScope,@NonNullDslContainerProvider<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);}}

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

@Overridepublic 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@Overrideprotected 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 buildTypedslContainers.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)
}

这里我特意多粘贴了点代码,是不是看着很熟悉,有productFlavorsigningConfigContainer, 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,查看它的方法:

@Overridepublic <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 {@Overridepublic <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;}@Overrideprotected T doCreate(String name) {return factory.create(name);}
}

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

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

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


public abstract class AbstractNamedDomainObjectContainer<T> extends DefaultNamedDomainObjectSet<T> implements NamedDomainObjectContainer<T>, HasPublicType {@Overridepublic T create(String name) {assertMutable("create(String)");return create(name, Actions.doNothing());}@Overridepublic T create(String name, Closure configureClosure) {assertMutable("create(String, Closure)");return create(name, ConfigureUtil.configureUsing(configureClosure));}@Overridepublic 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 = falset.isDebuggable = true}})}

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

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

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

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

@Overrideprotected 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@Overridepublic 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 {//多平台支持。如若支持,会默认配置多个flavorprivate 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 {@Overridepublic 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输出

在这里插入图片描述


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

好了,本篇到此结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/312445.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI光芯登上Science,开启算力新纪元

智能光芯片“太极”&#xff1a;清华大学的科技壮举&#xff0c;开启算力新纪元 在科技的浩瀚星海中&#xff0c;每一次创新都是对未知世界的探索和征服。近日&#xff0c;清华大学电子工程系与自动化系的联合团队&#xff0c;凭借其深厚的科研实力和创新精神&#xff0c;研发出…

Java | Leetcode Java题解之第32题最长的有效括号

题目&#xff1a; 题解&#xff1a; class Solution {public int longestValidParentheses(String s) {int left 0, right 0, maxlength 0;for (int i 0; i < s.length(); i) {if (s.charAt(i) () {left;} else {right;}if (left right) {maxlength Math.max(maxlen…

基于Springboot+Vue的Java项目-高校心理教育辅导系统开发实战(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

docker网路和主机通讯问题

#注 1&#xff0c;安装docker和启动容器服务的时候如果防火墙处于开启状态&#xff0c;那么重启docker里面的容器的时候必须开启防火墙&#xff0c;否则会出现iptable错误&#xff1b; 2&#xff0c;linux开启防火墙会导致主机和docker网络之间单向通讯&#xff0c;主机可以访…

Big Data and Cognitive Computing (IF=3.7) 计算机/大数据/人工智能期刊投稿

Special Issue: Artificial Cognitive Systems for Computer Vision 欢迎计算机/大数据/人工智能/计算机视觉相关工作的投稿&#xff01; 影响因子3.7&#xff0c;截止时间2024年12月31日 投稿咨询&#xff1a;lqyan18fudan.edu.cn 投稿网址&#xff1a;https://www.mdpi.com/j…

Java 语言程序设计(基础篇)原书第10版 梁勇著 PDF 文字版电子书

简介 Java 语言程序设计&#xff08;基础篇&#xff09;原书第 10 版 是 Java 语言的经典教材&#xff0c;中文版分为基础篇和进阶篇&#xff0c;主要介绍程序设计基础、面向对象程序设计、GUI 程序设计、数据结构和算法、高级 Java 程序设计等内容。本书通过示例讲解问题求解…

【鸿蒙开发】第二十一章 Media媒体服务(一)

1 简介 Media Kit&#xff08;媒体服务&#xff09;提供了AVPlayer和AVRecorder用于播放、录制音视频。 在Media Kit的开发指导中&#xff0c;将介绍各种涉及音频、视频播放或录制功能场景的开发方式&#xff0c;指导开发者如何使用系统提供的音视频API实现对应功能。比如使用…

分类预测 | Matlab实现PSO-BP-Adaboost基于粒子群算法优化BP神经网络结合Adaboost思想的分类预测模型

分类预测 | Matlab实现PSO-BP-Adaboost基于粒子群算法优化BP神经网络结合Adaboost思想的分类预测模型 目录 分类预测 | Matlab实现PSO-BP-Adaboost基于粒子群算法优化BP神经网络结合Adaboost思想的分类预测模型分类效果基本介绍程序设计参考资料 分类效果 基本介绍 1.Matlab实现…

C语言【指针】

1. 基本语法 1.1 指针变量的定义和使用(重点) 指针是一种数据类型&#xff0c;指针变量指向谁 就把谁的地址赋值给指针变量 1.2 通过指针间接修改变量的值 指针变量指向谁 就把谁的地址赋值给指针变量 可以通过 *指针变量 间接修改变量的值 1.3 const修饰的指针变量 语法…

蓝桥杯第十五届javab组个人总结

javab组 额今天早上打完了得对自己此次比赛做总结&#xff0c;无论是明年还参赛还是研究生蓝桥杯&#xff0c;体验感有点差&#xff0c;第一题其实一开始想手算但怕进位导致不准确还是让代码跑了&#xff0c;但跑第202420242024个数&#xff08;被20和24整除&#xff09;一直把…

学习Rust的第4天:常见编程概念

基于Steve Klabnik的《The Rust Programming Language》一书。昨天我们做了一个猜谜游戏 &#xff0c;今天我们将探讨常见的编程概念&#xff0c;例如&#xff1a; Variables 变量Constants 常数Shadowing 阴影Data Types 数据类型Functions 功能 Variables 变量 In layman ter…

03-JAVA设计模式-组合模式

组合模式 什么是组合模式 组合模式&#xff08;Composite Pattern&#xff09;允许你将对象组合成树形结构以表示“部分-整体”的层次结构&#xff0c;使得客户端以统一的方式处理单个对象和对象的组合。组合模式让你可以将对象组合成树形结构&#xff0c;并且能像单独对象一…

MR-JE-70A 三菱MR-JE伺服驱动器(750W通用型)

三菱MR-JE伺服驱动器(750W通用型) MR-JE-70A外部连接,MR-JE-70A用户手册,MR-JE-70A 三相或单相AC220V三菱通用型伺服放大器750W&#xff0c;配套电机HG-SN52J-S100、HG-KN73J-S100。 MR-JE-70A参数说明&#xff1a;伺服驱动器通用型750W&#xff0c;三相或单相AC200V~240V 三…

openHarmony 如何从API9升级到API10

最近用从官方下载的DevEco Studio3.1开发小app, 需要用到第三方库&#xff0c;加载第三方库&#xff0c;并添加代码&#xff0c;编译时如下错误&#xff1a; hvigor Finished :entry:defaultGenerateMetadata… after 3 ms hvigor ERROR: Failed :entry:defaultMergeProfile… …

Android 车载应用开发概述

前言 介绍 Android 车载应用开发 文章目录 前言一、Android Automotive OS 概述二、Android Automotive OS 架构三、常见的车载应用1、系统应用1&#xff09;SystemUI是什么开发工作 2&#xff09;Launcher是什么开发工作 3&#xff09;Settings是什么开发工作 4&#xff09;多…

ppt里的音乐哪里来的?

心血来潮&#xff0c;想照着大神的模板套一个类似于快闪的ppt。 ppt里是有一段音乐的&#xff0c;那段音乐就是从幻灯片第二页开始响起的。 但是我就找不到音乐在哪。 甚至我把ppt里的所有素材都删除了&#xff0c;再看动画窗格&#xff0c;仍然是空无一物&#xff0c;显然&…

【STL】list的模拟实现

目录 前言 list概述 list的节点 list的迭代器 list的结构 构造与析构 拷贝构造与赋值 list的元素操作 insert() push_back() push_front() erase() pop_back() pop_front() clear() swap() size() 完整代码链接 前言 如果你对链表还不熟悉或者忘了的话…

秸秆焚烧智能监测摄像机

秸秆焚烧是一种常见的农业作业方式&#xff0c;但其会造成大气污染&#xff0c;危害环境和人体健康。为了监测和控制秸秆焚烧产生的污染&#xff0c;可以使用秸秆焚烧智能监测摄像机。这种设备通过高清摄像头和智能识别算法&#xff0c;可以实时监测秸秆焚烧的情况&#xff0c;…

【C++】stack与queue(相关接口介绍、容器适配器、deque、模拟实现)

一、stack 1.1 stack介绍 stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行 元素的插入与提取操作。stack是作为容器适配器被实现的&#xff0c;容器适配器即是对特定类封装作为其底层的容器&#xff0c;并提供…

Go 单元测试之HTTP请求与API测试

文章目录 一、httptest1.1 前置代码准备1.2 介绍1.3 基本用法 二、gock2.1介绍2.2 安装2.3 基本使用2.4 举个例子2.4.1 前置代码2.4.2 测试用例 一、httptest 1.1 前置代码准备 假设我们的业务逻辑是搭建一个http server端&#xff0c;对外提供HTTP服务。用来处理用户登录请求…