今回は Xamarin.Forms でストレージの総容量と空き容量を取得する方法についてご紹介いたします。
iOS は SD カードもなく、OS のバージョン分岐もなかったおかげで簡単でしたが、Android では SD カードの取り扱いと OS バージョン毎にストレージに容量を計算する方法が異なり、大変苦労しました。実装方法に関しましては今回もデバイス特有の制御が必要となり、お馴染みの DependencyService を使用しています。
前提条件
・Windows10 Pro 64Bit 1903
・Visual Studio 2017 Community
・Xamarin 4.11.0.776 (NuGet Xamarin.Forms 3.4.0.1029999)
・macOS Mojave 10.14 / Xcode10.1 / Xamarin.iOS 12.0.0.10
1.PCLの記述方法
(1)PCL プロジェクト内に DependencyService で呼び出すためのインターフェースを配置します。
IDeviceService.cs
namespace AppName.Services
{
public interface IDeviceService
{
/// <summary>
/// ストレージ空き容量(byte)の取得
/// </summary>
long GetAvailableStorageSize();
/// <summary>
/// ストレージ総容量(byte)の取得
/// </summary>
long GetTotalStorageSize();
}
}
2.Androidの実装方法
(1)AndroidManifest.xml に外部ストレージを含むファイルアクセスに関する許可を記載します。
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
※ファイルプロバイダーの設定も別途必要かもしれません。
(2)Android プロジェクト内に DeviceService クラスを配置します。
Xamarin.Form v3 以降は Forms.Context が使用できなくなりましたので、予めMainActivity.OnCreate 内でインスタンスを取得して保持しておく必要があります。また、Android バージョン毎に実行結果が異なり、当ソースコードも総容量が若干低く見積られる端末があります。検証しながら、当ソースコードをブラッシュアップして参ります。
DeviceService.cs
using System;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Views;
using Xamarin.Forms;
using AppName.Droid.Services;
using AppName.Services;
[assembly: Dependency(typeof(DeviceService))]
namespace AppName.Droid.Services
{
public class DeviceService : IDeviceService
{
//MainActivity.csのOnCreate時にインスタンスを取得しておきます。
public static Activity MainActivity = null;
/// <summary>
/// ストレージ空き容量(byte)の取得
/// </summary>
public long GetAvailableStorageSize()
{
long size = 0;
//string externalPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
string externalPath = this.GetExternalStoragePath();
string internalPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath; // Android.OS.Environment.DataDirectory.AbsolutePath;
string rootPath = Android.OS.Environment.RootDirectory.AbsolutePath;
System.Diagnostics.Debug.WriteLine("externalPath:" + externalPath + " 空き容量:" + this.GetAvailableStorageSize(externalPath).ToString());
System.Diagnostics.Debug.WriteLine("internalPath:" + internalPath + " 空き容量:" + this.GetAvailableStorageSize(internalPath).ToString());
System.Diagnostics.Debug.WriteLine("rootPath :" + rootPath + " 空き容量:" + this.GetAvailableStorageSize(rootPath).ToString());
if (this.HasExternalStorage(externalPath))
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.P)
{
size += this.GetAvailableStorageSize(externalPath);
}
else
{
//size += Android.OS.Environment.ExternalStorageDirectory.FreeSpace;
size += Android.OS.Environment.GetExternalStoragePublicDirectory(externalPath).FreeSpace;
}
}
if (Build.VERSION.SdkInt <= BuildVersionCodes.P)
{
size += this.GetAvailableStorageSize(internalPath);
//size += this.GetAvailableStorageSize(rootPath);
}
else
{
//size += Android.OS.Environment.DataDirectory.FreeSpace;
//size += Android.OS.Environment.RootDirectory.FreeSpace;
size += Android.OS.Environment.ExternalStorageDirectory.FreeSpace;
}
return size;
}
private string _externalPath = String.Empty; //staticにするとSDカードを入れ替えた際にアプリを終了するまでパスが切り替わらない
/// <summary>
/// 外部ストレージのパスを取得する
/// (※ Android.OS.Environment.ExternalStorageDirectory.AbsolutePathで取得できないため)
/// </summary>
/// <returns></returns>
private string GetExternalStoragePath()
{
if (!String.IsNullOrEmpty(_externalPath))
{
return _externalPath;
}
string path = String.Empty;
//if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop &&
if (Build.VERSION.SdkInt <= BuildVersionCodes.P)
{
const string MountedListFile1 = "/system/etc/vold.fstab";
const string MountedDir1 = "dev_mount ext_sd ";
string[] list1 = System.IO.File.ReadAllLines(MountedListFile1);
path = list1.Where(r => r.StartsWith(MountedDir1)).FirstOrDefault();
if (!String.IsNullOrEmpty(path))
{
path = path.Replace(MountedDir1, "");
int index = path.IndexOf(" auto ");
if (index > 0 && index <= path.Length - 1)
{
path = path.Substring(0, index);
//foreach (Java.IO.File file in DeviceService.MainActivity.GetExternalFilesDirs(null))
//{
// if (file.AbsolutePath == path)
// {
// _externalPath = file.AbsolutePath;
// return _externalPath;
// }
//}
_externalPath = path;
return _externalPath;
}
}
const string MountedListFile2 = "/proc/mounts";
const string MountedDir2 = "/mnt/media_rw/";
List<string> list2 = System.IO.File.ReadAllLines(MountedListFile2).ToList();
string uuid = list2.Where(r => r.StartsWith(MountedDir2)).FirstOrDefault(); //"/mnt/media_rw/UUID /mnt/runtime/default/UUID sdcardfs rw,nosuid,nodev,noexec,noatime,fsuid…"
if (!String.IsNullOrEmpty(uuid))
{
uuid = uuid.Substring(uuid.IndexOf(MountedDir2) + MountedDir2.Length); //"UUID /mnt/runtime/default/UUID sdcardfs rw,nosuid,nodev,noexec,noatime,fsuid…"
uuid = uuid.Substring(0, uuid.IndexOf(" ")); //"UUID
Java.IO.File[] files = DeviceService.MainActivity.GetExternalFilesDirs(null);
foreach (Java.IO.File file in files)
{
if (file.AbsolutePath.Contains(uuid))
{
path = file.AbsolutePath; //"/storage/UUID/"
_externalPath = path.Substring(0, path.IndexOf(uuid) + uuid.Length); //"/storage/UUID"
return _externalPath;
}
}
}
}
//_externalPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
return _externalPath;
}
/// <summary>
/// パスを指定して空き容量を取得する(Androidバージョン9以下で有効)
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private long GetAvailableStorageSize(string path)
{
long size = 0;
try
{
if (!String.IsNullOrEmpty(path))
{
StatFs fs = new StatFs(path);
long blockSize = 0;
long availableBlock = 0;
if (Build.VERSION.SdkInt >= BuildVersionCodes.JellyBeanMr2)
{
blockSize = fs.BlockSizeLong;
availableBlock = fs.AvailableBlocksLong;
}
else
{
blockSize = fs.BlockSize;
availableBlock = fs.AvailableBlocks;
}
size = blockSize * availableBlock;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message + System.Environment.NewLine + ex.StackTrace);
}
return size;
}
/// <summary>
/// ストレージ総容量(byte)の取得
/// </summary>
public long GetTotalStorageSize()
{
long size = 0;
//string externalPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
string externalPath = this.GetExternalStoragePath();
string internalPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath; // Android.OS.Environment.DataDirectory.AbsolutePath;
string rootPath = Android.OS.Environment.RootDirectory.AbsolutePath;
//string etcPath = "/etc";
string mntPath = "/mnt";
string cachePath = Android.OS.Environment.DownloadCacheDirectory.AbsolutePath;
string procPath = "/proc/thread-self/fd";
System.Diagnostics.Debug.WriteLine("externalPath:" + externalPath + " 総容量:" + this.GetTotalStorageSize(externalPath).ToString());
System.Diagnostics.Debug.WriteLine("internalPath:" + internalPath + " 総容量:" + this.GetTotalStorageSize(internalPath).ToString());
System.Diagnostics.Debug.WriteLine("rootPath :" + rootPath + " 総容量:" + this.GetTotalStorageSize(rootPath).ToString());
//System.Diagnostics.Debug.WriteLine("etcPath :" + etcPath + " 総容量:" + this.GetTotalStorageSize(etcPath).ToString());
System.Diagnostics.Debug.WriteLine("mntPath :" + mntPath + " 総容量:" + this.GetTotalStorageSize(mntPath).ToString());
System.Diagnostics.Debug.WriteLine("cachePath :" + cachePath + " 総容量:" + this.GetTotalStorageSize(cachePath).ToString());
System.Diagnostics.Debug.WriteLine("procPath :" + procPath + " 総容量:" + this.GetTotalStorageSize(procPath).ToString());
if (this.HasExternalStorage(externalPath))
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.P)
{
size += this.GetTotalStorageSize(externalPath); //15700705280
}
else
{
//size += Android.OS.Environment.ExternalStorageDirectory.TotalSpace;
size += Android.OS.Environment.GetExternalStoragePublicDirectory(externalPath).TotalSpace;
}
}
if (Build.VERSION.SdkInt <= BuildVersionCodes.P)
{
size += this.GetTotalStorageSize(internalPath);
size += this.GetTotalStorageSize(rootPath);
//size += this.GetTotalStorageSize(etcPath);
size += this.GetTotalStorageSize(mntPath);
//size += this.GetTotalStorageSize(cachePath);
//size += this.GetTotalStorageSize(procPath);
}
else
{
//size += Android.OS.Environment.DataDirectory.TotalSpace;
//size += Android.OS.Environment.RootDirectory.TotalSpace;
size += Android.OS.Environment.ExternalStorageDirectory.TotalSpace;
}
return size;
}
/// <summary>
/// パスを指定して総容量を取得する(Androidバージョン9以下で有効)
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private long GetTotalStorageSize(string path)
{
long size = 0;
try
{
if (!String.IsNullOrEmpty(path))
{
StatFs fs = new StatFs(path);
long blockSize = 0;
long blockCount = 0;
if (Build.VERSION.SdkInt >= BuildVersionCodes.JellyBeanMr2)
{
blockSize = fs.BlockSizeLong;
blockCount = fs.BlockCountLong;
}
else
{
blockSize = fs.BlockSize;
blockCount = fs.BlockCount;
}
size = blockSize * blockCount;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message + System.Environment.NewLine + ex.StackTrace);
}
return size;
}
/// <summary>
/// 外部メディアを接続しているかどうか判定する
/// </summary>
/// <returns></returns>
private bool HasExternalStorage(string externalPath)
{
if (String.IsNullOrEmpty(externalPath))
{
return false;
}
return true;
//以下の判定式が正常にリターンされません。
//Java.IO.File file = new Java.IO.File(externalPath);
// //Android.OS.Environment.IsExternalStorageRemovable &&
//if (Android.OS.Environment.IsExternalStorageEmulated &&
// Android.OS.Environment.GetExternalStorageState(file) == Android.OS.Environment.MediaMounted)
//{
// return true;
//}
//return false;
}
}
}
3.iOSの実装方法
(1)iOS プロジェクト内に DeviceService クラスを配置します。
DeviceService.cs
using System;
using UIKit;
using Foundation;
using Xamarin.Forms;
using AppName.iOS.Services;
using AppName.Services;
[assembly: Dependency(typeof(DeviceService))]
namespace AppName.iOS.Services
{
public class DeviceService : IDeviceService
{
/// <summary>
/// ストレージ空き容量(byte)の取得
/// </summary>
/// <returns>空き容量(byte)</returns>
public long GetAvailableStorageSize()
{
long size = 0;
var path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var attributes = Foundation.NSFileManager.DefaultManager.GetFileSystemAttributes(path);
if (attributes != null)
{
size = (long)attributes.FreeSize;
}
return size;
}
/// <summary>
/// ストレージ総容量(byte)の取得
/// </summary>
/// <returns>総容量(byte)</returns>
public long GetTotalStorageSize()
{
long size = 0;
var path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var attributes = Foundation.NSFileManager.DefaultManager.GetFileSystemAttributes(path);
if (attributes != null)
{
size = (long)attributes.Size;
}
return size;
}
}
}
4.使用方法
PCL プロジェクトの中の任意のページに記述します。
ストレージ容量を取得するボタンを用意して DependencyService で呼び出すように記述します。
TestPage.xaml.cs
using AppName.Services;
using Xamarin.Forms;
public class TestPage : ContentPage
{
/// <summary>
/// ストレージの容量を取得する
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
async void btnStorageSize_Click(object sender, EventArgs e)
{
long value1 = DependencyService.Get<IDeviceService>().GetAvailableStorageSize();
long value2 = DependencyService.Get<IDeviceService>().GetTotalStorageSize();
const int thousand = 1000; //1024よりもなぜかどこのスマホ実機でも1000で割り戻した値が表示されている。
await DisplayAlert(String.Format("空き容量:{0}GB", (value1 / Math.Pow(thousand, 3)).ToString("N2")) + System.Environment.NewLine +
String.Format("総容量 :{0}GB", (value2 / Math.Pow(thousand, 3)).ToString("N2")), "GetAvailableStorageSize Test", "OK");
}
}
最後までお読みいただきありがとうございます。
当ブログの内容をまとめた Xamarin逆引きメニュー は以下のURLからご覧になれます。
http://itblogdsi.blog.fc2.com/blog-entry-81.html
- 関連記事
-