LoginSignup
10
9

More than 3 years have passed since last update.

*Android*外部ストレージへのファイルの保存・読み込みとパーミッションについて

Posted at

はじめに

こんにちは.前回に引き続き,Androidのストレージ操作について解説します.今回は,外部ストレージへのファイルの保存・読み込みという内容ですが,前回の内部ストレージの保存・読み込みとできる限り近いやり方で実現しようと思います.

前回も述べた通り,Androidの解説に関する記事は,どれも内容が少しずつ異なり,迷ってしまう方もいると思うので,標準的な方法を書くように心がけています.

前提

開発環境は以下の通りです.
*Android Studio 4.0.1
*targetSdkVersion 28
*Google Nexus 5x

外部ストレージへのファイル保存・読み込み

今回は,外部ストレージのアプリ固有の領域(/sdcard/Android/data/パッケージ名/files/)に,ファイルを保存します.外部ストレージにファイルを保存するというと,この場所に保存するのが標準的です.

Android 6.0(APIレベル23)からは,ユーザはアプリのインストール時ではなく,アプリを起動した後にPermissionを確認するようになりました.外部ストレージの読み書きでは,Permissionが必要になります.

Permissionに関して

Androidのアプリ開発では,Permissionには2つの保護レベルが存在し,それぞれNormal,Dangerousと呼びます.Permissionごとに保護レベルは決まっているので,必要なPermissionによって,Normalに必要な処理,Dangerousに必要な処理を実装する必要があります.

Normal Permissionに必要な処理

Normal Permissionに必要な処理は,AndroidManifest.xmlに許可を記述するだけです.例えば,Androidアプリでインターネットに接続するには,Normal Permissionが必要です.

AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />

上のような1行を追加するだけで,インターネット接続ができるようになります.

Dangerous Permissionに必要な処理

今回の,外部ストレージの読み書きでは,Dangerous Permissionの処理が必要になります.まず,AndroidManifest.xmlに許可を記述します.

AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

次に,permission checkの実装が必要になります.アプリを開くと,ユーザにPermissionの許可・不許可に関するダイアログを表示できるようにします.処理が長くなるので,サンプルプログラムを参照してください.

外部ストレージへのファイルの保存

まず,外部ストレージの保存したいフォルダへのパスを取得し,ファイル名を含めたパスを作成します.

this.fileName = "test.txt";
this.path = getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).toString();
this.file = this.path + "/" + this.fileName;

これにより,file変数には"/sdcard/Android/data/パッケージ名/files/Documents/test.txt"という文字列が代入されます.

現在外部ストレージに読み書きが可能かどうかを調べるために,読み書きの処理の前に以下を記述します.

String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)){
    //ファイルの読み書きの処理
}

次にファイル保存の処理です.内部ストレージへのファイル保存では FileOutputStream のインスタンスを openFileOutput() メソッドを使用することで,自動的に内部ストレージへのパスを指定しました.しかし,今回は外部ストレージのパスを自分で指定する必要があるので,openFileOutput()を使用しません.

FileOutputStream fileOutputStream = new FileOutputStream(file, true);

第1引数
"/パス/ファイル名"

第2引数
trueを設定すると,ファイルの末尾に書き込みます.
falseを指定すると,ファイルの先頭から上書きします.

あとは,ファイルに書き出したい文字列をbyte型の配列に変換し, write() を使ってストリームに書き出します.

fileOutputStream.write(str.getBytes());

外部ストレージからファイルを読み込む

ファイルの読み込みでは FileInputStream のインスタンスを取得します.これにより,ストリームを開くことができます.

FileInputStream fileInputStream = new FileInputStream(file);

その後の処理は前回と同様になります.

サンプルコード

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.keita.myapplication">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="50dp"
        android:layout_marginTop="100dp"
        android:text="Save File"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="100dp"
        android:layout_marginTop="100dp"
        android:text="Read File"
        app:layout_constraintStart_toEndOf="@+id/button1"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="120dp"
        android:text=""
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editText" />

    <EditText
        android:id="@+id/editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="Name"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
MainActivity.java
package com.example.keita.myapplication;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class MainActivity extends AppCompatActivity {
    private String file;
    private String path;
    public String state;
    private String fileName;
    private TextView textView;
    private Button button1;
    private Button button2;
    private EditText editText;
    private final String[] PERMISSIONS = {
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };
    private final int REQUEST_PERMISSION = 1000;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.textView = findViewById(R.id.textView);
        this.editText = findViewById(R.id.editText);
        this.button1 = findViewById(R.id.button1);
        this.button2 = findViewById(R.id.button2);
        this.fileName = "test.txt";
        this.path = getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).toString();
        this.file = this.path + "/" + this.fileName;

        checkPermission();
    }

    private void checkPermission(){
        if (isGranted()){
            setEvent();
        }
        else {
            requestPermissions(PERMISSIONS, REQUEST_PERMISSION);
        }
    }


    private boolean isGranted(){
        for (int i = 0; i < PERMISSIONS.length; i++){
            //初回はPERMISSION_DENIEDが返る
            if (checkSelfPermission(PERMISSIONS[i]) != PackageManager.PERMISSION_GRANTED) {
                //一度リクエストが拒絶された場合にtrueを返す.初回,または「今後表示しない」が選択された場合,falseを返す.
                if (shouldShowRequestPermissionRationale(PERMISSIONS[i])) {
                    Toast.makeText(this, "アプリを実行するためには許可が必要です", Toast.LENGTH_LONG).show();
                }
                return false;
            }
        }
        return true;
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_PERMISSION){
            checkPermission();
        }
    }


    private void setEvent(){
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String text = editText.getText().toString();
                state = Environment.getExternalStorageState();
                if (Environment.MEDIA_MOUNTED.equals(state)){
                    saveFile(file, text);
                }
            }
        });

        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                state = Environment.getExternalStorageState();
                if (Environment.MEDIA_MOUNTED.equals(state)){
                    String str = readFile(file);
                    textView.setText(str);
                }
            }
        });
    }


    private void saveFile(String file, String str){
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file, true);
            fileOutputStream.write(str.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private String readFile(String file){
        String text = null;
        try {
            FileInputStream fileInputStream = new FileInputStream(file);
            BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream, "UTF-8"));
            String lineBuffer;
            while (true){
                lineBuffer = reader.readLine();
                if (lineBuffer != null){
                    text += lineBuffer;
                }
                else {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return text;
    }
}

10
9
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
9