# 教程

# 啟用隔離後會發生什麼?

假設存在一個應用程式 ExampleApp(包名為 com.example),它使用了有濫用儲存空間行為的 SDK(假設它會建立 bad_sdk 資料夾)。那麼在授予 ExampleApp 儲存權限後,你的儲存空間將會是這樣的。

/storage/emulated/0
├───Android
├───bad_sdk
├───DCIM
├───Donwload
├───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 中的檔案並沒有所有者,我們無法自動地幫助你移動或刪除已有的檔案。

  • 使用者檔案,如圖片

    按照上面推薦的方式進行整理。

  • 其他

    對於絕大多數應用程式,刪除先前的檔案不會產生問題。但以防萬一,我們建議你跟隨下面的步驟。

    1. 建立一個臨時資料夾並將它們都移入其中。
    2. 執行所有被隔離的應用程式。
    3. 如果有應用程式因為無法找到之前的檔案而不正常工作,你可以藉助「檢視隔離儲存空間」功能瞭解特定資料夾可能由誰建立並由此移動那些資料夾。
    4. 所有應用程式都正常工作後,刪除臨時資料夾。
忘掉「清理程式」

一些「清理程式」有清理特定應用程式建立的檔案的功能(那些檔案不在資料資料夾中,解除安裝後不會被刪除),這種功能在隔離後因為檔案位置變化而無法使用。

但是在隔離後,應用程式建立的檔案都保存於隔離儲存空間內(位於其資料資料夾),它們會在解除安裝後被刪除。另外,你可以將隔離儲存空間位置設定為快取資料夾,快取資料夾會被 Android 系統自動清理。

忘掉「清理程式」吧,它們已不再有用。並且,它們從一開始就不應該存在。

「備份程式」不會受影響(甚至可以備份更多內容)

「備份程式」只能備份應用程式的數據資料夾中的檔案。

隔離後,應用程式建立的檔案都儲存於隔離儲存空間內(位於其數據資料夾),因此它們也能被備份了。注意,你可能需要在你的「備份程式」中啟用類似於「備份外部資料」的選項。

使用增強模式

增強模式是一個非常重要的組成部分,許多問題在使用增強模式的情況下才可以解決。

我們建議,當你確認一切正常後就開始嘗試增強模式(在應用程式內可以看到如何使用增強模式)。

# 應用程式不正常工作的解決方案

# 應用程式需要訪問特定的檔案

現在 ExampleApp 有了傳送圖片功能。但是因為被隔離,你無法在 ExampleApp 中找到你的圖片。

要解決這個問題,我們只需要關注「可訪問資料夾」選項中的「共享資料夾」部分。假設我們選擇了 DCIMPictures 資料夾,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 資料夾)而不是直接改變檔案的位置。

另一種「解決方案」

https://github.com/RikkaApps/SaveCopy

# 配合另外的應用程式使用時出現問題

# 使用其他應用程式檢視檔案(標準方式,即 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 共享配置,或是直接將配置保存於目標應用程式的資料資料夾。)