Android开发

Android Gradle Plugin —— 初窥门径 (二)

本文主要是介绍Android Gradle Plugin —— 初窥门径 (二),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

在如今这个时代,仅仅会简单的页面开发已经很难找到好工作了。

所以在实习的业余时间,我开始学习Android的Gradle构建流程,并且想将此心路历程整理为博客,方便加深理解。

简述

在本文,我将给大家介绍BasePluginconfigureProjectconfigureExtensioncreateTasks这三个回调方法的主要流程

PS:本文基于Gradle 3.2.1版本

BasePlugin.configureProject

    private void configureProject() {
        final Gradle gradle = project.getGradle();
        ObjectFactory objectFactory = project.getObjects();

        extraModelInfo = new ExtraModelInfo(project.getPath(), projectOptions, project.getLogger());

        sdkHandler = new SdkHandler(project, getLogger());
      	//如果gradle没有设置的离线,如果需要下载SDK的时候,会在这里下载SDK
        if (!gradle.getStartParameter().isOffline()
                && projectOptions.get(BooleanOption.ENABLE_SDK_DOWNLOAD)) {
            SdkLibData sdkLibData = SdkLibData.download(getDownloader(), getSettingsController());
            sdkHandler.setSdkLibData(sdkLibData);
        }
				// AndroidBuilder用于存放构建中所需要获取的数据,在构建的步骤中使用他们
        AndroidBuilder androidBuilder =
                new AndroidBuilder(
                        project == project.getRootProject() ? project.getName() : project.getPath(),
                        creator,
                        new GradleProcessExecutor(project),
                        new GradleJavaProcessExecutor(project),
                        extraModelInfo.getSyncIssueHandler(),
                        extraModelInfo.getMessageReceiver(),
                        getLogger(),
                        isVerbose());
      	//DataBinding的Builder,DataBinding相关的Task就是从这里取出数据
        dataBindingBuilder = new DataBindingBuilder();
        dataBindingBuilder.setPrintMachineReadableOutput(
                SyncOptions.getErrorFormatMode(projectOptions) == ErrorFormatMode.MACHINE_PARSABLE);
	
      	//如果有被移除的配置,则report
        if (projectOptions.hasRemovedOptions()) {
            androidBuilder
                    .getIssueReporter()
                    .reportWarning(Type.GENERIC, projectOptions.getRemovedOptionsErrorMessage());
        }

      	//如果有被废弃的配置,则report
        if (projectOptions.hasDeprecatedOptions()) {
            extraModelInfo
                    .getDeprecationReporter()
                    .reportDeprecatedOptions(projectOptions.getDeprecatedOptions());
        }
				
      	//如果有实验性的配置,则遍历实验性的配置,然后report
        if (!projectOptions.getExperimentalOptions().isEmpty()) {
            projectOptions
                    .getExperimentalOptions()
                    .forEach(extraModelInfo.getDeprecationReporter()::reportExperimentalOption);
        }

        // Enforce minimum versions of certain plugins
        GradlePluginUtils.enforceMinimumVersionsOfPlugins(
                project, androidBuilder.getIssueReporter());

        // Apply the Java plugin
      	// JavaBasePlugin很重要,之后会介绍
        project.getPlugins().apply(JavaBasePlugin.class);

        //...省略一波

        // 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.
        gradle.addBuildListener(
                new BuildListener() {
                    @Override
                    public void buildStarted(@NonNull Gradle gradle) {
                        BuildableArtifactImpl.Companion.disableResolution();
                    }

                    @Override
                    public void settingsEvaluated(@NonNull Settings settings) {}

                    @Override
                    public void projectsLoaded(@NonNull Gradle gradle) {}

                    @Override
                    public void projectsEvaluated(@NonNull Gradle gradle) {}

                    @Override
                    public void buildFinished(@NonNull BuildResult buildResult) {
                        // Do not run buildFinished for included project in composite build.
                        if (buildResult.getGradle().getParent() != null) {
                            return;
                        }
                        sdkHandler.unload();
                        threadRecorder.record(
                                ExecutionType.BASE_PLUGIN_BUILD_FINISHED,
                                project.getPath(),
                                null,
                                () -> {
                                    WorkerActionServiceRegistry.INSTANCE
                                            .shutdownAllRegisteredServices(
                                                    ForkJoinPool.commonPool());
                                  //在finish的回调里面清理PreDexCache的缓存
                                    PreDexCache.getCache()
                                            .clear(
                                                    FileUtils.join(
                                                            project.getRootProject().getBuildDir(),
                                                            FD_INTERMEDIATES,
                                                            "dex-cache",
                                                            "cache.xml"),
                                                    getLogger());
                                    Main.clearInternTables();
                                });
                    }
                });

        gradle.getTaskGraph()
                .addTaskExecutionGraphListener(
                        taskGraph -> {
                            for (Task task : taskGraph.getAllTasks()) {
                                if (task instanceof TransformTask) {
                                    Transform transform = ((TransformTask) task).getTransform();
                                    if (transform instanceof DexTransform) {
                                      //将PreDexCache加载进TransformerTask中
                                        PreDexCache.getCache()
                                                .load(
                                                        FileUtils.join(
                                                                project.getRootProject()
                                                                        .getBuildDir(),
                                                                FD_INTERMEDIATES,
                                                                "dex-cache",
                                                                "cache.xml"));
                                        break;
                                    }
                                }
                            }
                        });
				//添加lint classpath的配置
        createLintClasspathConfiguration(project);
    }
复制代码

​ 总结一下:

  1. 进行各种版本检查

  2. 创建AndroidBuilderDataBindingBuilder用于之后构建获取数据

  3. apply JavaBasePlugin插件

  4. getGradle().addBuildListenerfinish回调中进行PreDexCache清理

  5. getGradle().getTaskGraph().addTaskExecutionGraphListener中,将PreDexCache添加进TransformTask

    流程都比较好理解,接下来我们分析 JavaBasePlugin。分析一个Plugin,自然要从最重要的 apply 方法看起

JavaBasePlugin.apply

    @Override
    public void apply(final ProjectInternal project) {
        project.getPluginManager().apply(BasePlugin.class);
        project.getPluginManager().apply(ReportingBasePlugin.class);

        JavaPluginConvention javaConvention = addExtensions(project);
				
      	//这里比较重要
        configureSourceSetDefaults(javaConvention);
      	//获取sourceCompatibility和targetCompatibility
        configureCompileDefaults(project, javaConvention);
				//其余几个有兴趣大家都可以看看,基本都是注册Task
        configureJavaDoc(project, javaConvention);
        configureTest(project, javaConvention);
        configureBuildNeeded(project);
        configureBuildDependents(project);
        configureSchema(project);
        bridgeToSoftwareModelIfNecessary(project);
        configureVariantDerivationStrategy(project);
    }
复制代码

​ 从这里可以看到,都是一些配置方法,用于注册Task。这里我们着重分析configureSourceSetDefaults ,其他方法大家有兴趣可以私下去看

JavaBasePlugin.configureSourceSetDefaults

    private void configureSourceSetDefaults(final JavaPluginConvention pluginConvention) {
        final Project project = pluginConvention.getProject();
        pluginConvention.getSourceSets().all(new Action<SourceSet>() {
            @Override
            public void execute(final SourceSet sourceSet) {
                ConventionMapping outputConventionMapping = ((IConventionAware) sourceSet.getOutput()).getConventionMapping();

                ConfigurationContainer configurations = project.getConfigurations();

              	//这里定义了sourceSet里面各种需要用到的字段,例如runtimeClasspath、annotationProcessor、implementation等
                defineConfigurationsForSourceSet(sourceSet, configurations, pluginConvention);
              	//这里定义了sourceSet获取数据的源文件夹
                definePathsForSourceSet(sourceSet, outputConventionMapping, project);
								//创建资源处理Task,将源资源文件复制到target目录中
                createProcessResourcesTask(sourceSet, sourceSet.getResources(), project);
                //创建JavaCompileTask,在JavaCompile中调用Compiler<JavaCompileSpec>进行编译,这里是支持增量的
              	Provider<JavaCompile> compileTask = createCompileJavaTask(sourceSet, sourceSet.getJava(), project);
              	//创建classesTask, task的名字由sourceSet返回
                createClassesTask(sourceSet, project);
								
              	//创建Java编译输出的文件夹
                JvmPluginsHelper.configureOutputDirectoryForSourceSet(sourceSet, sourceSet.getJava(), project, compileTask, compileTask.map(new Transformer<CompileOptions, JavaCompile>() {
                    @Override
                    public CompileOptions transform(JavaCompile javaCompile) {
                        return javaCompile.getOptions();
                    }
                }));
            }
        });
    }
复制代码

definePathsForSourceSet

    private void definePathsForSourceSet(final SourceSet sourceSet, ConventionMapping outputConventionMapping, final Project project) {
        outputConventionMapping.map("resourcesDir", new Callable<Object>() {
            @Override
            public Object call() {
                String classesDirName = "resources/" + sourceSet.getName();
                return new File(project.getBuildDir(), classesDirName);
            }
        });

        sourceSet.getJava().srcDir("src/" + sourceSet.getName() + "/java");
        sourceSet.getResources().srcDir("src/" + sourceSet.getName() + "/resources");
    }
复制代码

如此一分析,在哪里配置sourceSetsourceSet资源文件夹有哪些、哪里创建JavaCompileTask等等问题就迎刃而解了。

BasePlugin.configureExtension

    private void configureExtension() {
      	// ======================= 1.创建三个容器集合 ===========================
        //...
      	final NamedDomainObjectContainer<BuildType> buildTypeContainer =...;
        final NamedDomainObjectContainer<ProductFlavor> productFlavorContainer =...;
        final NamedDomainObjectContainer<SigningConfig> signingConfigContainer =...;

        final NamedDomainObjectContainer<BaseVariantOutput> buildOutputs =...;
				// ================== 2. 创建各种对象 ==============================
        project.getExtensions().add("buildOutputs", buildOutputs);

        sourceSetManager = createSourceSetManager();

        extension =
                createExtension(....);

        globalScope.setExtension(extension);

        variantFactory = createVariantFactory(globalScope, extension);

        taskManager =
                createTaskManager(...);

        variantManager =
                new VariantManager(...);

        registerModels(registry, globalScope, variantManager, extension, extraModelInfo);

        // ========== 3. 将三个容器集合添加到 variantManager 中 ====================
        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.
      	//默认构建配置,创建一些buildType之类的,有兴趣可以去看看
        variantFactory.createDefaultComponents(
                buildTypeContainer, productFlavorContainer, signingConfigContainer);
    }
复制代码

总结一下,这里主要做了三件事情:

  1. 创建了三个NamedDomainObjectContainer,也就是三个容器集合。分别对应着BuildType(构建类型)、ProductFlavor(产品风味)和 SigningConfig(签署设置)
  2. 创建一系列的extensionTaskManagerVariantManager等等。其中VariantManager是用于管理 variant,variant意思为变体,也就是我们经常看见的AAAReleaseAAADebugBBBReleaseBBBDebug等。
  3. 将三个容器集合添加到 VariantManager中,BuildTypeProductFlavorSigningConfig都在variantManager对象中,由其来统一管理。

​ 接下来我们分析第二部分,毕竟重要的是createExtensioncreateVariantFactory以及 createTaskManager。这三个方法并不是在BasePlugin中实现的,而是在AbstractAppPlugin里。其中createExtension在之前的文章中已经分析过了,是用来创建“android”这个extentsion的。

createTaskManager

 		@NonNull
    @Override
    protected TaskManager createTaskManager(
            @NonNull GlobalScope globalScope,
            @NonNull Project project,
            @NonNull ProjectOptions projectOptions,
            @NonNull DataBindingBuilder dataBindingBuilder,
            @NonNull AndroidConfig androidConfig,
            @NonNull SdkHandler sdkHandler,
            @NonNull ToolingModelBuilderRegistry toolingRegistry,
            @NonNull Recorder recorder) {
        return new ApplicationTaskManager(...);
    }
复制代码

这里创建了ApplicationTaskManager,在它的构造方法中定义了许许多多的Task,用于Android的构建。

createVariantFactory

		@NonNull
    @Override
    protected ApplicationVariantFactory createVariantFactory(
            @NonNull GlobalScope globalScope,
            @NonNull AndroidConfig androidConfig) {
        return new ApplicationVariantFactory(globalScope, androidConfig);
    }
复制代码

这里创建了ApplicationVariantFactory,里面利用DensityABILanguage这三种属性创建了variantData。最后会生成一个splits集合,存放这三种属性组合的笛卡尔积,后面用于生成ApkData(里面包括了生成Apk文件的信息)。

总结一下:

1. 我们知道了BuildType、ProductFlavor和SigningConfig是在哪里定义的、由什么对象进行管理

2. `Extension`、`TaskManager`、`ApplicationVariantFactory`在哪里被初始化,同时里面大概做了什么事情。
复制代码

后面系列会对TaskManager中的一些Task作简单的分析

BasePlugin.createTasks

    private void createTasks() {
        threadRecorder.record(
                ExecutionType.TASK_MANAGER_CREATE_TASKS,
                project.getPath(),
                null,
                () -> taskManager.createTasksBeforeEvaluate());

        project.afterEvaluate(
                project -> {
                    sourceSetManager.runBuildableArtifactsActions();

                    threadRecorder.record(
                            ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
                            project.getPath(),
                            null,
                            () -> createAndroidTasks());
                });
    }
复制代码

​ 这里以 Evaluate 为界限,分为两个阶段:Evaluate之前和Evaluate之后。Evaluate可以简单理解为每一个build.gradle解析执行之前和之后。

Evaluate之前:taskManager.createTasksBeforeEvaluate()

public void createTasksBeforeEvaluate() {
  			//UninstallTask创建
        taskFactory.create(
                UNINSTALL_ALL,
                uninstallAllTask -> {
                    uninstallAllTask.setDescription("Uninstall all applications.");
                    uninstallAllTask.setGroup(INSTALL_GROUP);
                });
				//DeviceCheckTask创建
        taskFactory.create(
                DEVICE_CHECK,
                deviceCheckTask -> {
                    deviceCheckTask.setDescription(
                            "Runs all device checks using Device Providers and Test Servers.");
                    deviceCheckTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
                });
				//ConnectedCheckTask创建,
        taskFactory.create(
                CONNECTED_CHECK,
                connectedCheckTask -> {
                    connectedCheckTask.setDescription(
                            "Runs all device checks on currently connected devices.");
                    connectedCheckTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
                });
				//MAIN_PREBUILD其实就是PreBuild这个Task
        taskFactory.create(MAIN_PREBUILD, task -> {});

  			//生成一个Task,用于提取Proguard的文件
        ExtractProguardFiles extractProguardFiles =
                taskFactory.create(EXTRACT_PROGUARD_FILES, ExtractProguardFiles.class, task -> {});
        // Make sure MAIN_PREBUILD runs first:
        extractProguardFiles.dependsOn(MAIN_PREBUILD);
				// 这里创建的SourceSetsTask其实是做一些日志打印的工作
        taskFactory.create(new SourceSetsTask.ConfigAction(extension));

        taskFactory.create(
                ASSEMBLE_ANDROID_TEST,
                assembleAndroidTestTask -> {
                    assembleAndroidTestTask.setGroup(BasePlugin.BUILD_GROUP);
                    assembleAndroidTestTask.setDescription("Assembles all the Test applications.");
                });

        taskFactory.create(new LintCompile.ConfigAction(globalScope));

        // Lint task is configured in afterEvaluate, but created upfront as it is used as an
        // anchor task.
  			//创建全局LintTask
        createGlobalLintTask();
  			//如果自定义了Lint检查规则,这里可以配置进去
        configureCustomLintChecksConfig();

        globalScope.setAndroidJarConfig(createAndroidJarConfig(project));
				//清除BuildCache
        if (buildCache != null) {
            taskFactory.create(new CleanBuildCache.ConfigAction(globalScope));
        }

        // for testing only.
        taskFactory.create(
                new TaskConfigAction<ConfigAttrTask>() {
                    @NonNull
                    @Override
                    public String getName() {
                        return "resolveConfigAttr";
                    }

                    @NonNull
                    @Override
                    public Class<ConfigAttrTask> getType() {
                        return ConfigAttrTask.class;
                    }

                    @Override
                    public void execute(@NonNull ConfigAttrTask task) {
                        task.resolvable = true;
                    }
                });

        taskFactory.create(
                new TaskConfigAction<ConfigAttrTask>() {
                    @NonNull
                    @Override
                    public String getName() {
                        return "consumeConfigAttr";
                    }

                    @NonNull
                    @Override
                    public Class<ConfigAttrTask> getType() {
                        return ConfigAttrTask.class;
                    }

                    @Override
                    public void execute(@NonNull ConfigAttrTask task) {
                        task.consumable = true;
                    }
                });
    }
复制代码

这里没什么好讲的,着重还是讲解一下Evaluate之后

Evaluate之后:createAndroidTasks()

    @VisibleForTesting
    final void createAndroidTasks() {
        //..进行一些check
        extension.disableWrite();

        taskManager.configureCustomLintChecks();

        ProcessProfileWriter.getProject(project.getPath())
                .setCompileSdk(extension.getCompileSdkVersion())
                .setBuildToolsVersion(extension.getBuildToolsRevision().toString())
                .setSplits(AnalyticsUtil.toProto(extension.getSplits()));

        //...进行一些check

        // setup SDK repositories.
        sdkHandler.addLocalRepositories(project);

        threadRecorder.record(
                ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,
                project.getPath(),
                null,
                () -> {
                  	//注意这里,很关键
                    variantManager.createAndroidTasks();
                    ApiObjectFactory apiObjectFactory =
                            new ApiObjectFactory(
                                    globalScope.getAndroidBuilder(),
                                    extension,
                                    variantFactory,
                                    project.getObjects());
                    for (VariantScope variantScope : variantManager.getVariantScopes()) {
                        BaseVariantData variantData = variantScope.getVariantData();
                        apiObjectFactory.create(variantData);
                    }

                  //...省略一些
                });

        // create the global lint task that depends on all the variants
        taskManager.configureGlobalLintTask(variantManager.getVariantScopes());

        // now publish all variant artifacts.
        for (VariantScope variantScope : variantManager.getVariantScopes()) {
            variantManager.publishBuildArtifacts(variantScope);
        }
				//...
        variantManager.setHasCreatedTasks(true);
    }

复制代码

​ 从代码中可以看到,variantManager也调用了createAndroidTasks。之后又遍历了VariantScopes,将

BaseVariantData传入了ApiObjectFactory

variantManager.createAndroidTasks()

    public void createAndroidTasks() {
        //...
        if (variantScopes.isEmpty()) {
            recorder.record(
                    ExecutionType.VARIANT_MANAGER_CREATE_VARIANTS,
                    project.getPath(),
                    null /*variantName*/,
                    this::populateVariantDataList);
        }
				//...

        for (final VariantScope variantScope : variantScopes) {
            recorder.record(
                    ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
                    project.getPath(),
                    variantScope.getFullVariantName(),
                    () -> createTasksForVariantData(variantScope));
        }
        //...
    }
复制代码

这里做了两件事情:

1. 创建所有的`variantData`
2. 遍历`VariantScope`,为`variantData`创建`task`
复制代码

populateVariantDataList

    public void populateVariantDataList() {
        List<String> flavorDimensionList = extension.getFlavorDimensionList();

        //省略一部分,这部分针对不同情况进行了处理:
      	//如果productFlavors为空,则向createVariantDataForProductFlavors传入空集合,不创建variant
      	//如果flavorDimensionList为空,则report错误
      	//如果flavorDimensionList.size为1,则为每一个flavor设置相同的dimension
      	//否则,在多个dimension的情况下,则进行下面的逻辑

            // 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());
            }
        }
    }
复制代码

​ 通过源码里面我们可以看到,在非特殊情况下,则会调用createCombinations进行计算flavor,然后获得一个集合。之后遍历这个集合,为每一个计算好的flavor创建ProductFlavor对象。

createCombinations

		@NonNull
    public static <S extends DimensionAware & Named> List<ProductFlavorCombo<S>> createCombinations(
            @Nullable List<String> flavorDimensions,
            @NonNull Iterable<S> productFlavors) {

        List <ProductFlavorCombo<S>> result = Lists.newArrayList();
        if (flavorDimensions == null || flavorDimensions.isEmpty()) {
            for (S flavor : productFlavors) {
                result.add(new ProductFlavorCombo<>(ImmutableList.of(flavor)));
            }
        } else {
            // need to group the flavor per dimension.
            // First a map of dimension -> list(ProductFlavor)
            ArrayListMultimap<String, S> map = ArrayListMultimap.create();
            for (S flavor : productFlavors) {
                String flavorDimension = flavor.getDimension();

                if (flavorDimension == null) {
                    throw new RuntimeException(String.format(
                            "Flavor '%1$s' has no flavor dimension.", flavor.getName()));
                }
                if (!flavorDimensions.contains(flavorDimension)) {
                    throw new RuntimeException(String.format(
                            "Flavor '%1$s' has unknown dimension '%2$s'.",
                            flavor.getName(), flavor.getDimension()));
                }

                map.put(flavorDimension, flavor);
            }

            createProductFlavorCombinations(result,
                    Lists.<S>newArrayListWithCapacity(flavorDimensions.size()),
                    0, flavorDimensions, map);
        }
        return result;
    }
复制代码

​ 如果flavorDimensions不为空,将其放进一个map中:Key为dimension,Value为flavor。然后传入createProductFlavorCombinations

createProductFlavorCombinations

    private static <S extends DimensionAware & Named> void createProductFlavorCombinations(
            List<ProductFlavorCombo<S>> flavorGroups,
            List<S> group,
            int index,
            List<String> flavorDimensionList,
            ListMultimap<String, S> map) {
        if (index == flavorDimensionList.size()) {
            flavorGroups.add(new ProductFlavorCombo<>(Iterables.filter(group, Predicates.notNull())));
            return;
        }

        // fill the array at the current index.
        // get the dimension name that matches the index we are filling.
        String dimension = flavorDimensionList.get(index);

        // from our map, get all the possible flavors in that dimension.
        List<S> flavorList = map.get(dimension);

        // loop on all the flavors to add them to the current index and recursively fill the next
        // indices.
        if (flavorList.isEmpty()) {
            throw new RuntimeException(String.format(
                    "No flavor is associated with flavor dimension '%1$s'.", dimension));
        } else {
            for (S flavor : flavorList) {
                group.add(flavor);
                createProductFlavorCombinations(
                        flavorGroups, group, index + 1, flavorDimensionList, map);
                group.remove(group.size() - 1);
            }
        }
    }
复制代码

这里就是创建ProductFlavor的精髓所在了:利用递归的边界条件求取所有的情况,你品,你细品~

完成这里之后,AGP会遍历flavorComboList,利用createTasksForVariantData中生成各种Task,也就是我们熟悉的assembleXXXbundleXXXTask了。

在此,我们可以明白createTasks里面大概做了什么,我们的ProductFlavor是如何被生成的,运用了什么样子的算法。

后续

​ 这里讲完了BasePlugin的apply中的三个主要的回调方法,基本上整个Android构建流程都包含在里面。其实还有许许多多的Task值得我们去研究,之后系列的文章将会挑出几个关键的Task进行分析。

这篇关于Android Gradle Plugin —— 初窥门径 (二)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!