ByteScope

APK Installer

把 Android 安裝包拖到手機上,經 USB 安裝——split 怎麼挑有說明,版本與簽章先比對,INSTALL_FAILED 錯誤碼有對照表。

APK 在這個瀏覽器裡讀取,直接經 USB 送進手機,不會上傳到任何地方。

正在檢查瀏覽器支援…

INSTALL_FAILED_* 對照表

這頁認得的每一個失敗碼、它的意思,以及怎麼處理。這些碼是 Android 自己的常數,和桌面 `adb install` 印出來的一模一樣。

已經裝著的東西擋住了

INSTALL_FAILED_ALREADY_EXISTS
這個套件已經安裝了,而這次安裝沒有指定要覆蓋。
  • 先把已安裝的那份移除,再重裝一次。那個 App 的資料會一起不見。
INSTALL_FAILED_UPDATE_INCOMPATIBLE
裝置上已經有同樣套件名的 App,但簽章金鑰不同,所以 Android 不把這次安裝當成更新。
  • 把簽章憑證和已安裝的那個 App 比對一下。Android 不會讓不同金鑰簽出來的 App 覆蓋過去。
  • 先把已安裝的那份移除,再重裝一次。那個 App 的資料會一起不見。
INSTALL_FAILED_VERSION_DOWNGRADE
APK 的 version code 比已安裝的那份低,而降版預設會被拒絕。
  • 打開「允許降版」(`-d`)再試一次,或是先移除比較新的那版。新版寫下的資料,舊版不一定讀得動。
  • 先把已安裝的那份移除,再重裝一次。那個 App 的資料會一起不見。
INSTALL_FAILED_DUPLICATE_PACKAGE
裝置上已經有另一個套件在用這個套件名。
  • 先把已安裝的那份移除,再重裝一次。那個 App 的資料會一起不見。
INSTALL_FAILED_SHARED_USER_INCOMPATIBLE
APK 宣告的 `sharedUserId` 和已經掛在那個共用使用者底下的 App 衝突。
  • 先把已安裝的那份移除,再重裝一次。那個 App 的資料會一起不見。
  • 把簽章憑證和已安裝的那個 App 比對一下。Android 不會讓不同金鑰簽出來的 App 覆蓋過去。
INSTALL_FAILED_UID_CHANGED
已安裝的 App 擁有一個 Linux UID,這次安裝會改動它,原本的資料就會變成孤兒。
  • 先把已安裝的那份移除,再重裝一次。那個 App 的資料會一起不見。
INSTALL_FAILED_REPLACE_COULDNT_DELETE
Android 沒辦法把舊版移掉,好讓這一版接上去。
  • 把手機重開機,再試一次。
  • 先把已安裝的那份移除,再重裝一次。那個 App 的資料會一起不見。
INSTALL_FAILED_DUPLICATE_PERMISSION
APK 定義的權限名稱,已經有另一個安裝好的 App 定義過。
  • 已經有另一個 App 佔住了同樣的 content authority 或權限名稱。找出它、移除掉,再試一次。
INSTALL_FAILED_CONFLICTING_PROVIDER
APK 宣告的 content provider authority,已經被另一個安裝好的 App 佔住。
  • 已經有另一個 App 佔住了同樣的 content authority 或權限名稱。找出它、移除掉,再試一次。
INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE
已安裝的 App 用的是執行階段權限,而這個 APK 的目標 Android 舊到還在用安裝時授權模型——不允許往這個方向換。
  • 先把已安裝的那份移除,再重裝一次。那個 App 的資料會一起不見。

APK 和這台裝置對不上

INSTALL_FAILED_OLDER_SDK
APK 需要比這支手機更新的 Android(`minSdkVersion` 高於裝置的 API level)。
  • APK 宣告的 SDK 範圍和這個 Android 版本對不上。拿 `minSdkVersion` / `targetSdkVersion` 跟手機的版本核對一下。
INSTALL_FAILED_NEWER_SDK
APK 是為比這支手機更舊的 Android 建置的,它宣告的最高 SDK 把這台裝置排除在外。
  • APK 宣告的 SDK 範圍和這個 Android 版本對不上。拿 `minSdkVersion` / `targetSdkVersion` 跟手機的版本核對一下。
INSTALL_FAILED_NO_MATCHING_ABIS
APK 裡有原生函式庫,但沒有一個是這支手機支援的 CPU 架構。
  • 這個 APK 沒有這支手機 CPU 用的原生程式碼。換一份符合裝置 ABI 的建置,或改成安裝整組分割包而不是單一 APK。
  • 要安裝容器裡的整組分割包,不能只挑其中一個 APK——分割包無法單獨安裝。
INSTALL_FAILED_CPU_ABI_INCOMPATIBLE
APK 的原生程式碼跟手機的 CPU 架構對不上。
  • 這個 APK 沒有這支手機 CPU 用的原生程式碼。換一份符合裝置 ABI 的建置,或改成安裝整組分割包而不是單一 APK。
INSTALL_FAILED_MISSING_FEATURE
APK 要求的硬體或軟體功能,這台裝置沒有宣告自己具備。
  • APK 宣告的 SDK 範圍和這個 Android 版本對不上。拿 `minSdkVersion` / `targetSdkVersion` 跟手機的版本核對一下。
INSTALL_FAILED_MISSING_SPLIT
這只是分割式 App 的其中一塊,它依賴的其他部分沒有裝——config 分割或功能模組不能自己站著。
  • 要安裝容器裡的整組分割包,不能只挑其中一個 APK——分割包無法單獨安裝。
INSTALL_FAILED_DEPRECATED_SDK_VERSION
Android 14 以後會拒絕安裝目標 SDK 太舊的 App,不管簽章是什麼。
  • APK 宣告的 SDK 範圍和這個 Android 版本對不上。拿 `minSdkVersion` / `targetSdkVersion` 跟手機的版本核對一下。

儲存空間

INSTALL_FAILED_INSUFFICIENT_STORAGE
裝置的可用空間不夠放這個 App。
  • 先在手機上清出空間再試一次——安裝除了 APK 本身,還要放編譯後的程式碼。
INSTALL_FAILED_MEDIA_UNAVAILABLE
安裝的目標儲存空間沒有掛載,或是在安裝到一半時消失了。
  • 再試一次——這種失敗通常是暫時性的。
  • 把手機重開機,再試一次。
INSTALL_FAILED_CONTAINER_ERROR
Android 沒辦法備好用來放 App 檔案的容器——通常是底層的儲存問題。
  • 先在手機上清出空間再試一次——安裝除了 APK 本身,還要放編譯後的程式碼。
  • 再試一次——這種失敗通常是暫時性的。

驗證器、政策或建置旗標擋下來

INSTALL_FAILED_TEST_ONLY
這個 APK 標了 `android:testOnly`,一般安裝會拒絕它。
  • 打開「允許測試版」(`-t`)再試一次。這個 APK 是以除錯/測試用的形式建置的。
INSTALL_FAILED_VERIFICATION_FAILURE
套件驗證機制(多數手機上就是 Play Protect)檢查過這個 APK 並拒絕了它。
  • Play Protect 掃過這個 APK 並拒絕了它。關掉「使用 Play Protect 掃描應用程式」或開發人員選項裡的「透過 USB 驗證應用程式」,再試一次。
  • 再試一次——這種失敗通常是暫時性的。
INSTALL_FAILED_VERIFICATION_TIMEOUT
套件驗證機制沒有在時限內回應,所以安裝被取消。
  • 再試一次——這種失敗通常是暫時性的。
  • Play Protect 掃過這個 APK 並拒絕了它。關掉「使用 Play Protect 掃描應用程式」或開發人員選項裡的「透過 USB 驗證應用程式」,再試一次。
INSTALL_FAILED_USER_RESTRICTED
裝置政策或使用者限制禁止用這種方式安裝 App。
  • 解開螢幕鎖,並回應手機上跳出來的對話框——有些安裝需要在裝置端確認,沒人理它就會安靜地逾時。
  • 工作設定檔、裝置擁有者或家長監護政策擋住了安裝。去設定 → 使用者看看,或問管這台裝置的人。
INSTALL_FAILED_ABORTED
安裝工作階段在 commit 之前被取消了——常見原因是裝置上的對話框沒人回應、螢幕鎖著,或是另一個安裝把這個工作階段搶走。
  • 解開螢幕鎖,並回應手機上跳出來的對話框——有些安裝需要在裝置端確認,沒人理它就會安靜地逾時。
  • 再試一次——這種失敗通常是暫時性的。

檔案讀不成 APK

INSTALL_FAILED_INVALID_APK
Android 不接受這個 APK 為有效套件——可能是壓縮檔壞了、分割包不完整,或同一個分割被寫進工作階段兩次。
  • 要安裝容器裡的整組分割包,不能只挑其中一個 APK——分割包無法單獨安裝。
  • 這個檔案不是有效的 APK,或是傳過來就已經壞了。回原始來源重新下載。
INSTALL_FAILED_BAD_SIGNATURE
APK 的簽章驗不過:檔案在簽章之後被改過,或是簽章區塊本身壞了。
  • 把簽章憑證和已安裝的那個 App 比對一下。Android 不會讓不同金鑰簽出來的 App 覆蓋過去。
  • 這個檔案不是有效的 APK,或是傳過來就已經壞了。回原始來源重新下載。
INSTALL_PARSE_FAILED_NOT_APK
這根本不是 APK——安裝程式沒辦法把它當成 APK 讀。
  • 這個檔案不是有效的 APK,或是傳過來就已經壞了。回原始來源重新下載。
INSTALL_PARSE_FAILED_NO_CERTIFICATES
APK 沒有有效簽章。沒簽過的 APK,以及重新打包時把簽章檔弄掉的 APK,都會落在這裡。
  • 這個檔案不是有效的 APK,或是傳過來就已經壞了。回原始來源重新下載。
  • 把簽章憑證和已安裝的那個 App 比對一下。Android 不會讓不同金鑰簽出來的 App 覆蓋過去。
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES
這組 APK 不是全部用同一把金鑰簽的——同一個 App 的每個分割都必須和 base 同簽章。
  • 把簽章憑證和已安裝的那個 App 比對一下。Android 不會讓不同金鑰簽出來的 App 覆蓋過去。
  • 這個檔案不是有效的 APK,或是傳過來就已經壞了。回原始來源重新下載。
INSTALL_PARSE_FAILED_MANIFEST_MALFORMED
APK 的 `AndroidManifest.xml` 解析不了。
  • 這個檔案不是有效的 APK,或是傳過來就已經壞了。回原始來源重新下載。

安裝工作階段或框架本身失敗

INSTALL_FAILED_INTERNAL_ERROR
套件安裝程式踩到內部錯誤,而且沒有多說什麼。
  • 再試一次——這種失敗通常是暫時性的。
  • 把手機重開機,再試一次。
INSTALL_FAILED_DEXOPT
檔案複製完之後編譯 App 程式碼失敗——通常是可用空間不夠,偶爾是 DEX 壞了。
  • 先在手機上清出空間再試一次——安裝除了 APK 本身,還要放編譯後的程式碼。
  • 把手機重開機,再試一次。
  • 再試一次——這種失敗通常是暫時性的。
關於這個工具、FAQ 與相關工具

關於這個工具

用 USB 把 Android 手機接上 Chromium 系瀏覽器,把一個或多個安裝包拖到這一頁,它們會依序安裝。.apk 直接送進裝置的安裝程式;.apks.xapk.apkm 是裝著多個 split APK 的容器,會在瀏覽器裡解開,挑出這台手機需要的 split,全部寫進同一個安裝工作階段。整個過程不會上傳到任何地方——位元組是從你磁碟上的檔案經 USB 線直接灌進 cmd package install-write,而這一頁本身是沒有後端的靜態檔案。

split APK 是什麼,為什麼下載到的 App 是分割的

2018 年之後,Play 商店就不再一個 App 配一個 APK 了。它出貨的是 *app bundle*,由商店針對每台裝置組出一組 APK:帶程式碼的 base APK、每個 CPU 架構一個 config split、每個螢幕密度一個、每個語言一個,以及每個選用功能模組一個。你的手機只下載其中四五個,其餘二十個是給別的機型的。所以商店上 90 MB 的 App,整組匯出來會是 400 MB 的 .apks;也所以只從那一組裡挑 base APK 來裝,App 會開起來就以 UnsatisfiedLinkError 閃退——原生程式碼在你沒帶上的那個 ABI split 裡。Android 一個工作階段**每個維度最多只收一個 config split**:塞兩個 ABI 或兩個密度,會在整包傳完之後才吐 INSTALL_FAILED_INVALID_APK

.apks、.xapk、.apkm——同一件事,三個打包者

.apks 是 bundletool 自己的產出,也就是 bundletool build-apks 做出來的檔案,裡面有 toc.pb 記著每個 split 與它的適用條件。.xapk 來自 APKPure,帶一份 manifest.json,寫著套件名、版本與 split 清單。.apkm 是 APKMirror 的,帶 info.json;要注意*舊版*加密過的 .apkm 除了 APKMirror 自家安裝程式以外誰都打不開,這一頁會直說而不是丟一個看不懂的錯誤。SAI(Split APKs Installer)則寫 meta.sai_v2.json。四者本質都是 ZIP 加一份中繼資料,這一頁讀得到哪一份就用哪一份,遇到手工重新打包的容器才退回檔名比對。

為什麼簽章不同就一定失敗,以及該怎麼辦

Android 用套件名*加上*簽章憑證來認一個 App。如果允許用另一把鑰匙簽的東西去取代,那麼任何猜得到套件名的人都能替別人的 App 發更新,所以平台直接拒絕:INSTALL_FAILED_UPDATE_INCOMPATIBLE。沒有旗標可以繞過。只有兩條路:先把已安裝的那份解除安裝(它的資料會一起消失,重要的東西請先匯出),或者拿到一份 v3 簽章能證明自己是從原鑰匙輪替過來的 APK。這也是為什麼商店來的 App 不能用別人重新簽章的 APK 更新,以及為什麼 debug 版永遠蓋不過同一個 App 的 release 版。

送出任何位元組之前,這一頁會先檢查什麼

每個安裝包都在本機打開來讀:App 名稱、套件名、版本名與版本號、minSdkVersiontargetSdkVersion、它會要求的執行階段權限,還有簽章憑證。接著去問手機同一個套件名底下現在有什麼,兩邊相比——「10.2.1 → 10.3.0,升級」「比已裝的舊,需要降版旗標」「需要 API 33,這台是 API 30」。簽章比對用的是 Android 自己的 dumpsys package 為每張已安裝憑證印出的雜湊值,我們用 APK 裡的憑證算出同一個值來對;遇到不印那一行的裝置,頁面會說**無法比對**,這跟「相同」不是一回事。split 表則列出容器裡每一個 APK 與它的判定和理由——對到最優先的 ABI、被更前面的 ABI 蓋過、最接近的密度、你沒在用的語言——每一項都可以自己覆寫,覆寫時會提醒通常會發生什麼事。

從別的方向進來:Web ADB 工具箱的 Hub 有裝置儀表板和一個快速單檔安裝的拖放區,APK 分析器可以在不接手機的情況下把同樣的檔案挖得更深,Android 精簡工具則負責把你不想要的東西移掉。

常見問題

.apks、.xapk、.apkm 要怎麼安裝?

接上 USB 之後拖到這一頁就好。這三種格式是裝著 base APK 與 config split 的 ZIP,把整包丟給 `pm install` 是裝不起來的——安裝程式需要把每個內層 APK 用 `install-create` / `install-write` / `install-commit` 寫進同一個工作階段。這一頁會解開容器、讀它的 `toc.pb` / `manifest.json` / `info.json`,依照手機的 CPU ABI、螢幕密度與語言挑出對應的 split,然後只把那些寫進同一個工作階段。

INSTALL_FAILED_UPDATE_INCOMPATIBLE 是什麼意思?

已經裝了同名套件,但它是用另一張簽章憑證簽的,所以 Android 不把你的 APK 當成它的更新。這不是加旗標能解決的。要嘛先解除安裝已裝的那份(資料會一起消失),要嘛拿到同一把鑰匙簽出來的版本。用鏡像站下載的 APK 去蓋 Play 商店裝的 App 時最常遇到這個,因為鏡像站那份跟商店那份經常不是同一把鑰匙簽的。

為什麼我的 .apks 會出現 INSTALL_FAILED_INVALID_APK 或 MISSING_SPLIT?

幾乎都是因為進到工作階段的那組 split 不對。`MISSING_SPLIT` 是指 config 或功能 split 被單獨安裝、沒有帶上它所屬的 base,或者 base 裝了但 App 需要的功能模組沒跟上。split 安裝時的 `INVALID_APK` 多半是同一個維度放了兩個 split(兩個 ABI 或兩個密度),框架會拒絕。這兩件事正是這一頁的 split 表要防的:每個維度只放一個,而且一定帶 base。如果你自己改過選擇,那個警告講的就是這件事。

沒裝 adb 也能從電腦安裝 APK 嗎?

可以,這一頁就是在做這件事。WebUSB 讓 Chromium 系瀏覽器能用 ADB 協定跟開了 USB 偵錯的手機對話,不必下載 platform-tools、不必設 PATH、不必記指令。但限制是真的:只有桌面版 Chrome、Edge、Opera(Firefox 與 Safari 沒有 WebUSB,手機瀏覽器一律不行),而且同一時間只有一個程式能佔用 ADB 介面,所以桌面上已經跑著的 `adb` server 要先用 `adb kill-server` 停掉。

要怎麼把 App 降回舊版?

安裝前把降版旗標(`-d`)打開。Android 預設會用 `INSTALL_FAILED_VERSION_DOWNGRADE` 拒絕比較低的版本號,而它這麼做的理由值得尊重:App 存下來的資料是新版寫的,舊的程式碼經常讀不懂一個它沒見過遷移路徑的資料庫。就算裝得進去,也可能一開就閃退或安靜地弄丟內容。比較可預期的做法是先解除安裝再重新安裝舊版,代價是資料。

這樣裝會觸發 Play Protect 嗎?會影響保固嗎?

可能會觸發 Play Protect:套件驗證器會掃描側載的 APK 並可能拒絕它,出現的就是 `INSTALL_FAILED_VERIFICATION_FAILURE`。那是手機上的設定(「使用 Play Protect 掃描應用程式」,以及開發者選項裡的「透過 USB 驗證應用程式」),不是這一頁該替你關掉的東西。安裝 APK 不會改到系統分割區、不需要解鎖 bootloader、也不會 root,所以不屬於會動到保固的操作;但公司設定檔或裝置擁有者政策可能直接禁止,那會以 `INSTALL_FAILED_USER_RESTRICTED` 呈現。