第4回 Input from and Output to Files / ファイルへの入出力

最終更新日:2025年4月29日

ホームへ戻る

本日の提出課題一覧

  1. 演習問題4_1(必須)
  2. 演習問題4_2(必須)
  3. 演習問題4_3(必須)

演習問題4_1: Write a text file(必須)

「Androidストレージ、内部ストレージへアクセスする」などの説明を参考に、以下の内容を実施せよ。

  1. SetActivity Class において、Save ボタンをタッチした際、演習問題3_3で記録した Bundleの中身 をテキストファイルに保存させる。Bundle変数に対して toString() メソッドを用いることで、図2にあるような文字列に変換できる。
    ファイル書式は「xxsetactivity_pref-20210427_150500.txt」になるようにすること。
    • "xx" は各自の名前の頭文字
    • "20210427" は保存した年月日(例:2021年4月27日)
    • "150500" は保存した時刻(例:15時5分0秒)
    • 現在の年月日と時刻は Date class のオブジェクトから 取得できる。
    • Date オブジェクトを好みの文字列書式に出力するため、SimpleDateFormat classを用いる(使い方は先のリンクから調べること)。
  2. MainActivity Class において、メニューバーの「SAVE」メニューを推した際、演習問題2_3で実装した“クリックしたボタンとメニューの名前をTextViewに表示する”の TextView オブジェクトの中身をテキストファイルに保存させる(結果は図3を参照)。
    ファイル書式は「xxmainactivity_content-20210427_150500.txt」になるようにすること(“20210427_150500”は「a.」の指示と同様、クリック日時になるようにする)。
device_explorer_data_files.png
図1.保存後、Device Exlplorer にて確認できるアプリケーションのデータファイル。
preferences_file.png
図2.Preference の中身を保存したテキストファイル
content_file.png
図3.TextViewの中身を保存したテキストファイル

Androidのストレージ

Android は、他のプラットフォームのディスクベース ファイル システムと同様のファイル システムを使用しています。システムには、アプリデータの保存方法として次のような選択肢が用意されている(Android Developers Guideを参照)。

アプリ固有のファイル

内部ストレージの場合、許可(PERMISSION)は不要。
外部ストレージのファイルにアクセスする場合、READ_EXTERNAL_STORAGE または WRITE_EXTERNAL_STORAGE の許可(PERMISSION)が必要。

メディア

MediaStore APIを用いる。
基本的に、ファイルにアクセスする場合、READ_EXTERNAL_STORAGE または WRITE_EXTERNAL_STORAGE の許可(PERMISSION)が必要。

アプリ設定

SharedPreferenceKey - Value ペアを用いる

構造化データを保存するため、Room という永続ライブラリを用いる。

アプリ固有のファイルにアクセスする

多くの場合、アプリが作成するファイルは、他のアプリがアクセスする必要のないファイルまたはアクセスすべきでないファイルです。このようなアプリ固有のファイルには、以下の保存場所が用意されています。
ユーザーがアプリをアンインストールすると、アプリ固有のストレージに保存されているファイルは削除されます。そのため、アプリから独立して保持されることをユーザーが期待するファイルは、このストレージに保存すべきではありません。たとえば、ユーザーがアプリで写真を撮影した場合、通常はアプリをアンインストールした後でも写真にアクセスすることを望みます。その種のファイルは、共有ストレージを使用して適切なメディア コレクションに保存する必要があります。

内部ストレージへアクセスする

内部ストレージ内にはアプリごとにディレクトリが用意されており、アプリはそこでアプリ固有のファイルを整理できます。1 つのディレクトリはアプリの永続ファイル用に設計されています。
もう 1 つのディレクトリにはアプリのキャッシュ ファイルを格納します。これらのディレクトリ内のファイルを読み書きするためのシステム権限は必要ありません。

他のアプリは内部ストレージ内に保存されているファイルにアクセスできません。
したがって、内部ストレージは他のアプリがアクセスすべきでないアプリデータを保存する場所に適しています。

ただし、これらのディレクトリは容量が小さいことが多いため、注意が必要です。
アプリ固有のファイルを内部ストレージに書き込む前に、デバイスの空き領域を問い合わせる必要があります。

アプリの通常の永続ファイルは、コンテキスト オブジェクトの filesDir プロパティを使用してアクセスできるディレクトリにあります。
フレームワークには、このディレクトリ内のファイルにアクセスして保存する方法がいくつか用意されています。

File API を使用すると、ファイルにアクセスして保存できます。


	String filename = "myfile.txt";
	File file = new File(getFilesDir(), filename);

File API を使用する代わりに、openFileOutput() を呼び出して、filesDir ディレクトリ内のファイルへの書き込みを行う FileOutputStream を取得することもできます。
次のコード スニペットは、ファイルにテキストを書き込む方法を示しています。


	String fileContents = "Hello world!";
	try {
		FileOutputStream fos = openFileOutput(filename, Context.MODE_PRIVATE)
		fos.write(fileContents.getBytes());
	}

fileList() を呼び出すことにより、filesDir ディレクトリ内のすべてのファイル名を含む配列を取得できます。次のコード スニペットをご覧ください。


	File.createTempFile(filename, null, getCacheDir());
アプリは、コンテキスト オブジェクトの cacheDir プロパティと File API を次のように使用して、このディレクトリ内のファイルにアクセスします。

	File cacheFile = new File(getCacheDir(), filename);

キャッシュ ファイルは随時 Android によって自動的に削除されますが、だからといってキャッシュ ファイルのクリーンアップをシステムにまかせるべきではありません。内部ストレージ内のアプリのキャッシュ ファイルを常にメンテナンスする必要があります。
内部ストレージ内のキャッシュ ディレクトリからファイルを削除するには、次のいずれかのメソッドを使用します。

アプリ固有のファイルを保存するのに十分な容量が内部ストレージにない場合は、代わりに外部ストレージの使用を検討してください。外部ストレージには、特定のアプリ内でのみユーザーに価値を提供するファイルを整理するためのディレクトリが用意されています。1 つのディレクトリはアプリの永続ファイル用に設計されています。もう 1 つのディレクトリにはアプリのキャッシュ ファイルを格納します。

Android 9(API レベル 28)以下を実行するデバイスでは、適切なストレージ権限を持っていれば、どのアプリも外部ストレージ内のアプリ固有のファイルにアクセスできます。ユーザーがファイルをきめ細かく管理して整理できるように、Android 10(API レベル 29)以上をターゲットとするアプリには、外部ストレージに対する限定的なアクセス権がデフォルトで付与されます(そのようなストレージを対象範囲別ストレージと呼びます)。対象範囲別ストレージを有効にすると、アプリは他のアプリに属するアプリ固有のディレクトリにアクセスできなくなります。

外部ストレージはユーザーが削除できる物理ボリューム上にあるため、外部ストレージとの間でアプリ固有のデータの読み取りまたは書き込みを行う前に、ボリュームにアクセスできることを確認する必要があります。
ボリュームの状態を問い合わせるには、Environment.getExternalStorageState() を呼び出します。返された状態が MEDIA_MOUNTED の場合は、外部ストレージ内のアプリ固有のファイルの読み取りと書き込みを行うことができます。MEDIA_MOUNTED_READ_ONLY の場合は、それらのファイルの読み取りのみを行うことができます。
たとえば、以下のメソッドはストレージが使用可能かどうかを判断するのに役立ちます。


	// Checks if a volume containing external storage is available for read and write.
	private boolean isExternalStorageWritable() {
		return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED;
	}
	// Checks if a volume containing external storage is available to at least read.
	private boolean isExternalStorageReadable() {
    	return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED ||
            Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED_READ_ONLY;
	}

永続ファイルにアクセスする

外部ストレージからアプリ固有のファイルにアクセスするには、次のコード スニペットに示すように、getExternalFilesDir() を呼び出します。

外部キャッシュ ディレクトリからファイルを削除するには、ファイルを表す File オブジェクトで delete() メソッドを使用します。

Media Contents のように、特定のアプリ内でのみユーザーに価値を提供するメディア ファイルを使用する場合は、次のコード スニペットに示すように、外部ストレージ内のアプリ固有のディレクトリに保存することをおすすめします。

演習問題4_2: Access to application's files(必須)

「ファイルリストの取得と表示」の説明を参考に、以下の内容を実施せよ。

  1. menu_action_bar.xml にある「Preview」メニュを「Open」に置き換える(表示される文字列とIDの変更)。
  2. 新しいレイアウトファイル、open_layout.xml を作成し、その中に ListView コンポネントを入れる(「Palette → Legacy」から見つかる) 。
  3. 「Open」 メニューをタッチした時、MainActivity において、setContentViewメソッドを用いて、 content view が open_layout になるようにする。
  4. ListView にアプリで保存したファイルのリストが表示されるようにする(図4を参照)。
files_list.png
図4.これまで保存したファイルのリスト表示結果。

ファイルリストの取得と表示 / Get and Display Files List

fileList() を呼び出すことにより、アプリケーションの file directory 内のすべてのファイル名を含む配列を取得できる。

	String[] files = fileList();	

UI にある情報のリスト(配列)を表示したいとき、ListView を用いる。ListView とデータを結びつくため、文字列データは ArrayList として保持する。
ListView とリストを結びつけるのは、Adapter(アダプター)で、アダプターは android.widget.BaseAdapter から派生したオブジェクトです。
特に配列やリストと結びついて使うために、 ArrayAdapter (android.widget.ArrayAdapter ) がある。
例えば、listView 変数は findViewById() を用いて、取得するListView オブジェクトだとして、ArrayAdapter を使って、 ListView にコレクションを結びつく方法は以下のコードになる。

	ArrayAdapter adapter = new ArrayAdapter(this, 
			android.R.layout.simple_list_item_single_choice, 
			files);
	listView.setAdapter(adapter); 

List view の item のタッチ操作を取得するため、ListView オブジェクトに対して、setOnItemClickListener() メソッドにて、AdapterView.OnItemClickListener を設定し、onItemClick() メソッドの実装にて、必要な処理を行う。

	public class ... extends ... implements AdapterView.OnItemClickListener {
		...
			listView.setOnItemClickListener(this);
		...
		public void onItemClick(AdapterView adapterView, View view, int i, long l) {
			...
		}
	}

また、root directory と新しい directory の名前を File コンストラクタに渡すことにより、Nest Directory(ネスト ディレクトリ)を作成したり、内部ディレクトリを開いたりすることができる。

	File directory = getFilesDir();
	File file = new File(directory, filename);

演習問題4_3: Read a text file(必須)

「ファイルの中身を読み込む」の説明を参考に、以下の内容を実施せよ。

  1. 演習問題4_2で作成した list view のファイルにタッチしたとき、content view が activity_main.xml レイアウトに戻り、TextView に、タッチしたファイルの名前が表示されるようにする(図5を参照)。
  2. 「Edit」メニューをタッチしたとき、TextView に、ファイルの中身が表示されるようにする(図6を参照)。
opening_content_file.png
図5.前画面でファイルを選択した後、そのファイル名がTextView に表示される結果
viewing_content_file.png
図6.「Edit」メニューをタッチした際、TextView に開いたファイルの中身を表示させた結果

ファイルの中身を読み込む / Read a File Content

File オブジェクトをFileReaderの生成に用いる方法。

	FileReader fr = new FileReader(file);	

ストリームを使用してファイルにアクセスすることもできる。ファイルをストリームとして読み取るには、次のように openFileInput() を使用する。

	FileInputStream fis = openFileInput("myfilename.txt");
	InputStreamReader inputStreamReader =
			new InputStreamReader(fis, StandardCharsets.UTF_8);

注: インストール時にストリームとしてファイルにアクセスする必要がある場合は、ファイルをプロジェクトの /res/raw ディレクトリに保存する。それらのファイルを開くには、openRawResource() を使用し、R.raw プレフィックスを付けたファイル名をリソース ID として渡す。このメソッドから返される InputStream を使用して、ファイルを読み取ることができる。元のファイルに書き込むことはできない。

File reader および、input stream reader から、ファイルの中身を抽出するため、BufferedReader class を用いる。通常の file reader や input stream reader は Byte 配列としてファイルを読み込むのに対して、BufferedReader オブジェクトは文字単位および、文字列(行)単位で読み込むことできます。

try {
	BufferedReader reader = new BufferedReader(inputStreamReader);
	int moji = reader.read(); //一文字を読む	
	String line = reader.readLine(); //一行を読む
} catch (IOException e) {
	// Error occurred when opening raw file for reading.
}