当前位置: 首页 > 编程日记 > 正文

Android Gradle Plugin 源码解析(上)

一、源码依赖

本文基于:

android gradle plugin版本:

com.android.tools.build:gradle:2.3.0

gradle 版本:4.1

Gradle源码总共30个G,为简单起见,方便大家看源码,此处通过gradle依赖的形式来查看源码,依赖源码姿势:

创建一个新工程,app 项目目录中删除所有文件,仅留下gradle文件,依赖

apply plugin: 'java'
sourceCompatibility = 1.8
dependencies {compile gradleApi()compile 'com.android.tools.build:gradle:2.3.0'
}

将跟目录下的gradle文件,删除掉gradle依赖

buildscript {repositories {google()jcenter()}dependencies {
// compile 'com.android.tools.build:gradle:2.3.0'}
}

然后rebuild一下,就可以在External Libraries中查看到android gradle的源码已经依赖了

二、Android Gradle Plugin简介

我们知道Android gradle plugin是用来构建Android工程的gradle插件,在Android gradle 插件中,可以看到app工程和library工程所依赖的plugin是不一样的

// app 工程
apply plugin: 'com.android.application'
// library 工程
apply plugin: 'com.android.library'

而对应填写andorid块中所填写的配置也不同,这就是区分Application和Library的插件的extension块

分别为:

app工程 -> AppPlugin -> AppExtension
librar工程 -> LibraryPlugin -> LibraryExtension

对应的是AppPlugin和AppExtension,这两个插件构建的流程大抵是相同的,只是各自插件生成的任务不同,接下来我们着重分析Application插件是如何构建我们的Android应用的

三、AppPlugin的构建流程

我们先看下app工程中gradle的文件格式

apply plugin: 'com.android.application'
android {compileSdkVersion 25buildToolsVersion '26.0.2'defaultConfig {applicationId "com.zengshaoyi.gradledemo"minSdkVersion 15targetSdkVersion 25versionCode project.ext.versionCodeversionName project.ext.versionNametestInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}lintOptions {abortOnError false}
}

跟踪apply方法,其实是进入到

AppPlugin的apply的方法,我们可以看到内部实现是直接调用父类BasePlugin的apply方法

protected void apply(@NonNull Project project) {checkPluginVersion();this.project = project;ExecutionConfigurationUtil.setThreadPoolSize(project);checkPathForErrors();checkModulesForErrors();ProfilerInitializer.init(project);threadRecorder = ThreadRecorder.get();ProcessProfileWriter.getProject(project.getPath()).setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION).setAndroidPlugin(getAnalyticsPluginType()).setPluginGeneration(GradleBuildProject.PluginGeneration.FIRST);threadRecorder.record(ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,project.getPath(),null,this::configureProject);threadRecorder.record(ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,project.getPath(),null,this::configureExtension);threadRecorder.record(ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,project.getPath(),null,this::createTasks);// Apply additional pluginsfor (String plugin : AndroidGradleOptions.getAdditionalPlugins(project)) {project.apply(ImmutableMap.of("plugin", plugin));}}

threadRecoirder.recode()是记录最后一个参数的路径和执行的时间点,前面做了一些必要性的信息检测之前,其实主要做了以下几件事情:

// 配置项目,设置构建回调
this::configureProject
// 配置Extension
this::configureExtension
// 创建任务
this::createTasks

::是java 8引入的特性,详情可以查看java8特性 ,这里就是方法的调用

configureProject

直接来看源码

private void configureProject() {extraModelInfo = new ExtraModelInfo(project);checkGradleVersion();AndroidGradleOptions.validate(project);// Android SDK处理类sdkHandler = new SdkHandler(project, getLogger());// 设置项目评估阶段回调project.afterEvaluate(p -> {// TODO: Read flag from extension.if (!p.getGradle().getStartParameter().isOffline()&& AndroidGradleOptions.getUseSdkDownload(p)) {// 相关配置依赖的下载处理 SdkLibData sdkLibData =SdkLibData.download(getDownloader(), getSettingsController());dependencyManager.setSdkLibData(sdkLibData);sdkHandler.setSdkLibData(sdkLibData);}});// 创建AndroidBuilderandroidBuilder = new AndroidBuilder(project == project.getRootProject() ? project.getName() : project.getPath(),creator,new GradleProcessExecutor(project),new GradleJavaProcessExecutor(project),extraModelInfo,getLogger(),isVerbose());// dataBinding的相关处理dataBindingBuilder = new DataBindingBuilder();dataBindingBuilder.setPrintMachineReadableOutput(extraModelInfo.getErrorFormatMode() ==ExtraModelInfo.ErrorFormatMode.MACHINE_PARSABLE);// Apply the Java and Jacoco plugins.project.getPlugins().apply(JavaBasePlugin.class);project.getPlugins().apply(JacocoPlugin.class);// 给assemble任务添加描述project.getTasks().getByName("assemble").setDescription("Assembles all variants of all applications and secondary packages.");...

可以看到 configureProject 方法中在 project.afterEvaluate 设置了回调,当项目评估结束时,根据项目配置情况,设置 dependece 依赖;创建了 AndroidBuilder 对象,这个对象是用来合并manifest 和创建 dex 等作用,后面在创建任务的过程中会使用到,结下来继续看 configureProject 的源码

 // call back on execution. This is called after the whole build is done (not// after the current project is done).// This is will be called for each (android) projects though, so this should support// being called 2+ times.// 设置构建回调project.getGradle().addBuildListener(new BuildListener() {private final LibraryCache libraryCache = LibraryCache.getCache();@Overridepublic void buildStarted(Gradle gradle) {}@Overridepublic void settingsEvaluated(Settings settings) {}@Overridepublic void projectsLoaded(Gradle gradle) {}@Overridepublic void projectsEvaluated(Gradle gradle) {}@Overridepublic void buildFinished(BuildResult buildResult) {ExecutorSingleton.shutdown();sdkHandler.unload();threadRecorder.record(ExecutionType.BASE_PLUGIN_BUILD_FINISHED,project.getPath(),null,() -> {// 当任务执行完成时,清楚dex缓存PreDexCache.getCache().clear(FileUtils.join(project.getRootProject().getBuildDir(),FD_INTERMEDIATES,"dex-cache","cache.xml"),getLogger());JackConversionCache.getCache().clear(FileUtils.join(project.getRootProject().getBuildDir(),FD_INTERMEDIATES,"jack-cache","cache.xml"),getLogger());libraryCache.unload();Main.clearInternTables();});}});// 设置创建有向图任务回调project.getGradle().getTaskGraph().addTaskExecutionGraphListener(taskGraph -> {for (Task task : taskGraph.getAllTasks()) {// TransformTask是class编译成dex的重要任务if (task instanceof TransformTask) {Transform transform = ((TransformTask) task).getTransform();if (transform instanceof DexTransform) {PreDexCache.getCache().load(FileUtils.join(project.getRootProject().getBuildDir(),FD_INTERMEDIATES,"dex-cache","cache.xml"));break;} else if (transform instanceof JackPreDexTransform) {JackConversionCache.getCache().load(FileUtils.join(project.getRootProject().getBuildDir(),FD_INTERMEDIATES,"jack-cache","cache.xml"));break;}}}});

这里在添加了 BuildListener,在 buildFinished 的时候清楚了dex缓存,而在任务有向图创建的回调中,判断是否是 DexTransfrom,从而从缓存中加载dex。

总结一下 configureProject 做的事情,主要是进行版本有效性的判断,创建了 AndroidBuilder 对象,并设置了构建流程的回调来处理依赖和dex的加载和缓存清理。

configureExtension

这个阶段就是配置 extension 的阶段,就是创建我们 android 块中的可配置的对象

private void configureExtension() {final NamedDomainObjectContainer<BuildType> buildTypeContainer =project.container(BuildType.class,new BuildTypeFactory(instantiator, project, project.getLogger()));final NamedDomainObjectContainer<ProductFlavor> productFlavorContainer =project.container(ProductFlavor.class,new ProductFlavorFactory(instantiator, project, project.getLogger(), extraModelInfo));final NamedDomainObjectContainer<SigningConfig> signingConfigContainer =project.container(SigningConfig.class, new SigningConfigFactory(instantiator));extension =createExtension(project,instantiator,androidBuilder,sdkHandler,buildTypeContainer,productFlavorContainer,signingConfigContainer,extraModelInfo);...

首先创建了 BuildType、ProductFlavor、SigningConfig 三个类型的Container,接着传入到了createExtension方法中,点入查看是个抽象的方法,各自的实现在子类中,这里也就是我们的AppPlugin 中

@NonNull@Overrideprotected BaseExtension createExtension(@NonNull Project project,@NonNull Instantiator instantiator,@NonNull AndroidBuilder androidBuilder,@NonNull SdkHandler sdkHandler,@NonNull NamedDomainObjectContainer<BuildType> buildTypeContainer,@NonNull NamedDomainObjectContainer<ProductFlavor> productFlavorContainer,@NonNull NamedDomainObjectContainer<SigningConfig> signingConfigContainer,@NonNull ExtraModelInfo extraModelInfo) {return project.getExtensions().create("android",AppExtension.class,project,instantiator,androidBuilder,sdkHandler,buildTypeContainer,productFlavorContainer,signingConfigContainer,extraModelInfo);}

这里也就是可以看到我们android块配置是如何来的了,对应的Extension也确实是AppExtension,继续查看 configureExtension 的源码

 dependencyManager = new DependencyManager(project,extraModelInfo,sdkHandler);ndkHandler = new NdkHandler(project.getRootDir(),null, /* compileSkdVersion, this will be set in afterEvaluate */"gcc","" /*toolchainVersion*/);taskManager =createTaskManager(project,androidBuilder,dataBindingBuilder,extension,sdkHandler,ndkHandler,dependencyManager,registry,threadRecorder);variantFactory = createVariantFactory(instantiator, androidBuilder, extension);variantManager =new VariantManager(project,androidBuilder,extension,variantFactory,taskManager,instantiator,threadRecorder);// Register a builder for the custom tooling modelModelBuilder modelBuilder = new ModelBuilder(androidBuilder,variantManager,taskManager,extension,extraModelInfo,ndkHandler,new NativeLibraryFactoryImpl(ndkHandler),getProjectType(),AndroidProject.GENERATION_ORIGINAL);registry.register(modelBuilder);// Register a builder for the native tooling modelNativeModelBuilder nativeModelBuilder = new NativeModelBuilder(variantManager);registry.register(nativeModelBuilder);

这一部分主要是创建一些管理类,其中 createTaskManager、createVariantFactory 都是抽象方法,对应的实现类

createTaskManager
AppPlugin -> ApplicationTaskManager
LibraryPlugin -> LibraryTaskManager
createVariantFactory
AppPlugin -> ApplicationVariantFactory
LibraryPlugin -> LibraryVariantFactory

这里简单介绍一下 TaskManager 就是创建具体任务的管理类,app 工程和库 library 工程所需的构建任务是不同的,后面我们会介绍 app 工程创建的构建任务;VariantFactory 就是我们常说的构建变体的工厂类,主要是生成Variant(构建变体)的对象。我们回到 createExtension 的源码中

 // map the whenObjectAdded callbacks on the containers.signingConfigContainer.whenObjectAdded(variantManager::addSigningConfig);buildTypeContainer.whenObjectAdded(buildType -> {SigningConfig signingConfig =signingConfigContainer.findByName(BuilderConstants.DEBUG);buildType.init(signingConfig);variantManager.addBuildType(buildType);});productFlavorContainer.whenObjectAdded(variantManager::addProductFlavor);...// create default Objects, signingConfig first as its used by the BuildTypes.variantFactory.createDefaultComponents(buildTypeContainer, productFlavorContainer, signingConfigContainer);

这一部分做得事情,配置了 BuildTypeContainer、ProductFlavorContainer、SigningConfigContainer 这三个配置项的 whenObjectAdded 的回调,每个配置的添加都会加入到 variantManager 中;创建默认配置,下面是 ApplicationVariantFactory 的 createDefaultComponents 代码

 @Overridepublic void createDefaultComponents(@NonNull NamedDomainObjectContainer<BuildType> buildTypes,@NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,@NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs) {// must create signing config first so that build type 'debug' can be initialized// with the debug signing config.signingConfigs.create(DEBUG);buildTypes.create(DEBUG);buildTypes.create(RELEASE);}

总结一下 configureExtension 方法的作用,主要是创建 Android 插件的扩展对象,对配置项 BuildType、ProductFlavor、SigningConfig 做了统一的创建和回调处理, 创建taskManager、variantFactory、variantManager。

createTasks

private void createTasks() {threadRecorder.record(ExecutionType.TASK_MANAGER_CREATE_TASKS,project.getPath(),null,() -> // 在项目评估之前创建任务 taskManager.createTasksBeforeEvaluate(new TaskContainerAdaptor(project.getTasks())));project.afterEvaluate(project ->threadRecorder.record(ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,project.getPath(),null,// 在项目评估完成之后创建 androidTask() -> createAndroidTasks(false)));}

这里主要是分两块,一个是在 beforeEvaluate 创建任务;一个是在 afterEvaluate 创建任务。这里的区别是 AndroidTask 是依赖配置项的配置才能生成相应任务,所以是需要在 afterEvaluate 之后创建,如果对项目评估回调不理解的话,可以查阅Project文档。beforeEvaluate 创建的任务跟我们编译没有太大关系,我们重点查看一下 afterEvaluate 创建的任务 createAndroidTasks

 @VisibleForTestingfinal void createAndroidTasks(boolean force) {...threadRecorder.record(ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,project.getPath(),null,() -> {// 创建AndroidTasksvariantManager.createAndroidTasks();ApiObjectFactory apiObjectFactory =new ApiObjectFactory(androidBuilder, extension, variantFactory, instantiator);for (BaseVariantData variantData : variantManager.getVariantDataList()) {apiObjectFactory.create(variantData);}});...}

我们主要看下variantManager的createAndroidTasks的方法

 /*** Variant/Task creation entry point.** Not used by gradle-experimental.*/public void createAndroidTasks() {variantFactory.validateModel(this);variantFactory.preVariantWork(project);final TaskFactory tasks = new TaskContainerAdaptor(project.getTasks());if (variantDataList.isEmpty()) {recorder.record(ExecutionType.VARIANT_MANAGER_CREATE_VARIANTS,project.getPath(),null /*variantName*/,this::populateVariantDataList);}// Create top level test tasks.recorder.record(ExecutionType.VARIANT_MANAGER_CREATE_TESTS_TASKS,project.getPath(),null /*variantName*/,() -> taskManager.createTopLevelTestTasks(tasks, !productFlavors.isEmpty()));for (final BaseVariantData<? extends BaseVariantOutputData> variantData : variantDataList) {recorder.record(ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,project.getPath(),variantData.getName(),() -> createTasksForVariantData(tasks, variantData));}taskManager.createReportTasks(tasks, variantDataList);}

首先判断 variantDataList 是否是空,如果是空的就会进入到 populateVariantDataList 方法中

/*** Create all variants.*/public void populateVariantDataList() {if (productFlavors.isEmpty()) {createVariantDataForProductFlavors(Collections.emptyList());} else {List<String> flavorDimensionList = extension.getFlavorDimensionList();// Create iterable to get GradleProductFlavor from ProductFlavorData.Iterable<CoreProductFlavor> flavorDsl =Iterables.transform(productFlavors.values(),ProductFlavorData::getProductFlavor);// Get a list of all combinations of product flavors.List<ProductFlavorCombo<CoreProductFlavor>> flavorComboList =ProductFlavorCombo.createCombinations(flavorDimensionList,flavorDsl);for (ProductFlavorCombo<CoreProductFlavor> flavorCombo : flavorComboList) {//noinspection uncheckedcreateVariantDataForProductFlavors((List<ProductFlavor>) (List) flavorCombo.getFlavorList());}}}

从方法注释可以看到,这个方法主要的作用就是创建所有的 variants,试想一下该段代码会做哪些事情,是否是解析 buildType、productFlavor 配置?

创建构建变体(BuildVariant)

继续观察上面的代码,可以看到无论是否有配置productFlavor 子项,都会进入到 createVariantDataForProductFlavors 方法。如果有配置的话,通过获取配置的 flavorDimension 和 productFlavor 数组,调用 ProductFlavorCombo.createCombinations 组合出最后的产品风味数组 flavorComboList ,最后通过遍历调用 createVariantDataForProductFlavors 方法

 /*** Creates VariantData for a specified list of product flavor.** This will create VariantData for all build types of the given flavors.** @param productFlavorList the flavor(s) to build.*/private void createVariantDataForProductFlavors(@NonNull List<ProductFlavor> productFlavorList) {...for (BuildTypeData buildTypeData : buildTypes.values()) {boolean ignore = false;...if (!ignore) {BaseVariantData<?> variantData = createVariantData(buildTypeData.getBuildType(),productFlavorList);variantDataList.add(variantData);...}}...
}

看上述代码,通过 creatVariantData 方法,将 buildType 和 productFlavor 的作为参数传入,创建了 variantData,并且加入到了 variantDataList 集合中,这里我们就是将所有的构建变体集合到了 variantDataList 中。

接着我们返回继续看 createAndroidTasks 方法

 /*** Variant/Task creation entry point.** Not used by gradle-experimental.*/public void createAndroidTasks() {...for (final BaseVariantData<? extends BaseVariantOutputData> variantData : variantDataList) {recorder.record(ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,project.getPath(),variantData.getName(),() -> createTasksForVariantData(tasks, variantData));}...}

通过上面拿到的variantDataList,遍历该集合来创建任务

 /*** Create tasks for the specified variantData.*/public void createTasksForVariantData(final TaskFactory tasks,final BaseVariantData<? extends BaseVariantOutputData> variantData) {final BuildTypeData buildTypeData = buildTypes.get(variantData.getVariantConfiguration().getBuildType().getName());if (buildTypeData.getAssembleTask() == null) {// 创建assemble + buildType任务buildTypeData.setAssembleTask(taskManager.createAssembleTask(tasks, buildTypeData));}// Add dependency of assemble task on assemble build type task.tasks.named("assemble", new Action<Task>() {@Overridepublic void execute(Task task) {assert buildTypeData.getAssembleTask() != null;// 将 assemble 任务依赖于我们的 assemble + buildType 任务task.dependsOn(buildTypeData.getAssembleTask().getName());}});VariantType variantType = variantData.getType();// 根据 variantData 创建 assemble + flavor + buildType 任务createAssembleTaskForVariantData(tasks, variantData);if (variantType.isForTesting()) {...} else {// 根据 variantData 创建一系列任务taskManager.createTasksForVariantData(tasks, variantData);}}

首先会先根据 buildType 信息创建 assemble + buildType 的任务,可以看下taskManager. createAssembleTask里的代码

 @NonNullpublic AndroidTask<DefaultTask> createAssembleTask(@NonNull TaskFactory tasks,@NonNull VariantDimensionData dimensionData) {final String sourceSetName =StringHelper.capitalize(dimensionData.getSourceSet().getName());return androidTasks.create(tasks,// 设置任务名字为 assembleXXX"assemble" + sourceSetName,assembleTask -> {// 设置描述和任务组assembleTask.setDescription("Assembles all " + sourceSetName + " builds.");assembleTask.setGroup(BasePlugin.BUILD_GROUP);});}

创建完任务之后,将assemble任务依赖于我们的assembleXXX任务,随后调用 createAssembleTaskForVariantData 方法,此方法是创建 assemble + flavor + buildType 任务,流程多了 productFlavor 任务的创建,这里就不赘述了。后面会执 createTasksForVariantData,这个方法就是根据 variant 生成一系列 Android 构建所需任务(后面会详细介绍),回到 createAndroidTasks 方法中

threadRecorder.record(ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,project.getPath(),null,() -> {variantManager.createAndroidTasks();ApiObjectFactory apiObjectFactory =new ApiObjectFactory(androidBuilder, extension, variantFactory, instantiator);for (BaseVariantData variantData : variantManager.getVariantDataList()) {// 创建variantApi,添加到extensions中apiObjectFactory.create(variantData);}});

最后就遍历 variantDataList 通过 ApiObjectFactory 创建 variantApi,添加到 extensions 中。至此,我们就已经将配置的构建变种任务已经添加到我们的任务列表中,并形成了相关依赖。
    一篇文太长,还有一半下一章发出来。

最后

感谢你到这里,喜欢的话请帮忙点个赞让更多需要的人看到哦。更多Android进阶技术,面试资料整理分享,职业生涯规划,产品,思维,行业观察,谈天说地。可以加Android架构师群;701740775。

相关文章:

Guice系列之用户指南(七)

原文地址&#xff1a;https://code.google.com/p/google-guice/wiki/ToConstructorBindings Constructor Bindings&#xff08;构造器绑定&#xff09;&#xff1a;在父类型上绑定子类实现的构造函数。 贴代码&#xff1a; 12345678910111213141516171819202122232425262728293…

Linux系统火焰图

CentOS7.8 安装perf #yum install perf 执行perf 执行perf record 命令&#xff0c;记录该PID的行为 #perf record -a -g -p 14851 -- sleep 30 --30秒后退出 需要注意后面生成svg图片的所有命令要和当前perf在同一目录&#xff0c;不然会报错。 #perf report 安装git …

深圳杯---垃圾焚烧厂的经济补偿问题

垃圾围城是世界性难题&#xff0c;在今天的中国显得尤为突出。2012年全国城市生活垃圾清运量达到1.71亿吨&#xff0c;比2010年增长了1300万吨。数据显示&#xff0c;目前全国三分之二以上的城市面临垃圾围城问题&#xff0c;垃圾堆放累计侵占土地75万亩。因此&#xff0c;垃圾…

make -j8以及linux下查看cpu的核数

# 总核数 物理CPU个数 X 每颗物理CPU的核数 # 总逻辑CPU数 物理CPU个数 X 每颗物理CPU的核数 X 超线程数# 查看物理CPU个数 cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l# 查看每个物理CPU中core的个数(即核数) cat /proc/cpuinfo| grep "cpu …

IDEA2021.3.2拉取maven报错maven-default-http-blocker解决方法

因为IDEA2021.3.2 的Maven是3.8.1后&#xff0c;mvn编译的时候总是提示拉不到依赖&#xff0c;报错如下&#xff1a; Could not validate integrity of download from http://0.0.0.0/... 因为使用HTTP协议下载依赖&#xff0c;可能会导致中间人攻击。 所以Maven 3.8.1就禁止…

2013高教社杯---B碎纸片的拼接复原

破碎文件的拼接在司法物证复原、历史文献修复以及军事情报获取等领域都有着重要的应用。传统上&#xff0c;拼接复原工作需由人工完成&#xff0c;准确率较高&#xff0c;但效率很低。特别是当碎片数量巨大&#xff0c;人工拼接很难在短时间内完成任务。随着计算机技术的发展&a…

oracle--with as

with as把一段查询结果放在临时表&#xff0c;后面的查询中可多次使用 语法&#xff1a; with 别名 as(select * from table) 或 with 别名1 as(select * from table1), ............. 别名n as(select * from tablen) 示例&#xff1a; with 别名 as(select * from table wher…

Flask上下文管理源码分析

略略略...转载于:https://www.cnblogs.com/dzf123456/p/9446220.html

IDEA函数调用关系图插件

Call Graph是一款IDEA插件&#xff0c;用于可视化基于IntelliJ平台的IDE的函数调用图。 这个插件的目标是让代码更容易理解&#xff0c;有助于读懂和调试代码。 安装插件 安装后&#xff0c;通过View - Tool Windows - Call Graph &#xff0c;激活窗口 激活后&#xff0c;需要…

[Notice]博客地址转移 vitostack.com

个人博客地址转移至vitostack.com 这里可能不会经常更新。 欢迎访问新地址。 转载于:https://www.cnblogs.com/Vito2008/p/5595430.html

【MATLAB】find 函数 总结

【MATLAB版本为2014a】 MATLAB中函数find函数的作用是进行矩阵元素的查找&#xff0c;它通常与关系函数和逻辑运算相结合。 indfind(X,...)&#xff1a;该函数查找矩阵中的非零元素&#xff0c;函数返回这些元素的双下标[row,col]find(X,...)&#xff1a;该函数查找矩阵X中的…

与HTTP关系密切的协议:IP、TCP、DNS

TCP/IP协议族的协议挺多的&#xff0c;我们精力有限&#xff0c;不可能一个个都了如指掌&#xff0c;那就挑一些与HTTP协议关系了解吧~ 负责传输的IP协议 按层次分&#xff0c;IP协议位于网络层。 IP协议的作用是把各种数据包传送给对方。而要保证确实传送到对方那里&#xff0…

C#精髓 第四讲 GridView 72般绝技

说明&#xff1a;准备出一个系列&#xff0c;所谓精髓讲C#语言要点。这个系列没有先后顺序&#xff0c;不过尽量做到精。可能会不断增删整理&#xff0c;本系列最原始出处是csdn博客,谢谢关注。 C#精髓 第四讲 GridView 72般绝技 作者&#xff1a;清清月儿 主页&#xff1a;ht…

Android layer-list(3)

&#xfeff;&#xfeff;Android layer-list&#xff08;3&#xff09; 在附录文章3、4的基础上&#xff0c;就Android layer-list再写一个较为复杂的应用。 先写布局文件&#xff0c;该布局涉及到LinearLayoutCompat&#xff0c;关于LinearLayoutCompat参看附录文章5。 布局文…

【MATLAB】二维矩阵可视化 MATLAB绘图

基本绘图函数 1、plot(y) 如果y是向量&#xff0c;则采用向量的索引值作为横坐标值&#xff0c;以向量元素的值作为纵坐标值。 如果y是实数矩阵&#xff0c;则相当于对y的每一列进行二维绘图。 如果y是复数组成的向量&#xff0c;则相当于plot(real(y),imag(y))。 2、plot…

phpstudy一个域名配置两个网站(一个是thinkphp5,一个是原生php)

phpstudy一个域名配置两个网站&#xff08;一个是thinkphp5&#xff0c;一个是原生php&#xff09; 一、总结 一句话总结&#xff1a;把原生php的网站直接放到thinkphp5的public目录下可以解决以stem.aaaa.hk\**方式访问原生网站会被当成thinkphp子模块的问题 最后的解决方法&a…

bzoj3442 学习小组

目前处于迷之TLE状态 -----6.21更新 已AC 3442: 学习小组 Time Limit: 5 Sec Memory Limit: 128 MBSubmit: 200 Solved: 87Description 【背景】坑校准备鼓励学生参加学习小组。【描述】共有n个学生&#xff0c;m个学习小组&#xff0c;每个学生有一定的喜好&#xff0c;只愿…

C语言经典著作导读

本人不是卖书的&#xff0c;我也不会给出任何购书链接&#xff0c;只是给C语言学习者推荐一条学习的方向。如果你喜欢看电子书网上很多&#xff0c;如果你喜欢纸质那么就买吧&#xff0c;经典的书值得收藏&#xff0c;是对版权的尊重&#xff01; 基础篇 1.《写给大家看的C语言…

针对2013年B题碎纸片拼接问题(附件一、附件二)

题目链接&#xff1a;https://blog.csdn.net/CSDN___CSDN/article/details/82051821 http://www.shumo.com/wiki/doku.php?id2013_%E5%B9%B4%E5%85%A8%E5%9B%BD%E5%A4%A7%E5%AD%A6%E7%94%9F%E6%95%B0%E5%AD%A6%E5%BB%BA%E6%A8%A1%E7%AB%9E%E8%B5%9B_cumcm_%E8%AF%95%E9%A2%98…

什么是类型别名?什么是潜在类型?

2019独角兽企业重金招聘Python工程师标准>>> 别名类型 在Go语言里&#xff0c;可以用type声明自定义的各种类型。在这些自定义的类型中&#xff0c;有一种被叫做别名类型。 举个例子&#xff1a; type MyString string这句代码的意思是&#xff1a;MyString是strin…

Linux网络编程必看书籍推荐

首先要说讲述计算机网络和TCP/IP的书很多。 先要学习网络知识才谈得上编程 讲述计算机网络的最经典的当属Andrew S&#xff0e;Tanenbaum的《计算机网络》第五版&#xff0c;这本书难易适中。 《计算机网络&#xff08;第5版&#xff09;》是国内外使用最广泛、最权威的计算机…

5个最佳的Android测试框架

2019独角兽企业重金招聘Python工程师标准>>> 谷歌的Android生态系统正在不断地迅速扩张。有证据表明&#xff0c;新的移动OEM正在攻陷世界的每一个角落&#xff0c;不同的屏幕尺寸、ROM /固件、芯片组以及等等等等&#xff0c;层出不穷。于是乎&#xff0c;对于Andr…

【CTF】实验吧 凯撒变异

通过分析可以知道前四个“afZ_”四个的ASCII码值与“flag”的ASCII码值依次相差5&#xff0c;6&#xff0c;7&#xff0c;8。 #include <stdio.h> #include <string.h> int main () {char str[40]"afZ_r9VYfScOeO_UL^RWUc";int i0,j5;while(i<strlen…

ant design pro (八)构建和发布

一、概述 原文地址&#xff1a;https://pro.ant.design/docs/deploy-cn 二、详细 2.1、构建 当项目开发完毕&#xff0c;只需要运行一行命令就可以打包你的应用&#xff1a; npm run build 由于 Ant Design Pro 底层使用的 roadhog 工具&#xff0c;已经将复杂的流程封装完毕&a…

Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存

Linux进程间通信--进程&#xff0c;信号&#xff0c;管道&#xff0c;消息队列&#xff0c;信号量&#xff0c;共享内存 参考&#xff1a;《linux编程从入门到精通》,《Linux C程序设计大全》,《unix环境高级编程》 参考&#xff1a;C和指针学习 说明&#xff1a;本文非常的长…

PgSQL · 实战经验 · 如何预测Freeze IO风暴

背景和原理 有没有被突发的IO惊到过&#xff0c;有没有见到过大量的autovacuum for prevent wrap。 PostgreSQL 的版本冻结是一个比较蛋疼的事情&#xff0c;为什么要做版本冻结呢&#xff1f; 因为PG的版本号是uint32的&#xff0c;是重复使用的&#xff0c;所以每隔大约20亿…

【CTF】实验吧 传统知识+古典密码

对照顺序写下&#xff1a; 根据对应的干支得到 28 30 23 8 17 10 16 30 甲子 所有的数加60 得到 88 90 83 68 77 70 76 90 找到ASCII码对照表可得到XZSDMFLZ 题干中提到古典密码&#xff08;常用的就是栅栏密码和凯撒密码&#xff09; 栅栏密码&#xff08;两栏&#…

NSSize 尺寸

前言 结构体&#xff0c;这个结构体用来表示事物的宽度和高度。typedef CGSize NSSize;struct CGSize {CGFloat width;CGFloat height; };typedef struct CGSize CGSize; 1、NSSize 结构体变量的创建与调用 // NSSize 结构体变量的创建与赋值// 先定义变量&#xff0c;再赋值 N…

Android中对Log日志文件的分析[转]

一&#xff0c;Bug出现了&#xff0c; 需要“干掉”它 bug一听挺吓人的&#xff0c;但是只要你懂了&#xff0c;android里的bug是很好解决的&#xff0c;因为android里提供了LOG机制&#xff0c;具体的底层代码&#xff0c;以后在来分析&#xff0c;只要你会看bug&#xff0c;a…

Unix下C程序内存泄漏检测工具Valgrind安装与使用

Valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。 Valgrind的最初作者是Julian Seward&#xff0c;他于2006年由于在开发Valgrind上的工作获得了第二届Google-OReilly开源代码奖。 Valgrind遵守GNU通用公共许可证条款&#xff0c;是一款自由软件。 官网…