android compile tasks中dex过程源码情景分析_incrementaltaskinputs has been deprecated. this is-程序员宅基地

0x00 前言

MultiDex中出现的main dex capacity exceeded解决之道中我们知道main dex的class可以由maindexlist.txt指定,Android MultiDex机制杂谈中我们分析了google MultiDex机制中Secondary dex的install过程,那么,我们的app在android gradle build过程中,.dex文件是怎么创建的呢? 再者,Secondary dex中的class是按什么顺序分配到不同dex中的呢?


0x01 android build system概述

为了解答上面的两个问题,本文将进一步分析android build system源码。
android build system是google提供的一组用来构建、运行、测试和打包我们app的工具集,包含了aaptaidljavacdexapkbuilderJarsignerzipalign等工具。在我们构建app时,build进程会去按一定顺序调用上述工具来生成相应文件,而最终的输出将会是一个完整的可安装的.apk文件,构建流程如下:

构建系统先从product flavors, build types和dependencies中合并资源,如果不同目录下有重名资源,将按以下优先级进行覆盖:

dependencies > build types > product flavors > main source directory
  1. aapt编译应用的资源文件(如AndroidManifest.xml),输出R.java文件
  2. aidl把.aidl文件转换为对应的java interface文件
  3. javac编译所有.java文件,输出.class文件
  4. dex工具把上面生成的.class文件转换为.dex文件
  5. apkbuilder把所有没编译的资源(如图片),编译过的资源和dex文件打包输出为.apk文件
  6. 在release模式下,用zipalign工具对.apk进行对齐处理,以减少运行时内存占用

本文重点对第4步中.class经过dex到.dex过程源码进行分析。


0x02 android compile tasks分析

为了更好地分析.dex的产生过程,本文设定情景如下:

构建工具为gradle,采用android plugin 'com.android.application',method数超过65535,需要进行multidex,并且指定了multiDexEnabled = true

在shell终端cd到project根目录,输入:

gradle assemble

gradle进程会启动,在dex之前,进程控制流将进入VariantManager. createTasksForVariantData。添加完assemble task依赖后,会去调用taskManager.createTasksForVariantData(tasks, variantData)。由于android plugin为’com.android.application’,这里的taskManager是ApplicationTaskManager。

com/android/build/gradle/internal/VariantManager.java

/**
 * Create tasks for the specified variantData.
 */
public void createTasksForVariantData(
        final TaskFactory tasks,
        final BaseVariantData<? extends BaseVariantOutputData> variantData) {
       

    // Add dependency of assemble task on assemble build type task.
    tasks.named("assemble", new Action<Task>() {
       
        @Override
        public void execute(Task task) {
       
            BuildTypeData buildTypeData = buildTypes.get(
                            variantData.getVariantConfiguration().getBuildType().getName());
            task.dependsOn(buildTypeData.getAssembleTask());
        }
    });
    ...
        taskManager.createTasksForVariantData(tasks, variantData);
    }
}

ApplicationTaskManager.createTasksForVariantData()会通过ThreadRecorder.get().record()第二个callback参数的类型为Recorder.Block<Void>,在call回调中调用父类TaskManager.createPostCompilationTasks。ThreadRecorder可以记录该任务的在当前线程的执行时间,并且保证task之间是串行的。

/**
 * TaskManager for creating tasks in an Android application project.
 */
public class ApplicationTaskManager extends TaskManager {
       

    @Override
    public void createTasksForVariantData(
            @NonNull final TaskFactory tasks,
            @NonNull final BaseVariantData<? extends BaseVariantOutputData> variantData) {
       
            ...
        // Add a compile task
        ThreadRecorder.get().record(ExecutionType.APP_TASK_MANAGER_CREATE_COMPILE_TASK,
                new Recorder.Block<Void>() {
       
                    @Override
                    public Void call() {
       
                        AndroidTask<JavaCompile> javacTask = createJavacTask(tasks, variantScope);

                        if (variantData.getVariantConfiguration().getUseJack()) {
       
                            createJackTask(tasks, variantScope);
                        } else {
       
                            setJavaCompilerTask(javacTask, tasks, variantScope);
                            createJarTask(tasks, variantScope);
                            createPostCompilationTasks(tasks, variantScope);
                        }
                        return null;
                    }
                });
                ...
    }
}

TaskManager.createPostCompilationTasks方法,这个方法比较长,我们分段来分析。

首先从config得到isMultiDexEnabled,isMultiDexEnabled,isLegacyMultiDexMode,由于已经假设当前为需要MultiDex的场景,因此isMultiDexEnabled为true。若isMinifyEnabled也为true,则说明输入jar包需要进行混淆,本场景先不考虑。

TaskManager.java


/**
 * Creates the post-compilation tasks for the given Variant.
 *
 * These tasks create the dex file from the .class files, plus optional intermediary steps like
 * proguard and jacoco
 *
 */
public void createPostCompilationTasks(TaskFactory tasks, @NonNull final VariantScope variantScope) {
       
    checkNotNull(variantScope.getJavacTask());

    final ApkVariantData variantData = (ApkVariantData) variantScope.getVariantData();
    final GradleVariantConfiguration config = variantData.getVariantConfiguration();

    TransformManager transformManager = variantScope.getTransformManager();
...
    boolean isMinifyEnabled = config.isMinifyEnabled();
    boolean isMultiDexEnabled = config.isMultiDexEnabled();
    boolean isLegacyMultiDexMode = config.isLegacyMultiDexMode();

    AndroidConfig extension = variantScope.getGlobalScope().getExtension();

在支持MultiDex的场景中,先创建manifestKeepListTask,将依赖设置为ManifestProcessorTask,这些android compile task由AndroidTask<TransformTask>类型来描述。

接着创建multiDexClassListTask,依赖manifestKeepListTask。这两个tasks用来输出maindexlist.txt,其中包含了MainDex中必须的class,可参见MultiDex中出现的main dex capacity exceeded解决之道


// ----- Multi-Dex support

AndroidTask<TransformTask> multiDexClassListTask = null;
// non Library test are running as native multi-dex
if (isMultiDexEnabled && isLegacyMultiDexMode) {
       
    if (AndroidGradleOptions.useNewShrinker(project)) {
       
        throw new IllegalStateException("New shrinker + multidex not supported yet.");
    }

    // ----------
    // create a transform to jar the inputs into a single jar.
    if (!isMinifyEnabled) {
       
        // merge the classes only, no need to package the resources since they are
        // not used during the computation.
        JarMergingTransform jarMergingTransform = new JarMergingTransform(
                TransformManager.SCOPE_FULL_PROJECT);
        transformManager.addTransform(tasks, variantScope, jarMergingTransform);
    }
    
    // ----------
    // Create a task to collect the list of manifest entry points which are
    // needed in the primary dex
    AndroidTask<CreateManifestKeepList> manifestKeepListTask = androidTasks.create(tasks,
            new CreateManifestKeepList.ConfigAction(variantScope));
    manifestKeepListTask.dependsOn(tasks,
            variantData.getOutputs().get(0).getScope().getManifestProcessorTask());

    // ---------
    // create the transform that's going to take the code and the proguard keep list
    // from above and compute the main class list.
    MultiDexTransform multiDexTransform = new MultiDexTransform(
            variantScope.getManifestKeepListFile(),
            variantScope,
            null);
    multiDexClassListTask = transformManager.addTransform(
            tasks, variantScope, multiDexTransform);
    multiDexClassListTask.dependsOn(tasks, manifestKeepListTask);
}

最后创建dexTask,这个用来把.class文件转为.dex的task,它依赖multiDexClassListTask。

    // create dex transform
    DexTransform dexTransform = new DexTransform(
            extension.getDexOptions(),
            config.getBuildType().isDebuggable(),
            isMultiDexEnabled,
            isMultiDexEnabled && isLegacyMultiDexMode ? variantScope.getMainDexListFile() : null,
            variantScope.getPreDexOutputDir(),
            variantScope.getGlobalScope().getAndroidBuilder(),
            getLogger());
    AndroidTask<TransformTask> dexTask = transformManager.addTransform(
            tasks, variantScope, dexTransform);
    // need to manually make dex task depend on MultiDexTransform since there's no stream
    // consumption making this automatic
    dexTask.optionalDependsOn(tasks, multiDexClassListTask);
}

task执行时,gradle引擎会去调用含有@TaskAction注解的方法,TransformTask类拥有Transfrom类型字段,其transform方法被标记为@TaskAction。同样通过ThreadRecorder.get().record中回调call(),执行transform.transform()

TransformTask.java

/**
 * A task running a transform.
 */
@ParallelizableTask
public class TransformTask extends StreamBasedTask implements Context {
       

    private Transform transform;
    ...
    @TaskAction
    void transform(final IncrementalTaskInputs incrementalTaskInputs)
            throws IOException, TransformException, InterruptedException {
       
		 ...
        ThreadRecorder.get().record(ExecutionType.TASK_TRANSFORM,
                new Recorder.Block<Void>() {
       
                    @Override
                    public Void call() throws Exception {
       
                        transform.transform(
                                TransformTask.this,
                                consumedInputs.getValue(),
                                referencedInputs.getValue(),
                                outputStream != null ? outputStream.asOutput() : null,
                                isIncremental.getValue());
                        return null;
                    }
                },
                new Recorder.Property("project", getProject().getName()),
                new Recorder.Property("transform", transform.getName()),
                new Recorder.Property("incremental", Boolean.toString(transform.isIncremental())));
    }

上述android compile tasks关系可以用下图描述:

从gradle task角度上看,这些task都属于TransformTask(继承至DefaultTask),它们区别仅在于transform字段。DexTask是本文主要关心的task,下面分析这个task执行过程中都做了什么。


0x03 DexTask执行过程分析

android build system中dex过程发生在DexTask,DexTask关联的Transform是DexTransform。

当DexTransform.transfrom方法被调用时,会先创建并初始化main目录作为输出dex的目录,然后调用androidBuilder.convertByteCode方法进行.class到.dex的转换,此时jarInputs为classes.jar,directoryInputs长度为空,传递的boolean类型的multiDex参数来自build.gralde文件中在defaultConfigmultiDexEnabled = true的设置。

DexTransform.java

@Override
public void transform(
        @NonNull Context context,
        @NonNull Collection<TransformInput> inputs,
        @NonNull Collection<TransformInput> referencedInputs,
        @Nullable TransformOutputProvider outputProvider,
        boolean isIncremental) throws TransformException, IOException, InterruptedException {
       
        ...
    // Gather a full list of all inputs.
    List<JarInput> jarInputs = Lists.newArrayList();
    List<DirectoryInput> directoryInputs = Lists.newArrayList();
    for (TransformInput input : inputs) {
       
        jarInputs.addAll(input.getJarInputs());
        directoryInputs.addAll(input.getDirectoryInputs());
    }
    
    
    try {
       
        // if only one scope or no per-scope dexing, just do a single pass that
        // runs dx on everything.
        if ((jarInputs.size() + directoryInputs.size()) == 1 || !dexOptions.getPreDexLibraries()) {
       
            File outputDir = outputProvider.getContentLocation("main",
                    getOutputTypes(), getScopes(),
                    Format.DIRECTORY);
            FileUtils.mkdirs(outputDir);

            // first delete the output folder where the final dex file(s) will be.
            FileUtils.emptyFolder(outputDir);

            // gather the inputs. This mode is always non incremental, so just
            // gather the top level folders/jars
            final List<File> inputFiles = Lists.newArrayList();
            for (JarInput jarInput : jarInputs) {
       
                inputFiles.add(jarInput.getFile());
            }

            for (DirectoryInput directoryInput : directoryInputs) {
       
                inputFiles.add(directoryInput.getFile());
            }

            androidBuilder.convertByteCode(
                    inputFiles,
                    outputDir,
                    multiDex,
                    mainDexListFile,
                    dexOptions,
                    null,
                    false,
                    true,
                    new LoggedProcessOutputHandler(logger));
        } else {
       

为了把输入的.class转换为.dex,AndroidBuilder.convertByteCode会另起进程去做dex,实际上是在新进程中exec dex工具,接下来我们进入dex源码,看看到底发生了什么。

 public void convertByteCode(
         @NonNull Collection<File> inputs,
         @NonNull File outDexFolder,
                  boolean multidex,
         @Nullable File mainDexList,
         @NonNull DexOptions dexOptions,
         @Nullable List<String> additionalParameters,
         boolean incremental,
         boolean optimize,
         @NonNull ProcessOutputHandler processOutputHandler)
         throws IOException, InterruptedException, ProcessException {
       
...
     BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
     DexProcessBuilder builder = new DexProcessBuilder(outDexFolder);

     builder.setVerbose(mVerboseExec)
             .setIncremental(incremental)
             .setNoOptimize(!optimize)
             .setMultiDex(multidex)
             .setMainDexList(mainDexList)
             .addInputs(verifiedInputs.build());

     if (additionalParameters != null) {
       
         builder.additionalParameters(additionalParameters);
     }

     JavaProcessInfo javaProcessInfo = builder.build(buildToolInfo, dexOptions);

     ProcessResult result = mJavaProcessExecutor.execute(javaProcessInfo, processOutputHandler);
     result.rethrowFailure().assertNormalExitValue();
 }

0x04 dex过程分析

android 5.0中dex工具源码路径是dalvik/dx/src/com/android/dx,入口类是com.android.dx.command.Main,当解析到参数–dex时,转入com.android.dx.command.dexer.Main.main()

 public static void main(String[] args) {
       
...
     try {
       
	...
             if (arg.equals("--dex")) {
       
                 com.android.dx.command.dexer.Main.main(without(args, i));
                 break;
             } else if (arg.equals("--dump")) {
       
                 com.android.dx.command.dump.Main.main(without(args, i));
                 break;
             }
             ...
         }

main会调用com.android.dx.command.dexer.Main.run(),此时args.multiDex为true,直接进入runMultiDex

com.android.dx.command.dexer.Main.java

public static int run(Arguments arguments) throws IOException {
       
 ...
    try {
       
        if (args.multiDex) {
       
            return runMultiDex();
        } else {
       
            return runMonoDex();
        }
    } finally {
       
        closeOutput(humanOutRaw);
    }
}

runMultiDex会调用processAllFiles,第一行代码调用createDexFile()


 private static boolean processAllFiles() {
       
     createDexFile();
...

createDexFile先检查outputDex(: DexFile)字段是否为空,不为空则调用writeDex()把该dex的byte[]添加到dexOutputArrays(: List<byte[]>)。

writeDex()具体是通过outputDex.toDex(humanOutWriter, args.verboseDump)得到dex的byte[]。java中数组的下标是int类型,长度为32bits,因此一个dex文件最大理论是4G,但实际由于method, field数等限制,正常最大也就10M左右。

然后还会为outputDex字段新建一个DexFile对象,表示当前dex文件已经处理完毕,可以开始处理新的dex文件了。这里假设进程第一次执行createDexFile,因此outputDex为null。

private static void createDexFile() {
       
    if (outputDex != null) {
       
        dexOutputArrays.add(writeDex());
    }

    outputDex = new DexFile(args.dexOptions);

    if (args.dumpWidth != 0) {
       
        outputDex.setDumpWidth(args.dumpWidth);
    }
}

随后processAllFiles会根据args中numThreads来决定是否需要创建线程池。

if (args.numThreads > 1) {
       
    threadPool = Executors.newFixedThreadPool(args.numThreads);
    parallelProcessorFutures = new ArrayList<Future<Void>>();
}

接下来判断args.mainDexListFile,不为空说明指定了maindexlist.txt文件,这里假设不为空,filesNames数组是{‘path/way/to/classes.jar’},长度为1。方法在for循环中调用processOne()

...
   anyFilesProcessed = false;
   String[] fileNames = args.fileNames;
   ...
   try {
       
       if (args.mainDexListFile != null) {
       
           // with --main-dex-list
           FileNameFilter mainPassFilter = args.strictNameCheck ? new MainDexListFilter() :
               new BestEffortMainDexListFilter();

           // forced in main dex
           for (int i = 0; i < fileNames.length; i++) {
       
               processOne(fileNames[i], mainPassFilter);
           }

processOne调用ClassPathOpener.process处理输入的classes.jar。ClassPathOpener会遍历classes.jar中的每个ZipEntry,读出byte[],对每个ZipEntry在回调processFileBytes中调用Main.processFileBytes方法。

/**
 * Processes one pathname element.
 *
 * @param pathname {
        @code non-null;} the pathname to process. May
 * be the path of a class file, a jar file, or a directory
 * containing class files.
 * @param filter {
        @code non-null;} A filter for excluding files.
 */
private static void processOne(String pathname, FileNameFilter filter) {
       
    ClassPathOpener opener;

    opener = new ClassPathOpener(pathname, false, filter,
            new ClassPathOpener.Consumer() {
       

        @Override
        public boolean processFileBytes(String name, long lastModified, byte[] bytes) {
       
            return Main.processFileBytes(name, lastModified, bytes);
        }
...
   });

    if (args.numThreads > 1) {
       
        parallelProcessorFutures.add(threadPool.submit(new ParallelProcessor(opener)));
    } else {
       
        if (opener.process()) {
       
            anyFilesProcessed = true;
        }
    }
}

Main.processFileBytes把输入的bytes分为三类:

  • .class文件
  • .dex文件
  • 资源文件

如果输入是.dex或资源文件,则把bytes分别写入libraryDexBuffers字段或outputResources字段,此时输入name(: String)为.class。当发现是class,则进一步调用processClass处理


 /**
  * Processes one file, which may be either a class or a resource.
  *
  * @param name {
        @code non-null;} name of the file
  * @param bytes {
        @code non-null;} contents of the file
  * @return whether processing was successful
  */
 private static boolean processFileBytes(String name, long lastModified, byte[] bytes) {
       
     boolean isClass = name.endsWith(".class");
     boolean isClassesDex = name.equals(DexFormat.DEX_IN_JAR_NAME);
     boolean keepResources = (outputResources != null);
... 
     String fixedName = fixPath(name);

     if (isClass) {
       

         if (keepResources && args.keepClassesInJar) {
       
             synchronized (outputResources) {
       
                 outputResources.put(fixedName, bytes);
             }
         }
         if (lastModified < minimumFileAge) {
       
             return true;
         }
         return processClass(fixedName, bytes);
     } else if (isClassesDex) {
       
         synchronized (libraryDexBuffers) {
       
             libraryDexBuffers.add(bytes);
         }
         return true;
     } else {
       
         synchronized (outputResources) {
       
             outputResources.put(fixedName, bytes);
         }
         return true;
     }
 }

processClass方法主要做了以下几件事:

  1. 为传入的class创建DirectClassFile对象,对应.class字节码文件
  2. 得到已经生成的dex的numMethodIds,numFieldIds
  3. 得到新Class的constantPoolSize,计算maxMethodIdsInDex = numMethodIds + constantPoolSize + 新Class的方法数 + 2个预留method, 计算maxFieldIdsInDex = numFieldIds + constantPoolSize + 新Class的字段数 + 9个预留field
  4. 一旦发现maxMethodIdsInDex > args.maxNumberOfIdxPerDex 或者 maxFieldIdsInDex > args.maxNumber OfIdxPerDex,说明当前dex已经满了,调用createDexFile创建新dex来容纳该Class
  5. 否则,通过CfTranslator.translate方法将输入的DirectClassFile对象,得到ClassDefItem,添加到outputDex(: DexFile)

由此可以看出:

secondray dex中的class是根据classes.jar中ZipEntry的遍历顺序添加的。


/**
  * Processes one classfile.
  *
  * @param name {
        @code non-null;} name of the file, clipped such that it
  * <i>should</i> correspond to the name of the class it contains
  * @param bytes {
        @code non-null;} contents of the file
  * @return whether processing was successful
  */
 private static boolean processClass(String name, byte[] bytes) {
       
     if (! args.coreLibrary) {
       
         checkClassName(name);
     }

     DirectClassFile cf =
         new DirectClassFile(bytes, name, args.cfOptions.strictNameCheck);

     cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
     cf.getMagic();

     int numMethodIds = outputDex.getMethodIds().items().size();
     int numFieldIds = outputDex.getFieldIds().items().size();
     int constantPoolSize = cf.getConstantPool().size();

     int maxMethodIdsInDex = numMethodIds + constantPoolSize + cf.getMethods().size() +
             MAX_METHOD_ADDED_DURING_DEX_CREATION;
     int maxFieldIdsInDex = numFieldIds + constantPoolSize + cf.getFields().size() +
             MAX_FIELD_ADDED_DURING_DEX_CREATION;

     if (args.multiDex
         // Never switch to the next dex if current dex is already empty
         && (outputDex.getClassDefs().items().size() > 0)
         && ((maxMethodIdsInDex > args.maxNumberOfIdxPerDex) ||
             (maxFieldIdsInDex > args.maxNumberOfIdxPerDex))) {
       
         DexFile completeDex = outputDex;
         createDexFile();
         assert  (completeDex.getMethodIds().items().size() <= numMethodIds +
                 MAX_METHOD_ADDED_DURING_DEX_CREATION) &&
                 (completeDex.getFieldIds().items().size() <= numFieldIds +
                 MAX_FIELD_ADDED_DURING_DEX_CREATION);
     }

     try {
       
         ClassDefItem clazz =
             CfTranslator.translate(cf, bytes, args.cfOptions, args.dexOptions, outputDex);
         synchronized (outputDex) {
       
             outputDex.add(clazz);
         }
         return true;

     } catch (ParseException ex) {
       
         DxConsole.err.println("\ntrouble processing:");
         if (args.debug) {
       
             ex.printStackTrace(DxConsole.err);
         } else {
       
             ex.printContext(DxConsole.err);
         }
     }
     errors.incrementAndGet();
     return false;
 }

再回到processAllFiles,前面假设指定了maindexlist,如果minialMainDex也为true的话,会立即创建新的DexFile,保证这个main dex中只包含maindexlist里的类,如何指定可以参考MultiDex中出现的main dex capacity exceeded解决之道 0x05。前面没有过滤掉的class都会放入到secondary dex。


        if (dexOutputArrays.size() > 0) {
       
            throw new DexException("Too many classes in " + Arguments.MAIN_DEX_LIST_OPTION
                    + ", main dex capacity exceeded");
        }

        if (args.minimalMainDex) {
       
            // start second pass directly in a secondary dex file.
            createDexFile();
        }

        // remaining files
        for (int i = 0; i < fileNames.length; i++) {
       
            processOne(fileNames[i], new NotFilter(mainPassFilter));
        }
    } else {
       
        // without --main-dex-list
        for (int i = 0; i < fileNames.length; i++) {
       
            processOne(fileNames[i], ClassPathOpener.acceptAll);
        }
    }
} catch (StopProcessing ex) {
       
    /*
     * Ignore it and just let the error reporting do
     * their things.
     */
}

在runMultiDex的最后,dex文件将以classes(..N).dex的形式输出在由args.outName指定的目录之下。

private static int runMultiDex() throws IOException {
       
		...
        } else if (args.outName != null) {
       
            File outDir = new File(args.outName);
            assert outDir.isDirectory();
            for (int i = 0; i < dexOutputArrays.size(); i++) {
       
                OutputStream out = new FileOutputStream(new File(outDir, getDexFileName(i)));
                try {
       
                    out.write(dexOutputArrays.get(i));
                } finally {
       
                    closeOutput(out);
                }
            }

        }

0x05 结论

通过对android build system中android plugin tasks和dx工具源码的分析,我们可以得出如下结论:

  • .dex文件本质上是.class文件经过com.android.dx.dex.file.DexFile.toDex方法转换得到

  • Secondary dex是在指定了multiDexEnabled = true且MainDex满足65535限制,或者指定multiDexEnabled = true和minimalMainDex = true的情况下,才会创建的dex,其包含的class是根据classes.jar中ZipEntry的遍历顺序添加的。


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

智能推荐

多元线性回归分析(Stata)_stata多元回归-程序员宅基地

文章浏览阅读4.5w次,点赞69次,收藏630次。回归分析的介绍与分类回归分析的任务是:通过研究自变量X和因变量Y的关系,尝试去解释Y的形成机制,进而达到通过X去预测Y的目的三个关键字:相关性、因变量Y、自变量X常见的回归分析有五类(划分的依据是因变量Y的类型): 线性回归:因变量Y为连续性数值变量,例如GDP的增长率 0-1回归:因变量Y为0-1型变量,例如P2P公司研究借款人是否能按时还贷,那么Y可以设计为二值变量,Y=0时代表可以还贷,Y=1时代表不能还贷 定序回归:因变量Y为定序变量,例如1表示不喜欢,2._stata多元回归

mac环境下编译opencv源码 Java 并生成JAR包-程序员宅基地

文章浏览阅读1.2k次。一、准备工作 1、安装ant,并配置环境变量 2、下载CMake,我这边用的是CMAKE GUI 3、下载opencv源码 这是下载地址 二、开始 之前尝试过用brew直接安装ant,均提示404并试过,后面实在没有办法就直接去了这里下载,如图: 下载解压,然后通过【终端】配置..._opencv源码java

有孚网络北京云数据中心荣获绿色建筑国际LEED金牌认证和国家CQC A级机房认证...-程序员宅基地

文章浏览阅读735次。中国IDC圈报道,2018年1月16日,上海有孚网络股份有限公司(以下简称有孚网络)北京永丰E-Data云计算数据中心荣获国家CQC A级机房认证和国内首个国际LEED绿建金牌认证,CQC中国质量认证中心肖处长、美国绿色建筑委员会(USGBC)和绿色事业认证公司(GBCI)总裁兼首席执行官马晗、中国计量科学研究院武主任、有孚网络CEO安柯、有孚网络CS..._cqc 数据中心a级认证

红点中国完成新一期4亿美元基金募集 -程序员宅基地

文章浏览阅读103次。消息,红点创投中国基金(下简称“红点中国”)宣布完成新一期4亿美元基金的募集。其中3亿美元将继续布局TMT行业及其细分领域里优秀的早期项目,约80%以上的资金用于布局A轮及更早阶段的项目。另外1亿美元则是用于加注高潜力高成长的领跑项目。 这是红点中国独立运营两年多以来募集完成的第三支基金。尽管资本环境在过去一段时间内有一些波动,但红点中国团队依然保...

微软必应(Bing)打不开解决方案(2021.12.16)_必应打不开-程序员宅基地

文章浏览阅读3.4w次,点赞10次,收藏18次。问题描述2021.12.16开始必应就打不开了。。解决方案1. 打开主页将原先网址https://cn.bing.com/更换为https://www4.bing.com/2. 更改浏览器默认主页以Chrome为例,打开chrome://settings/onStartup或设置中选择启动时将网址修改为https://www4.bing.com/3. 更改浏览器默认搜索引擎打开网址chrome://settings/searchEngines或设置中选择搜索引擎网址格式添加为htt_必应打不开

机器学习——聚类算法-层次聚类算法-程序员宅基地

文章浏览阅读676次,点赞15次,收藏7次。层次聚类算法是一种无需预先指定簇数的聚类方法,它通过计算样本之间的相似度来构建聚类树,从而得到样本之间的聚类关系。本文介绍了层次聚类算法的原理、步骤以及Python实现的示例代码。通过层次聚类算法,可以对数据集进行探索性分析,发现数据中的内在结构和模式。

随便推点

keil出现unrecognized character escape sequence(无法识别的字符转义序列)错误_warning: #192-d: unrecognized character escape seq-程序员宅基地

文章浏览阅读5.9k次。我们在使用编程软件进行编程时,可能会使用一些特殊字符或者转义字符,这时候如果不注意就会出错从而出现unrecognized character escape sequence的错误比如我今天写’'的时候就出现了该错误,这是因为""在C语言中有特殊的用法**,想要表示"" 就要使用"\"来表示**..._warning: #192-d: unrecognized character escape sequence

基于机器学习的多模态检索研究_多模态图像检索算法流程图解-程序员宅基地

文章浏览阅读1k次。近年来,随着深度学习技术的发展,人们对于多模态数据检索的研究和应用越来越受到关注。然而,多模态数据的特点和其间的异质性导致多模态检索面临诸多挑战。本文对基于生成对抗网络的多模态检索方法进行了综述和分析,该方法具有以下特点:通过生成器和判别器编码多样数据并学习它们的共性,在特征提取和多模态匹配两个阶段实现自动化。在特征提取阶段,使用深度学习模型,如卷积神经网络和循环神经网络,提取不同类型数据的特征,并采用多任务学习策略,使提取网络最小化误差并获得低维特征表示。在多模态匹配阶段,通过生成对抗网络将不同类型特征_多模态图像检索算法流程图解

力扣315计算右侧小于当前元素的个数-程序员宅基地

文章浏览阅读71次。代码】力扣315计算右侧小于当前元素的个数。

pyhton object is not subscriptable 解决_polygon' object is not subscriptable-程序员宅基地

文章浏览阅读6.9w次,点赞2次,收藏6次。这是网上的错误例子:dicts = [ {'name': 'Michelangelo', 'food': 'PIZZA'}, {'name': 'Garfield', 'food': 'lasanga'}, {'name': 'Walter', 'food': 'pancakes'}, {'name': 'Galactus', ..._polygon' object is not subscriptable

Linux系统引导.服务与实验-程序员宅基地

文章浏览阅读776次,点赞18次,收藏20次。systemd unit(单元),systemd方便管理程序,将程序按照特定的功能分成很多单元,服务,单元,写配置。一般来说第一启动项是硬盘,找到硬盘后,会根据mbr的指引 找到完整的grub程序,yum以及rpm安装的软件可以直接使用systemctl去启动关闭,重启,开机自启等功能。加电后biso程序会自检硬件,硬件无故障后,会根据第一启动项去找到内核。移动设备,U盘 移动硬盘 ,光驱。检测硬件是否正常,然后根据biso中的启动项设置,去找内核文件。再根据grub的配置文件找到内核文件的具体位置,

在线黑色响应式全屏滚动主页html源码_黑色响应式全屏滚动主页源码-程序员宅基地

文章浏览阅读742次。html5黑色大气的个人博客全屏滚动个人主页源码。直接上传服务器空间就可使用。_黑色响应式全屏滚动主页源码