☰
Android Kotlin 第一个应用:环境配置到签名打包全链路
2026/9/29 5:36:25 网站建设 项目流程

1. 从零起步:先搞清楚这套工具链到底在干什么

Android 加 Kotlin 这两个词摆在一起,基本就是当下安卓原生开发的标准起手式。我早年用 Java 写过不少 Activity,后来官方把 Kotlin 扶正为一级语言,新开的工程我基本不再考虑 Java 了。这些年我带过几个刚入行的人,发现他们卡住的地方往往不是 Kotlin 语法本身,而是前面那一堆工具装不明白、Gradle 一同步就红一片、真机连不上、包打不出来。其实这些东西单独看都不难,难的是没人告诉你它们之间是什么关系,于是第一次动手就像在雾里走路。这篇就按我自己的实操顺序,从安装 Android Studio、创建第一个 Android Kotlin 应用讲到一个能点、能存数据、能装到手机上的小成品,中间那些文档里一笔带过、实际会绊人的地方我都会点出来。

先说一个我反复强调的观念:不要一上来就背语法,也不要先把 Kotlin 面试题刷一遍。第一个项目的目的不是学会所有东西,而是把"编辑—构建—安装—运行—调试"这条链路走通一遍。链路通了,后面学什么都是往这条管线上挂插件;链路不通,学再多语法也只是纸上谈兵。我见过太多人收藏了一堆 Kotlin 中文文档,结果连一个可以运行的工程都没跑起来,热情就这么耗没了。

1.1 Android Studio、SDK、JDK、Gradle 四者的关系

很多人第一次装环境会懵,是因为界面上同时出现了 Android Studio、Android SDK、JDK、Gradle 四个名字,不知道谁管谁。我用一句话概括:Android Studio 是车间,JDK 是电力,Android SDK 是原材料和机床,Gradle 是流水线的调度员。

Android Studio 本身是基于 IntelliJ IDEA 社区版做的 IDE,负责写代码、给提示、跑调试器、管理模拟器和设备,它是你唯一需要主动打开的东西。它内部捆绑了一个 JetBrains Runtime,也就是一份定制过的 JDK,所以现在的安装包基本都是"自带电力"的,你不需要再单独去 Oracle 官网折腾 JDK。

Android SDK 是一整套工具和库的集合,你会在 SDK Manager 里看到几类东西:platforms目录下是各个 API 级别的android.jar,也就是你编译时对照的"标准答案";build-tools里有把资源编译打包的aapt2、把字节码转换压缩的d8/r8;platform-tools里有我们后面要反复用的adb。这些东西缺一个,构建就可能报错。

Gradle 是构建系统,负责把源码、资源、依赖、签名一步步组装成 APK 或 AAB。但 Gradle 自己不认识安卓,所以还需要一个叫 AGP(Android Gradle Plugin) 的插件来告诉它"安卓项目该怎么构建"。这三者有严格的版本对应关系,对不上就是满屏红字,这也是新手最常踩的坑:

组件当前主流版本关键约束
Android Studio2024 及以后版本决定能用的 AGP 上限
AGP8.x8.x 要求 JDK 17、Gradle 8.x
Gradle8.x由 wrapper 锁定,通常不用手动装
JDK17低于 17 会在同步阶段直接失败

记住一条:Gradle 的版本不要自己去装,项目里的gradle/wrapper/gradle-wrapper.properties已经写死了这个项目该用哪个版本,点同步时它会自己下载。手动装一个全局 Gradle 反而容易造成版本打架。

1.2 安装过程中的几个关键选择

去 Android Studio 官网下载这件事本身没什么可讲的,选对应系统版本、点下一步就行。倒是安装过程中有几个细节,我吃过亏,值得单独拎出来说。

第一个是路径。SDK 的安装路径绝对不要包含中文和空格。我第一次装的时候图省事放在"我的文档"下面,结果 Gradle 同步时反复报路径解析错误,排查了一下午才发现是路径里有个中文目录名。把它挪到D:\Android\Sdk这种纯英文短路径下,世界立刻清净了。

第二个是安装类型。安装向导会问你是 Standard 还是 Custom,新手直接选 Standard 就好,它会帮你把 SDK、platform-tools、模拟器镜像都下好。唯一的代价是下载量大,如果网速一般,可以在 SDK Manager 里把不需要的 API 级别和系统镜像去掉,只留下当前要用的那一个。

第三个是汉化。这个被问得太多了。现在 Android Studio 官方市场里就有中文语言包,路径是Settings → Plugins → Marketplace,搜索Chinese就能看到一个简体中文语言包插件,装上重启界面就变中文了。注意是官方市场里的那个,不要从乱七八糟的地方下所谓"汉化包"覆盖安装目录,那种做法升级一次 IDE 就会出问题。我个人的建议是:菜单汉化没问题,但代码里的 API 名字、报错信息还是英文,早点习惯英文报错反而学得更快。

第四个是初次启动的 SDK 配置。进Settings → Languages & Frameworks → Android SDK,把SDK Platforms里最新的两三个 API 勾上,SDK Tools里把Android SDK Build-Tools、Android SDK Platform-Tools、Android SDK Command-line Tools勾上。命令行工具这一项特别容易被忽略,但很多自动化脚本和 CI 配置都依赖它。

提示:如果你的网络环境下载 SDK 很慢,可以在 SDK Manager 里找镜像地址设置,或者在init.gradle里把仓库地址统一改成国内的镜像源。这个配置写一次,之后所有项目都会生效,比每个项目改一遍build.gradle省事得多。

2. 创建工程:模板选择与 Gradle 配置的门道

环境齐了,接下来就是 New Project。这一步看起来只是点几下,但里面藏着不少要理解的东西,尤其是模板生成了哪些文件、这些文件各自负责什么。我见过不少人做了半年项目,还是不敢动build.gradle,一改就崩,本质上是没搞清这几个文件的分工。

2.1 Empty Activity 模板到底生成了什么

在 New Project 界面,你会看到 Phone and Tablet 下面有一排模板:Empty Activity、Empty Views Activity、Basic Views Activity、Bottom Navigation Activity 等等。现在的新版本里,"Empty Activity" 默认用的是 Jetpack Compose 那套声明式 UI,而 "Empty Views Activity" 才是传统的 XML 布局。这两条路差别很大,新手容易选错方向。

我的建议是:如果你打算系统学安卓,先把 Views 这条路走通,因为它对应的 XML 布局、findViewById、RecyclerView这些概念在现有的存量项目和面试里出现频率依然极高;等你对生命周期、资源系统、事件分发有了概念,再上 Compose,会顺很多。反过来先学 Compose,遇到自定义 View、老项目迁移就会发懵。

选完模板点 Finish,工程会开始第一次 Gradle Sync,这是最慢的一次,因为要下载依赖。同步完成后,左侧目录大致是这样几块:

  • app/目录:一个典型的应用模块,你的代码、资源、清单文件都在这。多模块项目里会有多个这样的模块。
  • app/src/main/java/:Kotlin 源码目录,包名会自动按你填的域名倒序生成。
  • app/src/main/res/:资源目录,layout放布局,values放字符串和颜色,drawable放图片,mipmap放应用图标。
  • app/src/main/AndroidManifest.xml:应用的"户口本",声明 Activity、权限、应用图标和标签。
  • app/build.gradle.kts:这个模块的构建脚本,依赖、compileSdk、签名配置都在这。
  • build.gradle.kts(项目根):全局配置,一般只放插件声明。
  • settings.gradle.kts:声明参与构建的模块和仓库地址。
  • gradle.properties:全局的构建参数,比如开启 AndroidX、调整内存。
  • gradle/wrapper/gradle-wrapper.properties:锁定这个项目用的 Gradle 版本。

理解这张清单,你就知道改一个依赖该去哪个文件、加一个权限该去哪、换图标该动哪里。新手最常见的一个错误是把依赖加到了项目根的build.gradle.kts里,结果app模块里根本用不了。记住:跟代码相关的依赖,加在app/build.gradle.kts的dependencies块里。

2.2 Kotlin DSL 与 Groovy DSL 的区别与选型

如果你看过几年前的老教程,build.gradle里写的是apply plugin: 'com.android.application'这种不带括号的奇特语法,那是 Groovy DSL。而现在的模板默认生成.kts后缀的文件,用的是 Kotlin DSL。这两者的区别被问得很多,我按自己的理解梳理一下。

Groovy DSL 的优点是语法宽松、写起来短,网上老资料一大片;缺点是 IDE 基本给不了补全和类型检查,写错一个字母只有同步失败时才知道。Kotlin DSL 恰好相反:它是强类型的,你在dependencies块里敲一个库名,IDE 能提示、能跳转、能标红,配合 Gradle 的配置缓存,增量构建也更快。代价是语法更啰嗦,有些写法必须显式声明类型。

维度Groovy DSLKotlin DSL
文件后缀.gradle.gradle.kts
类型检查运行时才报错编译期就能发现
IDE 补全几乎没有完整支持
构建性能一般配置缓存下更快
网上资料存量多新增为主
迁移成本—需要把语法改写

我实际的做法是:新项目一律用 Kotlin DSL,老项目不动它,除非有明确收益。硬要迁移,最容易出错的地方是ext变量和属性赋值的写法,Groovy 里compileSdk 34这种省略等号的调用,在 Kotlin DSL 里必须写成compileSdk = 34,很多人漏了这个等号就同步失败。

2.3 几个必须搞懂的构建参数

app/build.gradle.kts里有几个参数,看着简单,但选错了后果不小。我把它们的作用和取值逻辑讲清楚:

compileSdk是编译时使用的 SDK 版本,你应该选当前最新或次新的稳定版本。它决定了你能调用哪些新 API,但不会影响运行,因为这只是编译期的对照表。

minSdk是应用能安装的最低系统版本。这个值的选取是一道取舍题:调高,能用更多新特性、少写兼容代码,但会丢用户;调低,覆盖面广,但要处理更多兼容分支。我一般看数据决定,如果产品没有特殊要求,定在 24 或 26 是比较省事的区间,能把绝大多数还在用的设备覆盖住,又躲开了一些古老的兼容坑。

targetSdk表示你针对哪个版本做过适配测试。系统会用这个值决定要不要给你套上某些行为变更。它和minSdk的关系是:minSdk ≤ targetSdk ≤ compileSdk。每次系统发新版本,把targetSdk往上抬之前,一定要去读官方的行为变更说明,因为有些变更会直接让你的旧代码失效,比如后台限制、存储权限、通知权限的调整。

注意:targetSdk不是"为了上架才改"的数字,它实实在在影响运行时行为。我曾经遇到过一个后台任务在新版本上突然不执行,排查半天,最后发现就是targetSdk提升后系统收紧了后台限制,代码本身没任何问题。

3. 写第一个界面:从布局到交互

环境通了、工程建好了,接下来写点能看见的东西。我习惯用一个小计数器作为第一个练手项目,因为它足够简单,却覆盖了安卓开发最核心的几件事:布局、控件查找、事件监听、状态保存。麻雀虽小,五脏俱全。

3.1 用 XML 描述界面长什么样

res/layout/activity_main.xml是界面的"图纸"。我把它换成一个垂直排列的布局,包含一个显示数字的文本、一个进度条、两个按钮:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" android:padding="24dp"> <TextView android:id="@+id/tvCount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="48sp" android:text="0" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:max="10" android:layout_marginTop="16dp" /> <Button android:id="@+id/btnAdd" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="24dp" android:text="加一" /> <Button android:id="@+id/btnReset" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="重置" /> </LinearLayout>

这里有几个尺寸单位要分清:dp用于控件大小和间距,它会根据屏幕密度自动换算,保证不同手机上物理大小接近;sp用于字体,它还会跟随用户的系统字体设置缩放,所以文字一定要用sp,用dp会导致用户调大字体时界面不变,体验很差。这个细节写进新项目的第一天,比后面改一堆布局强。

再一个是android:id,后面代码要通过它找到控件。命名习惯上我推荐用控件类型缩写做前缀,像tvCount、btnAdd、progressBar,在几十个控件的页面里,一眼就能找到目标。

3.2 用 ViewBinding 代替 findViewById

老教程里找控件的写法是findViewById<TextView>(R.id.tvCount),写多了又长又容易出错,还要处理可空类型。现在推荐用 ViewBinding,它是官方提供的、编译期生成绑定类的方式,安全且不用手写查找。

开启方式是在app/build.gradle.kts的android块里加一行:

android { // ... 其他配置 buildFeatures { viewBinding = true } }

同步之后,每个布局文件都会生成一个同名的绑定类,activity_main.xml对应ActivityMainBinding。Activity 里的写法变成这样:

package com.example.firstkotlinapp import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.example.firstkotlinapp.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private var count = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.btnAdd.setOnClickListener { count++ refreshUi() } binding.btnReset.setOnClickListener { count = 0 refreshUi() } refreshUi() } private fun refreshUi() { binding.tvCount.text = count.toString() binding.progressBar.progress = count.coerceAtMost(10) } }

lateinit var表示这个属性稍后初始化,告诉编译器"我心里有数,别逼我在声明时赋值"。它在 Activity 这种有明确生命周期的地方很合适,因为binding一定会在onCreate里被赋值,之后的点击回调里访问它不会为空。coerceAtMost(10)是 Kotlin 提供的一个很顺手的方法,意思是"最多取到 10",超过就按 10 算,省掉了自己写if判断。

3.3 几个必会的 Kotlin 语法点

写这个小项目会用到几个 Kotlin 的高频语法,我顺便讲清楚它们为什么这么设计,比死记硬背管用。

空安全。Kotlin 把类型分成可空和不可空两种,String不能为 null,String?才可以。这是它最值钱的设计之一,把空指针异常从运行期提前到了编译期。实操里你会经常遇到三种操作符:?.表示左边为空就整体返回空,不继续调用;?:表示左边为空就用右边的默认值;!!表示"我确定它不为空",但一旦真的为空就会崩,所以我在正式项目里尽量避免用它,除非能百分百确定。

字符串模板。上面代码里count.toString()其实可以写成"$count",这就是字符串模板。如果表达式复杂一点,用花括号包起来,比如"总计 ${count + 1} 次"。它比 Java 里的字符串拼接清爽太多,日常写日志、拼提示文字基本离不开。

字符串格式化。需要控制小数位或者补零的时候,用String.format()。比如把计数显示成三位数:

val text = String.format("%03d", count)

%03d的意思是"整数,不足三位前面补零"。Kotlin 里它底层还是调 Java 的String.format,所以格式占位符的写法和 Java 完全一致。因为它是 Java 的方法,在 Kotlin 里参数为 null 时不会走 Kotlin 的空检查,这是个容易忽略的小坑,传参前最好确认一下。

when 表达式。它是switch的升级版,既能当语句也能当表达式返回值,而且不需要写break,不会出现忘记 break 导致的穿透问题。

这几个点掌握了,写一个页面的交互基本不会卡住。剩下的就是多写。

4. 让数据活下来:用 SharedPreferences 存一点东西

计数器有个明显的毛病:退出应用再进来,数字归零了。要让它记住上次的数值,得做持久化。轻量级的键值对存储,SharedPreferences 是最直接的选择。

4.1 SharedPreferences 的基本用法与封装

SharedPreferences 本质是一个 XML 文件,用键值对的方式存少量数据,适合保存开关状态、上次登录的用户名、简单的计数这类信息。拿到它的方式有两种,一种是按名字拿一个独立的文件,一种是直接用 Activity 默认的:

val sp = getSharedPreferences("app_config", Context.MODE_PRIVATE)

MODE_PRIVATE表示这个文件只有本应用能读写,这也是现在唯一推荐的模式,其他几个模式要么废弃,要么涉及跨应用共享的安全风险,不要用。

读写用起来非常直白:

// 写入 sp.edit().putInt("count", count).apply() // 读取,第二个参数是找不到时的默认值 val saved = sp.getInt("count", 0)

这里有个新手必踩的点:apply()和commit()的区别。commit()是同步写入,会阻塞当前线程直到写盘完成,返回一个布尔值表示成功与否;apply()是先把改动写到内存,然后异步落盘,不返回结果。在主线程里,一律用apply(),用commit()写大数据时会引起界面卡顿。只有在极少数需要确认写入结果的场景下才考虑commit(),而且那种场景也应该放到子线程。

把上面这套接进 Activity 的思路是:在onCreate里读一次,恢复上次的值;在每次计数变化后写一次。但每次都直接调sp.edit()写一遍,代码会散落在各处。我习惯做一层薄封装:

class ConfigStore(context: Context) { private val sp = context.getSharedPreferences("app_config", Context.MODE_PRIVATE) var count: Int get() = sp.getInt(KEY_COUNT, 0) set(value) = sp.edit().putInt(KEY_COUNT, value).apply() companion object { private const val KEY_COUNT = "count" } }

用 Kotlin 的属性委托式写法,外面用起来就像普通字段一样configStore.count = 5,可读性好,键名集中管理也不容易写错。这是我个人比较推荐的写法,比到处传递SharedPreferences对象干净得多。

注意:SharedPreferences 存的是明文,任何能拿到设备文件的人都能读到里面的内容。所以密码、令牌这类敏感信息不要往里放,需要本地加密存储的,应该用专门的加密方案。

4.2 什么时候不该用 SharedPreferences

SharedPreferences 好用,但不是万能。它有几个明确的边界,越界使用会给你埋雷。

第一,数据量大的时候不要用。它在apply()时会把整个文件的内容读进内存,文件越大,读写的代价越高,而且这些内容会一直占着内存。如果你要存几百条记录,它就不合适了。

第二,结构化数据不要用。它只能存基本类型和字符串集合,存一个对象列表就得自己序列化成 JSON 字符串塞进去,读的时候再反序列化,用起来别扭,性能也差。

第三,不要拿它当数据库。需要按条件查询、排序、分页的,应该用 Room。需要存配置流、希望读的时候能感知变化的,现在更推荐 DataStore,它同样是键值对,但基于协程和 Flow,异步、类型安全,还不会像 SharedPreferences 那样在主线程做磁盘操作。

我自己的判断标准很简单:几对键值、体积小、能接受每次都全量读写,用 SharedPreferences;只要有一个条件不满足,就往上换 DataStore 或 Room。把它当成一个"便利贴",而不是"档案柜"。

5. 真机调试与常见问题排查

代码写完,得让它跑起来。模拟器能跑大部分场景,但真机的表现才是最终标准,特别是涉及性能、蓝牙、传感器这些功能时,模拟器基本帮不上忙。

5.1 ADB 常用命令速查

ADB(Android Debug Bridge) 是连接电脑和设备的桥梁,它本身在 SDK 的platform-tools目录下。真机调试的前提是打开开发者选项里的 USB 调试,插上线后在设备上确认授权。第一次连接通常要在手机上点一次"允许调试",并且勾选"始终允许",否则每次插拔都要重新授权。

几个我每天都会用到的命令:

# 查看当前连接的设备,unauthorized 表示还没在手机上点同意 adb devices # 安装一个 APK,-r 表示覆盖安装保留数据 adb install -r app-debug.apk # 卸载,-k 表示保留数据 adb uninstall com.example.firstkotlinapp # 抓日志,按标签过滤,看自己应用的输出 adb logcat -s MainActivity:D # 清空日志缓冲,排查前先清一次更清爽 adb logcat -c # 进设备 shell,可以看文件、查进程 adb shell # 杀掉应用进程,方便测试冷启动 adb shell am force-stop com.example.firstkotlinapp

adb devices显示unauthorized是最常见的问题,解决办法是先确认手机屏幕上是否弹出了授权对话框,如果没有,可以在开发者选项里点一下"撤销 USB 调试授权",然后拔插一次线。如果还是不行,换个 USB 口、换根数据线,很多所谓的"连不上"其实是线只能充电不能传数据。

5.2 构建与安装常见报错速查

我把这几年被问得最多、自己也踩过的坑整理成一张表,遇到的时候直接对照:

现象常见原因处理思路
Gradle sync failed网络导致依赖没下全换仓库镜像,重试同步,必要时删缓存
Unsupported class file major versionJDK 版本与 AGP 不匹配在设置里把 Gradle JDK 切到 17
SDK location not found换电脑后local.properties丢了新建该文件并写入sdk.dir路径
INSTALL_FAILED_UPDATE_INCOMPATIBLE手机上已装了签名不同的同包名应用先卸载旧版本再装
真机列表为空驱动、线材或授权问题换线换口,撤销授权重连
项目在别人电脑上打不开Gradle 版本、JDK 版本或 SDK 路径不一致统一版本,检查local.properties
应用图标不更新用了mipmap缓存或没改清单确认清单里的图标引用,清一次构建

说到项目移植,这是团队协作里高频出问题的地方。把工程从一台电脑拷到另一台,最容易丢的就是local.properties,因为它通常被.gitignore排除掉,里面存着本机的 SDK 路径。新机器上要么让 IDE 自动生成,要么手动写一行sdk.dir=D\:\\Android\\Sdk。另外gradle/wrapper目录一定要一起拷过去,它是保证所有人用同一个 Gradle 版本的关键。至于那几个 Gradle 缓存目录,反而不用拷,那是可再生的。

提示:项目里如果出现"三角形模糊箭头"这类显示异常,多半是图标资源本身的问题,比如用了矢量图但缺少必要的属性声明,或者图片分辨率不足被拉伸。图标建议统一用矢量资源,一张图适配所有密度,比准备五套 PNG 省事。

5.3 看懂报错信息的正确姿势

新手看到一屏红字就慌,其实报错信息是有层级的。Gradle 的输出通常是"一个主错误 + 一堆被牵连的次生错误",真正有用的往往是最上面那条带Caused by的。我的习惯是先滚到最顶上看第一个错误,把它的关键词复制去搜,而不是从下往上一条条看。

另外,日志抓取也有技巧。Log.d()打出来的东西在logcat里混着系统和其他应用的海量信息,用标签过滤能立刻聚焦。我的做法是给自己的类名做标签,或者直接用TAG常量,抓日志时adb logcat -s TAG:D,只看自己的输出,效率高得多。发布版本里记得把调试日志去掉,一来避免泄露信息,二来减少性能开销。

6. 打包出第一个能装的安装包

调试包跑通之后,最后一步是打一个可以发给别人安装的正式包。这一步的门槛在于签名,很多人卡在这里,其实是没理解签名的意义。

6.1 签名的意义与生成流程

Android 要求每个安装包都必须签名,签名的核心作用是证明"这个包确实来自同一个开发者"。系统用签名来判断两个包能不能互相覆盖安装、能不能共享数据。所以同一个应用的所有版本,必须用同一个签名文件,一旦换了签名,老用户就无法直接升级,必须先卸载。

生成签名文件用 JDK 自带的keytool:

keytool -genkeypair -v \ -keystore my-release.jks \ -alias mykey \ -keyalg RSA \ -keysize 2048 \ -validity 10000

执行后会让你输入密码和一堆主体信息。这里的几个参数值得说明:-keyalg RSA -keysize 2048是当前普遍推荐的密钥算法和长度;-validity 10000是有效期天数,一万天大约二十七年。填写时的密码和别名一定要记牢,尤其是.jks文件本身要备份好,丢了基本等于这个应用再也无法更新。

生成之后,在app/build.gradle.kts里配置签名信息,不要把密码明文写在构建文件里提交到仓库,应该放在local.properties或环境变量里读取。这是很多团队出过安全事故的地方。

打包路径有两处:Build → Generate Signed Bundle / APK。它会让你选 AAB 还是 APK。AAB 是上传到应用商店用的格式,商店会根据设备配置拆出更小的安装包;APK 是可以直接分发安装的格式。自己测试用 APK 更方便,上架用 AAB。

6.2 顺手看一眼包体积和签名指纹

打完包,可以用Build → Analyze APK打开产物,看看每个部分占了多少体积。这一步在新手阶段看着多余,但养成习惯后,你能很快发现"某个库莫名其妙占了好几兆"这类问题。

签名指纹也就是 SHA1,是很多第三方服务接入时要填的东西。查看方式:

keytool -list -v -keystore my-release.jks -alias mykey

输出里会列出 MD5、SHA1、SHA256 等指纹。注意,调试包和正式包用的是不同签名文件,指纹是不一样的,接入第三方服务时要用正式签名的那一套,否则上线后会验证失败。这个坑我见过不止一个团队踩过——开发阶段一切正常,一上正式包功能就失效,原因就是填了调试签名的指纹。

至于包体积优化,我一般做三件事:开启 R8 代码压缩和资源压缩、去掉没用到的库、把大图换成矢量或压缩格式。但这些都是后话,第一个项目不必纠缠于此,先把链路跑通最重要。


我在带新人的过程中最大的感受是:第一个 Android Kotlin 应用的价值,不在于它有多完善,而在于它逼你把整条链路走了一遍。环境装好那一刻、Gradle 第一次同步成功那一刻、真机上图标亮起来那一刻,这些"通了"的瞬间才是真正让你入门的东西。后面再学 Jetpack、学架构、学性能优化,都是在一条已经打通的管线上加东西,而不是对着一堆零散的知识点发愁。我个人的习惯是每学一个新东西,就把它塞进这个计数器小项目里试试,项目慢慢就长成了自己的知识仓库,比单纯看文档记得牢得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询