Xamarin : Android 取得媒體儲存裝置 retrieve emulated or removable storage path

這是一個很觀念的問題,其實android對於儲存位置的概念跟Windows是不一樣的。

我們所知的 Windows 的儲存媒體,通常都會是以『磁碟機』(Device)概念呈現的,每個 Device 都會被賦予一個『磁碟機代號』(Device Letter),像是 『C:\』『 D:\』 等等。

但是除了 Windows 以外的世界,基本上會是用『路徑』來呈現,由其 Android 的 Dalvik 系統本身就是 java 的支線,所以儲存空間會像是 『/Storage/dev0』『/Storage/dev1』之類的呈現法。

瞭解了以上的概念之後,再來瞭解 Android 對於儲存空間的認定。

一般我們在看儲存位置的時候,直覺上會把手機的儲存空間分為『內建』『內部』『外部』『擴充』等等的用法,因為 Android 有分為內建記憶空間和一個可以擴充的 SD 卡(或是TF卡)。

而我們程式在開發時相關文件都會去 Google 找類似 Internal 或是 External 類的單字搜尋,但實際上找到的文件範例基本上都會牛頭不對馬嘴,最大的原因應該會出在 Android 開發團隊對於這種一般人認定的常規式用法式有很大出入的。

Android 對於內建記憶空間和擴充的SD卡認定如下:

Emulated - 內部記憶空間,用的單字是『仿真』,而不是 Internal (內部),是因為 Android 真正內部記憶體指的是ROM,也就是作業系統OS存在的地方,而ROM本身不能經常寫入,因為有寫入壽命限制,但是作為一個作業系統難免會有許多設定或變更或紀錄,那這樣要存在哪裡呢?事實上, Android 手機真正可以讀寫的內建空間其實也是一個 SD 卡,指是手機製造商會在手機裡面直接燒上SD的儲存晶片作為內建儲存空間,凡是OS以外的程式APP或是紀錄都會存在此處,所以把這種內建 SD 記憶體稱為『仿真記憶體』。

Removable - 擴充的記憶體(SD卡或TF卡),這被稱為『可移除式』媒體,名字簡而易瞭,代表這記憶體可以被移除或是安插。

External - 而這個『外部』的意思就是泛指除了 OS 的 ROM 之外的都會稱為 External,所以上面兩個『Emulated』、『Removable』都算在 External 下面。而 External 下只有一個 storages,從這個 storages 下才會開始區分各類儲存空間。

所以記憶體歸類會像下面結構:


先看看程式樣本,再來解說:



using Android.App;
using Android.Widget;
using Android.OS;
using System;

namespace StoragePath
{
    [Activity(Label = "StoragePath", MainLauncher = true)]
    public class MainActivity : Activity
    {
        TextView tv1;
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            //取得顯示視窗
            tv1 = FindViewById<TextView>(Resource.Id.textView1);
            //讓TextView可以Scroll(要一併設定 TextView 的 ScrollBar 和 MaxLines 屬性)
            tv1.MovementMethod = new Android.Text.Method.ScrollingMovementMethod();

            tv1.Text = "AbsolutePath:\r\n";

            //顯示實際取得的路徑
            //這種取得路徑樣式皆為 [StorageMediaPath]/Android/data/path.YourAppName/files
            Java.IO.File[] dirs = Application.Context.GetExternalFilesDirs(null);
            foreach(Java.IO.File path in dirs)
            {
                tv1.Text += path.AbsolutePath + "\r\n";
            }
            tv1.Text += "\r\n== AbsolutePath ==\r\n";
            tv1.Text += "內建媒體:\r\n";
            string[] Storages = GetStorageDirectory(false);
            foreach(string path in Storages)
            {
                tv1.Text += path + "\r\n";
            }
            tv1.Text += "\r\n可移除媒體:\r\n";
            Storages = GetStorageDirectory(true);
            foreach (string path in Storages)
            {
                tv1.Text += path + "\r\n";
            }
        }

        public string[] GetStorageDirectory(bool getRemovable)
        {
            int Index = 0;
            string[] Result = { };

            //取得應用程式可以使用的所有路徑
            //利用這功能可以取得所有可存取的媒體位置
            Java.IO.File[] dirs = Application.Context.GetExternalFilesDirs(null);
            foreach (Java.IO.File path in dirs)
            {
                //判斷是內建還是可移除媒體
                bool IsEmulated = Android.OS.Environment.InvokeIsExternalStorageEmulated(path);
                bool IsRemovable = Android.OS.Environment.InvokeIsExternalStorageRemovable(path);

                //如果指定查出可移除媒體儲存位置
                if (getRemovable)
                {
                    if (IsRemovable && !IsEmulated)
                    {
                        Array.Resize<string>(ref Result, Index + 1);
                        //去除路徑中不必要的元素
                        Result[Index] = path.AbsolutePath.Substring(0, path.AbsolutePath.IndexOf(@"/Android/data"));
                        Index++;
                    }
                }
                else
                {
                    //取得內建媒體儲存位置
                    if (IsEmulated)
                    {
                        Array.Resize<string>(ref Result, Index + 1);
                        //去除路徑中不必要的元素
                        Result[Index] = path.AbsolutePath.Substring(0, path.AbsolutePath.IndexOf(@"/Android/data"));
                        Index++;
                    }
                }
            }

            return Result;
        }
    }
}


首先,我們可以用 GetExternalFilesDirs 來取得所有可用路徑,這個功能可以讓APP取得所有可用的儲存路徑,這也包括了內部和外部的儲存空間

Java.IO.File[] dirs = Application.Context.GetExternalFilesDirs(null);

然後,再利用

InvokeIsExternalStorageEmulated

InvokeIsExternalStorageRemovable

來判斷屬於哪一種類型的儲存位置

當然這儲存位置會包含APP的儲存路徑,
例如:/storage/emulated/0/Android/data/path.YourAppName/files

最後把這路徑去除即可得到實際儲存空間的起始位置

path.AbsolutePath.Substring(0, path.AbsolutePath.IndexOf(@"/Android/data"))
















留言

這個網誌中的熱門文章

【研究】列印的條碼為什麼很難刷(掃描)

統一發票列印小程式

C# 使用 Process.Start 執行外部程式