来自 编程 2019-10-01 10:18 的文章
当前位置: 澳门三合彩票 > 编程 > 正文

)是三个职务驱动型的营造筑工程具,修改 Man

澳门三合彩票 1

本文已授权微信公众号 Android技术经验分享 独家发布

转载请注明出处:Gradle从入门到了解

什么是 Gradle ?

所谓构建工具就是对你的项目进行编译、运行、签名、打包、依赖管理等一系列功能的合集,传统的构建工具有 Make、Ant、Maven、Ivy等,而 Gradle 是新一代的自动化构建工具。

上面说了,Gradle 是新一代的自动化构建工具,它是一个独立的项目,跟 AS、Android 无关,官方网站:https://gradle.org/, 类似 Ant、Maven这类构建工具都是基于 xml 来进行描述的,很臃肿,而 Gradle 采用的是一种叫做 Groovy 的语言,语法跟 Java 语法很像,但是是一种动态语言,而且在 Java 基础上做了不少改进,用起来更加简洁、灵活,而且 Gradle 完全兼容 Maven、Ivy,这点基本上宣布了 Maven、Ivy 可以被抛弃了,Gradle 的推出主要以 Java 应用为主,当然目前还支持 Android、C、C++。

Gradle 作为一款灵活多变的构建插件,与 Android Studio 的结合,能够解决过去使用 Eclipse 开发 App 时所遇到的诸多问题。同时,基于 Groove 这样一款 DSL 语言的脚本特性,记住各种语法显然又是一件比较困难的事情。

  1. 在自己的项目中使用脚本准备起飞。一次打包十几个项目不嫌累。
  2. 面对下载下来的开源项目编译报错有一定处理能力。
  3. 水群吹牛逼直起腰板。

Gradle 与 Android Studio 的关系

上面也提到,Gradle 跟 Android Studio 其实没有关系,但是 Gradle 官方还是很看重 Android 开发的,Google 在推出 AS 的时候选中了 Gradle 作为构建工具,为了支持 Gradle 能在 AS 上使用,Google 做了个 AS 的插件叫 Android Gradle Plugin ,所以我们能在 AS 上使用 Gradle 完全是因为这个插件的原因。在项目的根目录有个 build.gradle 文件,里面有这么一句代码:

classpath ‘com.android.tools.build:gradle:2.3.3’

这个就是依赖 gradle 插件的代码,后面的版本号代表的是 android gradle plugin 的版本,而不是 Gradle 的版本,这个是 Google 定的,跟 Gradle 官方没关系。

事实上,Gradle 插件的常用使用场景并不是很多,也不需要死记硬背,或者完全学会 Groove 的所有使用方式。这里将 Android 开发中较为常用的 Gradle 使用场景总结出来,用于将来需要的时候有个参考。

Gradle(英[g'reɪdl])是一个任务驱动型的构建工具,是一个依赖管理工具,更是一个编程框架。它抛弃了基于XML的各种繁琐配置,取而代之的是一种基于Groovy的内部领域特定语言。在android studio中,我们使用这个工具可以完成app的编译打包等工作。

Gradle基本组件

每一个build.gradle文件代表着一个Project。Tasks在build.gradle中定义。当初始化构建进程时,gradle会基于build文件,集合所有的Project和Tasks,一个Tasks包含了一系列动作,然后它们将会按照顺序执行,一个动作就是一段被执行的代码,很像Java中的方法。

Project

每一个待编译的工程(可以是一个jar包,一个web应用,或者一个android app等)都称为一个Project。

Task

每一个Project在构建的时候都包含一系列的Task。一个Task其实就是构建过程中一个原子性的操作。比如一个Android APK的编译可能包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。

Plugin

Gradle是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译Java有Java插件,编译Groovy有Groovy插件,编译Android APP有Android APP插件,编译Android Library有Android Library插件。

简单来说,插件就是一系列任务的集合,主要作用是把一些重复利用的逻辑打包,这样就可以在不同的项目中可以重复的使用。

要使用插件,可以通过引入依赖的方式添加。

举一个最常见的多渠道使用场景,友盟统计,看看最基本的 Gradle 多渠道打包方式的用法。

  • Make it easy to reuse code and resources
  • Make it easy to create several variants of an application, either for multi-apk distribution or for different flavors of an application
  • Make it easy to configure, extend and customize the build process
  • Good IDE integration

As如何依赖Gradle让Gradle作为自身的构建工具呢?

Google开发了一个Gradle插件,让As项目依赖这个插件,就相当于让Gradle作为自身的的构建工具。

现在比如我们新建一个As项目,打开项目的根目录的gradle.build文件。有如下代码:

buildscript {

     repositories {

               jcenter()    //表示编译过程中依赖的仓库

     }

     dependencies {

              classpath 'com.android.tools.build:gradle:2.2.0'     //依赖android开发使用的gradle插件

      }

}

而要引入Android APP插件,就需要在build.gradle引用Android APP插件:

//申明使用插件,表明要编译的内容和产物

apply plugin: 'com.android.application'

//配置插件属性

android {

        compileSdkVersion 24

        buildToolsVersion "24.0.1"

        defaultConfig {

                applicationId "zhj.gradledemo"

                minSdkVersion 15

                targetSdkVersion 24

                versionCode 1

                versionName "1.0"

                testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

         }

         buildTypes {

               release {

                      minifyEnabled false

                      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

               }

          }

}

Android其实就是写了两个插件:com.android.application 和 com.android.library 

应用这两个插件就可以实现Android APP和Android Library的构建。

修改 Manifest 文件中友盟统计的渠道名为引用变量:

.gradle文件夹

.gradle文件夹 是gradle 运行以后生成的缓存文件夹。

<meta-data android:name="${UMENG_CHANNEL_VALUE}" android:value="Channel_ID" />
  • 让重用代码和资源变得更加容易。
  • 让创建同一应用程序的不同版本变得更加容易,无论是多个apk发布版本还是同一个应用的不同定制版本。
  • 让构建过程变得更加容易配置,扩展和定制。
  • 更好的IDE集成。

Project中的build.gradle文件

project下的build.gradle是基于整个project的配置,主要配置gradle 版本及全局依赖仓库、库或者其他全部参数。

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {

        repositories {

                //这里依赖的jcenter仓库是gradle脚本自身需要的资源

                jcenter()

        }

        dependencies {

                classpath 'com.android.tools.build:gradle:2.2.0'

                 // NOTE: Do not place your application dependencies here; they belong

                 // in the individual module build.gradle files

         }

}

allprojects {

         repositories {

                 //这里依赖的jcenter仓库是项目所有模块需要的资源

                 jcenter()

         }

}

task clean(type: Delete) {

       delete rootProject.buildDir

}

然后在 build.gradle 文件 productFlavors 配置项中添加渠道名,并统一设置到上面提到的变量名:

Gradle Plugin User Guide 官方原文地址

module中build.gradle文件

//申明使用插件,表明要编译的内容和产物

apply plugin: 'com.android.application'

android {

         compileSdkVersion 24

         buildToolsVersion "24.0.1"

         //默认配置,会同时应用到debug和release版本上

         defaultConfig {

                  applicationId "zhj.gradledemo"

                  minSdkVersion 15

                  targetSdkVersion 24

                  versionCode 1

                  versionName "1.0"

                  testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

         }

         buildTypes {

                  release {

                           minifyEnabled true  //是否混淆

                           proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' //混淆文件的位置

                  }

                  debug {

                           minifyEnabled false

                  }

         }

         // 多渠道

         productFlavors {

         //可以设置不同渠道渠道号,应用名称

                  pro {

                  }

                  fre {

                  }

         }

}

//依赖第三方库

dependencies {

         //编译libs目录下所以jar包

         compile fileTree(include: ['*.jar'], dir: 'libs')  //导入所有的jar包

         androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {

                  exclude group: 'com.android.support', module: 'support-annotations'

         })

         compile 'com.android.support:appcompat-v7:24.2.0'

         compile 'com.android.support:design:24.2.0'

         testCompile 'junit:junit:4.12'

         proCompile 'com.android.support:recyclerview-v7:24.2.0'

}

android { productFlavors { xiaomi {} yingyongbao {} } productFlavors.all { flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name] }}

中文版在线阅读地址

Project中setting.gradle

这个文件是全局的项目配置文件,里面主要声明Project中所包括的所有module

//一个Project中所包括的所有module

include ':Gotobus', ':android-support-v7-appcompat'

include ':google-play-services_lib'

include ':TakeTours'

include ':Common'

include ':CompanyCommon'

执行打包 Task 的命令语句即可:

简单来说有以下几点:

  1. 独立项目,和Google无关
  2. Gradle基于Groovy。(Maven、Ant基于xml)Groovy是拓展了Java语言的一种动态语言,语法更简洁,可以作为Java平台的脚本语言使用 ,拥有类似Python、Ruby和Smalltalk中的一些特性。Gradle是基于Groovy定义了一套DSL,所谓DSL,就是专门针对某一特定问题的计算机语言。而Gradle我们可以认为是经过“定制”的Groovy,专门用于项目构建的语言。
  3. Gradle兼容Maven、Ant
  4. Gradle 的推出主要以 Java 应用为主,当然还支持 Android、C、C++等。

Project中gradle.properties

//编译版本信息

APPLICATION_ID = com.jin.myAPP

COMPILE_SDK_VERSION = 23

BUILD_TOOLS_VERSION = 23.0.1

MIN_SDK_VERSION = 15

TARGET_SDK_VERSION = 1

VERSION_CODE = 1

VERSION_NAME = 1.0.0.0

//keystore信息

STORE_FILE = ../app/mykey.keystore

STORE_PASSWORD = your password

KEY_ALIAS = your alias

KEY_PASSWORD = your password

./gradle assembleRelease

每一个build.gradle文件代表着一个Project。Tasks在build.gradle中定义。当初始化构建进程时,gradle会基于build文件,集合所有的Project和Tasks,一个Tasks包含了一系列动作,然后它们将会按照顺序执行,一个动作就是一段被执行的代码,很像Java中的方法。

配置应用的签名信息

在android.signingConfigs{}下定义一个或者多个签名信息,然后在buildTypes{}配置使用即可。比如这里:

android {

        signingConfigs {

                  release {

                           storeFile file("release.keystore")

                           keyAlias "release"

                           keyPassword "123456"

                           storePassword "123456"

                  }

                  debug {

                           ...

                  }

         }

         buildTypes {

                 release {

                           signingConfig signingConfigs.release

                  }

                  debug {

                           signingConfig signingConfigs.debug

                  }

          }

}

storeFile是签名证书文件,keyAlias是别名,keyPassword是key的密码,storePassword是证书的密码。配置好相关信息即可在buildTypes配置使用。

一般重要的信息,例如签名信息,可以直接将信息写到gradle.properties,然后在然后在build.gradle中引用即可。

buildTypes是指建构的类型,一般只用两种默认类型 debug 和 release ,顾名思义 debug 用来配置开发过程中的一些内容;release 用来配置正式发布版本的内容。有时我们需要发布介于debug与release之间的preview 版本。

或者有针对性的只打特定渠道包,如:

  • Project每一个待编译的工程(可以是一个jar包,一个web应用,或者一个android app等)都称为一个Project。

  • Task每一个Project在构建的时候都包含一系列的Task。一个Task其实就是构建过程中一个原子性的操作。比如一个Android APK的编译可能包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。

  • PluginGradle是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译Java有Java插件,编译Groovy有Groovy插件,编译Android APP有Android APP插件,编译Android Library有Android Library插件。简单来说,插件就是一系列任务的集合,主要作用是把一些重复利用的逻辑打包,这样就可以在不同的项目中可以重复的使用。要使用插件,可以通过引入依赖的方式添加。

Build Variant 差异管理

比如app生成不同版本(免费,收费),适配特殊机型,多渠道等需要发多个包,最终能编译出的apk的数量是由Product Flavor(产品种类)与Build Type(构建类型)决定的,

公式:Build Variant = Build Type x Product Flavor

BuildType(构建类型)

默认有debug和release两种,标示编译的类型,通常在混淆代码、可调式、资源压缩上做一些区分。

Product Flavor(产品种类)

为了满足“同一个project,根据一个很小的区分,来打不同的包”这个需求。实现多渠道打包。注意:这里的Flavor名如果是数字开头,必须用引号引起来。

./gradle assembleXiaomiRelease
As如何依赖Gradle让Gradle作为自身的构建工具呢?
  • 答:Google开发了一个Gradle插件,让As项目依赖这个插件,就相当于让Gradle作为自身的的构建工具。

现在比如我们新建一个As项目,打开项目的根目录的gradle.build文件。有如下代码:

buildscript { repositories { jcenter() //表示编译过程中依赖的仓库 } dependencies { //依赖android开发使用的gradle插件 classpath 'com.android.tools.build:gradle:2.2.0' }}

而要引入Android APP插件,就需要在build.gradle引用Android APP插件:

//申明使用插件,表明要编译的内容和产物apply plugin: 'com.android.application' //配置插件属性android { compileSdkVersion 24 buildToolsVersion "24.0.1" defaultConfig { applicationId "zhj.gradledemo" minSdkVersion 15 targetSdkVersion 24 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }}

Android其实就是写了两个插件:

  • com.android.application和com.android.library。

应用这两个插件就可以实现Android APP和Android Library的构建。

.gradle文件夹 是gradle 运行以后生成的缓存文件夹。

project下的build.gradle是基于整个project的配置,主要配置gradle 版本及 全局依赖仓库、库或者其他全部参数。

// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { //这里依赖的jcenter仓库是gradle脚本自身需要的资源 jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }} allprojects { repositories { //这里依赖的jcenter仓库是项目所有模块需要的资源 jcenter() }} task clean(type: Delete) { delete rootProject.buildDir}

//申明使用插件,表明要编译的内容和产物apply plugin: 'com.android.application' android { compileSdkVersion 24 buildToolsVersion "24.0.1" //默认配置,会同时应用到debug和release版本上 defaultConfig { applicationId "zhj.gradledemo" minSdkVersion 15 targetSdkVersion 24 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled true //是否混淆 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' //混淆文件的位置 } debug { minifyEnabled false } } // 多渠道 productFlavors { //可以设置不同渠道渠道号,应用名称 pro { } fre { } }}//依赖第三方库dependencies { //编译libs目录下所以jar包 compile fileTree(include: ['*.jar'], dir: 'libs') //导入所有的jar包 androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:24.2.0' compile 'com.android.support:design:24.2.0' testCompile 'junit:junit:4.12' proCompile 'com.android.support:recyclerview-v7:24.2.0'}

这个文件是全局的项目配置文件,里面主要声明Project中所包括的所有module

//一个Project中所包括的所有moduleinclude ':Gotobus', ':android-support-v7-appcompat'include ':google-play-services_lib'include ':TakeTours'include ':Common'include ':CompanyCommon'

gradle.properties为gradle的配置文件,里面可以定义一些常量供build.gradle使用,比如可以配置签名相关信息如keystore位置,密码,keyalias等,build.gradle就可以直接引用gradle 中的一些配置参数建议写到gradle.properties

//编译版本信息APPLICATION_ID = com.jin.myAPPCOMPILE_SDK_VERSION = 23BUILD_TOOLS_VERSION = 23.0.1MIN_SDK_VERSION = 15TARGET_SDK_VERSION = 1VERSION_CODE = 1VERSION_NAME = 1.0.0.0 //keystore信息STORE_FILE = ../app/mykey.keystoreSTORE_PASSWORD = your passwordKEY_ALIAS = your aliasKEY_PASSWORD = your password

在android.signingConfigs{}下定义一个或者多个签名信息,然后在buildTypes{}配置使用即可。比如这里

android { signingConfigs { release { storeFile file("release.keystore") keyAlias "release" keyPassword "123456" storePassword "123456" } debug { ... } } buildTypes { release { signingConfig signingConfigs.release } debug { signingConfig signingConfigs.debug } } }
  • storeFile是签名证书文件,keyAlias是别名,keyPassword是key的密码,storePassword是证书的密码。配置好相关信息即可在buildTypes配置使用。

一般重要的信息,例如签名信息,可以直接将信息写到gradle.properties,然后在然后在build.gradle中引用即可。

  • buildTypes是指建构的类型,一般只用两种默认类型 debug 和 release ,顾名思义 debug 用来配置开发过程中的一些内容;release 用来配置正式发布版本的内容。有时我们需要发布介于debug与release之间的preview 版本。

比如app生成不同版本,适配特殊机型,多渠道等需要发多个包,最终能编译出的apk的数量是由Product Flavor与Build Type决定的,公式:Build Variant = Build Type x Product Flavor

  • BuildType默认有debug和release两种,标示编译的类型,通常在混淆代码、可调式、资源压缩上做一些区分。

  • Product Flavor为了满足“同一个project,根据一个很小的区分,来打不同的包”这个需求。实现多渠道打包。注意:这里的Flavor名如果是数字开头,必须用引号引起来。

默认情况下,java文件和resource文件分别在src/main/java和src/main/res目录下,在build.gradle文件的andorid{}里面添加下面的代码,便可以将java文件和resource文件放到src/java和src/resources目录下。

sourceSets { main { manifest.srcFile 'AndroidManifest.xml' //设置java文件的位置 java.srcDirs = ['src'] resources.srcDirs = ['src'] aidl.srcDirs = ['src'] renderscript.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] }}

可以在顶层build.gradle脚本中定义一些全局变量,提供给子脚本引用

ext { // global variables definition compileSdkVersion = 'Google Inc.:Google APIs:23' buildToolsVersion = "23.0.3" minSdkVersion = 14 targetSdkVersion = 23}

子脚本引用

android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion }}

打开Android Studio内置的Terminal终端,输入如下命令

澳门三合彩票 2执行gradlew -v

指令代码 指令功能
gradlew 下载更新gradle
gradlew -v 查询版本号
gradlew clean 清楚项目的output文件
gradlew check 运行检测和测试任务
gradlew build 运行check和assemble,检查依赖并编译打包(debug、release环境的包)
gradlew clean build 运行 clean 和 build 两个 gradle task
gradlew assemble 编译并打Debug和Release包
gradlew assembleDebug 编译并把本项目下所有模块所有渠道的Debug版本打包
gradlew assembleRelease 编译并把本项目下所有模块所有渠道的Release版本打包
gradlew assembleWandoujia 生成wandoujia渠道的Release和Debug版本
gradlew assembleWandoujiaRelease 打包wandoujia渠道的Release版本
gradlew assembleWandoujiaRelease -p app 打包app工程下wandoujia渠道的Release版本(使用-p选项,决定执行哪个工程)
gradlew installRelease Release模式打包并安装
gradlew uninstallRelease 卸载Release模式包

gradlew代表 gradle wrapper,意思是gradle的一层包装,大家可以理解为在这个项目本地就封装了gradle,即gradle wrapper。在./gradle/wrapper/gralde-wrapper.properties文件中声明了它指向的目录和版本。只要下载成功即可用grdlew wrapper的命令代替全局的gradle命令。

assemble 命令创建task有如下语法:

  • 允许直接构建一个Variant版本,例如assembleFlavor1Debug。
  • 允许构建指定Build Type的所有APK,例如assembleDebug将会构建Flavor1Debug和Flavor2Debug两个Variant版本。
  • 允许构建指定flavor的所有APK,例如assembleFlavor1将会构建Flavor1Debug和Flavor1Release两个Variant版本。

在我们打包发版的时候,一次性打几十个包,这时候我们就想让生成的apk文件名有区分,比如一眼就能看出这个apk是哪个版本的,哪个渠道的,是哪天打的包等等,这就需要我们在生成apk文件的时候动态修改生成的apk文件名达到这一目的。代码如下:

def buildTime() { def date = new Date() def formattedDate = date.format('yyyyMMdd') return formattedDate} android { buildTypes { release { applicationVariants.all { variant -> variant.outputs.each { output -> if (output.outputFile != null && output.outputFile.name.endsWith &&'release'.equals(variant.buildType.name)) { def apkFile = new File( output.outputFile.getParent(), "Gtobus_${variant.flavorName}_v${variant.versionName}_${buildTime output.outputFile = apkFile } } } } }}

以baidu渠道为例,以上的代码会生成一个名字为Gtobus__gotobus_v5.1.2_20161115.apk安装包。

这里是循环处理每个applicationVariant,当他们的输出文件名以apk结尾并且buildType是release时,重新设置新的输出文件名,这样就达到了我们批量修改生成的文件名的目的。

android studio的编译时屏蔽掉lint检查,可以避免由于编译条件太过严格而编译不过的问题:

 lintOptions { abortOnError false }

如果遇到多个jar包中的某个文件冲突,可以在对应module下的build.gradle文件的android标签下加上如下属性:

 packagingOptions { exclude 'META-INF/NOTICE.txt'// 这里是具体的冲突文件全路径 exclude 'META-INF/LICENSE.txt' }

调整module的目录结构sourceSets

默认情况下,java文件和resource文件分别在src/main/java和src/main/res目录下,在build.gradle文件的andorid{}里面添加下面的代码,便可以将java文件和resource文件放到src/java和src/resources目录下。

sourceSets {

         main {

                  manifest.srcFile 'AndroidManifest.xml'

                  //设置java文件的位置

                  java.srcDirs = ['src']

                  resources.srcDirs = ['src']

澳门三合彩票,                  aidl.srcDirs = ['src']

                  renderscript.srcDirs = ['src']

                  res.srcDirs = ['res']

                  assets.srcDirs = ['assets']

        }

}

备注:在 Gradle projects 窗口中能够查看所有可执行的 Tasks 列表。这种原生态打包方式适合渠道名比较少的使用场景。当渠道多达数十个甚至上百个时,打包时间就会比较长,推荐其他打包方案,如:

依赖版本冲突

依赖冲突是所以依赖管理中最头痛的问题,这常常出现在传递依赖中。Gradle对解决传递依赖提供了两种策略,使用最新版本或者直接导致构建失败。默认的策略是使用最新版本。虽然这样的策略能够解决一些问题,但是还是不够。常见的一种情况是,NoSuchMethond或者ClassNotFound。这时候,你可能需要一些特殊手段,比如排除不想要的传递依赖。

全局变量定义及引用

可以在顶层build.gradle脚本中定义一些全局变量,提供给子脚本引用

ext {

        // global variables definition

        compileSdkVersion = 'Google Inc.:Google APIs:23'

        buildToolsVersion = "23.0.3"

        minSdkVersion = 14

        targetSdkVersion = 23

}

子脚本引用

android {

        compileSdkVersion rootProject.ext.compileSdkVersion

        buildToolsVersion rootProject.ext.buildToolsVersion

        defaultConfig {

                minSdkVersion rootProject.ext.minSdkVersion

                targetSdkVersion rootProject.ext.targetSdkVersion

         }

}

本文由澳门三合彩票发布于编程,转载请注明出处:)是三个职务驱动型的营造筑工程具,修改 Man

关键词: