Android Dagger2 框架依赖图构建模块深度剖析(三)

一、引言

在 Android 开发中,依赖注入(Dependency Injection,简称 DI)是一种重要的设计模式,它能够降低代码的耦合度,提高代码的可测试性和可维护性。Dagger 2 作为一款高效的依赖注入框架,在编译时生成依赖注入代码,避免了运行时反射带来的性能开销。其中,依赖图构建模块是 Dagger 2 的核心组成部分,它负责解析依赖关系,构建依赖图,为后续的依赖注入提供基础。本文将深入分析 Dagger 2 框架的依赖图构建模块,从源码级别详细介绍其工作原理和实现细节。

二、依赖图构建模块概述

2.1 依赖图的概念

依赖图是一种有向图,用于表示对象之间的依赖关系。在 Dagger 2 中,依赖图的节点表示依赖对象,边表示依赖关系。通过构建依赖图,可以清晰地了解各个依赖对象之间的依赖关系,从而实现正确的依赖注入。

2.2 依赖图构建模块的作用

依赖图构建模块的主要作用是解析 Dagger 2 注解(如@Inject@Module@Provides@Component等),并根据注解信息构建依赖图。在构建过程中,会检查依赖关系的合法性,处理循环依赖等问题,确保依赖图的正确性。

2.3 依赖图构建模块的工作流程

依赖图构建模块的工作流程主要包括以下几个步骤:

  1. 注解扫描:扫描源代码中的 Dagger 2 注解,收集依赖信息。
  2. 依赖解析:根据注解信息,解析各个依赖对象之间的依赖关系。
  3. 图构建:将解析得到的依赖关系转化为依赖图。
  4. 图验证:检查依赖图的合法性,处理循环依赖等问题。
  5. 代码生成:根据依赖图生成依赖注入代码。

三、注解扫描

3.1 注解处理器

Dagger 2 使用注解处理器在编译时处理注解。注解处理器是一个实现了javax.annotation.processing.AbstractProcessor接口的类,它会在编译过程中扫描源代码中的注解,并根据注解信息进行相应的处理。以下是一个简化的注解处理器示例:

java

java 复制代码
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.Set;

// 定义支持的注解类型
@SupportedAnnotationTypes({
        "javax.inject.Inject",
        "dagger.Module",
        "dagger.Provides",
        "dagger.Component"
})
// 定义支持的源代码版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class DaggerProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 遍历所有支持的注解类型
        for (TypeElement annotation : annotations) {
            // 获取被该注解标记的所有元素
            Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
            for (Element element : annotatedElements) {
                // 处理注解元素
                processAnnotatedElement(element, annotation);
            }
        }
        return true;
    }

    private void processAnnotatedElement(Element element, TypeElement annotation) {
        // 根据注解类型进行不同的处理
        if (annotation.getQualifiedName().contentEquals("javax.inject.Inject")) {
            // 处理 @Inject 注解
            processInjectAnnotation(element);
        } else if (annotation.getQualifiedName().contentEquals("dagger.Module")) {
            // 处理 @Module 注解
            processModuleAnnotation(element);
        } else if (annotation.getQualifiedName().contentEquals("dagger.Provides")) {
            // 处理 @Provides 注解
            processProvidesAnnotation(element);
        } else if (annotation.getQualifiedName().contentEquals("dagger.Component")) {
            // 处理 @Component 注解
            processComponentAnnotation(element);
        }
    }

    private void processInjectAnnotation(Element element) {
        // 处理 @Inject 注解的具体逻辑
        // 例如,记录需要注入的字段或构造函数
    }

    private void processModuleAnnotation(Element element) {
        // 处理 @Module 注解的具体逻辑
        // 例如,记录模块类和提供依赖的方法
    }

    private void processProvidesAnnotation(Element element) {
        // 处理 @Provides 注解的具体逻辑
        // 例如,记录提供依赖的方法和返回类型
    }

    private void processComponentAnnotation(Element element) {
        // 处理 @Component 注解的具体逻辑
        // 例如,记录组件类和依赖的模块
    }
}

3.2 注解信息收集

在注解处理器中,会收集各个注解的信息,包括注解标记的元素、注解的属性等。例如,对于@Inject注解,会记录需要注入的字段或构造函数;对于@Module注解,会记录模块类和提供依赖的方法;对于@Provides注解,会记录提供依赖的方法和返回类型;对于@Component注解,会记录组件类和依赖的模块。以下是一个简化的注解信息收集示例:

java

java 复制代码
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.util.ArrayList;
import java.util.List;

// 注解信息收集器
class AnnotationInfoCollector {
    private List<Element> injectElements = new ArrayList<>();
    private List<TypeElement> moduleElements = new ArrayList<>();
    private List<ExecutableElement> providesElements = new ArrayList<>();
    private List<TypeElement> componentElements = new ArrayList<>();

    public void collectInjectElement(Element element) {
        injectElements.add(element);
    }

    public void collectModuleElement(TypeElement element) {
        moduleElements.add(element);
    }

    public void collectProvidesElement(ExecutableElement element) {
        providesElements.add(element);
    }

    public void collectComponentElement(TypeElement element) {
        componentElements.add(element);
    }

    public List<Element> getInjectElements() {
        return injectElements;
    }

    public List<TypeElement> getModuleElements() {
        return moduleElements;
    }

    public List<ExecutableElement> getProvidesElements() {
        return providesElements;
    }

    public List<TypeElement> getComponentElements() {
        return componentElements;
    }
}

3.3 注解扫描的实现细节

在注解处理器的process方法中,会遍历所有支持的注解类型,并获取被该注解标记的所有元素。然后,调用processAnnotatedElement方法对每个注解元素进行处理。在processAnnotatedElement方法中,根据注解类型调用不同的处理方法,如processInjectAnnotationprocessModuleAnnotation等。在这些处理方法中,会将注解信息收集到AnnotationInfoCollector中。以下是一个完整的注解扫描示例:

java

java 复制代码
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import java.util.Set;

// 定义支持的注解类型
@SupportedAnnotationTypes({
        "javax.inject.Inject",
        "dagger.Module",
        "dagger.Provides",
        "dagger.Component"
})
// 定义支持的源代码版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class DaggerProcessor extends AbstractProcessor {
    private AnnotationInfoCollector infoCollector = new AnnotationInfoCollector();

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 遍历所有支持的注解类型
        for (TypeElement annotation : annotations) {
            // 获取被该注解标记的所有元素
            Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
            for (Element element : annotatedElements) {
                // 处理注解元素
                processAnnotatedElement(element, annotation);
            }
        }
        return true;
    }

    private void processAnnotatedElement(Element element, TypeElement annotation) {
        // 根据注解类型进行不同的处理
        if (annotation.getQualifiedName().contentEquals("javax.inject.Inject")) {
            // 处理 @Inject 注解
            infoCollector.collectInjectElement(element);
        } else if (annotation.getQualifiedName().contentEquals("dagger.Module")) {
            // 处理 @Module 注解
            infoCollector.collectModuleElement((TypeElement) element);
        } else if (annotation.getQualifiedName().contentEquals("dagger.Provides")) {
            // 处理 @Provides 注解
            infoCollector.collectProvidesElement((ExecutableElement) element);
        } else if (annotation.getQualifiedName().contentEquals("dagger.Component")) {
            // 处理 @Component 注解
            infoCollector.collectComponentElement((TypeElement) element);
        }
    }

    public AnnotationInfoCollector getInfoCollector() {
        return infoCollector;
    }
}

四、依赖解析

4.1 依赖关系的表示

在 Dagger 2 中,依赖关系可以通过Dependency类来表示。Dependency类包含了依赖对象的类型和提供依赖的方式。以下是一个简化的Dependency类示例:

java

java 复制代码
import javax.lang.model.type.TypeMirror;

// 依赖关系类
class Dependency {
    private TypeMirror type; // 依赖对象的类型
    private boolean isProvidedByModule; // 是否由模块提供依赖

    public Dependency(TypeMirror type, boolean isProvidedByModule) {
        this.type = type;
        this.isProvidedByModule = isProvidedByModule;
    }

    public TypeMirror getType() {
        return type;
    }

    public boolean isProvidedByModule() {
        return isProvidedByModule;
    }
}

4.2 依赖解析的过程

依赖解析的过程主要是根据注解信息,确定各个依赖对象之间的依赖关系。具体步骤如下:

  1. 解析@Inject注解 :对于被@Inject注解标记的字段或构造函数,确定其依赖的对象类型。

  2. 解析@Module@Provides注解 :对于被@Module注解标记的模块类,解析其中被@Provides注解标记的方法,确定这些方法提供的依赖对象类型。

  3. 建立依赖关系:根据解析得到的信息,建立各个依赖对象之间的依赖关系。以下是一个简化的依赖解析示例:

java

java 复制代码
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// 依赖解析器
class DependencyResolver {
    private AnnotationInfoCollector infoCollector;
    private Map<TypeMirror, List<Dependency>> dependencyMap = new HashMap<>();

    public DependencyResolver(AnnotationInfoCollector infoCollector) {
        this.infoCollector = infoCollector;
    }

    public void resolveDependencies() {
        // 解析 @Inject 注解
        resolveInjectAnnotations();
        // 解析 @Module 和 @Provides 注解
        resolveModuleAndProvidesAnnotations();
    }

    private void resolveInjectAnnotations() {
        List<Element> injectElements = infoCollector.getInjectElements();
        for (Element element : injectElements) {
            if (element.getKind().isField()) {
                // 处理字段注入
                TypeMirror fieldType = element.asType();
                Dependency dependency = new Dependency(fieldType, false);
                addDependency(fieldType, dependency);
            } else if (element.getKind().isConstructor()) {
                // 处理构造函数注入
                ExecutableElement constructor = (ExecutableElement) element;
                for (Element parameter : constructor.getParameters()) {
                    TypeMirror parameterType = parameter.asType();
                    Dependency dependency = new Dependency(parameterType, false);
                    addDependency(parameterType, dependency);
                }
            }
        }
    }

    private void resolveModuleAndProvidesAnnotations() {
        List<ExecutableElement> providesElements = infoCollector.getProvidesElements();
        for (ExecutableElement providesElement : providesElements) {
            TypeMirror returnType = providesElement.getReturnType();
            Dependency dependency = new Dependency(returnType, true);
            addDependency(returnType, dependency);
        }
    }

    private void addDependency(TypeMirror type, Dependency dependency) {
        List<Dependency> dependencies = dependencyMap.computeIfAbsent(type, k -> new ArrayList<>());
        dependencies.add(dependency);
    }

    public Map<TypeMirror, List<Dependency>> getDependencyMap() {
        return dependencyMap;
    }
}

4.3 依赖解析的实现细节

DependencyResolver类中,resolveDependencies方法是依赖解析的入口。它会依次调用resolveInjectAnnotationsresolveModuleAndProvidesAnnotations方法,分别解析@Inject注解和@Module@Provides注解。在resolveInjectAnnotations方法中,会处理被@Inject注解标记的字段和构造函数,确定其依赖的对象类型,并将依赖信息添加到dependencyMap中。在resolveModuleAndProvidesAnnotations方法中,会处理被@Provides注解标记的方法,确定这些方法提供的依赖对象类型,并将依赖信息添加到dependencyMap中。

五、图构建

5.1 图的表示

在 Dagger 2 中,依赖图可以通过DependencyGraph类来表示。DependencyGraph类包含了图的节点和边的信息。以下是一个简化的DependencyGraph类示例:

java

java 复制代码
import javax.lang.model.type.TypeMirror;
import java.util.*;

// 依赖图类
class DependencyGraph {
    private Map<TypeMirror, List<TypeMirror>> adjacencyList = new HashMap<>(); // 邻接表表示图

    public void addNode(TypeMirror node) {
        adjacencyList.putIfAbsent(node, new ArrayList<>());
    }

    public void addEdge(TypeMirror from, TypeMirror to) {
        adjacencyList.computeIfAbsent(from, k -> new ArrayList<>()).add(to);
    }

    public List<TypeMirror> getNeighbors(TypeMirror node) {
        return adjacencyList.getOrDefault(node, Collections.emptyList());
    }

    public Set<TypeMirror> getNodes() {
        return adjacencyList.keySet();
    }
}

5.2 图构建的过程

图构建的过程主要是根据依赖解析得到的依赖关系,将其转化为依赖图。具体步骤如下:

  1. 添加节点:将所有依赖对象的类型作为节点添加到图中。

  2. 添加边:根据依赖关系,在图中添加边,表示依赖关系。以下是一个简化的图构建示例:

java

java 复制代码
import javax.lang.model.type.TypeMirror;
import java.util.Map;
import java.util.List;

// 图构建器
class GraphBuilder {
    private DependencyResolver resolver;
    private DependencyGraph graph = new DependencyGraph();

    public GraphBuilder(DependencyResolver resolver) {
        this.resolver = resolver;
    }

    public void buildGraph() {
        Map<TypeMirror, List<Dependency>> dependencyMap = resolver.getDependencyMap();
        // 添加节点
        for (TypeMirror type : dependencyMap.keySet()) {
            graph.addNode(type);
        }
        // 添加边
        for (Map.Entry<TypeMirror, List<Dependency>> entry : dependencyMap.entrySet()) {
            TypeMirror from = entry.getKey();
            List<Dependency> dependencies = entry.getValue();
            for (Dependency dependency : dependencies) {
                TypeMirror to = dependency.getType();
                graph.addEdge(from, to);
            }
        }
    }

    public DependencyGraph getGraph() {
        return graph;
    }
}

5.3 图构建的实现细节

GraphBuilder类中,buildGraph方法是图构建的入口。它会首先获取依赖解析得到的dependencyMap,然后遍历dependencyMap的键,将所有依赖对象的类型作为节点添加到图中。接着,遍历dependencyMap的每个条目,根据依赖关系在图中添加边。

六、图验证

6.1 循环依赖的检测

循环依赖是指依赖关系中存在环路,即 A 依赖 B,B 又依赖 A。循环依赖会导致依赖注入无法正常进行,因此需要在图构建完成后进行循环依赖的检测。可以使用深度优先搜索(DFS)算法来检测循环依赖。以下是一个简化的循环依赖检测示例:

java

java 复制代码
import javax.lang.model.type.TypeMirror;
import java.util.*;

// 循环依赖检测器
class CycleDetector {
    private DependencyGraph graph;
    private Set<TypeMirror> visited = new HashSet<>();
    private Set<TypeMirror> recursionStack = new HashSet<>();

    public CycleDetector(DependencyGraph graph) {
        this.graph = graph;
    }

    public boolean hasCycle() {
        for (TypeMirror node : graph.getNodes()) {
            if (!visited.contains(node)) {
                if (dfs(node)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean dfs(TypeMirror node) {
        visited.add(node);
        recursionStack.add(node);

        for (TypeMirror neighbor : graph.getNeighbors(node)) {
            if (!visited.contains(neighbor)) {
                if (dfs(neighbor)) {
                    return true;
                }
            } else if (recursionStack.contains(neighbor)) {
                return true;
            }
        }

        recursionStack.remove(node);
        return false;
    }
}

6.2 图验证的过程

图验证的过程主要是检测依赖图中是否存在循环依赖。如果存在循环依赖,会抛出异常,提示开发者解决循环依赖问题。以下是一个简化的图验证示例:

java

java 复制代码
// 图验证器
class GraphValidator {
    private DependencyGraph graph;

    public GraphValidator(DependencyGraph graph) {
        this.graph = graph;
    }

    public void validateGraph() {
        CycleDetector detector = new CycleDetector(graph);
        if (detector.hasCycle()) {
            throw new IllegalStateException("Dependency graph contains a cycle!");
        }
    }
}

6.3 图验证的实现细节

GraphValidator类中,validateGraph方法是图验证的入口。它会创建一个CycleDetector对象,并调用其hasCycle方法检测依赖图中是否存在循环依赖。如果存在循环依赖,会抛出IllegalStateException异常。

七、代码生成

7.1 代码生成的目标

代码生成的目标是根据依赖图生成依赖注入代码,实现依赖对象的创建和注入。生成的代码通常包括组件类的实现、模块类的包装类、依赖提供者类等。

7.2 代码生成的过程

代码生成的过程主要包括以下几个步骤:

  1. 确定生成的代码结构:根据依赖图和注解信息,确定生成的代码结构,如组件类的接口和实现、模块类的包装类、依赖提供者类等。

  2. 生成代码模板:根据代码结构,生成代码模板,包括类的定义、方法的定义、字段的定义等。

  3. 填充代码模板:根据依赖图和注解信息,填充代码模板,生成具体的代码。以下是一个简化的代码生成示例:

java

java 复制代码
import javax.lang.model.type.TypeMirror;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;
import java.util.List;

// 代码生成器
class CodeGenerator {
    private DependencyGraph graph;
    private Map<TypeMirror, List<Dependency>> dependencyMap;

    public CodeGenerator(DependencyGraph graph, Map<TypeMirror, List<Dependency>> dependencyMap) {
        this.graph = graph;
        this.dependencyMap = dependencyMap;
    }

    public void generateCode() {
        // 生成组件类的实现
        generateComponentImplementation();
        // 生成模块类的包装类
        generateModuleWrappers();
        // 生成依赖提供者类
        generateDependencyProviders();
    }

    private void generateComponentImplementation() {
        // 生成组件类的实现代码
        try (FileWriter writer = new FileWriter("ComponentImpl.java")) {
            writer.write("public class ComponentImpl {\n");
            // 生成组件类的字段和方法
            for (TypeMirror node : graph.getNodes()) {
                writer.write("    private " + node.toString() + " " + node.toString().toLowerCase() + ";\n");
            }
            writer.write("    public ComponentImpl() {\n");
            // 生成组件类的构造函数代码
            for (TypeMirror node : graph.getNodes()) {
                writer.write("        this." + node.toString().toLowerCase() + " = new " + node.toString() + "();\n");
            }
            writer.write("    }\n");
            writer.write("}\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void generateModuleWrappers() {
        // 生成模块类的包装类代码
        // 具体实现省略
    }

    private void generateDependencyProviders() {
        // 生成依赖提供者类代码
        // 具体实现省略
    }
}

7.3 代码生成的实现细节

CodeGenerator类中,generateCode方法是代码生成的入口。它会依次调用generateComponentImplementationgenerateModuleWrappersgenerateDependencyProviders方法,分别生成组件类的实现、模块类的包装类和依赖提供者类的代码。在generateComponentImplementation方法中,会创建一个FileWriter对象,将生成的组件类实现代码写入文件。

八、依赖图构建模块的整体流程

8.1 整体流程概述

依赖图构建模块的整体流程包括注解扫描、依赖解析、图构建、图验证和代码生成。以下是一个简化的整体流程示例:

java

java 复制代码
// 依赖图构建模块的整体流程示例
public class DependencyGraphBuilder {
    public static void main(String[] args) {
        // 创建注解处理器
        DaggerProcessor processor = new DaggerProcessor();
        // 模拟注解扫描过程
        // 这里省略具体的注解扫描代码,假设已经完成注解扫描
        // 获取注解信息收集器
        AnnotationInfoCollector infoCollector = processor.getInfoCollector();
        // 创建依赖解析器
        DependencyResolver resolver = new DependencyResolver(infoCollector);
        // 解析依赖关系
        resolver.resolveDependencies();
        // 创建图构建器
        GraphBuilder graphBuilder = new GraphBuilder(resolver);
        // 构建依赖图
        graphBuilder.buildGraph();
        // 获取依赖图
        DependencyGraph graph = graphBuilder.getGraph();
        // 创建图验证器
        GraphValidator validator = new GraphValidator(graph);
        try {
            // 验证依赖图
            validator.validateGraph();
            // 创建代码生成器
            CodeGenerator generator = new CodeGenerator(graph, resolver.getDependencyMap());
            // 生成代码
            generator.generateCode();
        } catch (IllegalStateException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}

8.2 整体流程的实现细节

DependencyGraphBuilder类的main方法中,首先创建了一个DaggerProcessor对象,模拟注解扫描过程。然后获取注解信息收集器,创建DependencyResolver对象,解析依赖关系。接着创建GraphBuilder对象,构建依赖图。再创建GraphValidator对象,验证依赖图。如果依赖图验证通过,创建CodeGenerator对象,生成代码。如果依赖图存在循环依赖,会捕获IllegalStateException异常,并输出错误信息。

九、依赖图构建模块的优化

9.1 性能优化

  • 缓存机制:在依赖解析和图构建过程中,可以使用缓存机制来避免重复计算。例如,对于已经解析过的依赖关系,可以将其缓存起来,下次需要时直接从缓存中获取。
  • 并行处理:对于大规模的项目,可以考虑使用并行处理来提高依赖图构建的性能。例如,将注解扫描、依赖解析、图构建等步骤并行执行。

9.2 错误处理优化

  • 详细的错误信息:在图验证过程中,如果发现循环依赖或其他错误,应该提供详细的错误信息,帮助开发者快速定位和解决问题。
  • 错误恢复机制:在代码生成过程中,如果出现错误,应该有错误恢复机制,避免程序崩溃。例如,可以捕获异常,记录错误信息,并尝试继续生成其他部分的代码。

9.3 代码生成优化

  • 代码模板优化:可以使用更灵活的代码模板,根据不同的依赖关系和注解信息生成更优化的代码。例如,对于单例对象,可以生成单例模式的代码。
  • 代码压缩:生成的代码可能会比较冗长,可以使用代码压缩工具对生成的代码进行压缩,减少代码体积。

十、总结

本文深入分析了 Android Dagger 2 框架的依赖图构建模块,从源码级别详细介绍了其工作原理和实现细节。依赖图构建模块是 Dagger 2 的核心组成部分,它通过注解扫描、依赖解析、图构建、图验证和代码生成等步骤,实现了依赖关系的解析和依赖注入代码的生成。在实际开发中,掌握依赖图构建模块的原理和实现细节,有助于更好地使用 Dagger 2 进行依赖注入,提高代码的可测试性和可维护性。同时,通过对依赖图构建模块的优化,可以进一步提高其性能和可靠性。

以上是一篇关于 Android Dagger 2 框架依赖图构建模块的技术博客,涵盖了从注解扫描到代码生成的整个流程,以及相关的优化建议。希望对你有所帮助!

相关推荐
xiangxiongfly91531 分钟前
Android setContentView()源码分析
android·setcontentview
人间有清欢2 小时前
Android开发补充内容
android·okhttp·rxjava·retrofit·hilt·jetpack compose
人间有清欢3 小时前
Android开发报错解决
android
每次的天空4 小时前
Android学习总结之kotlin协程面试篇
android·学习·kotlin
每次的天空6 小时前
Android学习总结之Binder篇
android·学习·binder
峥嵘life6 小时前
Android 有线网开发调试总结
android
是店小二呀7 小时前
【算法-链表】链表操作技巧:常见算法
android·c++·算法·链表
zhifanxu9 小时前
Kotlin 遍历
android·开发语言·kotlin
追随远方9 小时前
Android NDK版本迭代与FFmpeg交叉编译完全指南
android·ffmpeg
柯南二号20 小时前
Android Studio根目录下创建多个可运行的模块
android·ide·android studio