設(shè)計技術(shù)選型全解析)
我評過不少 Android 方向的畢業(yè)設(shè)計發(fā)現(xiàn)一個很有意思的現(xiàn)象開題報告里寫“本系統(tǒng)使用 Java/Kotlin 語言開發(fā)”的學(xué)生很多但你要是追問一句“為什么選它”一半人會說“Java 我課設(shè)用過”另一半會說“Kotlin 現(xiàn)在比較火”。真正能把技術(shù)選型和畢設(shè)功能對應(yīng)起來的人其實沒幾個。如果你正在為“還在糾結(jié)用 Java 還是 Kotlin”這個問題翻來覆去睡不著我勸你先別急著收藏語法教程。因為選型這件事表面上是在選一種編程語言本質(zhì)上是在選你未來三個月的工作方式遇到空指針能不能少掉點頭發(fā)調(diào)藍牙、進度條、文件權(quán)限這些畢設(shè)高頻功能時到底順手不順手最后答辯的時候?qū)熗粨籼釂柲隳懿荒芙拥米?。這篇技術(shù)選型篇我就按自己做項目、帶畢設(shè)的實際經(jīng)驗把這筆賬給你算清楚。1. 選型之前先想清楚這三件事1.1 畢設(shè)的評價標準功能完整度大于語法時髦度先說一個很扎心的事實本科畢業(yè)設(shè)計的評分標準里語言本身占的權(quán)重低到可以忽略不計。老師更關(guān)心的是你有沒有完整的業(yè)務(wù)流程、數(shù)據(jù)庫設(shè)計合不合理、幾個核心功能能不能跑通、論文里有沒有像樣的系統(tǒng)結(jié)構(gòu)圖。所以你在 Java 和 Kotlin 之間糾結(jié)的時候真正該問的問題不是“哪個語言未來工資高”而是“哪個語言能讓我在有限時間把功能做完”。很多同學(xué)選了 Java不是因為 Java 更簡單而是因為課程設(shè)計、實驗課、甚至考研復(fù)試問的東西全是 Java你手上有一堆能抄的模板。如果畢業(yè)設(shè)計只有三個月熟悉的工具永遠比新潮的工具靠譜。但反過來說也有不少同學(xué)現(xiàn)在就在學(xué) Kotlin原因也現(xiàn)實出去面試的時候很多 Android 崗位已經(jīng)在 JD 里明寫著“熟悉 Kotlin 優(yōu)先”。語言本身不是亮點語言背后能不能做出完整 App 才是亮點。1.2 你的真實基礎(chǔ)會 Java 不等于會用 Kotlin有人覺得“Kotlin 不是能直接調(diào) Java 庫嗎那我用 Java 去寫 Kotlin 項目不就行了”。理論上行得通實際體驗完全不是一回事。Kotlin 里有很多 Java 里沒有的寫法空安全類型、擴展函數(shù)、協(xié)程、數(shù)據(jù)類、when 表達式。你用 Java 思路寫 Kotlin代碼能跑但跑得很難受而且老師看到代碼風(fēng)格也會覺得不倫不類。反過來如果你 Java 本身也就停留在“照著課設(shè)改改”的水平那切換到 Kotlin 的前兩三天會非常痛苦因為連findViewById的寫法都變了。我的建議很直接先打開 Android Studio 新建一個空項目兩種語言各寫一遍登錄界面加 RecyclerView 列表哪個上手更順就用哪個。這個測試成本很低比你在論壇看十篇“Kotlin 比 Java 好”的帖子都有用。1.3 未來半年你可能面對的三種場景畢設(shè)選型這種事不能只看當下也要往后想半年。第一個場景是答辯現(xiàn)場老師大概率會問“你這個項目為什么不用協(xié)程”“這個回調(diào)為什么要這么處理”你要是用 Java舊寫法能解釋明白就行你要是用 Kotlin又解釋不出協(xié)程的優(yōu)勢反而會減分。第二個場景是畢業(yè)求職如果你的目標崗位明確是 Android那 Kotlin 基本跑不掉如果目標是后端那其實這兩種語言對 Java 崗的影響都不大Java 反而更對口。第三個場景是二次開發(fā)很多導(dǎo)師會把畢設(shè)接著往下做成比賽作品或者論文實驗?zāi)菚r候代碼的可維護性就很重要了Kotlin 的空安全和協(xié)程確實能省不少事。一句話總結(jié)沒有絕對的對錯只有適不適合你當前的時間、基礎(chǔ)和后續(xù)路線。2. 一張表看懂 Java 和 Kotlin 在畢設(shè)里的真實差距2.1 語法和工程效率空安全、數(shù)據(jù)類、協(xié)程帶來的差異Kotlin 被 Android 官方選為第一開發(fā)語言不是因為長得好看而是因為它在工程效率上確實解決了很多 Java 時代的老大難。我拿三個畢設(shè)里最常見的痛點來對比。第一個是空指針。Java 里你判斷一個對象空不空得像寫論文摘要一樣到處if (obj ! null)忘了判斷就直接崩。Kotlin 的類型系統(tǒng)在編譯期就在逼你想清楚這個變量到底能不能為空能接受為空的用String?不能為空的直接String寫到后面很多崩潰在編譯階段就被提前攔住了。第二個是樣板代碼。Java 寫一個帶幾個字段的 Bean要寫 getter、setter、toString又臭又長。Kotlin 一個data class全搞定尤其是做 JSON 解析的時候數(shù)據(jù)類的優(yōu)勢非常明顯。你要是做“校園二手交易 App”“圖書館座位預(yù)約系統(tǒng)”這類畢設(shè)數(shù)據(jù)模型起碼要寫十幾個 Bean用 Kotlin 能省掉大量篇幅。第三個是協(xié)程。Kotlin 的協(xié)程是把異步任務(wù)從“回調(diào)地獄”里解救出來的利器。Java 里網(wǎng)絡(luò)請求做完回調(diào)要在主線程更新界面你得手寫runOnUiThread或者用 Handler寫完邏輯稍微復(fù)雜一點回調(diào)套回調(diào)人都要暈。Kotlin 里suspend函數(shù)配合withContext(Dispatchers.Main)讀起來跟同步代碼一樣調(diào)試方便很多。2.2 生態(tài)與文檔Java 資料多Kotlin 官方力推這個平衡點很微妙。Java 在 Android 上折騰了十幾年你隨便搜一個功能都能找到十年前的博客和 Stack Overflow 答案。比如你想做一個加載網(wǎng)頁的 WebViewJava 的教程多到你根本看不完。Kotlin 雖然能直接調(diào)用這些 Java 代碼但很多老教程里的 API 在 Kotlin 寫起來已經(jīng)不太一樣了你得會“翻譯”。Kotlin 的官方文檔質(zhì)量高Google 官方也在所有新樣例里默認用 KotlinAndroid Studio 新建項目默認語言就是 Kotlin。說白了你現(xiàn)在學(xué) Kotlin是在沿著 Google 鋪好的路走以后會越來越順。Java 在 Android 這條路上則是能走但前方似乎不再有新的高速公路。所以我的看法是如果是做課程設(shè)計Java 資料多、老師懂、查問題快穩(wěn)妥如果是做畢設(shè)并打算以后專攻 Android別猶豫了Kotlin 就是現(xiàn)在和未來的主流。你論文里寫“為什么要選 Kotlin”理由可以寫得比選 Java 更充分。2.3 三種典型畢設(shè)類型的選型建議我用下面這張表把自己的建議總結(jié)一下畢設(shè)類型推薦語言原因工具類 App記賬、打卡、天氣、日程Kotlin界面邏輯簡單重點在網(wǎng)絡(luò)請求和數(shù)據(jù)庫協(xié)程加 Jetpack 組件寫起來很順手仿電商/社交/課程平臺類 AppJava 或 Kotlin 均可主要工作集中在界面和數(shù)據(jù)交互兩種語言差別不大看你哪個更熟藍牙硬件交互、傳感器采集類首選 Kotlin這類項目回調(diào)多Kotlin 協(xié)程能把回調(diào)轉(zhuǎn)成掛起函數(shù)代碼清晰度直接提升一個檔次算法/圖像處理/地圖定位方向Java部分底層 SDK 和算法庫的示例代碼還是 Java 為主照抄資料更方便這個表不是金科玉律只是我見過太多項目之后的一個普遍規(guī)律。你要是已經(jīng)有明確的功能模塊就針對這幾個模塊去搜一下資料量哪個語言能找到更多相關(guān)代碼就選哪個這是最務(wù)實的判斷方法。3. 實操搭建項目環(huán)境并完成語言遷移3.1 環(huán)境準備JDK、Android Studio 中文界面設(shè)置不管最后選哪種語言第一步都是把 Android Studio 裝好。這里要提醒一句新的 Android Studio 版本會要求 JDK 版本匹配裝完 AS 后它會自帶一個 JBR一般不推薦自己亂配環(huán)境變量。但如果你還要寫 Java 后端接口那環(huán)境變量還是得配JAVA_HOME指向 JDK 安裝目錄PATH里加上%JAVA_HOME%\bin命令行里敲java -version能出來版本號就算成功。很多同學(xué)剛打開 Android Studio 時面對的全英文界面很慌。設(shè)置中文其實很簡單打開Settings - Plugins搜索“Chinese Language Pack”這是官方中文語言包安裝后重啟 IDE 界面就變中文了。注意這里是 IDE 的顯示語言不是項目代碼的語言別搞混。還有更重要的一個配置新建項目時選“Empty Activity”里面會有Language下拉框Java 和 Kotlin 二選一。這個選擇在項目創(chuàng)建后也能改但后期改的成本遠大于一開始就定下來。3.2 新建項目時的語言選擇與目錄結(jié)構(gòu)差異新建項目時你會發(fā)現(xiàn) Kotlin 和 Java 項目的目錄結(jié)構(gòu)有個明顯區(qū)別Java 源碼放在java/目錄Kotlin 源碼放在kotlin/目錄其實還是同一個main之下只是子目錄命名不同。這個目錄本身不影響功能但后續(xù)你復(fù)制網(wǎng)上的源碼時要看清別人貼的是 Java 文件還是 Kotlin 文件放錯目錄雖然也能編譯但很容易出現(xiàn)包名混亂的問題。Kotlin 項目里MainActivity.kt的默認寫法是這樣的class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }Java 版本則是public class MainActivity extends AppCompatActivity { Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }差別看起來就是幾行多說幾個類之后就體現(xiàn)出區(qū)別了。Kotlin 里findViewById可以直接用ViewBinding替代Java 項目也能用但很多老模板默認不用你得自己加依賴。3.3 移植 Android Studio 項目Java 轉(zhuǎn) Kotlin 的底線操作有些同學(xué)做畢設(shè)不是從零開始而是在 GitHub 上找到源碼然后改造成自己的項目。這種“移植 Android Studio 項目”的需求特別常見。如果你是拿 Java 項目改成 KotlinAndroid Studio 提供了自動轉(zhuǎn)換的入口打開某個 Java 文件菜單欄選Code - Convert Java File to Kotlin File。單文件轉(zhuǎn)換很輕松但整個項目多文件轉(zhuǎn)換時我強烈建議不要一鍵全轉(zhuǎn)因為你不知道哪一步會引入編譯錯誤。我總結(jié)的移植底線操作有三條。第一先讓原項目能跑起來再開始轉(zhuǎn)語言。第二依賴庫盡量保持原來的版本不要順手把 support 庫換成 AndroidX那會引發(fā)連鎖反應(yīng)。第三在build.gradle里檢查 Kotlin 插件版本和 Gradle 版本是否匹配。你經(jīng)常在依賴里看到這一行dependencies { compileOnly fileTree(dir: libs, include: [*.aar]) }這里有個關(guān)鍵點compileOnly表示只在編譯期使用不打進 APKimplementation fileTree則會把 libs 目錄下的 jar 都打進包里。如果你把 aar 文件放到 libs 目錄下并寫成compileOnly運行時類找不到是很正常的。所以移植項目時看到類似配置要先搞清楚這個 aar 是不是第三方 SDK 提供的是不是還需要配套的 so 文件和初始化代碼。4. 畢設(shè)里最高頻的四個代碼場景解析4.1 Spinner 變化監(jiān)聽Java 與 Kotlin 寫法對照Spinner 是 Android 里最常見的下拉選擇控件畢設(shè)里選專業(yè)、選宿舍、選時間段都要用。很多同學(xué)第一次寫就卡在“我怎么知道用戶選了哪一項”。Java 里傳統(tǒng)寫法是Spinner spinner findViewById(R.id.spinner); spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { Override public void onItemSelected(AdapterView? parent, View view, int position, long id) { String selected parent.getItemAtPosition(position).toString(); } Override public void onNothingSelected(AdapterView? parent) { } });Kotlin 里最直觀的寫法是一個匿名對象spinner.onItemSelectedListener object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView*?, view: View?, position: Int, id: Long) { val selected parent?.getItemAtPosition(position).toString() } override fun onNothingSelected(parent: AdapterView*?) { } }注意一個坑Spinner 初始化時可能會主動觸發(fā)一次onItemSelected導(dǎo)致你在進入頁面后還沒操作就先把默認項的邏輯執(zhí)行了一遍。通常的解決辦法是用一個Boolean標志位在加載完數(shù)據(jù)之后再開放事件處理或者先setSelection(0)占位等數(shù)據(jù)更新后再刷新。這種細節(jié)在答辯里很少被問到但自己開發(fā)時會踩得很煩。4.2 回調(diào)改協(xié)程BluetoothGattCallback 轉(zhuǎn) suspend 的通用套路如果說畢設(shè)里什么場景最讓人崩潰藍牙和傳感器絕對排前列。傳統(tǒng)寫法是注冊一個BluetoothGattCallback然后連接結(jié)果在onConnectionStateChange回調(diào)里通知你。你要是走過去連接、讀服務(wù)、再讀特征值三步操作就要面對三層嵌套回調(diào)。Kotlin 協(xié)程解決這個問題的思路很優(yōu)雅把回調(diào)包成一個掛起函數(shù)。網(wǎng)上那些“android kotlin bluetoothgattcallback改為suspend”的提問問的就是這個套路。核心代碼長這樣suspend fun connectGattAndWait( context: Context, device: BluetoothDevice ): Boolean suspendCancellableCoroutine { continuation - val gattCallback object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { if (newState BluetoothProfile.STATE_CONNECTED) { continuation.resume(true) } else if (newState BluetoothProfile.STATE_DISCONNECTED) { continuation.resume(false) } } } val gatt device.connectGatt(context, false, gattCallback) continuation.invokeOnCancellation { gatt.disconnect() } }這里的核心思想是suspendCancellableCoroutine把回調(diào)包裝成協(xié)程的掛起點等回調(diào)來了再恢復(fù)執(zhí)行。寫這種代碼時有兩個很隱蔽的坑第一回調(diào)可能會被觸發(fā)多次如果continuation已經(jīng)被恢復(fù)過再調(diào)用resume會直接拋異常建議用AtomicBoolean或者continiation.isActive判斷第二一定要在取消時釋放資源否則用戶退出頁面后藍牙回調(diào)還會觸發(fā)協(xié)程造成內(nèi)存泄漏。這個通用模板在你要處理各種傳感器回調(diào)、文件讀寫回調(diào)時都能套用。4.3 初始化塊里不能直接調(diào)用掛起函數(shù)怎么辦Kotlin 的類初始化塊init里不能直接調(diào)用掛起函數(shù)因為init不是協(xié)程環(huán)境。這個限制坑過很多人。比如你在 App 啟動時想先讀配置、再初始化數(shù)據(jù)庫很自然想在class App : Application()的init里await一下編譯器直接報錯。解決方式有很多種我推薦最干凈的一種用一個獨立的管理類加協(xié)程作用域。class AppRepository(context: Context) { private val scope CoroutineScope(SupervisorJob() Dispatchers.IO) val ready MutableStateFlow(false) fun start() { scope.launch { val config loadConfig() // 掛起函數(shù) db createDatabase(config) ready.value true } } }然后在Application里調(diào)start()界面層再收集ready這個狀態(tài)做成啟動等待頁。這樣既繞開了init限制又不會卡主線程。千萬不要圖省事在init里寫runBlocking畢業(yè)設(shè)計的小項目可能跑不出問題但答辯老師問“為什么不阻塞主線程”的時候你會很難受而且以后做正式產(chǎn)品這是不良示范。4.4 把 AI 模型接進 Android 項目時的選型提醒這兩年大模型特別火已經(jīng)有同學(xué)在畢設(shè)里做“本地 AI 助手”“智能摘要工具”想把 GGUF、TFLite 一類的模型放到 App 里。這里要提一句模型接入本身跟 Kotlin 還是 Java 關(guān)系不大真正決定成敗的是模型體積、運行平臺支持和內(nèi)存開銷。如果你打算集成 GGUF 格式的大模型到 Android 端通常要用到帶本地推理能力的原生庫通過 JNI 加載.so文件然后把模型文件放到 assets 目錄或外部存儲目錄。這個過程中容易出現(xiàn)兩個問題第一不同 CPU 架構(gòu)要配置對應(yīng)的 ABI推薦在build.gradle里用ndk { abiFilters arm64-v8a }縮小包體第二模型加載到內(nèi)存動輒幾百 MB如果不做異步加載和降級處理你的閃退率會高到懷疑人生。畢設(shè)階段我更推薦先把模型跑通一個最簡單的問答界面再去談效果調(diào)優(yōu)不要一上來就指望把完整模型封裝成“原生能力”發(fā)布。選型層面記住一個原則語言負責把模型調(diào)起來能不能調(diào)得流暢是資源策略的問題。5. 常見問題排查速查表與心得5.1 依賴沖突、aar 引入和 Gradle 配置畢設(shè)階段最浪費時間的事情排名第一的是環(huán)境問題排名第二的是依賴問題。我見過有人為了引入一個第三方庫把build.gradle改得亂七八糟最后編譯都過不去。這里分享一個最穩(wěn)的流程先在項目官網(wǎng)找到最新文檔復(fù)制它的implementation坐標同步后再跑一次“Make Project”看到紅色報錯再往上查不要一次寫五六個依賴然后到夜里兩點才排查是哪行寫錯。之前提到的compileOnly fileTree(dir: libs, include: [*.aar])在網(wǎng)上的開源項目里經(jīng)常出現(xiàn)用來在編譯階段引用沒有發(fā)布到 Maven 倉庫的 SDK。很多同學(xué)直接照著抄結(jié)果運行時報ClassNotFoundException。因為compileOnly不會把包打進 APK真要用到運行時的類應(yīng)該改成implementation或者api。如果你不確定是哪種建議直接把 aar 文件手動放進app/libs然后寫implementation fileTree(dir: libs, include: [*.jar, *.aar])這里再補充一個細節(jié)aar 文件和 jar 文件不一樣aar 里可能還帶 Android 資源文件和 so 庫。放在libs目錄下后如果還提示資源找不到檢查一下packaging配置有沒有把對應(yīng)資源排除。5.2 content:// 文件投遞與權(quán)限路徑問題畢設(shè)里經(jīng)常要做“從聊天軟件里導(dǎo)入文件”或者“接收其他 App 分享過來的文檔”的功能。你會收到類似content://com.xxx.fileprovider/external_path/android/data/com.xxx/...這樣的 Uri。很多新手第一反應(yīng)是把這個路徑轉(zhuǎn)換成/storage/...再打開文件大錯特錯。content://是 ContentProvider 提供的統(tǒng)一資源標識你不能拿它當本地文件路徑用。正確做法是通過ContentResolver打開輸入流val inputStream contentResolver.openInputStream(uri) val bytes inputStream?.readBytes()這段邏輯不區(qū)分 Java 還是 Kotlin只要搞清楚“Uri 不是路徑”這個觀念就能少踩一個大坑。另外如果對方 App 的文件路徑帶/android/data/這種目錄在高版本 Android 上很多目錄加了訪問限制除了拿SAF讓用戶自己選文件基本沒有太取巧的辦法。畢設(shè)里做文件分享導(dǎo)入功能優(yōu)先彈出系統(tǒng)文件選擇器別硬編碼替用戶找路徑。5.3 進度條、主線程和數(shù)據(jù)刷新“android 進度條”也是一個搜索熱詞很多同學(xué)在網(wǎng)絡(luò)請求時想顯示一個加載進度。這里的基本常識是網(wǎng)絡(luò)回調(diào)發(fā)生在子線程更新進度條必須在主線程。Kotlin 協(xié)程能幫你省掉runOnUiThreadlifecycleScope.launch { val result withContext(Dispatchers.IO) { api.loadData() } progressBar.isVisible false textView.text result }如果你在 Java 項目里想更新進度條就得runOnUiThread(new Runnable() { Override public void run() { progressBar.setProgress(progress); } });我建議畢設(shè)里別把進度條做得太花哨一個ProgressBar加一個加載失敗重試按鈕就足夠。很多同學(xué)把精力放在動畫效果上卻忽略了“加載失敗后怎么恢復(fù)”這個更重要的狀態(tài)處理答辯時被老師一問就卡殼。5.4 最后的一些個人建議說了這么多我想用自己最真實的體會收個尾。帶畢設(shè)這幾年我越來越覺得“用 Java 還是 Kotlin”其實是一個被過度放大的問題。真正決定項目成敗的是你能不能把功能完整跑通、代碼結(jié)構(gòu)能不能講清楚、遇到問題有沒有排查思路。Kotlin 再方便你三天打魚兩天曬網(wǎng)也做不出東西Java 再老認認真真做也能做出挺優(yōu)秀的學(xué)生信息管理系統(tǒng)。關(guān)鍵是選定之后別再反復(fù)橫跳別今天看教程說 Kotlin 好就重寫一遍明天看知乎說 Java 穩(wěn)又改回去。如果你實在拿不準我的底線建議是項目周期不足兩個月選你最熟的語言項目周期超過兩個月且你愿意每天抽一小時學(xué)新語法就直接上 Kotlin配合 Jetpack 組件寫起來確實值得如果你想在畢設(shè)里第一次嘗試 Compose那就老老實實把 Kotlin 基礎(chǔ)打牢先做一個小 Demo別拿畢業(yè)論文賭自己學(xué)新框架的速度。做完這些基礎(chǔ)判斷之后剩下的事情就是打開 Android Studio新建項目選好語言然后開始寫第一行代碼。只要方向定了后面每走一步都是在為答辯積累素材。