開發(fā)實(shí)戰(zhàn):MVVM架構(gòu)與Room數(shù)據(jù)庫應(yīng)用)
簡介本資源是一套完整的Android應(yīng)用開發(fā)實(shí)戰(zhàn)項(xiàng)目源碼面向計(jì)算機(jī)專業(yè)本科生、移動(dòng)開發(fā)初學(xué)者及課程設(shè)計(jì)實(shí)踐者聚焦日常生活場景中的智能衣櫥管理問題提供從天氣感知、衣物分類存儲(chǔ)到個(gè)性化推薦的端到端解決方案。壓縮包共99個(gè)文件含23個(gè)Java業(yè)務(wù)邏輯與Activity類、32個(gè)XML布局與資源定義文件、18個(gè)PNG圖標(biāo)與界面素材以及Gradle構(gòu)建配置、Git版本控制文件等結(jié)構(gòu)規(guī)范模塊清晰便于理解MVC架構(gòu)與Android組件通信機(jī)制。資源包大小為933KB輕量易導(dǎo)入適合作為Android Studio實(shí)訓(xùn)項(xiàng)目快速上手。已有66人下載學(xué)習(xí)源碼包含完整的天氣接口集成、用戶多賬戶管理、衣物圖像存儲(chǔ)與標(biāo)簽分類、基于季節(jié)/價(jià)格/風(fēng)格的新衣推送等核心功能模塊配套R(shí)EADME.md與多張界面截圖可直接編譯運(yùn)行并二次拓展。1. 項(xiàng)目概述與核心價(jià)值最近在整理個(gè)人項(xiàng)目倉庫時(shí)翻出了一個(gè)幾年前做的“智能衣櫥管理系統(tǒng)”的Android應(yīng)用源碼。這個(gè)項(xiàng)目雖然不算復(fù)雜但麻雀雖小五臟俱全完整地走通了從需求分析、UI設(shè)計(jì)、數(shù)據(jù)庫建模到業(yè)務(wù)邏輯實(shí)現(xiàn)的移動(dòng)端開發(fā)全鏈路。對(duì)于想從“Hello World”邁向“獨(dú)立項(xiàng)目”的Android開發(fā)者尤其是學(xué)生朋友這個(gè)項(xiàng)目提供了一個(gè)非常不錯(cuò)的練手模板。它不涉及復(fù)雜的網(wǎng)絡(luò)通信或高并發(fā)核心聚焦于本地?cái)?shù)據(jù)的管理、分類與可視化能讓你扎實(shí)地掌握Activity/Fragment、SQLite數(shù)據(jù)庫、RecyclerView適配器、圖片處理等Android開發(fā)的核心基本功。所謂“智能衣櫥”其核心訴求是解決我們?nèi)粘4┐钪械膸讉€(gè)痛點(diǎn)衣服太多記不住有什么、搭配靈感稍縱即逝、季節(jié)更替時(shí)整理費(fèi)時(shí)費(fèi)力。這個(gè)系統(tǒng)就是試圖用數(shù)字化的方式將你衣櫥里的每一件衣物都錄入手機(jī)通過標(biāo)簽化管理和可視化搭配幫你快速找到今天想穿什么或者規(guī)劃出一周的穿搭方案。從技術(shù)實(shí)現(xiàn)上看它本質(zhì)上是一個(gè)針對(duì)“衣物”這個(gè)特定實(shí)體的增刪改查CRUD應(yīng)用但其中涉及的圖片存儲(chǔ)、多條件篩選、自定義分類等細(xì)節(jié)恰恰是考驗(yàn)開發(fā)者設(shè)計(jì)能力和代碼功底的地方。2. 項(xiàng)目整體架構(gòu)與技術(shù)選型解析2.1 技術(shù)棧與開發(fā)環(huán)境搭建這個(gè)項(xiàng)目采用經(jīng)典的Android原生開發(fā)技術(shù)棧。核心開發(fā)工具是Android Studio版本建議在Arctic Fox以上以確保對(duì)較新的Jetpack組件有良好支持。語言自然是Kotlin相比Java其更簡潔的語法和空安全特性能讓業(yè)務(wù)邏輯代碼更健壯、更易讀。項(xiàng)目構(gòu)建工具是Gradle采用Kotlin DSLbuild.gradle.kts進(jìn)行依賴管理是當(dāng)下的主流選擇配置起來更靈活。在架構(gòu)模式上項(xiàng)目采用了MVVMModel-View-ViewModel。這是Google官方推薦的應(yīng)用架構(gòu)能有效將UI邏輯與業(yè)務(wù)邏輯分離提高代碼的可測試性和可維護(hù)性。具體來說Model層由實(shí)體類Entity和倉庫類Repository組成。實(shí)體類定義衣物、分類等數(shù)據(jù)結(jié)構(gòu)倉庫類作為單一數(shù)據(jù)源協(xié)調(diào)本地?cái)?shù)據(jù)庫Room和可能的其他數(shù)據(jù)源雖然本項(xiàng)目目前只有本地。ViewModel層為UI準(zhǔn)備數(shù)據(jù)并處理用戶交互觸發(fā)的業(yè)務(wù)邏輯。它持有LiveData或StateFlow確保數(shù)據(jù)變化能自動(dòng)通知到View層。View層由Activity和Fragment構(gòu)成負(fù)責(zé)繪制UI和接收用戶輸入并通過Data Binding或View Binding與ViewModel交互。數(shù)據(jù)庫方面選擇了Room Persistence Library。它是SQLite的抽象層提供了編譯時(shí)SQL校驗(yàn)、方便的ORM對(duì)象關(guān)系映射支持和與LiveData/Flow的原生集成極大地簡化了數(shù)據(jù)庫操作。對(duì)于衣物管理這種結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)場景Room是不二之選。2.2 核心功能模塊設(shè)計(jì)在動(dòng)手寫代碼之前對(duì)功能模塊進(jìn)行清晰劃分至關(guān)重要。本系統(tǒng)主要分為四大模塊衣物管理模塊這是系統(tǒng)的核心。功能包括添加新衣物錄入名稱、品牌、類別、季節(jié)、顏色、材質(zhì)、購買日期、價(jià)格、圖片等、編輯衣物信息、刪除衣物以及查看衣物詳情。難點(diǎn)在于如何設(shè)計(jì)一個(gè)既全面又用戶友好的表單以及如何處理用戶上傳的衣物圖片。衣櫥瀏覽與篩選模塊當(dāng)衣物數(shù)量積累到幾十上百件時(shí)快速定位成為關(guān)鍵。此模塊需要提供多種瀏覽視圖如網(wǎng)格圖、列表和強(qiáng)大的篩選功能。用戶應(yīng)能根據(jù)類別上衣、褲子、外套、季節(jié)春、夏、秋、冬、顏色、甚至最近穿著頻率等條件組合篩選快速找到目標(biāo)衣物。穿搭搭配與收藏模塊這是體現(xiàn)“智能”的地方。用戶可以手動(dòng)創(chuàng)建穿搭方案將多件衣物組合在一起并添加描述、適用場景通勤、約會(huì)、運(yùn)動(dòng)等。創(chuàng)建的搭配可以被收藏方便日后直接選用。進(jìn)階一點(diǎn)的想法還可以引入簡單的推薦算法基于顏色搭配規(guī)則或歷史選擇記錄進(jìn)行智能推薦。數(shù)據(jù)統(tǒng)計(jì)與洞察模塊可視化你的衣櫥構(gòu)成。通過餅圖展示衣物類別的分布通過柱狀圖顯示各季節(jié)衣物的數(shù)量甚至統(tǒng)計(jì)你最常穿的顏色和單品。這些數(shù)據(jù)能幫助你更理性地進(jìn)行購物決策避免重復(fù)購買。2.3 數(shù)據(jù)庫表結(jié)構(gòu)設(shè)計(jì)詳解數(shù)據(jù)庫設(shè)計(jì)是項(xiàng)目的基石設(shè)計(jì)的好壞直接影響到后續(xù)開發(fā)的復(fù)雜度和應(yīng)用性能。我們主要設(shè)計(jì)了三張核心表1. 衣物表 (garment_table)這是最主要的一張表存儲(chǔ)每件衣物的所有信息。CREATE TABLE garment_table ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- 主鍵自增 name TEXT NOT NULL, -- 衣物名稱如“藍(lán)色條紋襯衫” category_id INTEGER NOT NULL, -- 外鍵關(guān)聯(lián)分類表 season TEXT, -- 季節(jié)可用枚舉或字符串如“SPRING_AUTUMN” color TEXT, -- 顏色存儲(chǔ)RGB值或顏色名稱 brand TEXT, -- 品牌 material TEXT, -- 材質(zhì)如“棉”、“羊毛” purchase_date INTEGER, -- 購買日期存儲(chǔ)時(shí)間戳 price REAL, -- 價(jià)格 image_uri TEXT, -- 衣物圖片在本地存儲(chǔ)的URI路徑 last_worn_date INTEGER, -- 最后穿著日期用于智能推薦 times_worn INTEGER DEFAULT 0, -- 穿著次數(shù) created_time INTEGER NOT NULL, -- 創(chuàng)建時(shí)間 FOREIGN KEY (category_id) REFERENCES category_table (id) );注意image_uri字段存儲(chǔ)的是圖片的URIUniform Resource Identifier。絕對(duì)不要直接存儲(chǔ)Bitmap對(duì)象到數(shù)據(jù)庫這會(huì)導(dǎo)致數(shù)據(jù)庫膨脹且效率低下。正確的做法是將圖片保存到應(yīng)用的內(nèi)部或外部私有存儲(chǔ)空間然后將文件路徑或ContentProvider的URI存入數(shù)據(jù)庫。2. 分類表 (category_table)用于管理衣物的分類支持用戶自定義實(shí)現(xiàn)靈活的類別管理。CREATE TABLE category_table ( id INTEGER PRIMARY KEY AUTOINCREMENT, category_name TEXT NOT NULL UNIQUE, -- 分類名稱唯一 icon_resource TEXT -- 分類圖標(biāo)資源名或本地路徑 );預(yù)置一些常用分類如“上衣”、“下裝”、“外套”、“鞋履”、“配飾”等。3. 穿搭搭配表 (outfit_table)存儲(chǔ)用戶創(chuàng)建的穿搭方案。CREATE TABLE outfit_table ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, -- 搭配名稱如“周一通勤裝” description TEXT, -- 描述 scenario TEXT, -- 適用場景 created_time INTEGER NOT NULL, is_favorite INTEGER DEFAULT 0 -- 是否收藏0否1是 );4. 穿搭-衣物關(guān)聯(lián)表 (outfit_garment_relation_table)這是一個(gè)多對(duì)多的關(guān)聯(lián)表因?yàn)橐粋€(gè)穿搭包含多件衣物一件衣物也可以屬于多個(gè)穿搭。CREATE TABLE outfit_garment_relation_table ( outfit_id INTEGER NOT NULL, garment_id INTEGER NOT NULL, PRIMARY KEY (outfit_id, garment_id), FOREIGN KEY (outfit_id) REFERENCES outfit_table (id) ON DELETE CASCADE, FOREIGN KEY (garment_id) REFERENCES garment_table (id) ON DELETE CASCADE );使用ON DELETE CASCADE外鍵約束當(dāng)刪除一個(gè)穿搭或一件衣物時(shí)關(guān)聯(lián)表中的對(duì)應(yīng)記錄會(huì)自動(dòng)刪除保持?jǐn)?shù)據(jù)一致性。3. 核心功能實(shí)現(xiàn)與關(guān)鍵技術(shù)點(diǎn)3.1 衣物增刪改查與圖片處理實(shí)戰(zhàn)1. 添加/編輯衣物界面實(shí)現(xiàn)這是一個(gè)典型的表單頁面。使用ConstraintLayout或MaterialComponents中的TextInputLayout來構(gòu)建表單項(xiàng)確保界面美觀且符合Material Design規(guī)范。對(duì)于“顏色”選擇可以集成開源的顏色選擇器庫如com.github.duanhong169:colorpicker讓用戶通過取色板或輸入HEX值來選擇最終將選中的顏色值如#FF6F61存入數(shù)據(jù)庫。圖片處理是本模塊的重中之重也是容易踩坑的地方。標(biāo)準(zhǔn)流程如下觸發(fā)選擇提供按鈕點(diǎn)擊后調(diào)用Intent(Intent.ACTION_PICK)選擇系統(tǒng)圖庫或Intent(Intent.ACTION_GET_CONTENT)選擇文件。處理返回結(jié)果在onActivityResult或使用Activity Result API中獲取返回的URI。解析與壓縮通過ContentResolver打開URI獲取輸入流。務(wù)必進(jìn)行壓縮原圖可能幾MB甚至十幾MB直接存儲(chǔ)和顯示是不可接受的。可以使用BitmapFactory.decodeStream配合BitmapFactory.Options的inSampleSize進(jìn)行采樣壓縮或者使用Compressor等第三方庫進(jìn)行更高效的壓縮。// 示例計(jì)算合適的采樣率 fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { val (height, width) options.run { outHeight to outWidth } var inSampleSize 1 if (height reqHeight || width reqWidth) { val halfHeight height / 2 val halfWidth width / 2 while ((halfHeight / inSampleSize) reqHeight (halfWidth / inSampleSize) reqWidth) { inSampleSize * 2 } } return inSampleSize }保存到私有存儲(chǔ)將壓縮后的Bitmap保存到應(yīng)用內(nèi)部存儲(chǔ)的私有目錄context.filesDir或context.cacheDir或外部存儲(chǔ)的私有目錄context.getExternalFilesDir()。使用時(shí)間戳或UUID生成唯一文件名避免沖突。val outputFile File(context.externalCacheDir, ${System.currentTimeMillis()}.jpg) bitmap.compress(Bitmap.CompressFormat.JPEG, 85, FileOutputStream(outputFile)) val savedUri Uri.fromFile(outputFile) // 注意Android Q及以上版本需使用MediaStore重要提示從Android 10 (API 29) 開始作用域存儲(chǔ)Scoped Storage被強(qiáng)制執(zhí)行。對(duì)于應(yīng)用私有文件上述方法在私有目錄下仍可用。但如果想保存到公共目錄如Pictures供其他應(yīng)用訪問則必須使用MediaStoreAPI。本項(xiàng)目為簡化建議將所有圖片保存在應(yīng)用私有目錄。存儲(chǔ)URI將最終保存好的圖片文件URI字符串形式存入數(shù)據(jù)庫的image_uri字段。2. 使用Room實(shí)現(xiàn)數(shù)據(jù)持久化首先定義衣物實(shí)體類使用Room的注解。Entity(tableName garment_table) data class Garment( PrimaryKey(autoGenerate true) val id: Long 0, val name: String, ColumnInfo(name category_id) val categoryId: Long, val season: String?, val color: String?, // ... 其他字段 ColumnInfo(name image_uri) val imageUri: String? )接著創(chuàng)建Data Access Object (DAO) 接口定義查詢方法。Room支持編譯時(shí)檢查SQL語句非常安全。Dao interface GarmentDao { Insert(onConflict OnConflictStrategy.REPLACE) suspend fun insert(garment: Garment): Long Update suspend fun update(garment: Garment) Delete suspend fun delete(garment: Garment) Query(SELECT * FROM garment_table ORDER BY created_time DESC) fun getAllGarments(): FlowListGarment // 返回Flow便于在ViewModel中觀察 Query(SELECT * FROM garment_table WHERE category_id :categoryId) fun getGarmentsByCategory(categoryId: Long): FlowListGarment // 復(fù)雜篩選查詢示例 Query( SELECT * FROM garment_table WHERE (:categoryId IS NULL OR category_id :categoryId) AND (:season IS NULL OR season :season) AND (:color IS NULL OR color LIKE % || :color || %) ORDER BY last_worn_date DESC ) fun filterGarments( categoryId: Long?, season: String?, color: String? ): FlowListGarment }最后創(chuàng)建AppDatabase抽象類并構(gòu)建數(shù)據(jù)庫實(shí)例。在ViewModel中通過Repository調(diào)用DAO的方法并將FlowListGarment轉(zhuǎn)換為LiveData或直接在Compose中收集驅(qū)動(dòng)UI更新。3.2 高效衣櫥瀏覽與多條件篩選當(dāng)衣物數(shù)據(jù)量增大時(shí)直接在RecyclerView中加載所有物品的圖片和詳情會(huì)非常卡頓。這里必須引入分頁加載機(jī)制。我們可以使用Jetpack Paging 3庫它提供了開箱即用的分頁解決方案能優(yōu)雅地處理大數(shù)據(jù)集。首先定義PagingSourceclass GarmentPagingSource( private val garmentDao: GarmentDao, private val filterParams: FilterParams // 封裝了篩選條件的類 ) : PagingSourceInt, Garment() { override suspend fun load(params: LoadParamsInt): LoadResultInt, Garment { return try { val page params.key ?: 0 // 從第0頁開始 val pageSize params.loadSize val offset page * pageSize // 根據(jù)filterParams構(gòu)建動(dòng)態(tài)查詢這里簡化實(shí)際需拼接SQL val garments garmentDao.loadGarmentsPage( categoryId filterParams.categoryId, season filterParams.season, offset offset, limit pageSize ) LoadResult.Page( data garments, prevKey if (page 0) null else page - 1, nextKey if (garments.size pageSize) null else page 1 ) } catch (e: Exception) { LoadResult.Error(e) } } }在Repository中創(chuàng)建Pager在ViewModel中暴露PagingData Flow最后在UI層Activity/Fragment或Compose中使用collectLatestAsLazyPagingItems()來收集并展示。這樣列表滑動(dòng)時(shí)才會(huì)按需加載數(shù)據(jù)內(nèi)存占用和流暢度都有保障。篩選功能的實(shí)現(xiàn)關(guān)鍵在于構(gòu)建動(dòng)態(tài)查詢。如上文DAO示例所示我們可以編寫一個(gè)支持可選參數(shù)的查詢語句。在UI層提供一個(gè)篩選對(duì)話框或側(cè)邊欄用戶設(shè)置好條件后ViewModel中更新filterParams并觸發(fā)PagingSource的刷新PagingSource.invalidate()列表就會(huì)自動(dòng)更新為篩選后的結(jié)果。3.3 穿搭搭配功能的實(shí)現(xiàn)思路創(chuàng)建穿搭的界面可以設(shè)計(jì)為一個(gè)可拖拽的視覺化畫板。上半部分是當(dāng)前已選擇的衣物縮略圖可拖拽排序下半部分是衣櫥的篩選列表。用戶可以從下方列表中將衣物拖拽到上方畫板完成搭配組合。技術(shù)實(shí)現(xiàn)上可以使用RecyclerView配合ItemTouchHelper來實(shí)現(xiàn)拖拽排序。當(dāng)用戶保存搭配時(shí)需要執(zhí)行兩個(gè)數(shù)據(jù)庫操作向outfit_table插入一條新記錄獲取生成的outfit_id。遍歷畫板中的衣物ID列表向outfit_garment_relation_table中插入多條關(guān)聯(lián)記錄outfit_id,garment_id。查詢一個(gè)穿搭的所有衣物時(shí)需要使用JOIN查詢Query( SELECT g.* FROM garment_table g INNER JOIN outfit_garment_relation_table r ON g.id r.garment_id WHERE r.outfit_id :outfitId ORDER BY r.order_index -- 可以增加一個(gè)排序字段 ) fun getGarmentsForOutfit(outfitId: Long): FlowListGarment3.4 數(shù)據(jù)可視化與統(tǒng)計(jì)使用開源圖表庫可以快速實(shí)現(xiàn)統(tǒng)計(jì)功能例如MPAndroidChart。在ViewModel中通過Repository從數(shù)據(jù)庫聚合數(shù)據(jù)類別分布SELECT category_id, COUNT(*) as count FROM garment_table GROUP BY category_id季節(jié)分布SELECT season, COUNT(*) as count FROM garment_table WHERE season IS NOT NULL GROUP BY season顏色分布需要將顏色值如HEX歸類到幾個(gè)主色系紅、藍(lán)、綠等再進(jìn)行統(tǒng)計(jì)。將查詢結(jié)果轉(zhuǎn)換為PieEntry或BarEntry列表傳遞給圖表視圖即可渲染。這部分邏輯相對(duì)獨(dú)立關(guān)鍵是數(shù)據(jù)聚合的SQL要寫對(duì)。4. 開發(fā)中的常見問題與性能優(yōu)化4.1 圖片相關(guān)的問題與優(yōu)化內(nèi)存溢出OOM這是處理圖片時(shí)最常見也最嚴(yán)重的問題。加載大圖或同時(shí)加載多張縮略圖時(shí)極易發(fā)生。解決方案壓縮后再加載如前所述在保存和顯示前務(wù)必進(jìn)行壓縮。使用圖片加載庫強(qiáng)烈推薦使用Glide或Coil。它們內(nèi)部實(shí)現(xiàn)了復(fù)雜的圖片緩存、內(nèi)存管理和生命周期綁定能自動(dòng)處理OOM問題。在RecyclerView的適配器中一行代碼就能安全加載圖片Glide.with(itemView).load(garment.imageUri).into(imageView)。配置RecyclerView的RecycledViewPool和setItemViewCacheSize合理控制離屏緩存的數(shù)量避免持有過多Bitmap引用。圖片URI失效或無法顯示可能因?yàn)閳D片文件被誤刪或者存儲(chǔ)權(quán)限變化導(dǎo)致路徑失效。解決方案始終使用應(yīng)用私有目錄存儲(chǔ)圖片。在刪除衣物記錄時(shí)同步刪除其對(duì)應(yīng)的圖片文件。可以使用File(uri.path).delete()嘗試刪除并做好異常捕獲。4.2 數(shù)據(jù)庫與列表性能優(yōu)化列表滑動(dòng)卡頓根本原因主線程執(zhí)行了耗時(shí)操作如解碼Bitmap、復(fù)雜計(jì)算或onBindViewHolder中邏輯太重。排查與解決使用Android Studio的Profiler工具特別是CPU和Memory Profiler監(jiān)控列表滑動(dòng)時(shí)的性能瓶頸。確保所有圖片加載都在后臺(tái)線程圖片庫已處理。在onBindViewHolder中避免創(chuàng)建新對(duì)象、進(jìn)行字符串拼接等操作。使用DiffUtil來更新RecyclerView的數(shù)據(jù)集而不是粗暴的notifyDataSetChanged()。DiffUtil會(huì)計(jì)算新舊數(shù)據(jù)集的差異只更新必要的Item效率極高。數(shù)據(jù)庫查詢慢為常用篩選字段建立索引例如如果經(jīng)常按category_id和season篩選可以建立復(fù)合索引。Entity(tableName garment_table, indices [Index(value [category_id, season])])避免SELECT *只查詢需要的字段特別是在關(guān)聯(lián)查詢時(shí)。將復(fù)雜查詢放在后臺(tái)線程Room的Query方法默認(rèn)就是suspend掛起函數(shù)會(huì)在IO線程執(zhí)行。4.3 其他實(shí)用技巧與避坑指南數(shù)據(jù)備份與恢復(fù)這是一個(gè)用戶非常關(guān)心但容易被開發(fā)者忽略的功能??梢岳肦oom數(shù)據(jù)庫文件.db本身結(jié)合Android的AutoBackup功能或?qū)?shù)據(jù)庫文件導(dǎo)出到用戶選擇的目錄需要MANAGE_EXTERNAL_STORAGE權(quán)限上架商店審核較嚴(yán)。更優(yōu)雅的方式是定期將數(shù)據(jù)庫內(nèi)容轉(zhuǎn)換為JSON文件備份到云端或本地。主題與深色模式適配從設(shè)計(jì)之初就考慮使用MaterialComponents主題并定義好light和night兩種顏色主題資源。對(duì)于自定義的視圖顏色和圖標(biāo)務(wù)必引用?attr/或color/資源而不是硬編碼。處理配置變更如屏幕旋轉(zhuǎn)ViewModel配合ViewModelProvider已經(jīng)可以很好地保存數(shù)據(jù)。但對(duì)于包含復(fù)雜狀態(tài)如正在編輯的表單的界面可能需要額外使用onSaveInstanceState或SavedStateHandle來保存臨時(shí)狀態(tài)。權(quán)限管理如果應(yīng)用需要訪問相冊(cè)選擇圖片在Android 13及以上需要在運(yùn)行時(shí)申請(qǐng)READ_MEDIA_IMAGES權(quán)限。務(wù)必遵循最小權(quán)限原則并在權(quán)限被拒絕時(shí)提供友好的引導(dǎo)說明。這個(gè)“智能衣櫥管理系統(tǒng)”項(xiàng)目雖然業(yè)務(wù)邏輯不復(fù)雜但幾乎涵蓋了Android本地應(yīng)用開發(fā)的所有核心知識(shí)點(diǎn)。把它做扎實(shí)、做完善不僅能讓你對(duì)Android開發(fā)有一個(gè)系統(tǒng)性的理解更能積累解決實(shí)際問題的經(jīng)驗(yàn)。在開發(fā)過程中多思考用戶體驗(yàn)多寫注釋多進(jìn)行性能測試你的收獲會(huì)遠(yuǎn)超項(xiàng)目本身。源碼中那些為了解決某個(gè)具體問題而寫的“小技巧”和“繞過的坑”才是最有價(jià)值的部分。本文還有配套的精品資源點(diǎn)擊獲取