# 教程
# 啟用隔離後會發生什麼?
假設存在一個應用程式 ExampleApp(包名為 com.example
),它使用了有濫用儲存空間行為的 SDK(假設它會建立 bad_sdk
資料夾)。那麼在授予 ExampleApp 儲存權限後,你的儲存空間將會是這樣的。
/storage/emulated/0
├───Android
├───bad_sdk
├───DCIM
├───Download
├───Pictures
└───...
現在,我們對 ExampleApp 啟用隔離,它的儲存空間成為其數據資料夾中的一個資料夾。我們稱該資料夾為「隔離儲存空間」。
ExampleApp 會只能使用該資料夾內的檔案,由它建立的資料夾也會保存於該資料夾。
/storage/emulated/0
├───Android/data/com.example <---- ExampleApp 的數據資料夾
│ └───sdcard <---- 隔離儲存空間
│ └───bad_sdk
└...
另外,由於選用了它的數據資料夾,我們還可以取得這些好處:
- 解除安裝或清除數據時,這些檔案會被一併刪除
- 在系統的應用資訊中,這些檔案也會被計入儲存空間使用量
# 針對新使用者的知識
推薦的組織檔案的方式
對於照片、圖片、下載的檔案等使用者檔案,Android 系統提供了一系列的標準資料夾。
Alarms
(鬧鈴)Pictures
(圖片)DCIM
(相機)Documents
(文件)Download
(下載)Movies
(影片)Music
(音樂)Notifications
(通知音)Ringtones
(鈴聲)
以最常用的 Pictures
為例,通行的做法是,每個應用程式各自在其中建立自己的資料夾。比如 Twitter 儲存圖片至 Pictures/Twitter
。
我們的建議是,按照上面的方式組織各個應用程式儲存的檔案。
清理/移動已有的檔案
由於 /storage/emulated
中的檔案並沒有所有者,我們無法自動地幫助你移動或刪除已有的檔案。
使用者檔案,如圖片
按照上面推薦的方式進行整理。
其他
對於絕大多數應用程式,刪除先前的檔案不會產生問題。但以防萬一,我們建議你跟隨下面的步驟。
- 建立一個臨時資料夾並將它們都移入其中。
- 執行所有被隔離的應用程式。
- 如果有應用程式因為無法找到之前的檔案而不正常工作,你可以藉助「檢視隔離儲存空間」功能瞭解特定資料夾可能由誰建立並由此移動那些資料夾。
- 所有應用程式都正常工作後,刪除臨時資料夾。
忘掉「清理程式」
一些「清理程式」有清理特定應用程式建立的檔案的功能(那些檔案不在資料資料夾中,解除安裝後不會被刪除),這種功能在隔離後因為檔案位置變化而無法使用。
但是在隔離後,應用程式建立的檔案都保存於隔離儲存空間內(位於其資料資料夾),它們會在解除安裝後被刪除。另外,你可以將隔離儲存空間位置設定為快取資料夾,快取資料夾會被 Android 系統自動清理。
忘掉「清理程式」吧,它們已不再有用。並且,它們從一開始就不應該存在。
「備份程式」不會受影響(甚至可以備份更多內容)
「備份程式」只能備份應用程式的數據資料夾中的檔案。
隔離後,應用程式建立的檔案都儲存於隔離儲存空間內(位於其數據資料夾),因此它們也能被備份了。注意,你可能需要在你的「備份程式」中啟用類似於「備份外部資料」的選項。
# 應用程式不正常工作的解決方案
# 應用程式需要訪問特定的檔案
現在 ExampleApp 有了傳送圖片功能。但是因為被隔離,你無法在 ExampleApp 中找到你的圖片。
要解決這個問題,我們只需要關注「可訪問資料夾」選項中的「共享資料夾」部分。假設我們選擇了 DCIM
和 Pictures
資料夾,ExampleApp 就可以訪問這兩個資料夾中的檔案了。
/storage/emulated/0
├───Android/data/com.example
│ └───sdcard <---- 隔離儲存空間
│ ├───bad_sdk
│ ├───DCIM <---- 真實的 DCIM
│ └───Pictures <---- 真實的 Pictures
└...
對於其他情況,你只需要選擇對應的資料夾即可。
注意,應用程式不止可以讀取,還可以寫入這些資料夾。
# 不要濫用!
我們只推薦必要的資料夾設為可訪問資料夾。如果你將所有的資料夾都設為可訪問資料夾,隔離將失去意義。
# 找不到應用程式儲存的檔案
現在 ExampleApp 有了下載圖片功能,並且你用它下載了 1.png
。因為被隔離,1.png
被儲存至隔離儲存空間,你無法在相簿應用程式中看到它。
/storage/emulated/0
├───Android/data/com.example
│ └───sdcard <---- 隔離儲存空間
│ ├───bad_sdk
│ ├───DCIM
│ ├───images
│ │ └───1.png
│ └───Pictures
└...
要解決這個問題,我們需要建立一個「匯出被隔離的檔案」規則。
來源:images
目標:Pictures/ExampleApp
新增到媒體儲存:是
建立規則後,你就可以在相簿應用程式及 Pictures/ExampleApp
中看到 1.png
。
/storage/emulated/0
├───Android/data/com.example
│ └───sdcard <---- 隔離儲存空間
│ ├───images
│ │ └───1.png
│ └───...
├───Pictures
│ └───ExampleApp
│ └───1.png
└...
注意,由於使用了 hard link,因此雖然在兩處存在相同的檔案,但是它們只會佔用一份儲存空間。有關「匯出被隔離的檔案」的技術細節,你可以在這裡閱讀。
# 使用線上規則
如果線上規則中已經有了需要的規則,你只需要直接新增它們。只有沒有規則的情況或是規則有錯誤時你才需要自己編寫規則。你還可以提交你的規則到線上規則庫(通過「上傳按鈕」)。
# 不要濫用!
匯出的目的是匯出使用者檔案(由使用者發起的儲存檔案操作,如儲存圖片、下載檔案等操作)。
如果應用程式將使用者檔案儲存至私有資料夾(如 Android/data/<package>/files/example
),此處並不屬於隔離儲存空間,不適用於匯出功能。
/storage/emulated/0
├───Android/data/com.example
│ ├───files <---- 不屬於隔離儲存空間
│ └───sdcard <---- 隔離儲存空間
└...
如果你發現應用程式將使用者檔案儲存於私有資料夾,你應該要求它們的開發者做出改變。
為什麼應該要求應用程式開發者做出改變?
在 Android 11 中,Android
資料夾是真正的私有資料夾。即使有儲存權限,應用程式也只能訪問其中屬於自己的部分。
/storage/emulated/0
├───Android
│ ├───data
│ │ └───com.example <---- 屬於 com.example
│ ├───media
│ │ └───com.example <---- 屬於 com.example
│ └───obb
│ └───com.example <---- 屬於 com.example
└...
將使用者檔案存放在私有資料夾意味著只有應用自己能看見,其它任何應用程式,包括檔案管理器,都看不到。這麼做顯然是不對的。另外,此處的檔案會在解除安裝或清除資料時被刪除,將使用者檔案保存於此顯然不合適。
注意,對於「聊天程式中接收檔案」這樣的場景,將檔案先存放於私有資料夾是合理的。因此,在向應用程式開發者反饋時,訴求應該是新增“儲存到下載”功能(將私有資料夾中的檔案移動至 Download
資料夾)而不是直接改變檔案的位置。
# 配合另外的應用程式使用時出現問題
# 使用其他應用程式檢視檔案(標準方式,即 ACTION_VIEW)
現在 ExampleApp 有了使用其他應用程式開啟圖片的功能。很不幸 ExampleApp 直接將檔案路徑傳遞給其他應用程式(這種做法幾年前就應該被拋棄!),你會發現其他應用程式提示「找不到檔案」。
ExampleApp 自己並不會知道自己被隔離了,它所看到的儲存空間是這樣的。
因此,情況是這樣的。
ExampleApp:給你,
example_app/1.png
。圖片檢視器:讓我們看看試試開啟這個檔案... 誒~好像並沒有這個檔案誒!
我們都知道檔案實際是位於 /storage/emulated/0/Android/data/com.example/sdcard/images/1.png
。
不再需要對這種情況提供支援
在 Android 11 中 Android
資料夾是真正的私有資料夾,這種做法不能正常工作。這麼做的應用程式一定會做出改變。因此,從 v4.4.0 起,對這種情況的支援被移除。
# 以非標準方式將檔案路徑傳遞給其他被隔離的應用程式
現在 ExampleApp 添加了向 ExampleSocial 分享圖片的功能(ExampleSocial 也是一個被隔離的應用程式)。很不幸 ExampleSocial 要求使用它的 SDK(這種做法也應該被拋棄!),這意味著又是直接傳遞檔案路徑,並且我們無法通過「修復應用程式間互動」功能改變傳遞的檔案路徑。
假設 ExampleSocial 的 SDK 是這樣工作:將圖片儲存至 tmp
資料夾,將檔案路徑傳遞給 ExampleSocial。
/storage/emulated/0
├───Android/data/com.example
│ └───sdcard <---- ExampleApp 的隔離儲存空間
│ └───tmp/shared_image
└───Android/data/com.social.example
│ └───sdcard <---- ExampleSocial 的隔離儲存空間
│ └───...
└...
顯然,對於 ExampleSocial,tmp
資料夾並不存在,因此分享會失敗。
要解決這個問題,我們需要建立一個「可訪問資料夾」-「其他程式的資料夾」規則。
來源應用程式:ExampleApp
目標應用程式:ExampleSocial
共享資料夾:tmp
這樣 ExampleSocial 就可以訪問來自 ExampleApp 的 tmp
資料夾。
# 如何建立自己的規則?
你需要拿起你的「武器」——「檔案監視」。檔案監視是增強模式的功能。
繼續上面的例子,在 ExampleApp 分享到 ExampleSocial 失敗後,你可以在檔案監視中看到來自 ExampleApp 和 ExampleSocial 的 tmp
資料夾的記錄。由此可以知道,你需要建立訪問 tmp
資料夾的規則。
# 使用線上規則
如果線上規則中已經有了需要的規則,你只需要直接新增它們。只有沒有規則的情況或是規則有錯誤時你才需要自己編寫規則。你還可以提交你的規則到線上規則庫(通過「上傳按鈕」)。
# 補充包
涉及 Xposed 模組
首先,你需要了解,Xposed 模組不止以模組應用程式本身執行,它還會在其他應用程式中執行。
比如一個名為 ExampleXposedModule 的模組有修改 ExampleApp 的功能,那麼它也會在 ExampleApp 中執行。如果 ExampleXposedModule 通過建立檔案的方式儲存設定,ExampleApp 就也需要去讀取儲存的檔案,就會產生與 ExampleApp 分享到 ExampleSocial 同樣的情況。
你需要做的還是藉助「檔案監視」監視瞭解哪些檔案被使用並建立對應的規則。
但是,最正確的做法應該是要求 Xposed 模組開發者做出更改!(要求模組開發者使用 ContentProvider
共享配置,或是直接將配置保存於目標應用程式的資料資料夾。)