Android Groovy 迁移到 KTS

news2025/1/15 12:43:03

文章目录

  • Groovy 迁移到 KTS
    • 概述
    • 迁移流程
      • setting.gradle
      • project/build.gradle
      • module/build.gradle
      • 处理ext扩展函数
        • 依次创建如下目录和文件
        • 使用
    • 源码

Groovy 迁移到 KTS

概述

Android Studio是使用Gradle来编译,而默认的构建语言是Groovy,但是Gradle实际上是支持Kotlin来编写Gradle构建脚本的,常见的构建脚本是.gradle结尾,而Koltin语法编写的脚本则是.gradle.kts 。

官方文档

主要修改以下配置文件:

  • settings.gradle
  • project/build.gradle
  • app/build.gradle

迁移流程

setting.gradle

以前代码:

rootProject.name = "My Application"
include ':app'

修改为:

rootProject.name = "My Application"
include(":app")

project/build.gradle

以前代码:

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:${Deploy.gradleVersion}")
        classpath(kotlin("gradle-plugin", Deploy.kotlinVersion))
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

tasks {
    val clean by registering(Delete::class) {
        delete(buildDir)
    }
}

修改为:

buildscript {
    val kotlinVersion = "1.5.31"
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:4.2.2")
        
//        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
        //上面可简写为:
        classpath(kotlin("gradle-plugin", kotlinVersion))
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
        jcenter()
    }
}

tasks {
    val clean by registering(Delete::class) {
        delete(buildDir)
    }
}

module/build.gradle

在 Gradle 的 Kotlin DSL(KTS)中,register 和 create 都是用于创建和配置对象的方法,但它们的行为有所不同。

  • register:这个方法会创建一个新的对象,但不会立即配置它。配置会在对象被实际需要时(即懒加载)进行。这意味着如果你的构建脚本中没有使用到这个对象,那么它就不会被创建和配置,从而节省了构建时间。
  • create:这个方法会立即创建和配置对象。这意味着无论你的构建脚本中是否使用到这个对象,它都会被创建和配置。 在大多数情况下,你应该优先使用 register,因为它可以提高构建性能。然而,如果你需要立即访问和修改新创建的对象,那么你应该使用 create。

以前代码:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.0"

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdkVersion 22
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    signingConfigs {
        debug {
            storeFile file('./myjks.jks')
            storePassword "123456"
            keyAlias "app"
            keyPassword "123456"
        }
        release {
            storeFile file('./myjks.jks')
            storePassword "123456"
            keyAlias "app"
            keyPassword "123456"
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.1'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

修改为:

plugins {
    id("com.android.application")
    
//    id("kotlin-android")
    //上面可简写为:
    kotlin("android")
}

android {
    compileSdkVersion(30)
    buildToolsVersion("30.0.3")

    defaultConfig {
        applicationId = "com.example.myapplication"
        minSdkVersion(22)
        targetSdkVersion(30)
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    // 配置签名
    signingConfigs {
        getByName("debug") {
            storeFile = file("./myjks.jks")
            storePassword = "123456"
            keyAlias = "app"
            keyPassword = "123456"
        }
        register("release") {
            storeFile = file("./myjks.jks")
            storePassword = "123456"
            keyAlias = "app"
            keyPassword = "123456"
        }
    }

    // 编译类型
    buildTypes {
        getByName("debug") {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
            signingConfig = signingConfigs.getByName("debug")
        }
        getByName("release") {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
            signingConfig = signingConfigs.getByName("release")
        }
    }

    // 输出文件名称
    android.applicationVariants.all {
        val buildType = this.buildType.name
        outputs.all {
            if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) {
                if (buildType == "debug") {
                    this.outputFileName = "app_V${defaultConfig.versionName}_${buildType}.apk"
                } else if (buildType == "release") {
                    this.outputFileName = "app_V${defaultConfig.versionName}_${buildType}.apk"
                }
            }
        }
    }

    // 部署资源文件
    fun listSubFile(): ArrayList<String> {
        // 新资源目录
        val resFolder = "src/main/res/layouts"
        // 新资源目录下的文件夹
        val files = file(resFolder).listFiles()
        val folders = ArrayList<String>()
        // 遍历路径
        files?.let {
            it.forEach { file ->
                folders.add(file.absolutePath)
            }
        }
        // 资源整合
        folders.add(file(resFolder).parentFile.absolutePath)
        return folders
    }

    // 资源重定向
    sourceSets {
        getByName("main") {
            res.srcDirs(listSubFile())
        }
    }

    // 指定JDK
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }

    // 开启ViewBinding
    buildFeatures {
        viewBinding = true
    }
}

dependencies {
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*jar"))))
    implementation("org.jetbrains.kotlin:kotlin-stdlib:1.5.31")
    implementation("androidx.core:core-ktx:1.3.1")
    implementation("androidx.appcompat:appcompat:1.2.0")
    implementation("com.google.android.material:material:1.2.1")
    implementation("androidx.constraintlayout:constraintlayout:2.0.1")
    testImplementation("junit:junit:4.+")
    androidTestImplementation("androidx.test.ext:junit:1.1.2")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0")
    implementation("com.google.code.gson:gson:2.10.1")
}

处理ext扩展函数

在使用 Groovy 语言构建的时候,一般都会抽取一个 config.gradle 来作为全局的变量控制,而ext扩展函数则是必须要使用到的,而在我们的 Gradle Kotlin DSL 中,如果想要使用全局控制,则需要 buildSrc。

buildSrc官网

依次创建如下目录和文件

在这里插入图片描述

build.gradle.kts:

plugins {
    `kotlin-dsl`
}

repositories {
    jcenter()
}

Versions.kt:

object Versions {
    //Android
    const val appCompat = "1.2.0"
    const val constraintlayout = "2.0.1"
    const val junit = "4.+"
    const val testExt = "1.1.2"
    const val espresso = "3.3.0"
    const val lifecycle = "2.5.0"
    const val material = "1.2.1"

    //Kotlin
    const val kotlinVersion = "1.5.31"
    const val coreKtx = "1.3.1"
    const val coroutines = "1.4.3"

    //KTX
    const val activityKtx = "1.2.3"
    const val fragmentKtx = "1.3.6"

    //other
    const val retrofit = "2.9.0"
    const val gson = "2.8.6"
    const val moshi = "1.9.3"
    const val glide = "4.11.0"
}

Libs:

object Libs {
    //Android
    const val appCompat = "androidx.appcompat:appcompat:${Versions.appCompat}"
    const val constraintlayout =
        "androidx.constraintlayout:constraintlayout:${Versions.constraintlayout}"
    const val junit = "junit:junit:${Versions.junit}"
    const val testExt = "androidx.test.ext:junit:${Versions.testExt}"
    const val espresso = "androidx.test.espresso:espresso-core:${Versions.espresso}"
    const val viewmodel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}"
    const val livedata = "androidx.lifecycle:lifecycle-livedata-ktx:${Versions.lifecycle}"
    const val lifecycle = "androidx.lifecycle:lifecycle-common-java8:${Versions.lifecycle}"
    const val lifecycleRT = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}"
    const val material = "com.google.android.material:material:${Versions.material}"

    //Kotlin
    const val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinVersion}"
    const val coreKtx = "androidx.core:core-ktx:${Versions.coreKtx}"
    const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}"
    const val coroutinesTest =
        "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.coroutines}"
    const val coroutinesAndroid =
        "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}"

    //KTX
    const val activityKtx = "androidx.activity:activity-ktx:${Versions.activityKtx}"
    const val fragmentKtx = "androidx.fragment:fragment-ktx:${Versions.fragmentKtx}"

    //other
    const val gson = "com.google.code.gson:gson:${Versions.gson}"
    const val moshi = "com.squareup.moshi:moshi:${Versions.moshi}"
    const val moshiCodeGen = "com.squareup.moshi:moshi-kotlin-codegen:${Versions.moshi}"
    const val converterMoshi = "com.squareup.retrofit2:converter-moshi:${Versions.retrofit}"
    const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}"
    const val glide = "com.github.bumptech.glide:glide:${Versions.glide}"
    const val glideCompiler = "com.github.bumptech.glide:compiler:${Versions.glide}"
}

ProjectProperties:

object ProjectProperties {
    const val compileSdk = 31
    const val buildTools = "31.0.0"
    const val minSdk = 22
    const val targetSdk = 31

    const val applicationId = "com.example.myapplication"
    const val versionCode = 1
    const val versionName = "1.0.0"

    const val agpVersion = "4.2.2"
}

Deloy:

object Deloy {

    fun getSystemTime(): String {
        val dateFormat = SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA)
        return dateFormat.format(System.currentTimeMillis())
    }
}

Signing:

object Signing {
    const val StoreFile = "./myjks.jks"
    const val StorePassword = "123456"
    const val KeyAlias = "app"
    const val KeyPassword = "123456"
}
使用

project/build.gradle:

buildscript {
    repositories {
        google()
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:${ProjectProperties.agpVersion}")

        classpath(kotlin("gradle-plugin", Versions.kotlinVersion))
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
        jcenter()
        maven { url = uri("https://www.jitpack.io") }
        maven { url = uri("https://jitpack.io") }
    }
}

tasks {
    val clean by registering(Delete::class) {
        delete(buildDir)
    }
}

module/build.gradle:

plugins {
    id("com.android.application")
    id("kotlin-android")
    kotlin("android")
    kotlin("kapt")
}

android {
    compileSdkVersion(ProjectProperties.compileSdk)
//    buildToolsVersion(ProjectProperties.buildTools)

    defaultConfig {
        applicationId = ProjectProperties.applicationId
        minSdkVersion(ProjectProperties.minSdk)
        targetSdkVersion(ProjectProperties.targetSdk)
        versionCode = ProjectProperties.versionCode
        versionName = ProjectProperties.versionName

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    // 配置签名
    signingConfigs {
        getByName("debug") {
            storeFile = file(Signing.StoreFile)
            storePassword = Signing.StorePassword
            keyAlias = Signing.KeyAlias
            keyPassword = Signing.KeyPassword
        }
        register("release") {
            storeFile = file(Signing.StoreFile)
            storePassword = Signing.StorePassword
            keyAlias = Signing.KeyAlias
            keyPassword = Signing.KeyPassword
        }
    }

    // 编译类型
    buildTypes {
        getByName("debug") {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
            signingConfig = signingConfigs.getByName("debug")
        }
        getByName("release") {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
            signingConfig = signingConfigs.getByName("release")
        }
    }

    // 输出文件名称
    android.applicationVariants.all {
        val buildType = this.buildType.name
        outputs.all {
            if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) {
                if (buildType == "debug") {
                    this.outputFileName = "app_V${defaultConfig.versionName}_${buildType}.apk"
                } else if (buildType == "release") {
                    this.outputFileName =
                        "app_V${defaultConfig.versionName}_${buildType}_${Deloy.getSystemTime()}.apk"
                }
            }
        }
    }

    // 部署资源文件
    fun listSubFile(): ArrayList<String> {
        // 新资源目录
        val resFolder = "src/main/res/layouts"
        // 新资源目录下的文件夹
        val files = file(resFolder).listFiles()
        val folders = ArrayList<String>()
        // 遍历路径
        files?.let {
            it.forEach { file ->
                folders.add(file.absolutePath)
            }
        }
        // 资源整合
        folders.add(file(resFolder).parentFile.absolutePath)
        return folders
    }

    // 资源重定向
    sourceSets {
        getByName("main") {
            res.srcDirs(listSubFile())
        }
    }

    // 指定JDK
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }

    // 开启ViewBinding
    buildFeatures {
        viewBinding = true
    }
}

dependencies {
    // Android
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
    implementation(Libs.appCompat)
    implementation(Libs.constraintlayout)
    implementation(Libs.viewmodel)
    implementation(Libs.livedata)
    implementation(Libs.lifecycle)
    implementation(Libs.lifecycleRT)
    implementation(Libs.material)
    testImplementation(Libs.junit)
    androidTestImplementation(Libs.testExt)
    androidTestImplementation(Libs.espresso)

    // Kotlin
    implementation(Libs.kotlinStdLib)
    implementation(Libs.coreKtx)
    implementation(Libs.coroutines)
    implementation(Libs.coroutinesTest)
    implementation(Libs.coroutinesAndroid)

    //KTX
    implementation(Libs.activityKtx)
    implementation(Libs.fragmentKtx)

    // Network
    implementation(Libs.gson)
    implementation(Libs.retrofit)
    implementation(Libs.moshi)
    implementation(Libs.converterMoshi)
    kapt(Libs.moshiCodeGen)

    // Image
    implementation(Libs.glide)
    kapt(Libs.glideCompiler)
}

源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1154699.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

竞赛选题 深度学习图像风格迁移

文章目录 0 前言1 VGG网络2 风格迁移3 内容损失4 风格损失5 主代码实现6 迁移模型实现7 效果展示8 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习图像风格迁移 - opencv python 该项目较为新颖&#xff0c;适合作为竞赛课题…

你真的会学习网络安全吗?

我敢说&#xff0c;现在网上90%的文章都没有把网络安全该学的东西讲清楚。 为什么&#xff1f;因为全网更多的都是在讲如何去渗透和公鸡&#xff0c;却没有把网安最注重的防御讲明白。 老话说得好&#xff1a;“攻击&#xff0c;是为了更好的防御。”如果连初衷都忘了&#xff…

Grafana 图表 Table 根据 Key 修改背景颜色

文章目录 前言1. 配置过程1.1 创建 override1.2 Add override property1.3 Value mappings 2. 效果展示 前言 需要配置一个备份任务的 Dashboard 展示备份的状态&#xff0c;如果备份状态是 Completed 表示正常&#xff08;绿色背景&#xff09;&#xff0c;如果是 Error 表示…

LOGO设计工具都有哪些?分享这6款

如果一个品牌想要脱颖而出&#xff0c;它必须有一个令人印象深刻的品牌标志。创建一个专业的标志&#xff0c;设计师不能简单地用刷子手绘&#xff0c;必须使用专业的标志设计软件来制作。 市场上有各种各样的标志设计软件&#xff1a;桌面、在线应用、免费&#xff0c;甚至人…

Kubernetes深度剖析,从基础到高级,带你领略K8s的魅力

一、Kubernetes 是 Google 团队发起并维护的基于 Docker 的开源容器集群管理系统&#xff0c;它不仅支持常见的云平台&#xff0c;而且支持内部数据中心。 建于 Docker 之上的 Kubernetes 可以构建一个容器的调度服务&#xff0c;其目的是让用户透过 Kubernetes 集群来进行云端…

星环科技分布式向量数据库Transwarp Hippo正式发布,拓展大语言模型时间和空间维度

随着企业、机构中非结构化数据应用的日益增多以及AI的爆发式增长所带来的大量生成式数据&#xff0c;所涉及的数据呈现了体量大、格式和存储方式多样、处理速度要求高、潜在价值大等特点。但传统数据平台对这些数据的处理能力较为有限&#xff0c;如使用文件系统、多类不同数据…

0002net程序设计-net家电维修保养信息系统

文章目录 **摘要**目录系统设计开发环境 摘要 家电维修保养信息系统提供给用户一个家电信息管理的网站&#xff0c;最新的家电信息让用户及时了解维修知识,保养方式的同时,还能通过交流区互动更方便。本系统采用了B/S体系的结构&#xff0c;使用了net技术以及SQL SERVER作为后…

uniapp 关于 video 组件的缩放比例问题

在 container 样式的 padding-bottom 设置比例值 9/16 比例值&#xff1a;56.25% 3/4 比例值&#xff1a;75% <view class"container"><video class"video-box" src"xxx.mp4" /> </view> .container {position: relative;wid…

【STL】:list用法详解

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关list的使用&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数据结构…

SQL注入思路扩展

目录 一、资产搜集 二、开始sql注入常规流程 三、sqlmap验证 总结&#xff1a;测试sql注入的时候不要只局限于明文传输&#xff0c;也要注意编码或者加密后的值。 还没看够&#xff1f;欢迎关注&#xff0c;带你走进黑客世界&#xff0c;下面也有免费的靶场视频 一、资产搜…

HCIA --- 综合实验(结束)

一、实验拓扑及要求 二、整体IP规划 三、解决方案 四、解决步骤配置命令 一、基本部分 一、交换机 1、创建对应VLAN&#xff0c;对应接口划入对应VLAN中&#xff0c;创建Trunk干道&#xff0c;配置HTTP服务器IP LSW1 [sw1]vlan batch 2 to 3 [sw1]interface e0/0/1 [sw1-E…

idea提交代码一直提示 log into gitee

解决idea提交代码一直提示 log into gitee问题 文章目录 打开setting->Version control->gitee,删除旧账号&#xff0c;重新配置账号&#xff0c;删除重新登录就好 打开setting->Version control->gitee,删除旧账号&#xff0c;重新配置账号&#xff0c;删除重新登…

部署前端项目到宝塔面板(腾讯阿里服务器均适用)

写在前面&#xff0c;本网站部署的是前端nuxt.js项目&#xff0c;后端部分在本人的其他博文&#xff0c;请移步 【起步】服务器端 打开自己的轻量服务器的管理面板 确保服务器已经打开&#xff0c;如下图所示 来到域名列表&#xff0c;解析域名&#xff0c;如下图所示 的…

[转载]C++序列化框架介绍和对比

Google Protocol Buffers Protocol buffers 是一种语言中立&#xff0c;平台无关&#xff0c;可扩展的序列化数据的格式&#xff0c;可用于通信协议&#xff0c;数据存储等。 Protocol buffers 在序列化数据方面&#xff0c;它是灵活的&#xff0c;高效的。相比于 XML 来说&…

怎么理解电流超前电压、电压超前电流?

电容和电感&#xff0c;电压超前电流&#xff0c;电流超前电压都是我们经常听到的。作为非专业人士&#xff0c;这些听起来确实有点摸不着头脑&#xff0c;今天特别查了下电容、电感、电压电流相关资料&#xff0c;总算是弄明白了&#xff0c;在此特地记录下&#xff01; 1. 电…

打造企业级门户,WorkPlus助您打造个性化与高效的企业通讯平台

在现代企业运营中&#xff0c;良好的内部沟通与信息管理至关重要。为满足企业对于高效沟通与信息发布的需求&#xff0c;WorkPlus推出了企业门户APP&#xff0c;为企业提供全新的信息管理与沟通协作体验。 作为一站式企业门户&#xff0c;WorkPlus连接了组织、员工和信息的纽带…

GitLab(2)——Docker方式安装Gitlab

目录 一、前言 二、安装Gitlab 1. 搜索gitlab-ce镜像 2. 下载镜像 3. 查看镜像 4. 提前创建挂载数据卷 5. 运行镜像 三、配置Gitlab文件 1. 配置容器中的/etc/gitlab/gitlab.rb文件 2. 重启容器 3. 登录Gitalb ① 查看初始root用户的密码 ② 访问gitlab地址&#…

阿里云国际版和国内版的区别是什么,为什么很多人喜欢选择国际版?

阿里云国际版和国内版区别如下&#xff1a; 谈到区别&#xff0c;我们不妨先来对比下相同点与不同点&#xff0c;才能清晰明确的知道二者区别 下面先介绍不同点&#xff1a; 面向市场更广泛 阿里云国际版主要是面向国际&#xff08;全球&#xff09;客户的&#xff0c;而国内…

如何用ChatGPT快速写出一份合格的PPT报告

我们【AI写稿专家】的小伙伴中有很多企业高管和公务员&#xff0c;大家经常有写报告写ppt的需求&#xff0c;下面小编给大家介绍一下我们新发布生成PPT的功能&#xff0c;很简单很方便&#xff0c;看完大家不到1分钟就能生成一份拿得出手的PPT报告&#xff0c;再也不用费尽心思…

华为云之使用CCE云容器引擎部署Nginx应用【玩转华为云】

华为云之使用CCE云容器引擎部署Nginx应用【玩转华为云】 一、本次实践介绍1.本次实践简介2.本次实践目的 二、CEE介绍1.CCE简介2.CCE产品链接 三、创建虚拟私有云VPC1.访问VPC2.创建VPC3.查看VPC列表 四、创建密钥对1.进入密钥对界面2.创建密钥对3.保存密钥文件到本地 五、创建…