Android Lint是Android SDK提供的一项静态代码分析工具,对于提高代码质量具有重要作用。到目前为止,Android SDK自带的Lint检查项目达到了253项,我们在开发过程中经常见到的提示信息比如“Id被重复定义”“HandlerLeak风险”其实都是由Lint检查实现的。

Android Studio 2.0 Stable版本已经于2016年4月7日正式发布。除了Instant Run让人眼前一亮,更让人惊喜的是,官方已经悄然把自定义Lint的检查与IDE整合起来了。在此之前,自定义Lint规则只能通过在终端中执行gradle任务来运行,然后生成报告文件。

Android Studio 2.0中整合自定义Lint检查的效果如图:

图中红线提示的错误是我自定义的Lint规则检查的结果,大意是Activity使用的布局文件应该以“activity_”为前缀进行命名。

关于Lint的一些基本知识,以及自定义Lint如何实现,可以参考我之前的文章:

从Android Studio的“偏好”设置窗口中,用户可以设置IDE整合的Lint检查功能的细节,如图:

虽然这个设置选项在旧版的Android Studio中也能看到,但实际上在旧版中是不起作用的。

除了在Editor中能够以红色下划线标注自定义检查项目外,使用Android Studio的 “Analyze” –> “Inspect Code”现在也会检查自定义Lint规则中定义的项目了。Android Studio 2.0的这一功能整合真是太棒了,大大提高了自定义Lint的实用性。

Lint工作流程探究

介绍完Android Studio 2.0的新特性,现在进入正题,我们来探究一下Lint检查的工作原理,包括系统默认的Lint检查项目以及用户自定义的Lint检查项目。

本文以终端运行gradle的lint任务为例进行分析。其中自定义lint的使用方式是把自定义lint以aar的形式提供给app进行引用,具体实现方式可以参考浅谈Android自定义Lint规则的实现 (一) 。

当我们在终端执行“gradle lint”任务后,会加载com.android.build.gradle.tasks.Lint类,它的源码位于Lint.groovy文件中。

Lint类的lint()方法会首先被执行,这也是整个lint检查流程开始运转的起点。这部分代码如下:

@SuppressWarnings("GroovyUnusedDeclaration")

@TaskAction

public void lint() {

def modelProject = createAndroidProject(project)

if (getVariantName() != null && !getVariantName().isEmpty()) {

for (Variant variant : modelProject.getVariants()) {

if (variant.getName().equals(getVariantName())) {

lintSingleVariant(modelProject, variant);

}

}

} else {

lintAllVariants(modelProject);

}

}

这里会根据getVariantName()的执行结果,选择去调用lintSingleVariant()还是lintAllVariants()。而观察lintSingleVariant()和lintAllVariants()的源码,发现这两个方法最终都要调用runLint()方法,这个runLint()方法很重要,代码片段如下:

/** Runs lint on the given variant and returns the set of warnings */

private List runLint(

@NonNull AndroidProject modelProject,

@NonNull Variant variant,

boolean report) {

IssueRegistry registry = createIssueRegistry()

LintCliFlags flags = new LintCliFlags()

LintGradleClient client = new LintGradleClient(registry, flags, project, modelProject, mSdkHome, variant, getBuildTools())

//..........这里省略部分代码.......

warnings = client.run(registry)

//.........

}

这个方法的第一句话是创建了一个IssueRegistry,而了解自定义Lint的用户一定对这个类不会陌生,在之前的文章中我们提到过,Android内建的Lint检查项目都是定义在BuiltinIssueRegistry类中,而BuiltinIssueRegistry就是派生自IssueRegistry,我们要实现的自定义Lint检查规则实际上也就是实现自定义的IssueRegistry子类。

IssueRegistry类的完整名称是com.android.tools.lint.client.api.IssueRegistry,它是一个Java类。自此,lint从一个gradle task开始与lint api包中的java类产生交互了。

createIssueRegistry()方法很简单,只有一句话:

private static BuiltinIssueRegistry createIssueRegistry() {

return new LintGradleIssueRegistry()

}

继续跟踪LintGradleIssueRegistry类:

public static class LintGradleIssueRegistry extends BuiltinIssueRegistry {

private boolean mInitialized;

public LintGradleIssueRegistry() {

}

@NonNull

@Override

public List getIssues() {

List issues = super.getIssues();

if (!mInitialized) {

mInitialized = true;

for (Issue issue : issues) {

if (issue.getImplementation().getDetectorClass() == GradleDetector.class) {

issue.setImplementation(GroovyGradleDetector.IMPLEMENTATION);

}

}

}

return issues;

}

}

这里的BuiltinIssueRegistry我们刚才也提到了,用户平时在执行gradle lint时默认会执行200多项检查,这些默认检查项目都是Android SDK通过BuiltinIssueRegistry定义的。

继续执行上面的run()方法,new出来的LintGradleClient实际上是com.android.tools.lint.LintCliClient的子类,这个类的作用是提供执行lint任务的环境信息(比如控制台、IDE的信息),执行IssueRegistry中定义的各种ISSUE检查,以及以多种形式输出lint报告等。

继续执行run()方法,也就是warnings = client.run(registry)。看到这里终于知道BuiltinIssueRegistry中定义的200多项ISSUE是如何被gradle的lint任务引入检查了。

到这里为止,对groovy文件的分析就结束了,由于LintGradleClient是继承自java类LintCliClient,后续真正的lint检查工作都通过client.run(registry)这句话转交给java实现的LintCliClient类来完成。

读到这里有人会问,client.run(registry)中的参数registry是派生自BuiltinIssueRegistry,那么lint检查的项目也就是BuiltinIssueRegistry中定义的那200多项默认检查项目。那么我们自定义的lint规则中的ISSUE又是如何被引入lint检查的呢?不要急,下面会有分析。

LintCliClient类的run()方法的主要代码如下:

//LintCliClient.run()部分代码

public int run(@NonNull IssueRegistry registry, @NonNull List files) throws IOException {

//............

mDriver = new LintDriver(registry, this);

//............

mDriver.analyze(createLintRequest(files));

Collections.sort(mWarnings);

boolean hasConsoleOutput = false;

for (Reporter reporter : mFlags.getReporters()) {

reporter.write(mErrorCount, mWarningCount, mWarnings);

if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {

hasConsoleOutput = true;

}

}

if (!mFlags.isQuiet() && !hasConsoleOutput) {

System.out.println(String.format(

"Lint found %1$d errors and %2$d warnings", mErrorCount, mWarningCount));

}

return mFlags.isSetExitCode() ? (mHasErrors ? ERRNO_ERRORS : ERRNO_SUCCESS) : ERRNO_SUCCESS;

}

不要被run()这个方法名迷惑了,以为LintCliClient是一个线程类。其实LintCliClient只是一个普通类,不是Runnable类,这里的方法也叫run()仅仅是一个巧合。

这里的run()方法中首先new了一个LintDriver对象,其实它才是真正用来对project和file进行lint分析的类,也就是通过mDriver.analyze()来进行lint分析。

LintDriver的analyze()方法精简后的代码如下:

//LintDriver.analyze()部分源码

private void analyze() {

//............

Collection projects;

projects = mRequest.getProjects();

if (projects == null) {

projects = computeProjects(mRequest.getFiles());

}

//............

registerCustomDetectors(projects);

if (mScope == null) {

mScope = Scope.infer(projects);

}

fireEvent(EventType.STARTING, null);

for (Project project : projects) {

mPhase = 1;

Project main = mRequest.getMainProject(project);

// The set of available detectors varies between projects

computeDetectors(project);

if (mApplicableDetectors.isEmpty()) {

// No detectors enabled in this project: skip it

continue;

}

checkProject(project, main);

if (mCanceled) {

break;

}

runExtraPhases(project, main);

}

fireEvent(mCanceled ? EventType.CANCELED : EventType.COMPLETED, null);

}

这里的projects变量中保存的就是等待进行Lint检查的工程项目,它是我们最开始在终端中执行gradle lint任务时指定的。比如在本例中,projects中保存的就是“Project [dir=/Users/netease/AndroidStudioProjects/HTLint/app]”这个项目。

继续往下执行,走到registerCustomDetectors(projects)这句话,看到这个方法名你是不是发现了什么?别急,我们先看看registerCustomDetectors()方法的源码:

private void registerCustomDetectors(Collection projects) {

// Look at the various projects, and if any of them provide a custom

// lint jar, "add" them (this will replace the issue registry with

// a CompositeIssueRegistry containing the original issue registry

// plus JarFileIssueRegistry instances for each lint jar

Set jarFiles = Sets.newHashSet();

for (Project project : projects) {

jarFiles.addAll(mClient.findRuleJars(project));

for (Project library : project.getAllLibraries()) {

jarFiles.addAll(mClient.findRuleJars(library));

}

}

jarFiles.addAll(mClient.findGlobalRuleJars());

if (!jarFiles.isEmpty()) {

List registries = Lists.newArrayListWithExpectedSize(jarFiles.size());

registries.add(mRegistry);

for (File jarFile : jarFiles) {

try {

IssueRegistry registry = JarFileIssueRegistry.get(mClient, jarFile);

if (myCustomIssues == null) {

myCustomIssues = Sets.newHashSet();

}

myCustomIssues.addAll(registry.getIssues());

registries.add(registry);

} catch (Throwable e) {

mClient.log(e, "Could not load custom rule jar file %1$s", jarFile);

}

}

if (registries.size() > 1) { // the first item is mRegistry itself

mRegistry = new CompositeIssueRegistry(registries);

}

}

}

对于projects中的每一项project,都通过mClient.findRuleJars(project)方法来寻找该project中的RuleJars,那么findRuleJars()是如何实现的呢?它返回的RuleJars又是什么呢?

由于在LintDriver的构造函数中,mClient被初始化为一个LintClientWrapper对象,而LintClientWrapper类的findRuleJars()方法内部只有一句话:

return mDelegate.findRuleJars(project)

所以上面的mClient.findRuleJars(project)实际上是被委托给了LintGradleClient.java来实现。LintGradleClient类又在它的createLintRequest()方法中调用了LintGradleProject的静态方法create(),其中有这样一个片段:

public static Pair> create(

@NonNull LintGradleClient client,

@NonNull AndroidProject project,

@NonNull Variant variant,

@NonNull org.gradle.api.Project gradleProject) {

//............

List customRules = Lists.newArrayList();

File appLintJar = new File(gradleProject.getBuildDir(), "lint" + separatorChar + "lint.jar");

if (appLintJar.exists()) {

customRules.add(appLintJar);

}

//............

}

这段代码会寻找当前项目的构建目录下是否引用了一个名为lint.jar文件,如果有就把它加入customRules列表中。

我们在《浅谈Android自定义Lint规则的实现》中提到过通过aar包装lint.jar文件,然后让需要自定义lint检查的android项目添加对aar的依赖,这也是本文的例子使用的引入自定义lint规则的方法。原来我们添加的依赖中的lint.jar文件是在这里被找出来的。

继续回去看registerCustomDetectors()方法后续的代码执行,也就是这一段:

for (Project library : project.getAllLibraries()) {

jarFiles.addAll(mClient.findRuleJars(library));

}

这段代码会对当前工程依赖的所有库文件进行检查,如果这些库文件有对名为lint.jar文件的引用,则把它们引用的lint.jar文件也加入到jarFiles集合中。

如此一来,不管是项目直接依赖的lint.jar文件,还是间接通过其他库引入的lint.jar文件,就都被放入jarFiles集合中了。

继续往下执行registerCustomDetectors()中的代码,走到了:

jarFiles.addAll(mClient.findGlobalRuleJars());

这里的findGlobalRuleJars()方法实际是由LintClient实现的:

//com.android.tools.lint.client.api.LintClient类

public List findGlobalRuleJars() {

// Look for additional detectors registered by the user, via

// (1) an environment variable (useful for build servers etc), and

// (2) via jar files in the .android/lint directory

List files = null;

try {

String androidHome = AndroidLocation.getFolder();

File lint = new File(androidHome + File.separator + "lint"); //$NON-NLS-1$

if (lint.exists()) {

File[] list = lint.listFiles();

if (list != null) {

for (File jarFile : list) {

if (endsWith(jarFile.getName(), DOT_JAR)) {

if (files == null) {

files = new ArrayList();

}

files.add(jarFile);

}

}

}

}

} catch (AndroidLocation.AndroidLocationException e) {

// Ignore -- no android dir, so no rules to load.

}

String lintClassPath = System.getenv("ANDROID_LINT_JARS"); //$NON-NLS-1$

if (lintClassPath != null && !lintClassPath.isEmpty()) {

String[] paths = lintClassPath.split(File.pathSeparator);

for (String path : paths) {

File jarFile = new File(path);

if (jarFile.exists()) {

if (files == null) {

files = new ArrayList();

} else if (files.contains(jarFile)) {

continue;

}

files.add(jarFile);

}

}

}

return files != null ? files : Collections.emptyList();

}

这段代码逻辑也很简单,就是在环境变量指定的路径和“.android/lint”路径下分别寻找是否存在自定义lint规则的jar文件。如果有,就把它们返回并加入jarFiles集合中。

现在,不管是通过引入依赖库的方式,还是在系统指定路径或环境变量指定路径下放置lint.jar的方式(这2种方式在《浅谈Android自定义Lint规则的实现》中都有介绍)引入的lint.jar文件都已经被找出来放到jarFiles集合中了。

继续往下执行registerCustomDetectors()中的代码,这一行:

registries.add(mRegistry);

会把最开始生成的LintGradleIssueRegistry(实际就是系统默认的Lint检查项目BuiltinIssueRegistry的子类)缓存到列表registries中。

然后紧接着的for循环会针对jarFiles中的每一项指定lint规则的jarFile,获取jarFile中包含的IssueRegistry,把这些IssueRegistry也都缓存到列表registries中,并把IssueRegistry中包含的所有ISSUE都缓存到集合myCustomIssues中。也就是这段代码(再贴一遍):

if (!jarFiles.isEmpty()) {

//............

for (File jarFile : jarFiles) {

//............

myCustomIssues.addAll(registry.getIssues());

registries.add(registry);

}

if (registries.size() > 1) { // the first item is mRegistry itself

mRegistry = new CompositeIssueRegistry(registries);

}

}

然后通过创建一个CompositeIssueRegistry对象,把所有lint检查的IssueRegistry对象(不论是系统默认的检查项目还是用户实现的自定义检查项目)都包装到CompositeIssueRegistry中。这样,在后面真正进行ISSUE检查工作时,就可以直接使用CompositeIssueRegistry对象中返回的ISSUE列表了,因为它包含了系统自带的和用户自定义的所有ISSUE。

到了这里,registerCustomDetectors(projects)方法就执行完了(你不会忘了我们其实是因为跟踪LintDriver的analyze()方法所以才会有上面这么多balabala吧o(╯□╰)o ),让我们继续回到LintDriver的analyze()方法中往下看,也就是这一段:

//LintDriver.analyze()部分源码

if (mScope == null) {

mScope = Scope.infer(projects);

}

fireEvent(EventType.STARTING, null);

for (Project project : projects) {

mPhase = 1;

Project main = mRequest.getMainProject(project);

// The set of available detectors varies between projects

computeDetectors(project);

if (mApplicableDetectors.isEmpty()) {

// No detectors enabled in this project: skip it

continue;

}

checkProject(project, main);

if (mCanceled) {

break;

}

runExtraPhases(project, main);

}

fireEvent(mCanceled ? EventType.CANCELED : EventType.COMPLETED, null);

首先会在mScope字段中缓存当前要做Lint检查的工程都需要对哪些Scope进行检查,比如需不需要检查Java源代码(Scope.JAVA_FILE)、Java字节码(Scope.CLASS_FILE)、资源文件(Scope.RESOURCE_FILE)等等。

fireEvent(EventType.STARTING, null)会回调所有已经注册过的LintListener,通知它们Lint检查开始了。LintListener是一个interface,可以对Lint检查的各个阶段进行响应。

接着一个for循环分别对projects中的每个project进行检查,由于每个project对lint的配置都不同,比如用户通过配置当前project目录下的lint.xml文件关闭了某些检查项目,或者更改了某些ISSUE的严重等级等。所以这里使用了computeDetectors(project)来获取当前检查的project的lint配置信息:

private void computeDetectors(@NonNull Project project) {

//...................

Configuration configuration = project.getConfiguration(this);

mScopeDetectors = new EnumMap>(Scope.class);

mApplicableDetectors = mRegistry.createDetectors(mClient, configuration,

mScope, mScopeDetectors);

validateScopeList();

}

这里获取到的configuration中包含了当前正接受lint检查的project的基本信息,以及lint属性配置文件的信息,debug截图如下:

然后这个configuration中的信息就被作为参数传给了mRegistry.createDetectors()方法,来获知需要使用哪些Detector来检查当前project,而这里的mRegistry对象其实是一个CompositeIssueRegistry对象,也就是把android sdk自带的lint检查项目和用户自定义实现的lint检查项目都包含在内了。这里的createDetectors()是没有被CompositeIssueRegistry重写,直接继承父类IssueRegistry的方法,主要代码如下:

final List extends Detector> createDetectors(

@NonNull LintClient client,

@NonNull Configuration configuration,

@NonNull EnumSet scope,

@Nullable Map> scopeToDetectors) {

List issues = getIssuesForScope(scope);

//.................

Set> detectorClasses = new HashSet>();

for (Issue issue : issues) {

Implementation implementation = issue.getImplementation();

Class extends Detector> detectorClass = implementation.getDetectorClass();

EnumSet issueScope = implementation.getScope();

if (!detectorClasses.contains(detectorClass)) {

if (!configuration.isEnabled(issue)) {

continue;

}

//.................

detectorClasses.add(detectorClass);

}

}

List detectors = new ArrayList(detectorClasses.size());

for (Class extends Detector> clz : detectorClasses) {

Detector detector = clz.newInstance();

detectors.add(detector);

//................

}

return detectors;

}

逻辑很简单,先获取CompositeIssueRegistry对象中所有的ISSUE,也就是默认的200多项检查加上用户自己实现的检查项目,然后分别对这些ISSUE进行判断:如果集合detectorClasses中还没有包含当前ISSUE对应的lint探测器实现类detectorClass,并且当前project的配置文件没有禁用这个issue,那么就把探测器实现类detectorClass加入集合detectorClasses中。当所有issue都通过这个循环检查完毕后,把这些测器实现类都实例化成对象detector,加入列表detectors,最后把detectors返回给调用者,这样上一级调用者就获得了当前project可以用的所有Detector的实例了。

回到上一级调用者,继续往下执行LintDriver.analyze()剩下的代码,终于完成了所有的前期准备工作,来到了checkProject(project, main)这一句,这个方法才是真正使用前面所有工作提供的信息,开始正式对project的文件、字节码等进行lint检查了。而checkProject()方法中又调用了一个最核心的方法runFileDetectors()来进行lint检查工作,大致结构如下:

//LintDriver.runFileDetectors()部分源码

private void runFileDetectors(@NonNull Project project, @Nullable Project main) {

// Look up manifest information (but not for library projects)

if (project.isAndroidProject()) {

for (File manifestFile : project.getManifestFiles()) {

//.................

fireEvent(EventType.SCANNING_FILE, context);

v.visitFile(context, manifestFile);

}

if (mScope.contains(Scope.ALL_RESOURCE_FILES)

|| mScope.contains(Scope.RESOURCE_FILE)

|| mScope.contains(Scope.RESOURCE_FOLDER)

|| mScope.contains(Scope.BINARY_RESOURCE_FILE)) {

if (......) {

checkIndividualResources(project, main, xmlDetectors, dirChecks,

binaryChecks, files);

} else {

checkResFolder(project, main, res, xmlDetectors, dirChecks,

binaryChecks);

}

}

//.................

}

if (mScope.contains(Scope.JAVA_FILE) || mScope.contains(Scope.ALL_JAVA_FILES)) {

if (......) {

checkIndividualJavaFiles(project, main, checks, files);

} else {

//.................

checkJava(project, main, sourceFolders, checks);

}

}

//.................

if (mScope.contains(Scope.CLASS_FILE)

|| mScope.contains(Scope.ALL_CLASS_FILES)

|| mScope.contains(Scope.JAVA_LIBRARIES)) {

checkClasses(project, main);

}

if (mScope.contains(Scope.GRADLE_FILE)) {

checkBuildScripts(project, main);

}

if (mScope.contains(Scope.OTHER)) {

List checks = mScopeDetectors.get(Scope.OTHER);

if (checks != null) {

OtherFileVisitor visitor = new OtherFileVisitor(checks);

visitor.scan(this, project, main);

}

}

if (project == main && mScope.contains(Scope.PROGUARD_FILE) &&

project.isAndroidProject()) {

checkProGuard(project, main);

}

if (project == main && mScope.contains(Scope.PROPERTY_FILE)) {

checkProperties(project, main);

}

}

这段原始代码比较长,这里只截取了一个大致的框架。可以看到首先判断如果当前检查的是一个android项目,那么就检查它所有的Manifest文件,检查顺序为:

The manifests should be provided such that the main manifest comes first, then any flavor versions, then any build types.

然后再检查所有的资源文件和文件夹。

到了这里,专门针对android项目的检查就完成了,接下来就是对所有类型项目都要进行的检查了,这段代码框架的结构很清晰的列出了检查顺序为:java源文件 --> java字节码 --> GRADLE文件 --> 其他文件 --> ProGuard文件 --> PROPERTY文件。这与http://tools.android.com/tips/lint/writing-a-lint-check上关于lint检查顺序的描述是一致的:

OK,对所有project的所有issue都已经检查完成了,现在让我们回到LintCliClient.run()的执行(别怪我一下跳的太远,实在是计算机就是这样执行的啊o(╯□╰)o,代码再贴一遍…):

//LintCliClient.run()部分代码

public int run(@NonNull IssueRegistry registry, @NonNull List files) throws IOException {

//............

mDriver = new LintDriver(registry, this);

//............

mDriver.analyze(createLintRequest(files));

Collections.sort(mWarnings);

boolean hasConsoleOutput = false;

for (Reporter reporter : mFlags.getReporters()) {

reporter.write(mErrorCount, mWarningCount, mWarnings);

if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {

hasConsoleOutput = true;

}

}

if (!mFlags.isQuiet() && !hasConsoleOutput) {

System.out.println(String.format("Lint found %1$d errors and %2$d warnings", mErrorCount, mWarningCount));

}

return mFlags.isSetExitCode() ? (mHasErrors ? ERRNO_ERRORS : ERRNO_SUCCESS) : ERRNO_SUCCESS;

}

我们前面这么长的篇幅其实都是在分析这里的mDriver.analyze(createLintRequest(files))方法,它会把lint检查出来的警告和错误信息保存在列表mWarnings中,然后用这句Collections.sort(mWarnings)对所有警告进行排序。剩下的工作当然就是把这些警告信息输出啦,输出成为我们平常见到的html报告、或者控制台报告、或者其他形式。输出报告的工作是由这段代码完成的:

for (Reporter reporter : mFlags.getReporters()) {

reporter.write(mErrorCount, mWarningCount, mWarnings);

if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {

hasConsoleOutput = true;

}

}

到此为止,Lint的工作流程就分析完了。

如何debug Lint源码

在终端执行gradle的lint任务默认是无法debug的,也就是说你在系统定义的200多项Issue或者你自定义的Issue中打的断点都不起作用。如果需要调试,可以用下面的方法:

1、在gradle.properties文件中加上下面这句话:

org.gradle.jvmargs='-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005'

2、创建一个Remote类型的debug配置文件:

3、在终端中以daemon模式启动gradle lint任务

然后快速点击debug按钮(注意此时要切换到刚才创建的Debug Lint的配置文件上),如图:

现在Lint源码(包括Android SDK中的和用户自定义的Lint规则)中的断点就可以调试了。

发布时间:2016-06-21, 11:45:50

最后更新:2016-06-22, 16:05:11

android 充电模式deamon_Android Lint工作原理剖析相关推荐

  1. android 充电模式deamon_Android MarsDaemon实现进程及Service常驻

    前段时间.就讨论过关于怎样让Service常驻于内存而不被杀死,最后的结论就是使用JNI实现守护进程,可是不得不说的是,在没有改动系统源代码的情况下,想真正实现杀不死服务,是一件非常难的事情.眼下除了 ...

  2. android camera(二):摄像头工作原理、s5PV310 摄像头接口(CAMIF)

    关键词: android  camera CMM 模组 camera参数  CAMIF 平台信息: 内核: linux 系统: android 平台:S5PV310(samsung exynos 42 ...

  3. NameNode与DataNode的工作原理剖析

    NameNode与DataNode的工作原理剖析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.HDFS写数据流程 1>.客户端通过Distributed FileSys ...

  4. namenode和datanode工作机制_NameNode与DataNode的工作原理剖析

    NameNode与DataNode的工作原理剖析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.HDFS写数据流程 1>.客户端通过Distributed FileSys ...

  5. 8086的两种工作模式_Buck变换器工作原理

    一.Buck变换器另外三种叫法 1.降压变换器:输出电压小于输入电压. 2.串联开关稳压电源:单刀双掷开关(晶体管)串联于输入与输出之间. 3.三端开关型降压稳压电源: 1)输入与输出的一根线是公用的 ...

  6. Android学习笔记View的工作原理

    自定义View,也可以称为自定义控件,通过自定义View可以使得控件实现各种定制的效果. 实现自定义View,需要掌握View的底层工作原理,比如View的测量过程.布局流程以及绘制流程,除此之外,还 ...

  7. android 充电模式deamon_拆解报告:belkin贝尔金10W无线充电器

    ---- 充电头网拆解报告 第1214篇 ----- 作为苹果推荐的数码配件品牌,belkin推出了不少经典电源配件产品,无线充电器就是其中一大品类,很多配件还都上架了苹果商店.此前充电头网也陆续拆解 ...

  8. android 充电模式deamon_安兔兔公布6月Android手机性价比排行榜

    月初的时候,安兔兔根据后台统计到的数据公布了2020年6月1日到6月30日Android手机性能榜单,其中OPPO find X2 Pro以608049分成功霸榜,小米 10 Pro为603266,排 ...

  9. (进阶篇)Redis6.2.0 集群 哨兵模式_哨兵工作原理_02

    文章目录 1. 主从复制哨兵架构图 2. 定时任务 3. 主观下线 4. 客观下线 5. 仲裁 6. 哨兵工作原理 1. 主从复制哨兵架构图 2. 定时任务 Sentinel内部有3个定时任务分别是: ...

最新文章

  1. 一台主机装两个mysql数据库_一台主机装两个mysql数据库
  2. 【深度学习笔记】python图像特征提取
  3. 马云都退休20天了,2019年剩下不到100天了:你还没掌握Python 编程思维吗?
  4. document.getElementsByName 标准
  5. servlet session持久化
  6. 工作227:小程序学习1开始布局页面
  7. 为什么要选择基于NAS存储方案
  8. elasticsearch sort illegal_argument_exception error
  9. [转]CString类型的头文件、CString的输出、CString的常用用法
  10. 基于springboot+vue的学生选课系统(前后端分离)
  11. Axure实操笔记之axure炫酷的作者信息设置
  12. OpenCV-Python 识别万用表七段数码管电流值
  13. 将IDM添加到谷歌浏览器
  14. 这届年轻人正在背着你偷偷攒钱
  15. SQL Server练习
  16. 计算机考研404是什么意思,研路分享:我的404分考研高分心得体会
  17. 实时获取股票数据,免费!——Python爬虫Sina Stock实战
  18. linux远程管理工具:putty
  19. 向上滚动 终端_终端 - 如何在Linux控制台上向上/向下滚动?
  20. 原来小米手机这么好用,这4大功能,各个都是黑科技,厉害了

热门文章

  1. Python批量OCR日语文字图片并输出内容至文本文档~(调用百度OCRapi)
  2. 35之后程序员何去何从
  3. 网络篇之三次握手(SYN+ACK)
  4. 计算机全能基础应用知识,[电脑基础知识]计算机全能大赛-观众题.ppt
  5. 艰难学习codepen之landscape
  6. MySQL空间数据函数
  7. android动态壁纸2.2.1,动态壁纸选择器|安卓动态壁纸选择器apkV2.1下载|好特下载
  8. Java—求某个范围内的所有素数
  9. 在linux中删除多级目录,如何使用一个Linux命令删除多个子目录 | MOS86
  10. 什么样的品牌公关事件会引人注目?