編譯 | 蘇宓
出品 | CSDN(ID:CSDNnews)
開發(fā)一款 App,難不難?
軟件工程師 Thomas SIMON 閑暇之余用實踐回答道:其實說難也不難。
當他拿起一個 Android 手機,想要將其作為電腦攝像頭和麥克風使用時,他只寫了 40 行代碼就實現(xiàn)了這一功能。
只不過,令他苦惱的是,寫 40 行代碼背后需要安裝超過 20GB 的工具、也需要創(chuàng)建數(shù)十個項目文件才能以運行。
當他把自己的這段經(jīng)歷分享到網(wǎng)上時,沒想到,引起了多位開發(fā)者的共鳴,紛紛表示,這種感覺太熟悉了。
在分享的文章中,Thomas SIMON 引用馬斯克曾經(jīng)說過的一句話,「聰明工程師會犯的最常見錯誤,就是優(yōu)化本不該存在的東西」。開發(fā)本身或許并不難,只是被平白增加了很多的復雜性。
接下來,我們將從 Thomas SIMON 的經(jīng)歷中了解開發(fā)者那些本該避開的坑。
需要一個 App,把 Android 手機變成電腦攝像頭和麥克風
之所以想要開發(fā)一款 App,是因為作為一名軟件工程師,Thomas SIMON 平時保持著視頻錄制分享技術(shù)的習慣。
此前,他主要使用 Android 手機來錄制視頻。與臺式機上的 linux 系統(tǒng)相比,Android 系統(tǒng)的兼容性很好,而且它很少出錯,即使出錯,也是以眾人可接受的方式,譬如電池電量不足、系統(tǒng)更新等等。
后來,Thomas SIMON 決定和一位朋友一起錄制他們技術(shù)分享,并將其作為播客片段上傳到網(wǎng)絡(luò)上,期間需要對錄制的文件進行剪輯、制作等等,還是電腦好操作一些。
不過,Thomas SIMON 的 Linux 系統(tǒng)電腦并沒有攝像頭,所以在他的計劃中,原來是想要將 Linux 電腦作為主設(shè)備進行錄制,然后使用手機上的攝像頭和麥克風加入通話。
然而,在錄制期間需要管理兩臺設(shè)備非常麻煩,況且搭載 Linux 系統(tǒng)的電腦設(shè)備本身就有一個不錯的麥克風,所以,此時只要有一個 Linux 兼容的網(wǎng)絡(luò)攝像頭存在就可以解決難題。
在研究市場上現(xiàn)有的網(wǎng)絡(luò)攝像頭之后,Thomas SIMON 認為那些設(shè)備不僅價格昂貴,而且質(zhì)量還不如幾年前的中檔手機后置攝像頭。
恰巧他手頭正好有一部三星 S20,手機的后置鏡頭是長焦鏡頭,非常適合拍攝人像,平行光線讓人臉看起來清晰、漂亮。
在這樣的背景下,Thomas SIMON 萌生了自己開發(fā)一個 Android App 的想法,只需要通過 Wi-Fi 或其他方式將手機攝像頭的數(shù)據(jù)流重定向到他的電腦上,讓他的 Linux 電腦相信 Android 手機是一個攝像頭,或者讓 Linux 的應用程序(如 google Meet 和 Zoom)相信它是一個攝像頭即可。
也許有人會說,其實市場中也早已有了這樣的軟件,譬如 DroidCam。DroidCam 軟件分為兩部分,一部分是手機上安裝的軟件,稱為服務器;另一部分是 PC 上安裝的軟件,稱為客戶端。只需要 PC 和手機連接到同一個 Wi-Fi,就可以把手機作為電腦攝像頭。
不過,Thomas SIMON 在使用后發(fā)現(xiàn),DroidCam 是一款被廣告限制且需要付費的應用程序,而且并非所有攝像頭都能顯示。同時,經(jīng)過測試,它的質(zhì)量令人無法接受,即使是 720p 的低分辨率也被官方推薦。
無法忍受之下,Thomas SIMON 決定自己開發(fā)一款 Android App,把 Android 手機相機變成電腦的攝像頭。
這有什么難的?
通過多年的積累,Thomas SIMON 已經(jīng)掌握了全棧、Linux、視頻流等方面的所有專業(yè)知識。其表示,”雖然我一直對手機應用程序敬而遠之,但我的整個職業(yè)生涯都是朝著網(wǎng)絡(luò)、服務器應用程序和桌面原生應用程序方向發(fā)展的。“
所以,對于 Thomas SIMON 而言,開發(fā)一款 Android App 難度其實并不大。而且在他的規(guī)劃中,他只想要一個包含選擇相機、分辨率和退出鍵這幾個菜單欄的 App, 并不需要有太多的設(shè)計感。
于是,Thomas SIMON 開始尋找制作 Android 應用程序的最簡單方法。他發(fā)現(xiàn),幾乎每個專家都在積極推動 Android Studio 作為 IDE 開發(fā)環(huán)境。
不難想象,他下載了一個 Android Studio,結(jié)果發(fā)現(xiàn),這是一個 1.1GB 的 .tar.gz 壓縮包,一旦安裝了所需的工具,最終會占用 20GB(分布在多個文件夾中)的空間。
安裝好工具之后,Thomas SIMON 開始尋找可以處理攝像頭的官方 APIs。
簡而言之,Thomas SIMON 試過 cameraX,這是一個 Jetpack 庫,旨在幫助簡化相機應用程序的開發(fā),但它太高級了。所以 Thomas SIMON 最終使用了 camera2。
Thomas SIMON 下載了一個 camera2 的官方示例項目,其實他只想拼接一個 http 服務器并用來傳輸幀,在他的設(shè)想中,整個過程應該只需要幾分鐘就可以完成的。沒想到,厄運從這里才真正開始......
Thomas SIMON 表示,這個官方示例項目(Github.com/android/camera-samples/tree/mAIn/Camera2Basic)的作用只有「顯示攝像頭、并拍攝一張照片」,但是沒想到它包含了很多文件。
僅以 gradle 為關(guān)鍵詞搜索,就有一大堆文件:
$:/tmp/Camera2Basic$ find . -type f -name "_gradle_" ./gradlew.bat ./gradle.properties ./gradlew ./settings.gradle ./utils/build.gradle ./gradle/wrapper/gradle-wrapper.jar ./gradle/wrapper/gradle-wrapper.properties ./build.gradle ./app/build.gradleThomas SIMON 表示,自己不可能花時間去慢慢理解這些東西,他只是要修改一些代碼,來實現(xiàn)自己想要的功能。
不過,還沒等他開始尋找代碼片段,Android Studio 就開始跳出了一個提示:
"不支持 JAVA,點擊此處更新 gradle thingy"。
于是,事情似乎進入了循環(huán):
Thomas SIMON 點擊。
事情發(fā)生了變化,左側(cè)窗格中的文件改變了結(jié)構(gòu),標簽頁反復打開和關(guān)閉。
"建議更新項目,點擊啟動 AGP 升級助手”
Thomas SIMON 繼續(xù)點擊:
顯示許多選項,其中一些已預選,一個按鈕上寫著 "運行選定步驟"。
Thomas SIMON 再次點擊:
事情發(fā)生了變化,左側(cè)窗格中的文件改變了結(jié)構(gòu),選項卡反復打開和關(guān)閉。
"建議更新項目,單擊啟動 AGP 升級助手
Thomas SIMON 點擊:
顯示大量選項,其中一些已預選,一個按鈕顯示 "運行選定步驟"。
Thomas SIMON 繼續(xù)點擊:
事情發(fā)生了變化,左側(cè)窗格中的文件改變了結(jié)構(gòu),標簽反復打開和關(guān)閉。
Thomas SIMON 無語道,“這是一款很棒的點擊冒險游戲。”
折騰了一會,Thomas SIMON 的 AGP 和 gradle 終于不再跳出更新提示。
接下來,正式進入代碼部分。
寫過不少 Java 和 Scala 代碼的 Thomas SIMON 發(fā)現(xiàn)這部分的代碼是用 Kotlin 寫的。
“從這里開始,我喜歡我所看到的,代碼很清晰。它在 xml 中定義一個視圖,在代碼中用標識符與之關(guān)聯(lián),非常標準且可預測。清晰的代碼讓我不需要學習任何東西就能提高工作效率”,Thomas SIMON 說道。
緊接著,Thomas SIMON 調(diào)用一個 Android API 來制作一個簡單的 http 服務器,然后把它插入、測試,它就能工作了!
現(xiàn)在,Thomas SIMON 的電腦瀏覽器標簽頁上有一個 mjpeg 流。對于實時調(diào)用來說,質(zhì)量和延遲都還可以。這樣,他就可以在終端輸入一行命令將其轉(zhuǎn)換為 Linux 網(wǎng)絡(luò)攝像頭設(shè)備:
ffmpeg -f mjpeg -i "http://192.168.1.2:8080" -vf "format=yuv420p" -f v4l2 /dev/video0
整體而言,Thomas SIMON 表示,網(wǎng)絡(luò)世界雖然有一些不必要的步驟和配置,但這個 Android 世界簡直是瘋了。
他下載了官方示例之后,刪除了未使用的視圖,添加了選擇相機、分辨率和質(zhì)量的下拉菜單,將所有功能移至前臺服務,以便在鎖定手機的情況下保持激活狀態(tài),并將其發(fā)布在 GitHub 上(https://github.com/Ruddle/RemoteCam),整個項目花了他兩個下午的時間(其中大部分時間都在了解 Android 希望你怎么做)。
實際工作量應該如何?
Thomas SIMON 認為,在理想的情況下,在手機端,只需一行代碼就足夠了,根本不需要上面那樣復雜的操作。倘若我們想編寫一個應用程序,只是為了準確地指定數(shù)據(jù)流,而不是依賴天才們已經(jīng)開發(fā)出來的高級工具(如桌面上的 ffmpeg 和 v4l2)。
在理想的世界里,具有這種規(guī)格的 App 應具備:
- 允許配置相機、分辨率和質(zhì)量/比特率
- 沒有設(shè)計,只有原始功能
- 在本地網(wǎng)絡(luò)上傳輸幀流
Thomas SIMON 表示,只需一個 40 行偽代碼(pseudo code,又稱虛擬代碼,是一種高層次描述算法的方法,它可能綜合使用多種編程語言的語法、保留字,甚至會用到自然語言)的文件就能實現(xiàn)以上功能:
1CAMERA.getPermission orquit
.NETWORK.getPermission orquit
3
4queue=Producer(size= 1)
5
6server = NETWORK.createHttpServer(port = 8080)
7server.onRequest = req ->
8queue.dropConsumers //only allows 1client, drop old ones
9req.sendHeader( "Content-Type", "multipart/x-mixed-replace;boundary=FRAME")
10queue.consumeUntilDrop( frame, consumer ->
11data = "--FRAME=rnContent-Type=image/jpegrn".bytes+frame.bytes
12req.sendBytes(data) orqueue.drop(consumer)
13frame. close// Free camera memory of this frame
14)
15req. close
16
17options = STORAGE.get( "config") or{sensor: 0, format: "jpeg", fps: 30, resolution:[ 1280, 720]}
18
19session = CAMERA. open(options)
20session.onFrame= frame -> queue.pushTry(frame) orframe. close
21
22UI. insert(UI.text).text= "Choose a sensor:"
23dropdownSensors = UI. insert(UI.dropdown)
24dropdownSensors.selection= session.sensor
25dropdownSensors.values = CAMERA.sensors.map(_.name)
26dropdownSensors.onSelect = index ->
27STORAGE.set( "config", options + {sensor: index})
28restart
29
30UI. insert(UI.text).text= "Choose a resolution:"
31dropdownResolution = UI. insert(UI.dropdown)
32dropdownResolution.selection= session.resolutions.indexOf(session.resolution)
33dropdownResolution.values = session.resolutions.map(_ .0+ "x"+_ .1)
34dropdownResolution.onSelect = index ->
35STORAGE.set( "config", options + {resolution: session.resolutions[index]})
36restart
37
38quitBtn = UI. insert(UI.button)
39quitBtn.onClick = quit
40quitBtn.text = "quit"
這段偽代碼雖然簡單,但明確指出了所需的內(nèi)容。 其中,對 API 的訪問如CAMERA(攝像頭)、NETWORK(網(wǎng)絡(luò))、UI(用戶界面)和 STORAGE(存儲)等都非常明顯易懂。
無需導入、無需 gradle、無需指定無用的用戶界面文件、無需多個事實來源。該文件非常簡短,Thomas SIMON 甚至懶得將存儲密鑰命名為 "config"。
Thomas SIMON 解釋道,“偽代碼假定是托管執(zhí)行,就像 Kotlin 或 Java 一樣,允許非常容易地使用高階函數(shù)。支持這種單文本文件應用程序并不需要什么新東西。我們只需選擇一種語言,如 Python/ target=_blank class=infotextkey>Python、Java 或 Kotlin,然后編寫一個庫來公開 API。我們甚至可以開發(fā)一個標準的 Android 應用程序,作為操作系統(tǒng)來執(zhí)行這些單文本文件應用程序。但 Google 并不允許這樣做。從技術(shù)上講,你可以進行動態(tài)代碼加載,但一旦 Google 發(fā)現(xiàn)你可以讓人們繞過 Play 商店來加載小于 1kB 的應用程序,你就完了。此外,Google 和 Apple 已經(jīng)在極力阻止 PWA(一種讓網(wǎng)頁擁有原生應用程序功能的技術(shù))運行得太好。”
對于 Thomas SIMON 而言,他大概只用了 10 分鐘就寫出了這段偽代碼。其中有 5 分鐘是在網(wǎng)上查找如何在多部分 http 流中分離幀(獲取信息的難度出乎意料)。
就目前而言,其實通過一個 LLM(比如 ChatGPT,甚至是本地運行的 LLM)可以在 10 秒內(nèi)寫完這段代碼,而且至少有一半的時間不會出錯。
經(jīng)過 Gzip 壓縮后,一個文本文件應用程序的大小為 688 字節(jié)。
1$> gzip -k text_app; ls -lh
21, 5K text_app
3688text_app.gz
相比之下,Thomas SIMON 用 Android Studio 生成的 APK 只有 14.9MB。
至于用戶可能存疑的所有的 gradle 配置文件都在哪里?Thomas SIMON 表示,這些文件中 99% 的內(nèi)容都是用來處理糟糕的抽象。
最后的 1% 可能只是文件頂部的一些注釋,如
// name:RemoteCam // author:Thomas SIMON // version:1.0 // icon: image/png;base64,ABC...對于安全性、兼容性和更新等問題,Thomas SIMO 解釋道,這些問題都已經(jīng)解決了。
- 安全性:只需根據(jù)你信任的文件進行簽名檢查。
- 兼容性:只需在文件頂部注釋 minAndroidVersion。
- 更新:替換應用程序文件即可。
這些問題都不能成為你在 Android 系統(tǒng)上制作應用程序的復雜性的借口。
這些問題都不能由應用程序商店自行解決(應用程序本身只有 40 行)。
結(jié)論
最后,Thomas SIMO 僅用了 40 行代碼就開發(fā)了一個網(wǎng)絡(luò)攝像頭 App,并將代碼開源在了 GitHub 上(https://github.com/Ruddle/RemoteCam)。
Thomas SIMO 表示,他用這款應用程序進行了兩次視頻通話,每次持續(xù)時間都在 1 小時 30 分鐘以上,它的表現(xiàn)非常出色。
經(jīng)過自己開發(fā)了一款 App,他也終于明白為什么 DroidCam 試圖推銷付費模式,主要是因為開發(fā)一款 App 必須忍受的非必要工作和挫折實在太多,代碼沒寫多少,但其中的工具安裝、項目支持文件實在過于臃腫,最終導致你想向別人收費來尋求心理平衡罷了。
來源:https://thomassimon.dev/ps/2