fc2ブログ

記事一覧

ストレージの総容量・空き容量を取得する方法 | Xamarin.Forms


今回は 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



関連記事

コメント

Re:Re: 現在だと『DeviceService.cs』が機能しない
お返事いただきありがとうございます。

こちらの未熟のためにお手数頂き申し訳ありません。
大変助かりました。

Re: 現在だと『DeviceService.cs』が機能しない
Mahiroさん

当ブログをご覧いただきありがとうございます。

IFileService は独自の DependencyService でした。廃止されているわけでも機能しないわけでもなく、単純に System.IO.File.ReadAllLines を DependencyService から呼び出しているだけの関数です。記事を修正しましたので、ご確認頂けると思います。
尚、このソースコードは私が現在公開しているアプリ内にて正常に動作しているソースコードになります。

また、当ブログでは基本的に既に掲載したソースコードのブラッシュアップも行っており、現在機能しないソースコードは掲載しておりません。(Obsolute 扱いになっているコードは若干存在しますが、動作しています。)

大変お手数ではございますが、再度ご確認頂きますようお願い申し上げます。
現在だと『DeviceService.cs』が機能しない
IFileServiceが廃止されていて、今はもう『DeviceService.cs』が機能しないみたいですね。

それとは別にxamarinは情報が少なく、とても参考になりました。
ありがとうございます。

現状でも機能する版が欲しいなぁなんて....これは贅沢な要求ですね(笑)
コメントの投稿

※名前とタイトルが入力されていないコメントでは他のコメントとの区別ができません。

 入力されていないコメントには返信しませんのであらかじめご了承くださいませ。

※ニックネームでも良いので必ずご入力ください。

    

※必ずご入力ください。

    
    

※必ずご入力ください。

※技術的な質問には環境やエラーについて正確かつ詳細にお教えください。

・正確なエラーの内容

・Windowsのバージョン番号

・Visual Studioのバージョン

・機器の型番

・アプリやソフトのバージョン

    

カテゴリ別記事一覧

広告

プロフィール

石河 純


著者名 :石河 純
自己紹介:素人上がりのIT技術者。趣味は卓球・車・ボウリング

IT関連の知識はざっくりとこんな感じです。
【OS関連】
WindowsServer: 2012/2008R2/2003/2000/NT4
Windows: 10/8/7/XP/2000/me/NT4/98
Linux: CentOS RedHatLinux9
Mac: macOS Catalina 10.15 / Mojave 10.14 / High Sierra 10.13 / Sierra 10.12 / OSX Lion 10.7.5 / OSX Snow Leopard 10.6.8
【言語】
VB.net ASP.NET C#.net Java VBA
Xamarin.Forms
【データベース】
Oracle 10g/9i
SQLServer 2016/2008R2/2005/2000
SQLAnywhere 16/11/8
【BI/レポートツール】
Cognos ReportNet (IBM)
Microsoft PowerBI
ActiveReport (GrapeCity)
CrystalReport
【OCX関連】
GrapeCity InputMan SPREAD MultiRow GridView
【ネットワーク関連】
CCNP シスコ技術者認定
Cisco Catalyst シリーズ
Yamaha RTXシリーズ
FireWall関連
【WEB関連】
SEO SEM CSS jQuery IIS6/7 apache2

休みの日は卓球をやっています。
現在、卓球用品通販ショップは休業中です。