为什么你的Opus编码出来的数据有杂音(解决Android平台架构问题)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: Gradle插件分为脚本插件和对象插件,脚本插件就是在普通的gradle中写一系列task,然后在别的gradle构建脚本中通过 apply from: 'xx.gradle' 引用这个脚本插件,下面主要介绍一下对象插件对象插件是指实现了org.gradle.api.Plugin接口的类。并且需要实现void apply(T target)这个方法,该方法中的泛型指的是此Plugin可以应用到的对象,而我们通常是将其应用到Project对象上。编写对象插件常见创建方式

背景


公司app使用的动态库都是只打包了armeabi架构,而NDK 16不再能编译出armeabi库,NDK 17后 apk打包时配置:


xternalNativeBuild {
            cmake {
                cppFlags "-std=c++11 -frtti -fexceptions"
                abiFilters 'armeabi' //NDK 17及以上不再支持ABIs [mips64, armeabi, mips]
            }
        }


会直接报错,专门下载了NDK 15终于编译除了armeabi库,但是QA测试时发现录制的声音急促并且有杂音,明明我的demo里面没问题啊,对比不同,发现armeabi架构下编译的opus封装时重采样有问题,使用armeabi-v7a的果然没问题.虽然增加armeabi-v7a架构库可以解决问题,但是明显会增大包体积,而且不是特别安全,可能某些手机下会crash.灵机一动,想起之前eclipse时代,ndk-build出动态库后放到libs目录下会自动打包到apk里面,那时候打包apk之前,把armeabi-v7a的so拷贝到armeabi下就可以了,但是AndroidStudio时代,特别是现在的3.5时代,gradle已经升级到5.4.1,android gradle也同步升级到3.5.0以后,已经不能随便的去操作打包出来的动态库了,于是想到了使用gradle插件来操作编译过程,在动态库打包出来后,手动创建armeabi文件夹,把armeabi-v7a下的so拷贝到armeabi下,打到打包aar或者apk时有支持armeabi架构的效果.


Gradle插件可以做什么


之前接触过Gradle插件的一些产物,包括:


  1. 修改编译后的apk文件名
  2. Router框架,解析代码中的注解,生成静态类
  3. 无埋点,hook关键类的生命周期方法
  4. 插件化中,重命名资源文件
  5. 编译时对图片资源进行压缩(github.com/chenenyu/im…)
  6. ...

这些操作都是基于gradle构建生命周期为基础,寻找hook点后进行操作,同样的道理,我也可以找到对应的hook点,进行文件拷贝


创建一个Gradle插件


Gradle插件分为脚本插件对象插件,脚本插件就是在普通的gradle中写一系列task,然后在别的gradle构建脚本中通过 apply from: 'xx.gradle' 引用这个脚本插件,下面主要介绍一下对象插件 对象插件是指实现了org.gradle.api.Plugin接口的类。并且需要实现void apply(T target)这个方法,该方法中的泛型指的是此Plugin可以应用到的对象,而我们通常是将其应用到Project对象上。 编写对象插件常见创建方式


  1. 直接在gradle脚本文件中
  2. 在buildSrc目录下
  3. 在独立的项目下 由于我的插件针对特定项目,所以暂时不说独立项目下的对象插件,介绍一下前两种:


Gradle中直接创建


gradle里面创建比较简单,直接实现类接口:


//app.gradle
class CustomPluginInBuildGradle implements Plugin<Project> {
    @Override
    void apply(Project target) {
       target.task('showCustomPluginInBuildGradle'){
            doLast {
                println("task in CustomPluginInBuildGradle")
            }
        }
    }
}


然后通过插件类名引用它:


//app.gradle
apply plugin: CustomPluginInBuildGradle


接着就可以直接执行这个task了,运行结果:


> Task :app:showCustomPluginInBuildGradle 
task in CustomPluginInBuildGradle


buildSrc目录中创建


buildSrc目录是Gradle在项目中配置自定义插件的默认目录,里面定义的task可以直接使用,首先我们需要创建一个Java Library Module(不能是Android Library,否则构建会失败),名字叫buildSrc,因为使用的groovy,所以目录下面的java需要修改为groovy,需要通过id符号引入的,也需要创建resources->META-INF->gradle-plugins 修改之后的目录结构


buildSrc
    .gradle
    build
    libs
    build.gradle
    src
        main
            groovy
                com.xxx.xxxplugin
                    UtilPlugin
            resources
                META_INF.gradle-plugins
                    myplugin.properties                


编写build.gradle:


apply plugin: 'groovy'
repositories {
  jcenter()
  google()
  mavenCentral()
}
dependencies {
  implementation localGroovy()
  implementation gradleApi()
  //可选
  implementation 'com.squareup.okhttp3:okhttp:3.12.1'
  implementation 'org.javassist:javassist:3.22.0-GA'
  implementation 'com.squareup.okio:okio:1.14.0'
  implementation "com.android.tools.build:gradle:3.1.4"
}


UtilPlugin是定义好的对象插件,resources中定义的内容是为了通过id识别的


import org.gradle.api.Project
import org.gradle.api.Plugin
class UtilPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        project.task('showCustomPluginInBuildSrc') {
            doLast {
                println('task in CustomPluginInBuildSrc')
            }
        }
    }
}


接着看下resources里面的myplugin.properties


implementation-class=com.com.xxx.xxxplugin.UtilPlugin


这其中myplugin是别的gradle构建脚本识别的id,implementation-class需要设置的是全路径名,当然resources目录里面的内容可以不用创建,我们在别的构建脚本中导入的时候,就需要导入全路径名


apply plugin: 'myplugin'


实现动态库拷贝


获取拷贝执行时机


构建生命周期


每次构建的本质其实就是执行一系列的Task,某些Task可能依赖其他Task,那些没有依赖的Task总会被最先执行,而且每个Task只会被执行一遍,每次构建的依赖关系是在构建的配置阶段确定的,在gradle构建中,构建的生命周期主要包括以下三个阶段:



初始化(Initialization)


构建工具会根据每个build.gradle文件创建出一个Project实例,初始化阶段会执行项目根目录下的Settings.gradle文件,来分析哪些项目参与构建


include ':app'
include ':libraries:someProject'


配置(Configuration)


这个阶段通过执行构建脚本来为每个project创建并分配Task。配置阶段会去加载所有参与构建的项目的build.gradle文件,会将build.gradle文件实例化为一个Gradle的project对象,然后分析project之间的依赖关系,下载依赖文件,分析project下的task之间的依赖关系


执行(Execution)


这是Task真正被执行的阶段,Gradle会根据依赖关系决定哪些Task需要被执行,以及执行的先后顺序。task是Gradle中的最小执行单元,我们所有的构建,编译,打包,debug,test等都是执行了某一个task,一个project可以有多个task,task之间可以互相依赖。例如我有两个task,taskA和taskB,指定taskA依赖taskB,然后执行taskA,这时会先去执行taskB,taskB执行完毕后在执行taskA。


寻找Hook点


我们需要在构建生命周期中找到hook点,实现我们的操作,gradle为我们提供了非常丰富的钩子,帮助我们针对项目的需求定制构建的逻辑,如下图所示:



要监听这些生命周期,主要有两种方式:


  • 添加监听器
  • 使用钩子的配置块 关于可用的钩子可以参考Gradle和Project中的定义,常用的钩子包括:


Project


Project提供的生命周期回调方法有


//在 Project 进行配置前调用
void beforeEvaluate(Closure closure)
//在 Project 配置结束后调用
void afterEvaluate(Closure closure)


beforeEvaluate 必须在父模块的 build.gradle 对子模块进行配置才能生效,因为在当前模块的 build.gradle 中配置,它自己本身都没配置好,所以不会监听到。


settings.gradle 代码:


include ":app"


build.gradle 代码:


//对子模块进行配置
subprojects { sub ->
    sub.beforeEvaluate { proj ->
        println "子项目beforeEvaluate回调..."
    }
}
println "根项目配置开始---"
task rootTest {
    println "根项目里任务配置---"
    doLast {
        println "执行根项目任务..."
    }
}
println "根项目配置结束---"


app/build.gradle 代码:


println "APP子项目配置开始---"
afterEvaluate {
    println "APP子项目afterEvaluate回调..."
}
task appTest {
    println "APP子项目里任务配置---"
    doLast {
        println "执行子项目任务..."
    }
}
println "APP子项目配置结束---"


在根目录执行:gradle -q,结果如下:


根项目配置开始---
根项目里任务配置---
根项目配置结束---
子项目beforeEvaluate回调...
APP子项目配置开始---
APP子项目里任务配置---
APP子项目配置结束---
APP子项目afterEvaluate回调...


Gradle


Gradle 提供的生命周期回调方法很多,部分与 Project 里的功能雷同:


//在project进行配置前调用,child project必须在root project中设置才会生效,root project必须在settings.gradle中设置才会生效
void beforeProject(Closure closure)
//在project配置后调用
afterProject(Closure closure)
//构建开始前调用
void buildStarted(Closure closure)
//构建结束后调用
void buildFinished(Closure closure)
//所有project配置完成后调用
void projectsEvaluated(Closure closure)
//当settings.gradle中引入的所有project都被创建好后调用,只在该文件设置才会生效
void projectsLoaded(Closure closure)
//settings.gradle配置完后调用,只对settings.gradle设置生效
void settingsEvaluated(Closure closure)


  • beforeProject()/afterProject()等同于Project中的beforeEvaluate和afterEvaluate
  • settingsEvaluated()settings脚本被执行完毕,Settings对象配置完毕
  • projectsLoaded()所有参与构建的项目都从settings中创建完毕
  • projectsEvaluated()所有参与构建的项目都已经被评估完


我们修改 setting.gradle 的代码如下:


gradle.settingsEvaluated {
    println "settings:执行settingsEvaluated..."
}
gradle.projectsLoaded {
    println "settings:执行projectsLoaded..."
}
gradle.projectsEvaluated {
    println "settings: 执行projectsEvaluated..."
}
gradle.beforeProject { proj ->
    println "settings:执行${proj.name} beforeProject"
}
gradle.afterProject { proj ->
    println "settings:执行${proj.name} afterProject"
}
gradle.buildStarted {
    println "构建开始..."
}
gradle.buildFinished {
    println "构建结束..."
}
include ":app"


这个时候的执行结果如下:


settings:执行settingsEvaluated...
settings:执行projectsLoaded...
settings:执行test beforeProject
根项目配置开始---
根项目里任务配置---
根项目配置结束---
settings:执行test afterProject
settings:执行app beforeProject
子项目beforeEvaluate回调...
APP子项目配置开始---
APP子项目里任务配置---
APP子项目配置结束---
settings:执行app afterProject
APP子项目afterEvaluate回调...
settings: 执行projectsEvaluated...
构建结束...


可以看到 gradle.beforeProject 与 project.beforeEvaluate 是类似的,同样 afterProject 与 afterEvaluate 也是类似的。 除此之外,Gradle 还有一个通用的设置生命周期监听器的方法:addListener


TaskExecutionGraph(Task执行图)


Gradle 在配置完成后,会对所有的 task 生成一个有向无环图,这里叫做 task 执行图,他们决定了 task 的执行顺序等。同样,Gradle 可以对 task 的执行生命周期进行监听。


//任务执行前掉用
void afterTask(Closure closure)
//任务执行后调用
void beforeTask(Closure closure)
//所有需要被执行的task已经task之间的依赖关系都已经确立
void whenReady(Closure closure)


通过 gradle.getTaskGraph() 方法来获取 task 执行图:


TaskExecutionGraph taskGraph = gradle.getTaskGraph()
taskGraph.whenReady {
    println "task whenReady"
}
taskGraph.beforeTask { Task task ->
    println "任务名称:${task.name} beforeTask"
}
taskGraph.afterTask { Task task ->
    println "任务名称:${task.name} afterTask"
}


生命周期回调的执行顺序:


gradle.settingsEvaluated->
gradle.projectsLoaded->
gradle.beforeProject->
project.beforeEvaluate->
gradle.afterProject->
project.afterEvaluate->
gradle.projectsEvaluated->
gradle.taskGraph.graphPopulated->
gradle.taskGraph.whenReady->
gradle.buildFinished


确定hook点


最常用android.applicationVariants,相关的:



在BaseVariant中



我们动态库的拷贝是跟getExternalNativeBuildTasks()和getNdkCompile()相关,于是我们在我们的gradle中:


def copyJniDebugLib() {
  println '拷贝文件 copyJniLib'
  mkdir "build/intermediates/cmake/debug/obj/armeabi"
  copy {
    from file("build/intermediates/cmake/debug/obj/armeabi-v7a")
    //into 是一个方法:指定拷贝的目的地>拷贝到根工程的output目录下
    into "build/intermediates/cmake/debug/obj/armeabi"
  }
}
def copyJniReleseLib() {
  println '拷贝文件 copyJniReleseLib'
  mkdir "build/intermediates/cmake/release/obj/armeabi"
  copy {
    from file("build/intermediates/cmake/release/obj/armeabi-v7a")
    //into 是一个方法:指定拷贝的目的地>拷贝到根工程的output目录下
    into "build/intermediates/cmake/release/obj/armeabi"
  }
}
project.afterEvaluate {
  externalNativeBuildRelease.doLast {
    copyJniReleseLib()
  }
  externalNativeBuildDebug.doLast {
    copyJniDebugLib()
  }
}


成功完成从armeabi-v7a到armeabi的拷贝.


补充Gradle进阶


Gradle本身的领域对象主要有Project和Task。Project为Task提供了执行上下文,所有的Plugin要么向Project中添加用于配置的Property,要么向Project中添加不同的Task。一个Task表示一个逻辑上较为独立的执行过程,比如编译Java源代码,拷贝文件,打包Jar文件,甚至可以是执行一个系统命令或者调用Ant。另外,一个Task可以读取和设置Project的Property以完成特定的操作。


核心概念

Project


自定义插件类是通过实现Plugin 接口,并将 org.gradle.api.Project作为模板参数,其中org.gradle.api.Project的实例对象将作为参数传给void apply(Project project)函数,根据官网,可以看出Project是与Gradle交互的主接口,通过Project可以使用gradle的所有特性,并且 Project与build.grale是一对一的关系。简而言之,就是通过代码使用Gradle,通过Project这个入口即可 我们对project的理解更多来源于项目目录中的build.gradle文件(因为它其实就是project对象的委托,在脚本中的配置方法都对应着Project中的API,当构建进程启动后Gradle基于build.gradle中的配置实例化org.gradle.api.Project类,本质上可以认为是包含多个Task的容器,所有的Task都存放在TaskContainer中,Project对象的类图如下所示:



项目配置


在build.gradle脚本文件中,我们不仅可以对单独project进行配置,也可以定义project块的共有逻辑等,参考下面的定义。



常见的例子


// 为所有项目添加仓库源配置
allprojects {
    repositories {
        jcenter()
        google()
    }
}
// 为所有子项目添加mavenPublish的配置块
subprojects {
    mavenPublish {
        groupId = maven.config.groupId
        releaseRepo = maven.config.releaseRepo
        snapshotRepo = maven.config.snapshotRepo
    }
}


Task


Gradle Task APIGradle构建脚本默认的名字是build.gradle,当在shell中执行gradle命令时,Gradle会去当前目录下寻找名字是build.gradle的文件。在Gradle中一个原子性的操作叫做task,简单理解为task是Gradle脚本中的最小可执行单元。 下面是task的类图。



Task的Actions


一个Task是由一序列Action组成的,当运行一个Task的时候,这个Task里的Action序列会按照顺序执行


Task的几种常见写法


task myTask1 {
    doLast {
        println "doLast in task1"
    }
}
task myTask2 << {
    println "doLast in task2"
}
//采用 Project.task(String name) 方法来创建
project.task("myTask3").doLast {
    println "doLast in task3"
}
//采用 TaskContainer.create(String name) 方法来创建
project.tasks.create("myTask4").doLast {
    println "doLast in task4"
}
project.tasks.create("myTask5") << {
    println "doLast in task5"
}


目前task的动作(action)声明主要包含两个方法:


  • doFirst  等价操作 缩写 leftShift <<(5.0会废弃)
  • doLast 在 Gradle 中定义 Task 的时候,可以指定更多的参数,如下所示: |参数名|含义默认值| |---|---|---| |name|task的名字|必须指定,不能为空| |type|task的父类|默认值为org.gradle.api.DefaultTask| |overwrite|是否替换已经存在的同名task|false| |group|task所属的分组名|null| |description|task的描述|null| |dependsOn|task依赖的task集合|无| |constructorArgs|构造函数参数|无|


Task的依赖


gradle中任务的执行顺序是不确定的。通过task之间的依赖关系,gradle能够确保所依赖的task会被当前的task先执行。使用task的dependsOn()方法,允许我们为task声明一个或者多个task依赖。


task first{
    doLast{
        println("first")
    }
}
task second{
    doLast{
        println("second")
    }
}
task third{
    doLast{
        println("third")
    }
}
task test(dependsOn:[second,first]){
    doLast{
        println("test1")
    }
}
third.dependsOn(test)


执行顺序:


> Task :app:first 
> Task :app:second 
> Task :app:test1 
> Task :app:third 


Task的类型


有copy、jar、Delete 等等 可以参考Doc文档


task copyFile(type: Copy) {
   from 'xml'
   into 'destination'
}


自定义Task


Gradle 中通过 task 关键字创建的 task,默认的父类都是 org.gradle.api.DefaultTask,这里定义了一些 task 的默认行为。看看下面这个例子:


//自定义Task类,必须继承自DefaultTask
class SayHelloTask extends DefaultTask {
    String msg = "default name"
    int age = 18        
    //构造函数必须用@javax.inject.Inject注解标识
    @javax.inject.Inject
    SayHelloTask(int age) {
        this.age = age
    }
    //通过@TaskAction注解来标识该Task要执行的动作
    @TaskAction
    void sayHello() {
        println "Hello $msg ! age is ${age}"
    }
}
//通过constructorArgs参数来指定构造函数的参数值
task hello1(type: SayHelloTask, constructorArgs: [30])
//通过type参数指定task的父类,可以在配置代码里修改父类的属性
task hello2(type: SayHelloTask, constructorArgs: [18]) {
        //配置代码里修改 SayHelloTask 里的字段 msg 的值
    msg = "hjy"
}


执行这两个task:


> Task :hello1
Hello default name ! age is 30
> Task :hello2
Hello hjy ! age is 18


Task的类图


Gradle所说的Task是org.gradle.api.Task接口,默认实现是org.gradle.api.DefaultTask类,其类图如下



TaskContainer接口解析


TaskContianer 是用来管理所有的 Task 实例集合的,可以通过 Project.getTasks() 来获取 TaskContainer 实例。


org.gradle.api.tasks.TaskContainer接口:
//查找task
findByPath(path: String): Task
getByPath(path: String): Task
getByName(name: String): Task
withType(type: Class): TaskCollection
matching(condition: Closure): TaskCollection
//创建task
create(name: String): Task
create(name: String, configure: Closure): Task 
create(name: String, type: Class): Task
create(options: Map<String, ?>): Task
create(options: Map<String, ?>, configure: Closure): Task
//当task被加入到TaskContainer时的监听
whenTaskAdded(action: Closure)
//当有task创建时
getTasks().whenTaskAdded { Task task ->
    println "The task ${task.getName()} is added to the TaskContainer"
}
//采用create(name: String)创建
getTasks().create("task1")
//采用create(options: Map<String, ?>)创建
getTasks().create([name: "task2", group: "MyGroup", description: "这是task2描述", dependsOn: ["task1"]])
//采用create(options: Map<String, ?>, configure: Closure)创建
getTasks().create("task3", {
    group "MyGroup"
    setDependsOn(["task1", "task2"])
    setDescription "这是task3描述"
})


默认情况下,我们常见的task都是org.gradle.api.DefaultTask类型。但是在gradle当中有相当丰富的task类型我们可以直接使用。要更改task的类型,我们可以参考下面的示例


task createDistribution(type:Zip){
}


更多关于task的类型,可以参考gradle的官方文档

常用Task:

Copy


Copy的API文档:docs.gradle.org/current/dsl…替换AndroidManifest文件


task chVer(type: Copy) { // 指定Type为Copy任务
    from "src/main/manifest/AndroidManifestCopy.xml"  // 复制src/main/manifest/目录下的AndroidManifest.xml
    into 'src/main'  // 复制到指定目标目录
    rename { String fileName -> //在复制时重命名文件
        fileName = "AndroidManifest.xml" // 重命名
    }
}


替换so文件


task chSo(type: Copy) {
    from "src/main/jniLibs/test"   // 复制test文件夹下的所有so文件
    into "src/main/jniLibs/armeabi-v7a" //复制到armeabi-v7a文件夹下
}


Sync


此任务与Copy任务类似,唯一的区别是当执行时会复制源文件到目标目录,目标目录中所有非复制文件将会被删除,除非指定Sync.preserve(org.gradle.api.Action)。 例子:


task syncDependencies(type: Sync) {
    from 'my/shared/dependencyDir'
    into 'build/deps/compile'
}
// 你可以保护目标目录已经存在的文件。匹配的文件将不会被删除。
task sync(type: Sync) {
    from 'source'
    into 'dest'
    preserve {
        include 'extraDir/**'
        include 'dir1/**'
        exclude 'dir1/extra.txt'
    }
}


Sync的API文档:docs.gradle.org/current/dsl…

Zip

创建ZIP归档文件,默认压缩文件类型为zip。 例子:


task zip(type: Zip) {
    from 'src/dist'
    into('libs') 
}


Zip的API文档:docs.gradle.org/current/dsl…


Task技巧


如果有多个任务需要执行是不是要执行多次任务呢?可以通过多任务命令调用一次即可。


gradlew task1 task2 [...]


任务名太长不想输入这么多字怎么办?可以采用简化操作,但是必须保证可以唯一区分出该任务的字符,如:


gradlew cV


不想每次打包前都输入命令怎么办?可以每次build时自动执行自定义任务。


afterEvaluate {
    tasks.matching {
        // 以process开头以ReleaseJavaRes或DebugJavaRes结尾的task
        it.name.startsWith('process') && (it.name.endsWith('ReleaseJavaRes') || it.name.endsWith
                ('DebugJavaRes'))
   }.each { task ->
        task.dependsOn(chVer, chSo)  // 任务依赖:执行task之前需要执行dependsOn指定的任务
    }
}


完整的build.gradle代码:


apply plugin: 'com.android.application'
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"
    defaultConfig {
        applicationId "com.skr.voip"
        minSdkVersion 15
        targetSdkVersion 19
    }
    buildTypes {
        debug {
            minifyEnabled false
        }
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}
dependencies {
    //    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:support-v4:23.4.0'
    ...
}
task chVer(type: Copy) {
    from "src/main/manifest/AndroidManifestCopy.xml"  // 复制src/main/manifest/目录下的AndroidManifest.xml
    into 'src/main'  // 复制到指定目标目录
    rename { String fileName -> //在复制时重命名文件
        fileName = "AndroidManifest.xml" // 重命名
    }
}
task chSo(type: Copy) {
    from "src/main/jniLibs/test"   // 复制test文件夹下的所有文件
    into "src/main/jniLibs/armeabi-v7a" //复制到armeabi-v7a文件夹下
}
afterEvaluate {
    tasks.matching {
        // 以process开头以ReleaseJavaRes或DebugJavaRes结尾的task
        it.name.startsWith('process') && (it.name.endsWith('ReleaseJavaRes') || it.name.endsWith
                ('DebugJavaRes'))
   }.each { task ->
        task.dependsOn(chVer, chSo)  // 任务依赖:执行task之前需要执行dependsOn指定的任务
    }
}


Task的增量构建


Gradle 支持一种叫做 up-to-date 检查的功能,也就是常说的增量构建。Gradle 的 Task 会把每次运行的结果缓存下来,当下次运行时,会检查输出结果有没有变更,如果没有变更则跳过运行,这样可以提高 Gradle 的构建速度。通常,一个 task 会有一些输入(inputs)和一些输出(outputs),task 的输入会影响其输出结果,以官网中的一张图为例:



图中表示一个java编译的task,它的输入有2种,一是JDK版本号,一是源文件,它的输出结果为class文件,只要JSK版本号与源文件有任何变动,最终编译出的class文件肯定不同的。当我们执行过一次·编译任务之后,再次运行该task,如果发现他的输入没有任何改动,那么它编译后的结果肯定也是不变的,可以直接从缓存里获取输出,这样Gradle会标识该task为UP-TO-DATE,从而跳过该task的执行


TaskInputs、TaskOutputs介绍


如何实现一个增量构建呢,至少指定一个输入,一个输出,Task.getInputs() 对象类型为 TaskInputs,Task.getOutputs() 对象类型为 TaskOuputs,从中也可以看到inputs、outputs都支持哪些数据类型


task test1 {
    //设置inputs
    inputs.property("name", "hjy")
    inputs.property("age", 20)
    //设置outputs
    outputs.file("$buildDir/test.txt")
    doLast {
        println "exec task task1"
    }
}
task test2 {
    doLast {
        println "exec task task2"
    }
}
//第一次的运行结果
> Task :test1
exec task task1
> Task :test2
exec task task2
BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed
//第二次的运行结果
> Task :test2
exec task task2
BUILD SUCCESSFUL in 0s
2 actionable tasks: 1 executed, 1 up-to-date


从结果中可以看到,第2次运行时,test1 task 并没有运行,而是被标记为 up-to-date,而 test2 task 则每次都会运行,这就是典型的增量构建。


taskInputs、taskOutputs注解


可以通过task注解来实现增量构建,这是一种更加灵活方便的方式


注解名 属性类型 描述
@Input 任意Serializable类型 一个简单的输入值
@InputFile File 一个输入文件,不是目录
@InputDirectory File 一个输入目录,不是文件
@InputFiles Iterable File列表,包含文件和目录
@OutputFile File 一个输出文件,不是目录
@OutputDirectory File 一个输出目录,不是文件
@OutputFiles Map<String, File>或Iterable 输出文件列表
@OutputDirectories Map<String, File>或Iterable 输出目录列表


class SayHelloTask extends DefaultTask {
    //定义输入
    @Input
    String username;
    @Input
    int age
    //定义输出
    @OutputDirectory
    File destDir;
    @TaskAction
    void sayHello() {
        println "Hello $username ! age is $age"
    }
}
task test(type: SayHelloTask) {
    age = 18
    username = "hjy"
    destDir = file("$buildDir/test")
}


Property


ext命名空间


Gradle中很多模型类都提供了特别的属性支持,比如Project.在gradle内部,这些属性会以键值对的形式存储。使用ext命名空间,我们可以方便的添加属性。下面的方式都是支持的:


//在project中添加一个名为groupId的属性
project.ext.groupId="tech.easily"
// 使用ext块添加属性
ext{
    artifactId='EasyDependency'
    config=[
            key:'value'
    ]
}


值得注意的是,只有在声明属性的时候我们需要使用ext命名空间,在使用属性的时候,ext命名空间是可以省略的。


属性文件


正如我们经常在Android项目中看到的,我们可以在项目的根目录下新建一个gradle.properties文件,并在文件中定义简单的键值对形式的属性。这些属性能够被项目中的gradle脚本所访问。如下所示:


# gradle.properties
# 注意文件的注释是以#开头的
groupId=tech.easily
artifactId=EasyDependency


有的时候,我们可能需要在代码中动态的创建属性文件并读取文件中的属性(比如自定义插件的时候),我们可以使用java.util.Properties类。比如:


void createPropertyFile() {
    def localPropFile = new File(it.projectDir.absolutePath + "/local.properties")
    def defaultProps = new Properties()
    if (!localPropFile.exists()) {
        localPropFile.createNewFile()
        defaultProps.setProperty("debuggable", 'true')
        defaultProps.setProperty("groupId", GROUP)
        defaultProps.setProperty("artifactId", project.name)
        defaultProps.setProperty("versionName", VERSION_NAME)
        defaultProps.store(new FileWriter(localPropFile), "properties auto generated for resolve dependencies")
    } else {
        localPropFile.withInputStream { stream ->
            defaultProps.load(stream)
        }
    }
}


关于属性很重要的一点是属性是可以继承的。在一个项目中定义的属性会自动的被其子项目继承,不管我们是用以上哪种方式添加属性都是适用的。


ExtensionContainer


Extension简介


就是 Gradle 的 Extension,翻译成中文意思就叫扩展。它的作用就是通过实现自定义的 Extension,可以在 Gradle 脚本中增加类似 android 这样命名空间的配置,Gradle 可以识别这种配置,并读取里面的配置内容。 一般我们通过ExtensionContainer来创建Extension,这个类跟TaskContainer命名有点类似。TaskContainer是用来创建并管理Task的,而ExtensionContainer则是用来创建并管理Extension的,通过Project的以下API可以获取到ExtensionContainer对象


ExtensionContainer getExtensions()


简单的Extension


/先定义一个普通的java类,包含2个属性
class Foo {
    int age
    String username
    String toString() {
        return "name = ${username}, age = ${age}"
    }
}
//创建一个名为 foo 的Extension
getExtensions().create("foo", Foo)
//配置Extension
foo {
    age = 30
    username = "hjy"
}
task testExt.doLast {
    //能直接通过 project 获取到自定义的 Extension
    println project.foo
}


foo 就是我们自定义的 Extension 了,它里面能配置的属性与类 Foo 中的字段是一致的,在 build.gradle 中可以直接通过 project.foo 来访问。每个 Extension 实际上与某个类是相关联的,在 build.gradle 中通过 DSL 来定义,Gradle 会识别解析并生成一个对象实例,通过该类可以获取我们所配置的信息。 Project 有个扩展属性是通过 ext 命名空间配置的,可以看到 ext 与这里是类似的,不同的是 ext 可以配置任何键值对的属性值,而这里只能识别我们定义的 Java 类里的属性值。


ExtensionContainer主要api及用法


<T> T create(String name, Class<T> type, Object... constructionArguments)
<T> T create(Class<T> publicType, String name, Class<? extends T> instanceType, Object... constructionArguments)


先来看看后面这个 API 所有参数的含义。


  • publicType:创建的 Extension 实例暴露出来的类类型;
  • name:要创建的Extension的名字,可以是任意符合命名规则的字符串,不能与已有的重复,否则会抛异常;
  • instanceType:该Extension的类类型;
  • constructionArguments:类的构造函数参数值


官方文档里还说明了一个特性,创建的 Extension 对象都默认实现了 ExtensionAware 接口,并注明出处。 示例


//父类
class Animal {
    String username
    int legs
    Animal(String name) {
        username = name
    }
    void setLegs(int c) {
        legs = c
    }
    String toString() {
        return "This animal is $username, it has ${legs} legs."
    }
}
//子类
class Pig extends Animal {
    int age
    String owner
    Pig(int age, String owner) {
        super("Pig")
        this.age = age
        this.owner = owner
    }
    String toString() {
        return super.toString() + " Its age is $age, its owner is $owner."
    }
}
//创建的Extension是 暴露出来Animal 类型,创建extension名称是name,该extension的类型是Pig,后面2个是参数
Animal aAnimal = getExtensions().create(Animal, "animal", Pig, 3, "hjy")
//创建的Extension是 Pig 类型
Pig aPig = getExtensions().create("pig", Pig, 5, "kobe")
animal {
    legs = 4    //配置属性
}
pig {
    setLegs 2   //这个是方法调用,也就是 setLegs(2)
}
task testExt << {
    println aAnimal
    println aPig
    //验证 aPig 对象是 ExtensionAware 类型的
    println "aPig is a instance of ExtensionAware : ${aPig instanceof ExtensionAware}"
}


增加Extension


  • create() 方法会创建并返回一个 Extension 对象,
  • add() 方法,唯一的差别是它并不会返回一个 Extension 对象 基于前面的这个实例,我们可以换一种写法如下:


getExtensions().add(Pig, "mypig", new Pig(5, "kobe"))
mypig {
    username = "MyPig"
    legs = 4
    age = 1
}
task testExt << {
    def aPig = project.getExtensions().getByName("mypig")
    println aPig
}


查找Extension


Object findByName(String name)
<T> T findByType(Class<T> type)
Object getByName(String name)       //找不到会抛异常
<T> T getByType(Class<T> type)  //找不到会抛异常


嵌套Extension 方式一


类似下面这样的配置应该随处可见:


outer {
    outerName "outer"
    msg "this is a outer message."
    inner {
        innerName "inner"
        msg "This is a inner message."
    }
}


可以通过下面的方式来创建


class OuterExt {
    String outerName
    String msg
    InnerExt innerExt = new InnerExt()
    void outerName(String name) {
        outerName = name
    }
    void msg(String msg) {
        this.msg = msg
    }
    //创建内部Extension,名称为方法名 inner
    void inner(Action<InnerExt> action) {
        action.execute(inner)
    }
    //创建内部Extension,名称为方法名 inner
    void inner(Closure c) {
        org.gradle.util.ConfigureUtil.configure(c, innerExt) 
    }
    String toString() {
        return "OuterExt[ name = ${outerName}, msg = ${msg}] " + innerExt
    }
}
class InnerExt {
    String innerName
    String msg
    void innerName(String name) {
        innerName = name
    }
    void msg(String msg) {
        this.msg = msg
    }
    String toString() {
        return "InnerExt[ name = ${innerName}, msg = ${msg}]"
    }
}
def outExt = getExtensions().create("outer", OuterExt)
outer {
    outerName "outer"
    msg "this is a outer message."
    inner {
        innerName "inner"
        msg "This is a inner message."
    }
}
task testExt doLast {
    println outExt
}


关键在以下下面的方法


void inner(Action<InnerExt> action)
void inner(Closure c)


定义在outer内部的inner,Gradle 解析时本质上会调用 outer.inner(……)方法,该方法的参数是一个闭包(Script Block) 所以在类OuterExt中必须定义inner方法


嵌套Extension方式二(NamedDomainObjectContainer)


使用场景


Gradle Extension 的时候,说到名为 android 的 Extension 是由 BaseExtension 这个类来实现的,里面对 buildTypes 是这样定义的:


private final NamedDomainObjectContainer<BuildType> buildTypes;


buildTypes 就是 NamedDomainObjectContainer 类型的,先来看看 buildTypes 在 Android 中是怎么使用的,下面这段代码应该都很熟悉了,它定义了 debug、relase 两种打包模式:


android {
    buildTypes {
        release {
            // 是否开启混淆
            minifyEnabled true
            // 开启ZipAlign优化
            zipAlignEnabled true
            //去掉不用资源
            shrinkResources true
            // 混淆文件位置
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            // 使用release签名
            signingConfig signingConfigs.hmiou
        }
        debug {
            signingConfig signingConfigs.hmiou
        }
    }
}


当我们新建一个项目时候,默认会有debug和release这2个配置,那么debug、release可以修改其他名字吗,能增加其他名字来配置吗,比如想增加一个测试包配置test,还有就是release里面都能配置哪些属性呢我来说下结果,如果不确定的,可以实际验证一下:


  • debug、release 是可以修改成其他名字的,你可以替换成你喜欢的名字;
  • 你可以增加任意不同名字的配置,比如增加一个开发版本的打包配置 dev ;
  • 可配置的属性可参考接口:com.android.builder.model.BuildType ;


可以看到它是非常灵活的,可以根据不同的场景定义不同的配置,每个不同的命名空间都会生成一个 BuildType 配置。要实现这样的功能,必须使用 NamedDomainObjectContainer 类型。


什么是NamedDomainObjectContainer 顾名思义就是命名领域对象容器,它的主要功能有:


  • 通过DSL创建指定type的对象实例
  • 指定的type必须有一个public构造函数,且必须带有一个String name的参数
  • 它是一个实现了SortedSet接口的容器,所以所有领域对象的name属性都必须是唯一的,在容器内部会用name属性来排序


named domain object container is a specialisation of NamedDomainObjectSet that adds the ability to create instances of the element type. Note that a container is an implementation of SortedSet, which means that the container is guaranteed to only contain elements with unique names within this container. Furthermore, items are ordered by their name.


创建NamedDomainObjectContainer


NamedDomainObjectContainer 需要通过 Project.container(...) API 来创建,其定义为:


<T> NamedDomainObjectContainer<T> container(Class<T> type)
<T> NamedDomainObjectContainer<T> container(Class<T> type, NamedDomainObjectFactory<T> factory)
<T> NamedDomainObjectContainer<T> container(java.lang.Class<T> type, Closure factoryClosure


来看个具体的实例:


//这是领域对象类型定义
class TestDomainObj {
    //必须定义一个 name 属性,并且这个属性值初始化以后不要修改
    String name
    String msg
    //构造函数必须有一个 name 参数
    public TestDomainObj(String name) {
        this.name = name
    }
    void msg(String msg) {
        this.msg = msg
    }
    String toString() {
        return "name = ${name}, msg = ${msg}"
    }
}
//创建一个扩展
class TestExtension {
    //定义一个 NamedDomainObjectContainer 属性
    NamedDomainObjectContainer<TestDomainObj> testDomains
    public TestExtension(Project project) {
        //通过 project.container(...) 方法创建 NamedDomainObjectContainer 
        NamedDomainObjectContainer<TestDomainObj> domainObjs = project.container(TestDomainObj)
        testDomains = domainObjs
    }
    //让其支持 Gradle DSL 语法
    void testDomain(Action<NamedDomainObjectContainer<TestDomainObj>> action) {
        action.execute(testDomains)
    }
    void test() {
        //遍历命名领域对象容器,打印出所有的领域对象值
        testDomains.all { data ->
            println data        
        }
    }
}
//创建一个名为 test 的 Extension
def testExt = getExtensions().create("test", TestExtension, project)
test {
    testDomain {
        domain2 {
            msg "This is domain2"
        }
        domain1 {
            msg "This is domain1"
        }
        domain3 {
            msg "This is domain3"
        }
    }   
}
task myTask doLast {
    testExt.test()
}


运行结果如下:


name = domain1, msg = This is domain1
name = domain2, msg = This is domain2
name = domain3, msg = This is domain3


查找和遍历

NamedDomainObjectContainer 既然是一个容器类,与之相应的必然会有查找容器里的元素和遍历容器的方法:


//遍历
void all(Closure action)
//查找
<T> T getByName(String name)
//查找
<T> T findByName(String name)


还是接着前面的例子:


//通过名字查找
TestDomainObj testData = testDomains.getByName("domain2")
println "getByName: ${testData}"
//遍历命名领域对象容器,打印出所有的领域对象值
testDomains.all { data ->
    println data        
}


需要注意的是,Gradle 中有很多容器类的迭代遍历方法有 each(Closure action)、all(Closure action),但是一般我们都会用 all(...) 来进行容器的迭代。all(...) 迭代方法的特别之处是,不管是容器内已存在的元素,还是后续任何时刻加进去的元素,都会进行遍历。


Android的Extension

我们在gradle中会看到 android{}


android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.xxx.sdk.plugin"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}


defaultConfig、productFlavors、signingConfigs、buildTypes 这4个内部 Extension对象是怎么定义的,通过查看源码可以找到一个叫 BaseExtension 的类,里面的相关代码如下:


private final DefaultConfig defaultConfig;
    private final NamedDomainObjectContainer<ProductFlavor> productFlavors;
    private final NamedDomainObjectContainer<BuildType> buildTypes;
    private final NamedDomainObjectContainer<SigningConfig> signingConfigs;
    public void defaultConfig(Action<DefaultConfig> action) {
        this.checkWritability();
        action.execute(this.defaultConfig);
    }
     public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
        this.checkWritability();
        action.execute(this.buildTypes);
    }
    public void productFlavors(Action<? super NamedDomainObjectContainer<ProductFlavor>> action) {
        this.checkWritability();
        action.execute(this.productFlavors);
    }
    public void signingConfigs(Action<? super NamedDomainObjectContainer<SigningConfig>> action) {
        this.checkWritability();
        action.execute(this.signingConfigs);
    }


在 app 的 build.gradle 里我们通常会采用插件 apply plugin: 'com.android.application' ,而在 library module 中则采用插件 apply plugin: 'com.android.library',AppPlugin 就是插件 com.android.application 的实现类,LibraryPlugin 则是插件 com.android.library 的实现类,接着再看看 AppPlugin 里是怎样创建 Extension 的:


public class AppPlugin extends BasePlugin implements Plugin<Project> {
    @Inject
    public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
        super(instantiator, registry);
    }
    protected BaseExtension createExtension(Project project, ProjectOptions projectOptions, Instantiator instantiator, AndroidBuilder androidBuilder, SdkHandler sdkHandler, NamedDomainObjectContainer<BuildType> buildTypeContainer, NamedDomainObjectContainer<ProductFlavor> productFlavorContainer, NamedDomainObjectContainer<SigningConfig> signingConfigContainer, NamedDomainObjectContainer<BaseVariantOutput> buildOutputs, ExtraModelInfo extraModelInfo) {
        return (BaseExtension)project.getExtensions().create("android", AppExtension.class, new Object[]{project, projectOptions, instantiator, androidBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelInfo});
    }
    public void apply(Project project) {
        super.apply(project);
    }
    //省略...
}


在 createExtension() 方法中,可以看到创建了一个名为 android 的 Extension,该 Extension 的类型为 AppExtension,而 AppExtension 的继承结构为 AppExtension -> TestedExtension -> BaseExtension,所以它的实现逻辑大部分都是在 BaseExtension 里实现的。 在Android 工程中的build.gradle 文件中,我们配置相关信息使用 android{} 节点,从 AppPlugin 也能看出其 Extension的名称为 android ,所以获取方法如下:


  • project.extensions.getByName
  • project.extensions.getByType


def getInfo() {
    //或者 直接 project.android
    BaseExtension extension = project.extensions.getByName("android")
    def android = project.extensions.getByType(AppExtension)
    project.android
    println "buildToolsVersion:${extension.buildToolsVersion}"
    println "compileSdkVersion:${extension.getCompileSdkVersion()}"
    println "applicationId:${extension.defaultConfig.applicationId}"
    println "minSdkVersion:${extension.defaultConfig.minSdkVersion}"
    println "targetSdkVersion:${extension.defaultConfig.targetSdkVersion}"
    println "versionCode:${extension.defaultConfig.versionCode}"
    println "versionName:${extension.defaultConfig.versionName}"
}


详细的请参考:ASL


Gradle小技巧


打印task依赖的所有task


在 含有 uploadArchives task 的 build.gradle 中加入以下代码,打印下 uploadArchives 的依赖。


void printTaskDependency(Task task) {
    task.getTaskDependencies().getDependencies(task).any() {
        println(">>${it.path}")
        printTaskDependency(it)
    }
}
gradle.getTaskGraph().whenReady {
    printTaskDependency project.tasks.findByName('uploadArchives')
}


随便运行一个 gradle 命令,为了方便,直接运行 ./gradlew clean ,查看打印的日志(uploadArchives 依赖的 tasks,每个人打印出来的内容可能不太一样,定义的 task 可能不同):


Configuration on demand is an incubating feature.
>>:opuslib:bundleReleaseAar
>>:opuslib:mergeReleaseConsumerProguardFiles
>>:opuslib:mergeReleaseGeneratedProguardFiles
>>:opuslib:compileReleaseJavaWithJavac
>>:opuslib:javaPreCompileRelease
>>:opuslib:preReleaseBuild
>>:opuslib:preBuild
>>:opuslib:generateReleaseBuildConfig
>>:opuslib:checkReleaseManifest
>>:opuslib:preReleaseBuild
...
>>:opuslib:preBuild
>>:opuslib:preReleaseBuild
>>:opuslib:preBuild
>>:opuslib:preReleaseBuild
>>:opuslib:preBuild
>>:opuslib:androidSourcesJar
>>:opuslib:androidJavadocsJar
>>:opuslib:androidJavadocs
>>:opuslib:androidSourcesJar
>>:opuslib:androidJavadocsJar
>>:opuslib:androidJavadocs
> Task :opuslib:externalNativeBuildCleanDebug
Clean ljmedia-lib armeabi
Cleaning... 0 files.
Clean ljmedia-lib armeabi-v7a
Cleaning... 0 files.
Clean ljmedia-lib arm64-v8a
Cleaning... 0 files.
Clean ljmedia-lib x86
Cleaning... 0 files.
Clean ljmedia-lib x86_64
Cleaning... 0 files.
Clean ljmedia-lib mips
Cleaning... 0 files.
Clean ljmedia-lib mips64
Cleaning... 0 files.
> Task :opuslib:externalNativeBuildCleanRelease
Clean ljmedia-lib armeabi
Cleaning... 1 files.
Clean ljmedia-lib armeabi-v7a
Cleaning... 145 files.
Clean ljmedia-lib arm64-v8a
Cleaning... 145 files.
Clean ljmedia-lib x86
Cleaning... 145 files.
Clean ljmedia-lib x86_64
Cleaning... 145 files.
Clean ljmedia-lib mips
Cleaning... 0 files.
Clean ljmedia-lib mips64
Cleaning... 0 files.


参考


源码分析篇


插件开发篇

Task相关

  • www.jianshu.com/p/cd1a78dc8…
  • www.ezlippi.com/blog/2015/0…
  • blog.csdn.net/lzyzsd/arti…

语法相关

Gradle开发快速入门--DSL语法原理与常用API介绍

目录
相关文章
|
1月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
166 4
|
1月前
|
安全 Android开发 iOS开发
深入探索Android与iOS的差异:从系统架构到用户体验
在当今的智能手机市场中,Android和iOS无疑是最受欢迎的两大操作系统。本文旨在探讨这两个平台之间的主要差异,包括它们的系统架构、开发环境、安全性、以及用户体验等方面。通过对比分析,我们可以更好地理解为何不同的用户群体可能会偏好其中一个平台,以及这些偏好背后的技术原因。
|
1月前
|
运维 监控 负载均衡
探索微服务架构下的服务治理:动态服务管理平台深度解析
探索微服务架构下的服务治理:动态服务管理平台深度解析
|
1月前
|
运维 监控 安全
探索微服务架构下的服务治理:动态服务管理平台的力量
探索微服务架构下的服务治理:动态服务管理平台的力量
|
1月前
|
运维 监控 负载均衡
动态服务管理平台:驱动微服务架构的高效引擎
动态服务管理平台:驱动微服务架构的高效引擎
28 0
|
11天前
|
NoSQL 关系型数据库 MySQL
《docker高级篇(大厂进阶):4.Docker网络》包括:是什么、常用基本命令、能干嘛、网络模式、docker平台架构图解
《docker高级篇(大厂进阶):4.Docker网络》包括:是什么、常用基本命令、能干嘛、网络模式、docker平台架构图解
111 56
《docker高级篇(大厂进阶):4.Docker网络》包括:是什么、常用基本命令、能干嘛、网络模式、docker平台架构图解
|
8天前
|
机器学习/深度学习 算法 数据可视化
基于深度混合架构的智能量化交易系统研究: 融合SSDA与LSTM自编码器的特征提取与决策优化方法
本文探讨了在量化交易中结合时序特征和静态特征的混合建模方法。通过整合堆叠稀疏降噪自编码器(SSDA)和基于LSTM的自编码器(LSTM-AE),构建了一个能够全面捕捉市场动态特性的交易系统。SSDA通过降噪技术提取股票数据的鲁棒表示,LSTM-AE则专注于捕捉市场的时序依赖关系。系统采用A2C算法进行强化学习,通过多维度的奖励计算机制,实现了在可接受的风险水平下最大化收益的目标。实验结果显示,该系统在不同波动特征的股票上表现出差异化的适应能力,特别是在存在明确市场趋势的情况下,决策准确性较高。
32 5
基于深度混合架构的智能量化交易系统研究: 融合SSDA与LSTM自编码器的特征提取与决策优化方法
|
9天前
|
机器学习/深度学习 前端开发 算法
婚恋交友系统平台 相亲交友平台系统 婚恋交友系统APP 婚恋系统源码 婚恋交友平台开发流程 婚恋交友系统架构设计 婚恋交友系统前端/后端开发 婚恋交友系统匹配推荐算法优化
婚恋交友系统平台通过线上互动帮助单身男女找到合适伴侣,提供用户注册、个人资料填写、匹配推荐、实时聊天、社区互动等功能。开发流程包括需求分析、技术选型、系统架构设计、功能实现、测试优化和上线运维。匹配推荐算法优化是核心,通过用户行为数据分析和机器学习提高匹配准确性。
38 3
|
21天前
|
IDE 开发工具 Android开发
移动应用开发之旅:探索Android和iOS平台
在这篇文章中,我们将深入探讨移动应用开发的两个主要平台——Android和iOS。我们将了解它们的操作系统、开发环境和工具,并通过代码示例展示如何在这两个平台上创建一个简单的“Hello World”应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧,帮助你更好地理解和掌握移动应用开发。
48 17
|
25天前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。

热门文章

最新文章