11/10号文檔資料已全面更新!;《【阿裡P7】移動網際網路架構師進階教程+BAT面試題》,可點選下方連結直接打開: 【阿裡P7】移動網際網路架構師進階進階教程+BAT面試題 本篇文章将繼續從自定義 Gradle 插件開發來介紹自動化建構系統Gradle:
Gradle 插件簡介
Gradle 插件是一個能夠将 Gradle 的建構邏輯(build logic)和建構任務(build task)打包到一起,以便在多個項目的建構腳本(build.gradle)中應用(apply)的工具。
例如,
build.gradle
建構腳本檔案内
apply plugin: 'java'
、
apply plugin: 'com.android.application'
中的 java、com.android.application 就是官方提供的 Gradle 插件,通過應用這些插件,可以豐富項目的建構任務與建構邏輯。
除官方提供的插件外,Gradle 還允許開發者定義自己的 Gradle 插件。開發者可以根據實際需求定義自己的建構邏輯和建構任務,将其打包為 Gradle 插件,進而在多個項目的建構腳本中複用。此外,還可以将自定義的 Gradle 插件釋出到 plugin portal或其他倉庫中,更為友善的分享給他人使用。
Gradle 插件對程式設計語言沒有太多限制,隻要是能夠被編譯為 JVM 位元組碼的程式設計語言,都能用來編寫 Gradle 插件。Gradle-API 的被設計為對 Groovy、Java、Koltin 友好的,通常情況下,使用 Java 或 Kotlin 這類靜态類型語言實作的 Gradle 插件的性能要比使用 Groovy 實作的相同常見的性能更好。
[]( http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/# 開始之前 "開始之前")開始之前
Gradle 作為一個普通的建構工具,本身并不依賴任何可視化 GUI 工具。為簡化步驟,本文将采用指令行方式來完成自定義 Gradle 插件的示範。在開始之前,需先将 gradle 指令添加到系統環境變量中。
若讀者在 IDEA / Android Studio 使用過 gradle ,則可在目前使用者目錄下找到 gradle 指令,具體路徑如下
- Mac:
/Users/目前使用者名/.gradle/wrapper/dists/gradle版本/沙盒路徑/gradle版本/bin
- Win:
C:\Users\目前使用者名\.gradle\wrapper\dists\gradle版本\沙盒路徑\gradle版本\bin
若讀者的電腦中尚未安裝 gradle,則可在 Gradle 官方 下載下傳安裝。
以筆者使用的環境為例,gradle 指令所在目錄為
~/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx/gradle-5.4.1/bin
将 gradle 指令所在目錄添加到環境變量中
- Mac:在
中添加~/.bashrc
export PATH=~/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx/gradle-5.4.1/bin:$PATH
- Win:在系統環境變量中添加
C:\Users\使用者名\.gradle\wrapper\dists\gradle-5.4.1-all\805usxkvhgx6e1wbo8o64g0tx\gradle-5.6.1\bin
牛刀小試 "牛刀小試")牛刀小試
在指令行中輸入
gradle --version
得到如下輸出則表示 gradle 環境設定成功
------------------------------------------------------------
Gradle 5.4.1
------------------------------------------------------------
Build time: 2019-04-26 08:14:42 UTC
Revision: 261d171646b36a6a28d5a19a69676cd098a4c19d
Kotlin: 1.3.21
Groovy: 2.5.4
Ant: Apache Ant(TM) version 1.9.13 compiled on July 10 2018
JVM: 1.8.0_171 (Oracle Corporation 25.171-b11)
OS: Mac OS X 10.14.6 x86_64
自定義-Gradle-插件 "自定義 Gradle 插件")自定義 Gradle 插件
自定義 gradle 插件可以在以下三個地方建立,分别是:
- 建構腳本内
- buildSrc 子產品内
- 單獨項目
建構腳本内建方式 "建構腳本内建方式")建構腳本内建方式
在
build.gradle
内直接建立 Gradle 插件
優點:
-
中建立的插件将被自動編譯并包含在 classpath 中,使用時無需在建構腳本内指定 classpathbuild.gradle
缺點:
- 此插件僅在目前建構腳本中有效,對外部檔案不可見,無法在目前建構腳本以外的其他地方複用此插件
示例
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#1- 建立-BuildInDemo-目錄 "1.建立 BuildInDemo 目錄")1. 建立 BuildInDemo
目錄
BuildInDemo
mkdir BuildInDemo
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#2- 在BuildInDemo目錄内中建立-build-gradle-檔案 "2.在BuildInDemo目錄内中建立 build.gradle 檔案")2. 在 BuildInDemo
目錄内中建立 build.gradle
檔案
BuildInDemo
build.gradle
cd BuildInDemo
touch build.gradle
使用
tree
指令檢視建立後的目錄結構,如下所示
BuildInDemo
.
└── build.gradle
0 directories, 1 file
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#3- 在-BuildInDemo-build-gradle-内建立并應用-Gradle-插件,代碼如下 "3.在 BuildInDemo/build.gradle 内建立并應用 Gradle 插件,代碼如下")3. 在 BuildInDemo/build.gradle
内建立并應用 Gradle 插件,代碼如下
BuildInDemo/build.gradle
// 1. 建立插件 BuildInPlugin
class BuildInPlugin implements Plugin<Project> {
// 2. 應用插件時執行此函數
@Override void apply(Project target) {
println("hello form build-in plugin")
}
}
//3. 應用插件
apply plugin: BuildInPlugin
// 2\. 應用插件時執行此函數
@Override void apply(Project target) {
println("hello form build-in plugin")
}
}
//3\. 應用插件
apply plugin: BuildInPlugin
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#4- 建構此-build-gradle-檔案 "4.建構此 build.gradle 檔案")4. 建構此 build.gradle
build.gradle
gradle build
Gradle 建構時将執行
build.gradle
中的代碼,當執行到
apply plugin: BuildInPlugin
時,将會調用 BuildInPlugin 的執行個體方法
apply(Project p)
。是以在建構過程中,可以看到如下輸出,其中第 2 行即為上一步自定義插件中列印的内容,表明插件應用成功
> Configure project :
hello form build-in plugin
> Task :buildEnvironment
------------------------------------------------------------
Root project
------------------------------------------------------------
classpath
No dependencies
A web-based, searchable dependency report is available by adding the --scan option.
BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#buildSrc- 子產品方式 "buildSrc 子產品方式")buildSrc 子產品方式
rootProject/buildSrc 檔案夾是 Gradle 的預留目錄,用來存放目前項目私有 Gradle 插件的源代碼與建構腳本
- 項目建構時,Gradle 會自動編譯項目目錄下的 buildSrc 檔案夾下的建構腳本和源碼,并将其添加到項目建構腳本的 classpath 中,是以在使用 buildSrc 中建立的插件時,無需再手動指定 classpath
- buildSrc 檔案夾中建構腳本和 Gradle 插件同一項目均可見,是以同一項目中的其他子產品也可以使用 buildSrc 中建立的插件
- 此處建立的插件對外部項目不可見,無法在其他項目中複用
建立-PluginBuildSrcDemo-項目子產品 "1. 建立 PluginBuildSrcDemo 項目子產品")1. 建立 PluginBuildSrcDemo
項目子產品
PluginBuildSrcDemo
建立
PluginBuildSrcDemo
目錄,并在該目錄下建立
build.gradle
mkdir PluginBuildSrcDemo && cd PluginBuildSrcDemo && touch build.gradle
建立-buildSrc-子子產品 "2. 建立 buildSrc 子子產品")2. 建立 buildSrc
子子產品
buildSrc
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#2-1- 在-PluginBuildSrcDemo-目錄下建立buildSrc目錄 "2.1 在 PluginBuildSrcDemo 目錄下建立buildSrc目錄")2.1 在 PluginBuildSrcDemo
目錄下建立 buildSrc
PluginBuildSrcDemo
buildSrc
mkdir buildSrc
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#2-2- 在-PluginBuildSrcDemo-buildSrc-目錄下建立-buildSrc-子子產品的建構腳本檔案-build-gradle "2.2 在 PluginBuildSrcDemo/buildSrc 目錄下建立 buildSrc 子子產品的建構腳本檔案 build.gradle")2.2 在 PluginBuildSrcDemo/buildSrc
目錄下建立 buildSrc 子子產品的建構腳本檔案 build.gradle
PluginBuildSrcDemo/buildSrc
build.gradle
cd buildSrc
touch build.gradle
PluginBuildSrcDemo/buildSrc/build.gradle
的内容如下
// 使用 plugins 塊文法應用插件
plugins {
// 應用 kotlin 插件
id 'org.jetbrains.kotlin.jvm' version '1.3.50'
}
dependencies {
// 僅在編譯時使用 Grdale-API 依賴
compileOnly gradleApi()
// 在插件源碼中添加 kotlin 标準庫依賴
implementation 'org.jetbrains.kotlin:kotlin-stdlib'
}
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#2-3- 建立-buildSrc-子產品的源碼目錄 "2.3 建立 buildSrc 子產品的源碼目錄")2.3 建立 buildSrc 子產品的源碼目錄
cd PluginBuildSrcDemo/buildSrc
mkdir -p /src/main/kotlin/com/example/plugin
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#2-4- 建立插件檔案PluginBuildSrc-kt "2.4 建立插件檔案PluginBuildSrc.kt")2.4 建立插件檔案 PluginBuildSrc.kt
PluginBuildSrc.kt
cd PluginBuildSrcDemo/buildSrc/src/main/kotlin/com/example/plugin
touch PluginBuildSrc.kt
PluginBuildSrc.kt
的代碼如下
package com.example.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
class PluginBuildSrc : Plugin<Project> {
override fun apply(target: Project) {
println("hello from buildSrc plugin")
}
}
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#2-5- 聲明-Gradle-插件的-ID-與實作類 "2.5 聲明 Gradle 插件的 ID 與實作類")2.5 聲明 Gradle 插件的 ID 與實作類
此步驟是可選的:若使用插件 ID 形式應用自定義插件,則必須進行此步驟;若使用插件實作類的形式應用自定義插件,則可跳過此步驟。
完成此步驟的方式有兩種,任選其一即可
方式1-META-INF-方式 "方式1. META-INF 方式")方式 1. META-INF 方式
PluginBuildSrcDemo/buildSrc/src/main/resources/META-INF/gradle-plugins
cd PluginBuildSrcDemo/buildSrc
mkdir -p src/main/resources/META-INF/gradle-plugins
gradle-plugins
目錄下建立 com.example.plugin.properties 屬性檔案,紅色部分表示插件的 ID
cd src/main/resources/META-INF/gradle-plugins
touch com.example.plugin.properties
屬性檔案的内容如下,表示插件 ID 為 com.example.plugin 的插件所對應的實作類為
com.example.plugin.PluginBuildSrc
implementations-class=com.example.plugin.PluginBuildSrc
方式2-java-gradle-plugin-插件方式 "方式2. java-gradle-plugin 插件方式")方式 2. java-gradle-plugin 插件方式
java-gradle-plugin 是一個用于開發 Gradle 插件的輔助插件,它内置了很多輔助功能:
- 為宿主子產品添加
配置塊,可在此處配置插件的 ID 和實作類gradlePlugin
-
依賴complile gradleApi()
- etc…
此處我們主要使用
gradlePlugin
配置塊代替
META-INF
目錄下的屬性檔案。
java-gradle-plugin
的使用方式非常簡單,隻需在
PluginBuildSrcDemo/buildSrc/build.gradle
建構腳本檔案中簡單配置即可,如下所示。
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.50'
+ //1. 應用 java-gradle-plugin 插件
+ id 'java-gradle-plugin'
}
dependencies {
- compileOnly gradleApi() // java-gradle-plugin 插件已為宿主添加 gradleApi 依賴,此行可移除
implementation 'org.jetbrains.kotlin:kotlin-stdlib'
}
+ //2. 添加 gradlePlugin 配置塊資訊
+ gradlePlugin {
+ plugins{
+ // 此處的 tag 可以為任意名稱
+ tag1{
+ id = 'com.example.plugin.buildsrc' //自定義插件的 ID
+ implementationClass = 'com.example.plugin.PluginBuildSrc' //自定義插件的實作類
+ }
+ }
+ }
此時在 PluginBuildSrcDemo 目錄下使用
tree
指令,可以看到目前的目錄結構如下
PluginBuildSrcDemo
.
├── build.gradle
├── buildSrc
│ ├── build.gradle
│ └── src
│ └── main
│ └── kotlin
│ └── com
│ └── example
│ └── plugin
│ └── PluginBuildSrc.kt
7 directories, 3 files
在-PruginBuildSrcDemo-項目子產品中應用-buildSrc-中聲明的-Gradle-插件 "3. 在 PruginBuildSrcDemo 項目子產品中應用 buildSrc 中聲明的 Gradle 插件")3. 在 PruginBuildSrcDemo
項目子產品中應用 buildSrc
中聲明的 Gradle 插件
PruginBuildSrcDemo
buildSrc
PluginBuildSrcDemo/build.gradle
建構腳本檔案中添加如下代碼
//apply plugin: '插件 ID'
apply plugin: 'com.example.plugin'
//apply plugin: 實作類
//apply plugin: com.example.plugin.PluginBuildSrc
應用插件時,指定插件 ID 或指定插件的實作類都可以,但指定插件 ID 的形式更為簡短及靈活,在實際開發中也更為常見。
PluginBuildSrcDemo
目錄下執行
gradle build
指令進行建構,可看到如下輸出
> Configure project :
hello from buildSrc plugin
> Task :buildEnvironment
------------------------------------------------------------
Root project
------------------------------------------------------------
classpath
No dependencies
A web-based, searchable dependency report is available by adding the --scan option.
BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed
其中第 2 行是我們在 buildSrc 子產品中定義的 Gradle 插件所列印日志,表明 buildSrc 子產品 中的自定義插件在根項目中已生效。
建立-SubModule-子子產品 "4. 建立 SubModule 子子產品")4. 建立 SubModule
SubModule
buildSrc 子產品中定義的插件可以在同一項目的任意子產品的建構腳本中使用,接下來便示範在 SubModule 子子產品中的應用。
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#4-1- 建立-SubModule-目錄與建構腳本 "4.1 建立 SubModule 目錄與建構腳本")4.1 建立 SubModule 目錄與建構腳本
// 1. 在 PluginBuildSrcDemo 目錄下建立 SubModule 目錄
cd PluginBuildSrcDemo && mkdir SubModule
// 2. 在 SubModule 目錄下建立 gradle 建構腳本
cd SubModule
touch build.gralde
PluginBuildSrcDemo/SubModule/build.gradle
apply plugin: 'com.example.plugin'
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#4-2- 将-SubModule-子產品關聯到-PluginBuildSrcDemo-項目中 "4.2 将 SubModule 子產品關聯到 PluginBuildSrcDemo 項目中")4.2 将 SubModule 子產品關聯到 PluginBuildSrcDemo 項目中
PluginBuildSrcDemo
目錄下建立
settings.gradle
檔案,内容如下
// 将SubModule 子子產品添加到 PluginBuildSrcDemo 項目子產品中
include ':SubModule'
此時在
PluginBuildSrcDemo
目錄下使用
tree
指令,可以看到項目目前的目錄結構如下
PluginBuildSrcDemo
.
├── SubModule
│ └── build.gradle
├── build.gradle
├── buildSrc
│ ├── build.gradle
│ └── src
│ └── main
│ └── kotlin
│ └── com
│ └── example
│ └── plugin
│ └── PluginBuildSrc.kt
└── settings.gradle
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#5- 在-SubModule-子產品執行建構指令 "5. 在 SubModule 子產品執行建構指令")5. 在 SubModule
子產品執行建構指令
SubModule
cd PluginBuildSrcDemo/SubModule
gradle build
得到如下輸出
> Configure project :SubModule
hello from buildSrc plugin
> Task :SubModule:buildEnvironment
------------------------------------------------------------
Project :SubModule
------------------------------------------------------------
classpath
No dependencies
A web-based, searchable dependency report is available by adding the --scan option.
BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed
獨立項目方式 "獨立項目方式")獨立項目方式
采用 buildSrc 子產品方式時,Gradle 會妥善處理 buildSrc 子產品的建構腳本與源碼,并将其添加到目前項目的 classpath 中。但 buildSrc 方式的插件隻能在項目内共享與複用,若要在其他項目中使用該插件,還需要再進行下列操作
- 将插件釋出到 maven 倉庫(任意倉庫)
- 在需要應用該插件的建構腳本中的 repository 部分添加該插件所在的 maven 倉庫
- 在需要應用該插件的建構腳本中的 classpath 部分添加該插件對應的 maven 坐标 (group : id : version)
因為是在其他項目中使用該項目 buildSrc 子產品 中的自定義 Gradle 插件,是以 Gradle 的 buildSrc 保留目錄優勢不再。如果将子產品名由
buildSrc
修改為其他名稱,則可将其稱為獨立的 Gradle 插件子產品。
在本小節中,我們主要學習 gradle 插件的釋出與使用。
建立-gradle-插件項目 "1. 建立 gradle 插件項目")1. 建立 gradle 插件項目
與 buildSrc 方式相同,先建立建構腳本與自定義插件類
mkdir StandaloneDemo
cd StandaloneDemo
touch build.gradle
mkdir src/main/kotlin/com/example/plugin
touch src/main/kotlin/com/example/plugin/StandalonePlugin.kt
StandaloneDemo/build.gradle
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.50'
id 'java-gradle-plugin'
}
gradlePlugin{
plugins{
sometag{
id = 'com.example.plugin'
implementationClass = 'com.example.plugin.StandalonePlugin'
}
}
}
StandaloneDemo/src/main/kotlin/com/example/plugin/StandalonePlugin.kt
package com.example.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
class StandalonePlugin : Plugin<Project> {
override fun apply(target: Project) {
println("hello from standalone plugin")
}
}
将-gradle-插件釋出到-maven-倉庫 "2. 将 gradle 插件釋出到 maven 倉庫")2. 将 gradle 插件釋出到 maven 倉庫
在StandaloneDemo-build-gradle-檔案中配置釋出資訊 "2.1 在StandaloneDemo/build.gradle 檔案中配置釋出資訊")2.1 在 StandaloneDemo/build.gradle
檔案中配置釋出資訊
StandaloneDemo/build.gradle
maven-publish
插件将自定義插件釋出到本地 maven 倉庫中,如下所示
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.50'
id 'java-gradle-plugin'
+ //1. 應用 maven-publish 插件
+ id 'maven-publish'
}
gradlePlugin{
plugins{
sometag{
id = 'com.example.plugin'
implementationClass = 'com.example.plugin.StandalonePlugin'
}
}
}
+//2. 設定釋出相關配置
+publishing {
+ publications {
+ //3. 将插件釋出到 maven 倉庫
+ maven(MavenPublication) {
+ //4. 設定插件的 maven 坐标
+ groupId 'com.example'//組織 ID
+ artifactId 'plugin' //制品 ID
+ version 'snapshot' //制品版本
+ from components.kotlin
+ }
+ }
+ //5. 設定釋出倉庫
+ repositories {
+ // 6. 釋出到本地 maven 倉庫
+ mavenLocal()
+ }
+}
使用-publishMavenPublicationToMavenLocal-任務将插件釋出到本地倉庫 "2.2 使用 publishMavenPublicationToMavenLocal 任務将插件釋出到本地倉庫")2.2 使用 publishMavenPublicationToMavenLocal
任務将插件釋出到本地倉庫
publishMavenPublicationToMavenLocal
cd StandaloneDemo
gradle publishMavenPublicationToMavenLocal
執行以上指令後,gradle 會把
StandaloneDemo
中的代碼編譯打包後釋出到本地 maven 倉庫中,本地 maven 倉庫通常存放于目前使用者目錄下,具體路徑為
-
~/.m2/repository
-
C:\Users\目前使用者名\.m2\repository
我們可以在此目錄下看到剛剛釋出的插件,如下圖所示
http://geek5nan.github.io/uploads/gradle-plugin-dev-1.png在其他項目中使用本地-maven-倉庫中的-gradle-插件 "3. 在其他項目中使用本地 maven 倉庫中的 gradle 插件")3. 在其他項目中使用本地 maven 倉庫中的 gradle 插件
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#3-1- 建立-OtherProject項目 "3.1 建立 OtherProject項目")3.1 建立 OtherProject
項目
OtherProject
mkdir OtherProject
cd OtherProject
touch build.gradle
build.gradle
的檔案内容如下,相比 buildSrc 方式,應用時多了 11 行的 buildscript 相關的配置資訊。
+buildscript {
+ repositories {
+ //1. 為目前建構腳本添加插件所在的 maven 倉庫,本例中為 maven 本地倉庫
+ mavenLocal()
+ }
+ dependencies {
+ //2. 為目前建構腳本添加如下依賴
+ //`com.exaple`、`plugin`、`snapshot` 是在上一步中設定的 maven 三維坐标
+ classpath 'com.example:plugin:snapshot'
+ }
+}
//3. 應用獨立插件項目中的自定義插件
// apply plugin: '插件 ID'
apply plugin: 'com.example.plugin'
http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/#3-2- 建構-OtherProject-項目 "3.2 建構 OtherProject 項目")3.2 建構 OtherProject
OtherProject
cd OtherProject
gradle build
輸入以上指令,得到如下輸出
> Configure project :
hello from standalone plugin
> Task :buildEnvironment
------------------------------------------------------------
Root project
------------------------------------------------------------
classpath
\--- com.example:plugin:snapshot
A web-based, searchable dependency report is available by adding the --scan option.
BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed
小結 "小結")小結
本文示範了 gradle 插件的三種建立方式,相關代碼已上傳至 github 中,點此即可檢視。行文匆忙難免疏忽,如有遺漏還請斧正。
- 本文作者: Geek5Nan
- 本文連結: http://geek5nan.github.io/2019/09/07/Developing-Gradle-Plugin-1/ ")
最後
我們今年整理了一份阿裡P7級别的Android架構師全套學習資料,特别适合有3-5年以上經驗的小夥伴深入學習提升。
主要包括騰訊,以及位元組跳動,華為,小米,等一線網際網路公司主流架構技術。如果你有需要,盡管拿走好了。
以下為我整理的資料免費分享;【阿裡P7】Android進階教程+BAT面試題
1.Android進階技術腦圖
2.P7級Android進階架構視訊教程
3.Android核心進階技術PDF文檔+BAT大廠面試真題解析
4.Android思維腦圖(技能樹)
1.Android進階技術腦圖;
查漏補缺,體系化深入學習提升
2.【Android進階架構視訊教程】;
全套部分展示;
java與Android核心進階專題視訊與源碼
阿裡P7級全套進階學習視訊;
3.Android核心進階技術PDF文檔,BAT大廠面試真題解析
免費分享
為什麼免費分享?
我的目的是讓更多需要的Android開發朋友能夠提升自己的技術水準
無論是Android,還是qq,微信,360等,想在網際網路上最大程度推廣,就必須免費!
如果我的學習資料對你有幫助,點個贊,謝謝!