Android Gradle Plugin 源码解析(上)

简介: 一、源码依赖本文基于:android gradle plugin版本:com.android.tools.build:gradle:2.

一、源码依赖

本文基于:

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 25
 buildToolsVersion '26.0.2'
 defaultConfig {
 applicationId "com.zengshaoyi.gradledemo"
 minSdkVersion 15
 targetSdkVersion 25
 versionCode project.ext.versionCode
 versionName project.ext.versionName
 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
 }
 buildTypes {
 release {
 minifyEnabled false
 proguardFiles 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 plugins
 for (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);
 }
 });
 // 创建AndroidBuilder
 androidBuilder = 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();
 @Override
 public void buildStarted(Gradle gradle) {}
 @Override
 public void settingsEvaluated(Settings settings) {}
 @Override
 public void projectsLoaded(Gradle gradle) {}
 @Override
 public void projectsEvaluated(Gradle gradle) {}
 @Override
 public 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
 @Override
 protected 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 model
 ModelBuilder 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 model
 NativeModelBuilder 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 代码

 @Override
 public 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

 @VisibleForTesting
 final void createAndroidTasks(boolean force) {
 ...
 threadRecorder.record(
 ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,
 project.getPath(),
 null,
 () -> {
 // 创建AndroidTasks
 variantManager.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 unchecked
 createVariantDataForProductFlavors(
 (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>() {
 @Override
 public 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里的代码

 @NonNull
 public 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。

相关文章
|
12月前
|
数据采集 监控 API
告别手动埋点!Android 无侵入式数据采集方案深度解析
传统的Android应用监控方案需要开发者在代码中手动添加埋点,不仅侵入性强、工作量大,还难以维护。本文深入探讨了基于字节码插桩技术的无侵入式数据采集方案,通过Gradle插件 + AGP API + ASM的技术组合,实现对应用性能、用户行为、网络请求等全方位监控,真正做到零侵入、易集成、高稳定。
1134 100
|
安全 Java Android开发
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
587 0
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1675 29
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
710 15
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
11262 2
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
705 4
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
12月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1947 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
12月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
1241 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡

热门文章

最新文章

推荐镜像

更多
  • DNS