从零开发笔记APP:Android Studio+Kotlin+Room全流程实战

发布时间:2026/9/26 22:55:17
从零开发笔记APP:Android Studio+Kotlin+Room全流程实战 经常有朋友问我“我想自己写个APP应该从哪开始”我的回答一直是同一个从一个笔记应用开始。做笔记APP不需要复杂的后端、不需要支付、不需要地图定位但它能把你带进Android开发的完整链路——界面怎么写、数据怎么存、列表怎么展示、事件怎么处理、APK怎么打包。说白了一个“记事本”就是Android开发里的“Hello World加强版”麻雀虽小五脏俱全。这篇文字我会用Android Studio从零开始把“写一个笔记APP”这件事完整地过一遍。从环境搭建、界面设计到数据库存储、列表展示再到调试打包每一步都会讲清楚“为什么这么做”和“我踩过的坑”。适合两类人一是有过一点点编程基础、想正式入门Android开发的朋友二是已经入行、想补一补数据持久化和完整项目流程的初级开发者。看完以后你可以不只是复制代码而是真正理解这个APP是怎么长出来的。1. 项目整体设计与思路拆解1.1 一个笔记需求背后藏着多少知识点先看需求本身“记一条文字保存下来下次打开还能看到并且能修改和删除。”就这么一句话落到Android开发上至少牵扯出五块核心内容界面搭建需要输入页、列表页涉及EditText、Button、RecyclerView等基础组件。事件处理点击按钮保存、点击列表项编辑、长按删除涉及事件监听和回调机制。数据持久化APP退出后数据不能丢这就要引入数据库而不是简单的变量存储。数据展示多条笔记有序排列需要列表控件和适配器模式Adapter。生命周期管理从Activity创建到销毁各种状态切换下要保证数据不丢、界面不崩。很多人一上来就啃《Android编程权威指南》这种大厚书翻两章就放弃了。笔记APP的好处恰恰在于它是“需要什么就学什么”每一个知识点都能立刻在屏幕上看到效果。这种即时反馈是保持学习动力的关键。1.2 技术选型为什么是Kotlin Room RecyclerView技术选型看似简单实际是第一个容易犹豫的地方。语言选Java还是Kotlin现在主流新项目基本都是KotlinGoogle官方文档和示例代码也全面切换到了Kotlin。对新手来说Kotlin的语法更简洁空安全机制能从源头避免一大类崩溃问题。这篇博文统一用Kotlin写如果你以前学过Java阅读起来也没有难度——语法上几乎是“减糖”的关系。数据存储怎么选方案有这么几个SharedPreferences存字符串、直接写txt文件、用SQLite、用Room框架。SharedPreferences适合存配置不适合存列表写文件需要你自己处理序列化和并发麻烦原生SQLite代码量大、容易写错。Room是Google在SQLite之上封装的一套框架用注解定义表结构用接口写SQL编译时还能自动校验SQL语句对错。新手用它省心不是一点点。列表用什么ListView已经是老古董了RecyclerView才是现在的标准。它自带ViewHolder复用机制滑动性能好而且配合LayoutManager可以轻松切换线性布局、网格布局。我们会用LinearLayoutManager做一个从上往下排列的普通笔记列表。所以最终技术栈定下来Kotlin Room RecyclerView 两个Activity。这个组合既覆盖了Android开发最典型的场景又没有引入网络请求这种超出范围的内容非常适合当第一个完整的APP项目。2. Android Studio环境搭建与优化2.1 下载、安装与版本选择Android Studio是Google官方的IDE直接去官网developer.android.com下载即可。版本怎么选建议选当前的稳定版不要追Preview预览版。热搜里提到过“android studio dolphin | 2021.3.1 patch 1 安装教程”Dolphin这个版本是老一代的稳定版现在新版本已经迭代了很多比如Koala、Ladybug等。原则只有一个下稳定版别下Beta和Canary。安装过程有几个注意点Windows下安装时建议勾选“Android SDK”和“Android Virtual Device”两个组件省得后面再手动补。安装路径尽量不要有空格和中文部分工具链对路径敏感。内存建议至少8GB起步IDE本身占2-3GB模拟器再占2GB如果电脑配置比较紧张后面可以考虑直接用真机调试。2.2 首次启动的SDK问题与配置很多新手在第一次启动Android Studio时卡在同一个报错上First Run unable to access android sdk add-on list。这个弹窗是Android Studio在首次运行时尝试联网获取SDK组件列表因为网络原因或者SDK目录权限问题获取失败就会弹窗。解决办法分两步走检查SDK是否已经安装到了指定目录。正常情况下Android Studio会自带一个SDK路径比如Windows在C:\Users\你的用户名\AppData\Local\Android\Sdk。打开SDK Manager右下角Configure SDK Manager手动勾选需要的组件Android SDK Platform-Tools、Android Emulator、Android SDK Build-Tools以及对应API级别的System Image。我自己遇到这个问题的场景是公司内网环境网络策略比较严格。这种环境下一味等待在线下载是没有意义的建议直接离线下载SDK压缩包解压后放在指定目录然后在Android Studio里重新指定SDK路径。这个操作也不复杂解压到目录后打开SDK Manager点击“Edit”把路径指向解压目录即可。2.3 设置中文界面让新手不迷路热搜里“android studio怎么设置中文”这个搜索量一直很高确实对一个刚开始接触开发的同学来说满屏英文容易让人没有安全感。Android Studio其实自带中文语言包的插件不需要去网上找汉化包覆盖文件。具体操作打开File SettingsmacOS是Android Studio Preferences。左侧选Plugins搜索Chinese (Simplified) Language Pack。安装后重启IDE界面就变成中文了。这个插件是JetBrains官方做的可以放心用。不过我个人的建议是界面用中文没问题但是代码、类名、报错信息尽量还是学着看英文。因为开发中的大多数报错提示、官方文档、Stack Overflow问答都是英文全中文界面只是降低了门槛真正的能力成长还是要面向英文资料。另外汉化插件在个别新版本里可能滞后升级IDE之后要记得同步更新插件。2.4 模拟器与真机我推荐双轨并行开发阶段我建议真机和模拟器都准备着。模拟器的优势是方便截图、环境干净真机的优势是传感器、真实性能、真实屏幕适配都更准确而且现在的Android Studio模拟器启动速度和内存占用一直在优化启动一个Pixel设备的镜像很快调试日常逻辑完全够用。创建模拟器的路径是Tools Device Manager点加号创建新设备。选机型建议选Pixel系列内存、屏幕尺寸都比较经典。系统镜像选和你的targetSdk接近的版本比如你项目里API定在34或35镜像就选Android 14或15。下载系统镜像这一步容易卡住和前面SDK下载一样网络问题居多耐心等待或者换网络环境。3. 从新项目到一个能跑的笔记骨架3.1 新建项目的模板与参数选择在Android Studio欢迎页点击“New Project”左侧模板列表选“Empty Views Activity”注意不是“Empty Activity”就是“Empty Views Activity”新版IDE用后者。这个模板会替你创建一个Activity、一个布局文件和一个基础的Gradle构建脚本。接下来有几个关键的配置项Name项目名比如NoteApp。Package name包名比如com.example.noteapp这是应用的唯一标识后面上架用。Save location保存路径建议单独建一个项目目录。Language选Kotlin。Minimum SDK选API 24或者API 26。解释一下API 24是Android 7.0覆盖了绝大多数存量设备API 26是Android 8.0。如果你的目标只是学习和自用API 24起步足够。这里说一个容易犯迷糊的点Minimum SDK和Target SDK的区别。Minimum是“最低能跑起来的版本”Target是“针对哪个版本优化过”。它们不是同一个值新手容易混淆看到compileSdk、targetSdk、minSdk三个值就蒙了。可以这样理解compileSdk编写代码时使用的API版本决定你能调用哪些新接口。minSdk应用安装的最低系统版本。targetSdk系统用这个值判断“是否要为兼容旧行为而给你开后门”。3.2 项目目录结构速览新项目生成后你会看到左侧多了一棵目录树先搞清楚每个文件夹是干什么的MainActivity.kt程序入口继承AppCompatActivity相当于应用的“主窗口”。activity_main.xmlMainActivity对应的布局文件界面长什么样由它决定。AndroidManifest.xml应用的“身份证”声明了有哪些Activity、有哪些权限、应用图标和名称。build.gradle项目级和模块级依赖管理和构建配置加第三方库就在模块级这个文件里操作。res目录资源文件包括布局layout、图片drawable、字符串values等。初学者最容易犯的错是“不知道改了哪一行导致报错”然后慌里慌张把文件改乱。我的习惯是每改一个重要文件之前先按一次Ctrl Z记住状态或者用Git提交一次。后面如果出了乱七八糟的问题回滚成本几乎为零。3.3 手写第一个界面输入框 保存按钮模板自带的界面是一个“Hello World”的TextView。我们先关掉自动生成的内容打开activity_main.xml切到代码模式写一个最简单的输入界面LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical android:padding16dp EditText android:idid/etNote android:layout_widthmatch_parent android:layout_heightmatch_parent android:hint写点什么... android:gravitytop / Button android:idid/btnSave android:layout_widthmatch_parent android:layout_heightwrap_content android:text保存笔记 / /LinearLayout对应地在MainActivity里做一个最简单的“临时保存”先不碰数据库验证事件链路通不通class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val etNote findViewByIdEditText(R.id.etNote) val btnSave findViewByIdButton(R.id.btnSave) btnSave.setOnClickListener { val content etNote.text.toString() if (content.isNotEmpty()) { Toast.makeText(this, 已保存$content, Toast.LENGTH_SHORT).show() etNote.setText() } } } }这一步跑通的意义是什么它验证了“布局文件到Activity的绑定、按钮点击事件的触发、文本的读取与反馈”这一整条链路。很多教程一上来就写一大坨数据库代码结果运行时报错根本定位不了是界面问题还是数据问题。我建议新手一定要先走通这个最小闭环再往后拆数据库和列表。4. 核心功能实现把笔记真正存起来4.1 Room数据库的三件套Entity、Dao、Database现在进入重头戏让笔记真正持久化。我们选用的Room框架本质上是在SQLite上面做了一层“注解驱动的封装”。要用它得先理解三个角色Entity表结构的映射。一个Entity类对应一张表类的字段对应表的列。DaoData Access Object数据访问接口你在这里写增删改查的方法Room会根据注解生成实现。Database数据库本身负责管理表和DAO实例一般用单例模式提供。为什么用Room而不用SQLiteOpenHelper因为手写SQLite时建表、升级、Cursor遍历、关闭连接这些代码非常多且容易出错。Room把模板化的部分全削掉了你只要专注写SQL和业务方法就行。当然它也有学习成本比如注解的使用、版本升级时要做Migration这些后面会提到。4.2 添加Room依赖与实体类实现打开模块级的build.gradle.kts在dependencies里添加implementation(androidx.room:room-runtime:2.6.1) ksp(androidx.room:room-compiler:2.6.1)这里有个版本点要注意老项目用的是kapt新项目推荐ksp。如果你看到网上教程还在写kapt androidx.room:room-compiler:2.x那是老配置新的Kotlin项目用KSP速度更快。如果启用了KSP记得在插件列表里加id(com.google.devtools.ksp) version 版本号。接着定义一个Note实体Entity(tableName notes) data class Note( PrimaryKey(autoGenerate true) val id: Long 0, ColumnInfo(name content) val content: String, ColumnInfo(name timestamp) val timestamp: Long System.currentTimeMillis() )PrimaryKey(autoGenerate true)表示主键自增每次插入都会生成一个递增的id。timestamp用来记录创建时间后面列表排序可以用它。数据类用data class在Kotlin里会帮我们自动重写equals、hashCode、toString这对后面列表刷新对比很有帮助。4.3 DAO接口与数据库单例DAO里面定义好我们的操作Dao interface NoteDao { Insert suspend fun insert(note: Note) Update suspend fun update(note: Note) Delete suspend fun delete(note: Note) Query(SELECT * FROM notes ORDER BY timestamp DESC) fun getAllNotes(): LiveDataListNote }这里有两个设计值得展开说明。第一为什么insert/update/delete用suspend因为数据库读写属于IO操作放在主线程会卡界面Room也不允许在主线程执行写操作。Kotlin协程的suspend修饰符解决了这个问题你可以在Activity里直接用lifecycleScope.launch调用。第二为什么查询用LiveDataLiveData是Android官方提供的“可观察数据容器”当数据库里的内容变化时它会自动通知界面刷新。也就是说你不需要手动调用“重新查询数据库”只要数据表有变动界面就会跟着变。这就是“响应式编程”的思想新手一开始理解不了没关系先用起来慢慢就体会到了。数据库类这样写Database(entities [Note::class], version 1) abstract class AppDatabase : RoomDatabase() { abstract fun noteDao(): NoteDao companion object { Volatile private var INSTANCE: AppDatabase? null fun getInstance(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, note_db ).build() INSTANCE instance instance } } } }这段代码里比较不容易懂的是Volatile和synchronized(this)。简单说这是为了保证多线程环境下数据库实例不会被重复创建——数据库只能有一个实例否则会报Room cannot create more than one database instance的错误。新手不用纠结太深先按这个模板用就行。4.4 RecyclerView列表与Adapter适配器列表界面是Android开发中太常见的一环了。布局文件里加一个RecyclerView然后写一个item布局用来表示单条笔记的外观。item布局就是一个简单的CardView或者LinearLayout里面放一个TextView显示内容另一个小TextView显示时间。androidx.cardview.widget.CardView xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_margin8dp LinearLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationvertical android:padding16dp TextView android:idid/tvContent android:layout_widthmatch_parent android:layout_heightwrap_content android:textSize16sp / TextView android:idid/tvTime android:layout_widthmatch_parent android:layout_heightwrap_content android:textSize12sp android:textColor#888888 / /LinearLayout /androidx.cardview.widget.CardViewAdapter是RecyclerView和数据源之间的“翻译官”。它负责把列表里的每一条数据“翻译”成一个item视图class NoteAdapter( private val onClick: (Note) - Unit, private val onLongClick: (Note) - Unit ) : RecyclerView.AdapterNoteAdapter.NoteViewHolder() { private val notes mutableListOfNote() fun submitList(newNotes: ListNote) { notes.clear() notes.addAll(newNotes) notifyDataSetChanged() } class NoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val tvContent: TextView itemView.findViewById(R.id.tvContent) val tvTime: TextView itemView.findViewById(R.id.tvTime) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder { val view LayoutInflater.from(parent.context) .inflate(R.layout.item_note, parent, false) return NoteViewHolder(view) } override fun onBindViewHolder(holder: NoteViewHolder, position: Int) { val note notes[position] holder.tvContent.text note.content holder.tvTime.text SimpleDateFormat(yyyy-MM-dd HH:mm, Locale.getDefault()) .format(Date(note.timestamp)) holder.itemView.setOnClickListener { onClick(note) } holder.itemView.setOnLongClickListener { onLongClick(note) true } } override fun getItemCount() notes.size }submitList是我自己封装的一个方法用来整体替换列表数据。这里强调一个小细节千万不要在Adapter外部对notes这个列表做add/remove操作因为Adapter持有的是同一个对象你改了它却不通知刷新界面就不会更新这种bug很难查。统一走submitList就安全了。4.5 主页面逻辑从数据库到界面的完整闭环现在串起来。MainActivity要做的事初始化RecyclerView和Adapter。开启一个协程查询数据库所有笔记拿到LiveData。当LiveData数据变化时调adapter.submitList(newList)刷新。“保存笔记”按钮点击后把EditText内容插入数据库。长按列表项弹出确认删除对话框。核心代码框架如下class MainActivity : AppCompatActivity() { private lateinit var noteDao: NoteDao private lateinit var adapter: NoteAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) noteDao AppDatabase.getInstance(this).noteDao() val etNote findViewByIdEditText(R.id.etNote) val btnSave findViewByIdButton(R.id.btnSave) val recyclerView findViewByIdRecyclerView(R.id.recyclerView) adapter NoteAdapter( onClick { note - editNote(note) }, onLongClick { note - deleteNote(note) } ) recyclerView.layoutManager LinearLayoutManager(this) recyclerView.adapter adapter noteDao.getAllNotes().observe(this) { notes - adapter.submitList(notes) } btnSave.setOnClickListener { val content etNote.text.toString().trim() if (content.isNotEmpty()) { lifecycleScope.launch { noteDao.insert(Note(content content)) etNote.setText() } } } } }这里我最想强调的一个坑也是在热搜里出现过的词条“Android Studio onBackPressed无效”。如果你用旧版本的模板重写onBackPressed是可以拦截返回键的但新版targetSdk到API 33以上后onBackPressed已经被标记为废弃。我在笔记APP里写“编辑页返回时自动保存”这个功能时最开始就踩了这个坑——从编辑页按返回键日志打印没问题但界面没有反应返回不了。正确做法是使用OnBackPressedCallbackonBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { // 在这里写你的返回逻辑 finish() } })记住新代码不要再重写onBackPressed了用OnBackPressedDispatcher。4.6 新增编辑页两个Activity互相传值笔记APP一般需要两个界面一个是列表主页一个是新增/编辑页。第二个界面可以叫EditNoteActivity通过Intent把笔记id传过去如果是新增就传-1编辑就传已有笔记的id。跳转代码很简单private fun editNote(note: Note) { val intent Intent(this, EditNoteActivity::class.java) intent.putExtra(note_id, note.id) startActivity(intent) }EditNoteActivity里根据id决定是“更新”还是“插入”lifecycleScope.launch { if (noteId -1L) { noteDao.insert(Note(content content)) } else { val note noteDao.getNoteById(noteId) noteDao.update(note.copy(content content)) } finish() }这里用到copy是Kotlin data class自带的浅拷贝方法可以基于原对象复制一个新对象只改动指定的字段非常方便。Room也要求更新操作传入的是带id的完整对象copy正好满足。5. 调试、测试与打包上线流程5.1 Logcat与断点调试的实用技巧开发调试是新手最容易忽略的事很多人写完代码直接点运行看到界面报错才手忙脚乱。我习惯的方式是“运行前先想过一遍”这个功能跑起来以后哪些数据是关键的打印出来看并注意Logcat里红色的Error条目。Logcat右侧可以选过滤级别Debug级别会输出所有信息污染很大。我更常用tag过滤打印时加一个标签比如Log.d(NoteDebug, insert result: $note)然后在Logcat搜索框输入NoteDebug就能只看自己的日志。这个操作看似简单但在复杂的项目中能帮你少翻一堆毫无头绪的系统日志。断点调试也很重要。在行号左边点击一下会出现红点然后点运行按钮旁边的“Debug App”图标代码运行到这一行就会停住。新手可以在onBindViewHolder里打个断点看看每次列表刷新时数据长什么样比单纯靠想象理解RecyclerView的复用机制要直观得多。5.2 真机调试与USB连接问题排查真机调试是迟早要过的一关。步骤是手机打开“开发者选项”——在“设置 关于手机”里连续点击“版本号”7次然后进开发者选项打开“USB调试”用数据线连接电脑。如果ADB识别不到手机大概率是驱动问题Windows下到手机官网下载USB驱动装上或者在开发者选项里切换“USB配置”为“RNDISUSB以太网”再切回。一个排查经验如果插上数据线后Android Studio顶部一直显示“No device”先检查手机端是否弹出了“允许USB调试”的授权弹窗有时候弹窗被手机系统拦截了需要手动去通知栏点开。还有尽量用原装数据线很多第三方数据线只支持充电不支持数据传输。5.3 打包签名把APK变成能分享的安装包本地调试没问题后可以打包一个APK分享给朋友安装。从菜单栏选择Build Generate Signed App Bundle or APK选择APK然后需要创建一个签名文件KeyStore。签名是什么概念上可以理解成“应用的身份印章”。Android系统要求所有安装的APP必须被签名目的是保证同一个应用在不同设备上可以被识别为“同一个开发者发布的版本”。没有签名的APK也能安装但每次签名不一致就会覆盖更新失败。创建签名时填的信息别乱写记住两项就够Key store password签名文件密码Key alias别名这两项如果忘了签名文件就废了。很多人会到上架前才发现签名丢了只能换包名全盘重来那叫一个难受。我的做法是无脑备份一份到云盘密码用密码管理器记住。5.4 发布前的检查清单如果你打算把APP发到应用市场要学会对着检查清单过自己项目检查项要求应用名称与图标名称不侵权图标尺寸符合市场要求版本号versionCode每次更新要递增versionName是展示给用户的版本号权限声明尽量少申请权限这款笔记APP不需要任何敏感权限兼容性测试至少覆盖Android 8.0和Android 14两台设备崩溃日志正式版建议接入日志上报比如Firebase Crashlytics隐私政策涉及用户数据的APP必须提供隐私政策页面签名证书保存好keystore文件换电脑也能找到这每一项展开都是一篇独立的文章我不逐条展开。但对新手来说真正要做的是先把“本地能跑、打完包能装”这个基本流程走通上架是后续更远的阶段。6. 高频问题排查与经验沉淀6.1 Gradle同步失败先别慌刚接触Android Studio的新手打开一个项目就会被Gradle同步吓到。“Gradle sync failed”这个报错出现频率极高通常原因就三种网络问题Android 构建工具要下载大量依赖国内网络直连可能很慢。解决办法是给Gradle配置国内镜像源比如在settings.gradle.kts里把仓库地址换成阿里云的镜像。代理冲突如果你本机配置了系统代理Gradle默认不读取也可能导致下载失败。在gradle.properties里加systemProp.http.proxyHost等配置或者直接检查代理是否可用。版本不匹配Gradle版本、AGP版本、JDK版本三者需要互相兼容。AS里报错一般会明确指出“需要哪个版本”照着改就行。我自己最常用的排查命令是在终端里执行./gradlew --stacktrace assembleDebug终端输出的错误信息比IDE弹窗详细得多“看原始报错”比反复刷新Gradle有用一百倍。6.2 中文乱码与界面错位问题中文乱码这个问题在Android Studio的XML布局里几乎不会出现因为res下的string.xml默认是UTF-8编码。真要说乱码大概率发生在手动新建的txt文件读取时。比如你在保存笔记功能里让用户导出到文本文件读取时要用指定的字符集val content File(path).readText(Charsets.UTF_8)还有一种“假乱码”把用户输入的内容存到数据库再读出来显示成了问号。这种一般是编码转换问题统一入口、统一用UTF-8就能解决。界面错位则是另一种经典问题。使用ConstraintLayout时初学者经常遇到“明明定义了约束但控件还是跑到左上角”的情况通常是因为漏了layout_constraintTop_toTopOfparent这类约束声明。我的排查套路很简单在预览窗口里选中组件右侧的Constraint Widget会高亮显示已有约束缺哪条补哪条就行。6.3 数据不刷新与LiveData观察失效有同学遇到一个问题往数据库插入数据后RecyclerView列表没有自动更新。原因往往是observe这行代码没有执行或者DAO返回的LiveData关联的Activity生命周期已经结束。排查顺序是数据库插入有没有成功在insert后打印日志。LiveData有没有执行到onChanged在回调里再打印一条日志。Adapter的submitList有没有被调用列表数据源到底变没变这种三层打印法能快速定位问题出在哪一层是写数据、传数据还是展示数据。很多时候问题都不出在数据库本身而是你忘了把LiveData的观察注册到正确的生命周期Owner上——observe(this)里的this存活期一定不能短于Activity。6.4 项目移植换电脑后打开项目报错的快速处理热搜里“移植android studio项目”也是一个高频需求。我把自己的项目从一个电脑搬到另一个电脑或者把同学的模板项目拷过来跑最常见的问题是Gradle和SDK路径变了。换电脑后打开别人的项目直接报“SDK location not found”是很正常的。因为项目里记录的SDK路径是对方电脑的绝对路径你的笔记本上当然找不到。解决办法是在项目根目录的local.properties文件里把sdk.dir改成你电脑上SDK的实际路径或者直接删掉local.properties让Android Studio自动生成。另外从别人那里拷贝项目时不要拷贝build文件夹和.gradle文件夹这两个都是本机构建产物拷贝过去除了占空间没有用处有时还会导致奇怪的缓存冲突。拷项目就拷源码目录到新电脑上让它自己重新构建一遍问题最少。6.5 几个让我少加班的工作习惯最后分享几个我在多个项目里沉淀下来的习惯不一定多高深但每一条都帮我实打实省过时间写一行代码就要“想清楚下一秒会发生什么”。很多人写完代码不马上编译而是攒了一堆再跑结果报错都不知道是哪个环节引起的。小步快跑每次改动都能跑通问题定位成本最低。学会阅读崩溃堆栈。报错信息一长串新手最容易吓得不行。其实发生异常时Android Studio会直接跳转到抛异常的源码行你只需要往上翻找到“Caused by”那一行那才是根因。红色字里有一半是无意义的框架内部信息别被带着跑。数据库版本升级永远早做规划。项目刚开始就预留好version 1后面如果加了字段要记得写Migration不然用户升级时会直接崩。对于学习项目来说如果数据库结构改了可以直接卸载APP重装但这只是权宜之计上了生产环境可不能这么干。KeyStore、签名密码、平台账号这类东西务必多重备份。这不是危言耸听我身边就有同行因为签名文件丢了整个应用无法更新最后只能换包名重新上架的惨痛案例。善用“搜索引擎 Stack Overflow”的啃错模式。遇到报错把英文报错原文复制到搜索引擎比翻译成中文再搜索效率高数倍。英文报错的关键词往往直接对应社区里的已知问题帖翻译以后再搜反而对不上号。回到这篇博文最初的问题——“从哪开始写一个自己的APP”如果你现在正盯着Android Studio的欢迎页发呆不妨就照着这个笔记APP的路子走一遍先把界面搭出来再把数据存进去最后打包分享给朋友。完成这个闭环以后你对Activity、布局、数据库、适配器、Gradle这一整套东西都会有一个比看十篇文章都透彻的理解。后面再想加个待办事项功能、加个富文本编辑器、加个云同步你知道该往模块里加什么代码也知道该去搜索什么关键词了。我个人在实际操作中还有一个很有意思的体会同一个笔记APP三年里我重写过三次。第一次用Java SQLite第二次用Kotlin Room第三次加了ViewModel和协程。每一次重写我都能明显感觉到自己对“数据从哪里来、到哪里去”的认识要深入一层。所以如果你也是刚开始走这条路请一定亲手敲完每一行代码而不是复制粘贴就以为完事了。代码这种东西真正进了脑子才算你的。