要在 Package.appxmanifest 宣告 <uap:Capability Name="removableStorage" /> 才可以使用,如下圖:
<App capability declarations>定義宣告這個特性可以讓 app 能存取在 remoable storage(例如:USB keys 或 external hard drives)中宣告的檔案類型或是使用 file picker 來提供各種類型的檔案。
在 Mobile 衹有宣告使用 removable Storage 是不夠的,要記得注冊要處理的檔案類型,例如:
不然會遇到無法存取的 System.UnauthorizedAccessException 。
存取 SD card 的方式主要兩種:
- File/Folder Picker
- Windows.Storage API
- 或是注冊特定的 fileTypeAssociation 關聯想要處理的檔案
[注意]
- 當 App 把檔案存放在 SD card,這些檔案不會被加密,其他 App 可以直接存取
- 接上 SD card 後,App 衹能讀寫注冊在 app manifest 中的特定檔案類型的檔案,跟 Desktop 連綫後也可以直接用 File Explorer 操作
- SD card 中系統的目錄或是檔案依舊無法看見或是讀取,包括被隱藏的檔案
- 如果 App 是直接被安裝在 SD card 裏面,寫在 LocalFolder 下的内容依舊會被機密,其他 App 不能使用
- 需要存取 Video, Music, Picture Library 内容的話,一樣要記得宣告對應的 capabilities,可參考<Files and folders in the Music, Pictures, and Videos libraries>
- 無法直接使用 KnownFolders.DocumentsLibrary,但是可透過 file system(或是 fileopenpicker, folderpicker) 與它連結
- 操作 SD Card 時,建議使用 background thread 去讀取或是寫入檔案,因爲 SD Card 大部分容量都很大,這樣做法可以減少畫面的等待
- 建議使用 folder picker 或是 file picker 取得特定的對象,減少大量查詢所耗的效能
- 利用 KnowsFolders.RemovableDevices 取得的對象,有兩個狀況:
- GetFilesAsync method 衹會得到在 app manifest 注冊要處理的檔案類型
- GetFileFromPathAsync method 時如果是給予未注冊的檔案類型,將會收到失敗
[範例程式]
1. 檢查設備是否已經有接上 SD card:
public static async Task<bool> CheckHasExternalStorage()
{
try
{
// 取得 logic root external folder
StorageFolder externalDevices = KnownFolders.RemovableDevices;
// 取得可用的 folders, 如果有 SD card 預設是第一個,
// 但是 Desktop 可以接上多個 external storages 所以用 Count 檢查
var folders = await externalDevices.GetFoldersAsync();
return folders.Count > 0;
}
catch (Exception)
{
return false;
}
}
KnownFolders.RemovableDevices folder 是一個 logical root StorageFolder, 代表目前設備有連結上的 removable devices,如果沒有 SD card 拿到的 Count 會是 0 或是 null。
預設 SD card 接上設備後預設第一個(或是唯一);如果是 Destop 的設備就不一定會是唯一值,需要特別處理有多少設備已經被連上。
2. 取得 SD card 的唯一識別值:
private async Task<bool> CheckSDCardId()
{
StorageFolder externalDevices = KnownFolders.RemovableDevices;
// 取得 SD card
StorageFolder sdCard = (await externalDevices.GetFoldersAsync()).FirstOrDefault();
if (sdCard != null)
{
// 取得 ExternalStorageId
var allProperties = sdCard.Properties;
IEnumerable<string> propertiesToRetrieve = new List<string> { "WindowsPhone.ExternalStorageId" };
var storageIdProperties = await allProperties.RetrievePropertiesAsync(propertiesToRetrieve);
string cardId = (string)storageIdProperties["WindowsPhone.ExternalStorageId"];
// 利用 ID 做一些判斷的邏輯或是相關的應用
}
}
當 SD card 第一次被安裝時,系統會自動生成一個唯一識別碼,并將它寫成一個檔案儲存在 SD card 根目錄中的 WPSystem 目錄中。(但是這個在 UWP 目前無法識別)
App 可以使用這個 ID 確定它是否爲能夠識別這個卡片。如果 App 可以識別這個卡片,它就能從中推遲先前完成的某些操作。然而卡片中的内容可能被最後一個連接的 App 所改變。
要取得這個識別 ID 的 key 爲:WindowsPhone.ExternalStorageId 。更多相關的應用可以參考<WP8 - 操作External Storage API>的介紹。
3. 利用 FolderPicker 取得指定的目錄,并且加入 FutureAccessList 讓 App 有能力存取裏面的内容:
由於 App 運作本身都是在 sandbox 的環境,預設 App 可讀寫的範圍衹有 LocalFolder 或是特定的範圍,確保 App 不會去影響其他系統或是不屬于這個 App 本身範圍的檔案與目錄。
換個角度來看,利用 FileOpenPicker / FolderPicker 是由用戶自己選擇提供哪些檔案與目錄給 App 有能力讀寫的話就不算在 sandox 的限制範圍内了。
因此,要怎麽讓 App 有能力存取到由 FileOpenPicker / FolderPicker 所選擇的目錄或是檔案呢,需要搭配 StorageApplicationPermissions 來指定。
系統提供一個 static properties 讓 app 可以設定專屬的 most recently used list (MRU, 最常使用的項目清單), 與 future-access list (將目錄檔案加入可以被存取的對象清單)。
Property | Description |
FutureAccessList | Read-only. Gets an object that represents a list that an app maintains so that the app can store files and/or locations (like folders) and easily access these items in the future. StorageItemAccessList 經由 picking 得到的 files 或 folders,代表用戶認同 app 有權利存取它們,所以把得到的 StorageFile 或 StorageFolder 加到 future-access list,下一次 app 要操作它們就不需要再詢問過用戶。 This list can store up to 1000 items and must be maintained by the app. (重點) |
MostRecentlyUsedList | Read-only. Gets an object that represents a list that an app can use to track the files and/or locations (like folders) that the app has accessed most recently. StorageItemMostRecentlyUsedList 可以存放 app 最常使用的 IStorageItem。最多 25 個,超過的時候最先被加入的會被移除,處罰 ItemRemoved 事件。 This list can store up to 25 items . While the app must add items to the MRU in order to track them, Windows maintains the 25-item limit by removing stale items if necessary. |
建議 app 要去維護加入 future-access list 或 most recently used list 得到的 token,它可以讓 app 直接快速得到 IStorageItem。更多關於檔案或是目錄存取權限的説明,可以參考<File access permissions>。
以 FolderPicker 爲例,讓用戶選擇 folder 加入讓 app 有能力可以控制它,并且加入維持有 1000 個數量的限制(利用時間來判斷):
public static async Task<StorageFolder> GetFolderFromPicker()
{
StorageFolder folder = null;
try
{
// 最大上限 1000, 要自己手動管理
RemoveOldestFolderOfAccessList();
var folderPicker = new FolderPicker();
folderPicker.SuggestedStartLocation = PickerLocationId.MusicLibrary;
folderPicker.FileTypeFilter.Add("*");
folder = await folderPicker.PickSingleFolderAsync();
if (folder != null)
{
// Application now has read/write access to all contents in the picked folder (including other sub-folder contents)
// 相同目錄拿到的 token 是一樣的
string token = StorageApplicationPermissions.FutureAccessList.Add(folder, DateTime.UtcNow.Ticks.ToString());
}
}
catch (Exception)
{
}
return folder;
}
private static void RemoveOldestFolderOfAccessList(int removeCount = 2)
{
// https://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh972344(v=win.10)
if (StorageApplicationPermissions.FutureAccessList.Entries.Count >= StorageApplicationPermissions.FutureAccessList.MaximumItemsAllowed)
{
var removeItems = StorageApplicationPermissions.FutureAccessList.Entries.Select(x =>
{
long ticks = DateTime.UtcNow.Ticks;
long.TryParse(x.Metadata, out ticks);
return new Tuple<string, long>(x.Token, ticks);
}).OrderBy(x => x).Take(removeCount);
foreach (var item in removeItems)
{
StorageApplicationPermissions.FutureAccessList.Remove(item.Item1);
}
}
}
[補充]
- 支援 BackgroundTask for Audio 的 App,利用 FileOpenPicker 離開 App,狀態馬上被 Suspended,在 Picker 返回時 App 已經從 OnResume 或 Launch 開始
參考<App Lifecycle - Keep Apps Alive with Background Tasks and Extended Execution>來延長 OnSuspened 的執行時間,等待用戶選擇回來(最長約 30 seconds 内,超過一樣沒有用),但是不是每次請求延長都會成功,還是要看當時系統是否有足夠的資源而定。
例如:
private async void StartTbTNavigationSession()
{
using (var session = new ExtendedExecutionSession())
{
session.Reason = ExtendedExecutionReason.LocationTracking;
session.Description = "Turn By Turn Navigation";
session.Revoked += session_Revoked;
var result = await session.RequestExtensionAsync();
if (result == ExtendedExecutionResult.Denied
{
ShowUserWarning("Background location tracking not available");
}
// Do Navigation
var completionTime = await DoNavigationSessionAsync(session);
}
}
- 如何取得目錄可用的與已用的空間
/// 取得指令目錄路徑的 可用空間 與 全部空間,單位: MB.
/// </summary>
private static async Task<Tuple<long, long>> GetFolderSpaceInfo(StorageFolder storageFolder)
{
try
{
// 關鍵字 System.FreeSpace 與 System.Capacity
var retrivedProperties = await storageFolder.Properties.RetrievePropertiesAsync(new string[] { "System.FreeSpace", "System.Capacity" });
long free = Convert.ToInt64(retrivedProperties["System.FreeSpace"]);
free = free / 1048576;
long capacity = Convert.ToInt64(retrivedProperties["System.Capacity"]);
capacity = capacity / 1048576;
return new Tuple<long, long>(free, capacity);
}
catch (Exception)
{
return null;
}
}
System.FreeSpace 與 System.Capacity 這兩個關鍵字可以從 StorageFolder 中取得可用與已用的空間資料。還有那些可以用的關鍵字呢,可以參考<Discover Properties of Storage Items in Windows Store Apps>與<Windows Properties>的介紹。
======
可以對 SD card 有讀寫的能力後,讓 App 可以操作的範圍就更廣闊了。也不會再遇到 internal storage 空間不足然後要把 App 移除或是清理不要的檔案,因爲可以在 App 裏面加入把一些比較大型的檔案搬到用戶覺得可以存放的地方,再藉由 Windows.Storage APIs 才讀取就可以了。
希望對大家有所幫助。
References:
- App Lifecycle - Keep Apps Alive with Background Tasks and Extended Execution
- Files, folders, and libraries
- Open files and folders with a picker
- How to continue your Windows Phone app after calling a file picker
- File access permissions
- [UWP]FileOpenPicker Bugs in WIn 10 Mobile when not debugging
- Access the SD card
- Files and folders in the Music, Pictures, and Videos libraries
- Discover Properties of Storage Items in Windows Store Apps
- Windows Properties (重要)
- How to track recently used files and folders
- File picker sample and the File access sample
- Quickstart: Accessing files with file pickers
- How to save files through file pickers
沒有留言:
張貼留言