【Android 修炼手册】Gradle 篇 -- Android Gradle Plugin 主要 Task 分析(1)-程序员宅基地

技术标签: 程序员  android  

mergeDebugResources
processDebugResources
transformClassesWithDexBuilderForDebug
transformDexArchiveWithExternalLibsDexMergerForDebug
transformDexArchiveWithDexMergerForDebug

分析过程主要下面几个步骤,实现类,整体实现图,调用链路(方便以后回看代码),以及重要代码分析

4.1 generateDebugBuildConfig
4.1.1 实现类

GenerateBuildConfig

4.1.2 整体实现图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.1.3 代码调用链路

GenerateBuildConfig.generate -> BuildConfigGenerator.generate -> JavaWriter

4.1.4 主要代码分析

在 GenerateBuildConfig 中,主要生成代码的步骤如下:

  1. 生成 BuildConfigGenerator
  2. 添加默认的属性,包括 DEBUG,APPLICATION_ID,FLAVOR,VERSION_CODE,VERSION_NAME
  3. 添加自定义属性
  4. 调用 JavaWriter 生成 BuildConfig.java 文件

// GenerateBuildConfig.generate()
@TaskAction
void generate() throws IOException {
// …
BuildConfigGenerator generator = new BuildConfigGenerator(
getSourceOutputDir(),
getBuildConfigPackageName());
// 添加默认的属性,包括 DEBUG,APPLICATION_ID,FLAVOR,VERSION_CODE,VERSION_NAME
generator
.addField(
“boolean”,
“DEBUG”,
isDebuggable() ? “Boolean.parseBoolean(“true”)” : “false”)
.addField(“String”, “APPLICATION_ID”, ‘"’ + appPackageName.get() + ‘"’)
.addField(“String”, “BUILD_TYPE”, ‘"’ + getBuildTypeName() + ‘"’)
.addField(“String”, “FLAVOR”, ‘"’ + getFlavorName() + ‘"’)
.addField(“int”, “VERSION_CODE”, Integer.toString(getVersionCode()))
.addField(
“String”, “VERSION_NAME”, ‘"’ + Strings.nullToEmpty(getVersionName()) + ‘"’)
.addItems(getItems()); // 添加自定义属性

List flavors = getFlavorNamesWithDimensionNames();
int count = flavors.size();
if (count > 1) {
for (int i = 0; i < count; i += 2) {
generator.addField(
“String”, “FLAVOR_” + flavors.get(i + 1), ‘"’ + flavors.get(i) + ‘"’);
}
}

// 内部调用 JavaWriter 生成 java 文件
generator.generate();
}

4.2 mergeDebugResources
4.2.1 实现类

MergeResources

4.2.2 整体实现图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.2.3 调用链路

MergeResources.doFullTaskAction -> ResourceMerger.mergeData -> MergedResourceWriter.end -> QueueableAapt2.compile -> Aapt2QueuedResourceProcessor.compile -> AaptProcess.compile -> AaptV2CommandBuilder.makeCompile

4.2.4 主要代码分析

MergeResources 这个类,继承自 IncrementalTask,按照前面说的阅读增量 Task 代码的步骤,依次看三个方法的实现:isIncremental,doFullTaskAction,doIncrementalTaskAction

  • isIncremental

// 说明 Task 支持增量
protected boolean isIncremental() {
return true;
}

  • doFullTaskAction
  1. 通过 getConfiguredResourceSets() 获取 resourceSets,包括了自己的 res/ 和 依赖库的 res/ 以及 build/generated/res/rs

// MergeResources.doFullTaskAction()
List resourceSets = getConfiguredResourceSets(preprocessor);

  1. 创建 ResourceMerger

// MergeResources.doFullTaskAction()
ResourceMerger merger = new ResourceMerger(minSdk);

  1. 创建 QueueableResourceCompiler,因为 gradle3.x 以后支持了 aapt2,所以这里有两种选择 aapt 和 aapt2。其中 aapt2 有三种模式,OutOfProcessAaptV2,AaptV2Jni,QueueableAapt2,这里默认创建了 QueueableAapt2,resourceCompiler = QueueableAapt2

// MergeResources.doFullTaskAction()
// makeAapt 中会判断使用 aapt 还是 aapt2,这里以 aapt2 为例,返回的是 QueueableAapt2 对象
QueueableResourceCompiler resourceCompiler =
makeAapt(
aaptGeneration,
getBuilder(),
fileCache,
crunchPng,
variantScope,
getAaptTempDir(),
mergingLog)

  1. 将第一步获取的 resourceSet 加入 ResourceMerger 中

for (ResourceSet resourceSet : resourceSets) {
resourceSet.loadFromFiles(getILogger());
merger.addDataSet(resourceSet);
}

  1. 创建 MergedResourceWriter
  2. 调用 ResourceMerger.mergeData 合并资源

// MergeResources.doFullTaskAction()
merger.mergeData(writer, false /doCleanUp/);

  1. 调用 MergedResourceWriter 的 start(),addItem(),end() 方法,伪代码如下:

// DataMerger.mergeData
consumer.start()
for item in sourceSets:
// item 包括了需要处理的资源,包括 xml 和 图片资源,每一个 item 对应的文件,会创建一个 CompileResourceRequest 对象,加入到 mCompileResourceRequests 里
consumer.addItem(item)
consumer.end()

  1. 调用 QueueableAapt2 -> Aapt2QueuedResourceProcessor -> AaptProcess 处理资源

// MergedResourceWriter.end()
Future result = this.mResourceCompiler.compile(new CompileResourceRequest(fileToCompile, request.getOutput(), request.getFolderName(), this.pseudoLocalesEnabled, this.crunchPng));
// AaptProcess.compile
public void compile(
@NonNull CompileResourceRequest request,
@NonNull Job job,
@Nullable ProcessOutputHandler processOutputHandler)
throws IOException {
// …
// 使用 AaptV2CommandBuilder 生成 aapt2 命令
mWriter.write(joiner.join(AaptV2CommandBuilder.makeCompile(request)));
mWriter.flush(); // 输出命令
}

这一步调用 aapt2 命令去处理资源,处理完以后 xxx.xml.flat 格式

  • doIncrementalTaskAction
    增量任务过程和全量其实差异不大,只不过是在获取 resourceSets 的时候,使用的是修改后的文件
4.3 processDebugResources
4.3.1 实现类

ProcessAndroidResources

4.3.2 整体实现图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.3.3 调用链路

ProcessAndroidResources.doFullTaskAction -> ProcessAndroidResources.invokeAaptForSplit -> AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink

4.3.4 主要代码分析

ProcessAndroidResources 也是继承自 IncrementalTask,但是没有重写 isIncremental,所以不是增量的 Task,直接看 doFullTaskAction 即可

  • doFullTaskAction
    这个里面代码虽然多,但是主要的逻辑比较简单,就是调用 aapt2 link 去生成资源包。
    这里会处理 splits apk 相关的内容,关于 splits apk 具体可以查看 splits apk,简单来说,就是可以按照屏幕分辨率,abis 来生成不同的 apk,从而让特定用户的安装包变小。
    分下面几个步骤:
  1. 获取 split 数据

List splitsToGenerate =
getApksToGenerate(outputScope, supportedAbis, buildTargetAbi, buildTargetDensity);

返回的是一个 ApkData 列表,ApkData 有三个子类,分别是 Main,Universal,FullSplit
我们配置 如下:

android {
splits {
// Configures multiple APKs based on screen density.
density {
// Configures multiple APKs based on screen density.
enable true
// Specifies a list of screen densities Gradle should not create multiple APKs for.
exclude “ldpi”, “xxhdpi”, “xxxhdpi”
// Specifies a list of compatible screen size settings for the manifest.
compatibleScreens ‘small’, ‘normal’, ‘large’, ‘xlarge’
}
}
}

这里的 ApkData 会返回一个 Universal 和多个 FullSplit,Universal 代表的是主 apk,FullSplit 就是根据屏幕密度拆分的 apk。
如果我们没有配置 splits apk,那么这里只会返回一个 Main 的实例,标识完整的 apk。
2. 先处理 main 和 不依赖 density 的 ApkData 资源

// ProcessAndroidResources.doFullTaskAction
List apkDataList = new ArrayList<>(splitsToGenerate);
for (ApkData apkData : splitsToGenerate) {
if (apkData.requiresAapt()) {
// 这里只处理 main 和不依赖 density 的资源
boolean codeGen =
(apkData.getType() == OutputFile.OutputType.MAIN
|| apkData.getFilter(OutputFile.FilterType.DENSITY) == null);
if (codeGen) {
apkDataList.remove(apkData);
invokeAaptForSplit(
manifestsOutputs,
libraryInfoList,
packageIdFileSet,
splitList,
featureResourcePackages,
apkData,
codeGen,
aapt);
break;
}
}
}

  1. 调用 invokeAaptForSplit 处理资源

// ProcessAndroidResources.invokeAaptForSplit
void invokeAaptForSplit(…) {
// …
String packageForR = null;
File srcOut = null;
File symbolOutputDir = null;
File proguardOutputFile = null;
File mainDexListProguardOutputFile = null;
// 如果传了 generateCode 参数,会生成 R.java
if (generateCode) {
packageForR = originalApplicationId;

// we have to clean the source folder output in case the package name changed.
srcOut = getSourceOutputDir();
if (srcOut != null) {
FileUtils.cleanOutputDir(srcOut);
}

symbolOutputDir = textSymbolOutputDir.get();
proguardOutputFile = getProguardOutputFile();
mainDexListProguardOutputFile = getMainDexListProguardOutputFile();
}
// …
getBuilder().processResources(aapt, config);
}

  1. 调用 AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink 处理资源,生成资源包以及 R.java 文件
  2. 处理其他 ApkData 资源,这里只会生成资源包而不会生成 R.java 文件

关于 aapt2 的 compile 和 link 参数,可以在 developer.android.com/studio/comm… 这里看

4.4 processDebugManifest
4.4.1 实现类

MergeManifests

4.4.2 整体实现图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.4.3 调用链路

MergeManifests.dofFullTaskAction -> AndroidBuilder.mergeManifestsForApplication -> Invoker.merge -> ManifestMerge2.merge

4.4.4 主要代码分析

MergeManifests 也是继承了 IncrementalTask,但是没有实现 isIncremental,所以只看其 doFullTaskAction 即可。
这个 task 功能主要是合并 mainfest,包括 module 和 flavor 里的,整个过程通过 MergingReport,ManifestMerger2 和 XmlDocument 进行。
这里直接看 ManifestMerger2.merge() 的 merge 过程 。 主要有几个步骤:

  1. 获取依赖库的 manifest 信息,用 LoadedManifestInfo 标识
  2. 获取主 module 的 manifest 信息
  3. 替换主 module 的 Manifest 中定义的某些属性,替换成 gradle 中定义的属性 例如: package, version_code, version_name, min_sdk_versin 等等

performSystemPropertiesInjection(mergingReportBuilder, xmlDocumentOptional.get());
// ManifestMerger2.performSystemPropertiesInjection
protected void performSystemPropertiesInjection(
@NonNull MergingReport.Builder mergingReport,
@NonNull XmlDocument xmlDocument) {
for (ManifestSystemProperty manifestSystemProperty : ManifestSystemProperty.values()) {
String propertyOverride = mSystemPropertyResolver.getValue(manifestSystemProperty);
if (propertyOverride != null) {
manifestSystemProperty.addTo(
mergingReport.getActionRecorder(), xmlDocument, propertyOverride);
}
}
}

  1. 合并 flavor,buildType 中的 manifest

for (File inputFile : mFlavorsAndBuildTypeFiles) {
LoadedManifestInfo overlayDocument = load(
new ManifestInfo(null, inputFile, XmlDocument.Type.OVERLAY,
Optional.of(mainPackageAttribute.get().getValue())),
selectors,
mergingReportBuilder);

// 检查 package 定义
Optional packageAttribute =
overlayDocument.getXmlDocument().getPackage();
if (loadedMainManifestInfo.getOriginalPackageName().isPresent() &&
packageAttribute.isPresent()
&& !loadedMainManifestInfo.getOriginalPackageName().get().equals(
packageAttribute.get().getValue())) {
// 如果 package 定义重复的话,会输出下面信息,我们平时应该或多或少见过类似的错误
String message = mMergeType == MergeType.APPLICATION
? String.format(
“Overlay manifest:package attribute declared at %1 s v a l u e = ( s value=(%2 svalue=(s)\n”

  • "\thas a different value=(%3$s) "
  • “declared in main manifest at %4$s\n”
  • "\tSuggestion: remove the overlay declaration at %5$s "
  • “\tand place it in the build.gradle:\n”
  • “\t\tflavorName {\n”
  • “\t\t\tapplicationId = “%2$s”\n”
  • “\t\t}”,
    packageAttribute.get().printPosition(),
    packageAttribute.get().getValue(),
    mainPackageAttribute.get().getValue(),
    mainPackageAttribute.get().printPosition(),
    packageAttribute.get().getSourceFile().print(true))
    : String.format(
    “Overlay manifest:package attribute declared at %1 s v a l u e = ( s value=(%2 svalue=(s)\n”
  • "\thas a different value=(%3$s) "
  • “declared in main manifest at %4$s”,
    packageAttribute.get().printPosition(),
    packageAttribute.get().getValue(),
    mainPackageAttribute.get().getValue(),
    mainPackageAttribute.get().printPosition());
    // …
    return mergingReportBuilder.build();
    }
    }
  1. 合并依赖库的 manifest

for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
mLogger.verbose("Merging library manifest " + libraryDocument.getLocation());
xmlDocumentOptional = merge(
xmlDocumentOptional, libraryDocument, mergingReportBuilder);
if (!xmlDocumentOptional.isPresent()) {
return mergingReportBuilder.build();
}
}

  1. 处理 manifest 的 placeholders

performPlaceHolderSubstitution(loadedMainManifestInfo, xmlDocumentOptional.get(), mergingReportBuilder, severity);

  1. 之后对最终合并后的 manifest 中的一些属性重新进行一次替换,类似步骤 4
  2. 保存 manifest 到 build/intermediates/manifest/fullxxx/AndroidManifest.xml 这就生成了最终的 Manifest 文件
4.5 transformClassesWithDexBuilderForDebug
4.5.1 实现类

DexArchiveBuilderTransform

4.5.2 整体实现图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.5.3 调用链路

DexArchiveBuilderTransform.transform -> DexArchiveBuilderTransform.convertJarToDexArchive -> DexArchiveBuilderTransform.convertToDexArchive -> DexArchiveBuilderTransform.launchProcessing -> DxDexArchiveBuilder.convert

4.5.4 主要代码分析

在 DexArchiveBuilderTransform 中,对 class 的处理分为两种方式,一种是对 目录下的 class 进行处理,一种是对 .jar 里的 class 进行处理。
为什么要分为这两种方式呢?.jar 中的 class 一般来说都是依赖库,基本上不会改变,gradle 在这里做了一个缓存,但是两种方式最终都会调用到 convertToDexArchive,可以说是殊途同归吧。

  • convertJarToDexArchive 处理 jar
    处理 .jar 时,会对 jar 包中的每一个 class 都单独打成一个 .dex 文件,之后还是放在 .jar 包中

private List convertJarToDexArchive(
@NonNull Context context,
@NonNull JarInput toConvert,
@NonNull TransformOutputProvider transformOutputProvider)
throws Exception {

File cachedVersion = cacheHandler.getCachedVersionIfPresent(toConvert);
if (cachedVersion == null) {
// 如果没有缓存,调用 convertToDexArchive 去生成 dex
return convertToDexArchive(context, toConvert, transformOutputProvider, false);
} else {
// 如果有缓存,直接使用缓存的 jar
File outputFile = getPreDexJar(transformOutputProvider, toConvert, null);
Files.copy(
cachedVersion.toPath(),
outputFile.toPath(),
StandardCopyOption.REPLACE_EXISTING);
// no need to try to cache an already cached version.
return ImmutableList.of();
}
}

  • convertToDexArchive 处理 dir 以及 jar 的后续处理
    对 dir 处理使用 convertToDexArchive
    其中会调用 launchProcessing

private static void launchProcessing(
@NonNull DexConversionParameters dexConversionParameters,
@NonNull OutputStream outStream,
@NonNull OutputStream errStream)
throws IOException, URISyntaxException {
// …
boolean hasIncrementalInfo =
dexConversionParameters.isDirectoryBased() && dexConversionParameters.isIncremental;
// 判断 class 是否新增或者修改过,如果新增或者修改过,就需要处理
Predicate toProcess =
hasIncrementalInfo
? path -> {
Map<File, Status> changedFiles =
((DirectoryInput) dexConversionParameters.input)
.getChangedFiles();

File resolved = inputPath.resolve(path).toFile();
Status status = changedFiles.get(resolved);
return status == Status.ADDED || status == Status.CHANGED;
}
: path -> true;

bucketFilter = bucketFilter.and(toProcess);

try (ClassFileInput input = ClassFileInputs.fromPath(inputPath)) {
// 内部调用 dx 或者 d8 去打 dex
dexArchiveBuilder.convert(
input.entries(bucketFilter),
Paths.get(new URI(dexConversionParameters.output)),
dexConversionParameters.isDirectoryBased());
} catch (DexArchiveBuilderException ex) {
throw new DexArchiveBuilderException("Failed to process " + inputPath.toString(), ex);
}
}

在 launchProcessing 中,有下面几个步骤:

  1. 判断目录下的 class 是否新增或者修改过
  2. 调用 DexArchiveBuilder.build 去处理修改过的 class
  3. DexArchiveBuilder 有两个子类,D8DexArchiveBuilder 和 DxDexArchiveBuilder,分别是调用 d8 和 dx 去打 dex
4.6 transformDexArchiveWithExternalLibsDexMergerForDebug
4.6.1 实现类

ExternalLibsMergerTransform

4.6.2 整体实现图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.6.3 调用链路

这一步是处理依赖库的 dex,把上一步生成的依赖库的 dex merge 成一个 dex

// dx
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。希望能够帮助到大家提升技术

高级UI,自定义View

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。

不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。希望能够帮助到大家提升技术

[外链图片转存中…(img-HZDy4qgp-1713432759974)]

高级UI,自定义View

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。

不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

[外链图片转存中…(img-epb5lE7w-1713432759975)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/onghaha/article/details/137929953

智能推荐

复试机试准备(自用)-程序员宅基地

文章浏览阅读530次。作为一个输出过程,自己看看。_复试机试

解决多线程批量插入中的事物一致性问题_多线程插入数据如何控制插入的数据互不影响-程序员宅基地

文章浏览阅读4.9k次,点赞4次,收藏21次。在开发当中我们,我们有时为了加快执行速度,会使用到多线程!尤其在大量数据插入数据库时,我们需要使用多线程进行批量插入,加快程序的执行效率!但是由于多线程中每个线程的事物是不一致的导致程序不一致!为了保证多个线程执行插入时,事物的一致性!我们需要将每个线程的事物状态保存到一个集合当中,当其中某个线程抛出异常时,我将所有线程的事物进行回滚!实现思路:1、将所有线程的事物状态保存到一个集合里面,该集合必须是线程安全的!2、定义一个集合,当线程发生异常时,往集合里面添加数据!3、当异常标志集合中_多线程插入数据如何控制插入的数据互不影响

linux下vim配置solarized配色_solarized gvim-程序员宅基地

文章浏览阅读2.7k次。1 让 gvim 正常工作  VIM 是一个非常强大的编辑器,可惜的是,无论是 Windows 还是 Linux,gvim 都存在一些小故障,令第一眼看到它的初学者感到非常不愉快。为了不让更多的初学者在第一次接触 gvim 时被它吓跑,我在这份教程中首先解决 gvim 令人感到不快的一些小问题,希望能让更多的用户平滑地过渡到 gvim 的环境中来。   VIM 的安装很简单,Windows 下只要_solarized gvim

react native 震动 Vibration 使用详解_react native震动-程序员宅基地

文章浏览阅读5.1k次。Vibration.vibrate()用于控制设备震动。vibrate(pattern, repeat) pattern:参数为一个不定长的数组。在Andriod上,数组第一个元素表示开始震动前的等待时间,然后是震动持续时长和等待时长的交替,例如[0, 500, 1000, 500]表示立刻开始震动500ms,然后等待1000ms,再震动500ms;但在iOS上震动时长是固定的,所以从数组第二_react native震动

分卷压缩与分卷解压_分段压缩和解压-程序员宅基地

分卷压缩与分卷解压是一种用于文件压缩和解压的技术。该技术可以将大文件分成多个较小的卷,并且可以分别对每个卷进行压缩和解压缩操作。这种技术在处理大型文件时非常有用,可以提高文件传输的效率和方便性。

【前端资源分享】推荐收藏的前端学习资源_前端分享内容-程序员宅基地

文章浏览阅读545次,点赞7次,收藏12次。今天分享一些个人收藏的前端学习资源,按一下几个维度简单划分了下,有 3D、框架、构建工具等等。由于这些地址都是我个人收藏的,所以带有一些强烈的主观意识,还有很多优秀的网址没有收录进来,会不断更新的,欢迎大家点赞,收藏。_前端分享内容

随便推点

html-QQ登陆界面_qq登录页面制作html-程序员宅基地

文章浏览阅读1.8w次,点赞18次,收藏120次。初学30节课html的入门课程,花了几个小时模仿了一个qq的登陆界面。效果图如下:代码如下:<!DOCTYPE html><html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scal..._qq登录页面制作html

django-admin.py startproject mysite命令失效的解决方法_django-admin startproject mysite没反应-程序员宅基地

文章浏览阅读5k次。最近学习Django,跟随网上的教程《The Django Book》,现记录之,与大家共勉。遇到问题的时候,我比较喜欢问为什么?因为单单知道了解决方法是远远不够的——也就是“知其然更知其所以然”。参考了一片文章,贴出地址:http://www.blogjava.net/mstar/archive/2008/07/11/Django_USEFUL_TIP.html首先分_django-admin startproject mysite没反应

Flink四大基石和流处理,批处理_flink stage类型-程序员宅基地

文章浏览阅读923次。之前介绍了FLink的一些基本原理,架构部署,那么Flink中的四大基石是什么?我们来研究研究这玩意我们了解到FLink中有四大基石:Checkpoint stage Time Window这四部分那么首先我们来看看Checkpoint机制是Flink中最重要的一个特性,FLink基于chandy_Lamport算法实现了一个分布式的一致性的快照,从而提供了一致性的语义,提供了一致性的语义之后,Flink为了让用户编程更加轻松,更加容易的去管理状态,还提供了一套stage API (也就是状态管理,状_flink stage类型

哈工大硕士生用 Python 实现了 11 种经典数据降维算法,源代码库已开放_std_broken = broken.std(axis=0)-程序员宅基地

文章浏览阅读3k次,点赞4次,收藏86次。导语:适合机器学习初学者和刚入坑数据挖掘的小伙伴雷锋网 AI 开发者按:网上关于各种降维算法的资料参差不齐,同时大部分不提供源代码。这里有个 GitHub 项目整理了使用 Python 实现了 11 种经典的数据抽取(数据降维)算法,包括:PCA、LDA、MDS、LLE、TSNE 等,并附有相关资料、展示效果;非常适合机器学习初学者和刚刚入坑数据挖掘的小伙伴。为什么要进行数据降维?..._std_broken = broken.std(axis=0)

建议收藏 | 数据化、信息化、数字化、智能化到底都是指什么?彼此有什么联系?_数据化智能化的象征代表-程序员宅基地

文章浏览阅读7.5k次,点赞2次,收藏31次。随着新技术、新产业、新业态、新模式的不断出现,各行各业的企业都在寻找新的突破口进行转型升级,“数据化、信息化、数字化、智能化”愈来愈频繁地出现在大众视野中,关于它们概念和解说也是层出不穷、百花齐放,到底它们之间有什么区别呢? Runwise整理了一些关于数据化、信息化、数字化、智能化的相关定义,结合组织定义与行业发展趋势,对四者之间的联系与区别进行解析,便于大家更好理解之间的关系。01 关于数据化、信息化、数字化、智能化的概念数据化1.数据化的定义数据代表着对某一件事物的描述,通过记录、分析、重组数_数据化智能化的象征代表

pdf转word用python轻松搞定_使用Python将PDF转化为word-程序员宅基地

文章浏览阅读1.6k次。60行Python代码,实现多线程PDF转Word分解任务把PDF转为Word,分几步?两步,第一步读取PDF文件,第二步写入Word文件。是的,就是这么简单,借助Python第三方包,可以轻松实现上面两个过程,我们要用到pdfminer3k和python-docx这两个包读取PDFfrom pdfminer.pdfinterp import PDFResourceManagerfrom pdfm..._python写pdf与word互转代码

推荐文章

热门文章

相关标签