내부 저장소에 파일 저장

앱의 내부 저장소 디렉터리는 다음 API를 사용하여 액세스할 수 있는 Android 파일 시스템의 특별한 위치에 앱의 패키지 이름으로 지정됩니다.

여유 공간 쿼리

저장하는 데이터의 규모를 미리 알고 있다면 getFreeSpace() 또는 getTotalSpace()를 호출하여 IOException을 초래하지 않고 사용할 여유 공간이 충분한지 확인할 수 있습니다. 이러한 메서드를 사용하면 저장소 볼륨에서 현재 사용 가능한 공간 및 전체 공간을 각각 파악할 수 있습니다. 이 정보는 저장소 볼륨이 특정 임계값을 초과하는 수준으로 차는 것을 방지하는 데도 유용합니다.

하지만 시스템은 getFreeSpace()에 의해 표시된 만큼의 바이트를 쓰기 작업에 사용할 수 있다고 보장하지 않습니다. 반환된 숫자가 사용자가 저장하려는 데이터 크기보다 몇 MB 더 크거나 파일 시스템이 90% 미만으로 찬 경우 작업을 계속해도 좋습니다. 그렇지 않으면 저장소에 데이터를 쓰지 않아야 합니다.

파일 쓰기

파일을 내부 저장소에 저장할 때 다음 메서드 중 하나를 호출하여 적절한 디렉터리를 File 개체로 얻을 수 있습니다.

getFilesDir()
앱의 내부 디렉터리를 나타내는 File을 반환합니다.
getCacheDir()

앱의 임시 캐시 파일의 내부 디렉터리를 나타내는 File을 반환합니다. 더 이상 필요하지 않은 파일을 모두 삭제하고 언제든지 사용할 수 있는 메모리 양에 관해 합리적인 크기 제한(예: 1MB)을 구현해야 합니다.

주의: 저장용량이 부족하면 시스템은 경고 없이 캐시 파일을 삭제할 수 있습니다.

이러한 디렉터리 중 하나에 새 파일을 생성하는 데 File() 생성자를 사용할 수 있습니다. 이 방법으로 내부 저장소 디렉터리를 지정하는 위의 메서드 중 하나에 의해 제공된 File을 전달할 수 있습니다. 예를 들면 다음과 같습니다.

Kotlin

    val file = File(context.filesDir, filename)
    

자바

    File file = new File(context.getFilesDir(), filename);
    

더 안전한 해결 방법을 위해 다음 코드 스니펫에서와 같이 Jetpack 보안 라이브러리를 사용할 수 있습니다.

Kotlin

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
    val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

    // Creates a file with this name, or replaces an existing file
    // that has the same name. Note that the file name cannot contain
    // path separators.
    val fileToWrite = "my_sensitive_data.txt"
    val encryptedFile = EncryptedFile.Builder(
        File(directory, fileToWrite),
        context,
        masterKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
    ).build()

    encryptedFile.bufferedWriter().use { writer ->
        writer.write("MY SUPER-SECRET INFORMATION")
    }
    

자바

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
    String masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

    // Creates a file with this name, or replaces an existing file
    // that has the same name. Note that the file name cannot contain
    // path separators.
    String fileToWrite = "my_sensitive_data.txt";
    try {
        EncryptedFile encryptedFile = new EncryptedFile.Builder(
                new File(directory, fileToWrite),
                context,
                masterKeyAlias,
                EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
        ).build();

        // Write to a file.
        BufferedWriter writer = new BufferedWriter(new FileWriter(encryptedFile));
        writer.write("MY SUPER-SECRET INFORMATION");
    } catch (GeneralSecurityException gse) {
        // Error occurred getting or creating keyset.
    } catch (IOException ex) {
        // Error occurred opening file for writing.
    }
    

또는 openFileOutput()을 호출하여 내부 디렉터리의 파일에 데이터를 쓰는 FileOutputStream을 가져올 수도 있습니다. 예를 들어 다음은 일부 텍스트를 파일에 쓰는 방법을 보여줍니다.

Kotlin

    val filename = "myfile"
    val fileContents = "Hello world!"
    context.openFileOutput(filename, Context.MODE_PRIVATE).use {
            it.write(fileContents.toByteArray())
    }
    

자바

    String filename = "myfile";
    String fileContents = "Hello world!";
    FileOutputStream outputStream;

    try {
        outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
        outputStream.write(fileContents.getBytes());
        outputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    

openFileOutput() 메서드에는 파일 모드 매개변수가 필요합니다. MODE_PRIVATE을 전달하면 파일이 비공개로 앱에 전달됩니다. 다른 모드 옵션인 MODE_WORLD_READABLEMODE_WORLD_WRITEABLE은 API 레벨 17부터 더 이상 사용되지 않습니다. Android 7.0(API 레벨 24)부터는 Android에서 이러한 옵션이 사용되면 SecurityException이 발생합니다. 앱이 다른 앱과 비공개 파일을 공유해야 한다면 FLAG_GRANT_READ_URI_PERMISSION 속성과 함께 FileProvider를 대신 사용해야 합니다. 자세한 내용은 파일 공유를 참조하세요.

Android 6.0(API 레벨 23) 이하에서는 누구든지 읽을 수 있도록 파일 모드(MODE_WORLD_READABLE)를 설정하면 다른 앱이 내부 파일을 읽을 수 있습니다. 하지만 다른 앱이 개발자 앱의 패키지 이름 및 파일 이름을 알아야 읽을 수 있습니다. 파일을 읽기 가능 또는 쓰기 가능으로 명시적으로 설정하지 않으면 다른 앱은 개발자 앱의 내부 디렉터리를 탐색할 수 없으며 읽기 또는 쓰기 권한도 갖지 못합니다. 따라서 내부 저장소의 파일에 MODE_PRIVATE을 사용하는 한 다른 앱이 이러한 파일에 액세스할 수 없습니다.

캐시 파일 쓰기

일부 파일을 캐싱해야 할 경우에는 createTempFile()을 대신 사용해야 합니다. 예를 들어 다음 메서드는 URL 개체에서 파일 이름을 추출한 후 앱의 내부 캐시 디렉터리에 그 이름으로 파일을 생성합니다.

Kotlin

    // For a more secure solution, use EncryptedFile from the Security library
    // instead of File.
    private fun getTempFile(context: Context, url: String): File? =
            Uri.parse(url)?.lastPathSegment?.let { filename ->
                File.createTempFile(filename, null, context.cacheDir)
            }
    

자바

    private File getTempFile(Context context, String url) {
        // For a more secure solution, use EncryptedFile from the Security library
        // instead of File.
        File file;
        try {
            String fileName = Uri.parse(url).getLastPathSegment();
            file = File.createTempFile(fileName, null, context.getCacheDir());
        } catch (IOException e) {
            // Error while creating file
        }
        return file;
    }
    

createTempFile()로 생성된 파일은 앱의 비공개 캐시 디렉터리에 배치됩니다. 정기적으로 더 이상 필요 없는 파일은 삭제해야 합니다.

기존 파일 열기

다음 코드 스니펫에서와 같이 보안 라이브러리를 사용하여 더 안전한 방식으로 파일을 읽을 수 있습니다.

Kotlin

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
    val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

    val fileToRead = "my_sensitive_data.txt"
    lateinit var byteStream: ByteArrayOutputStream
    val encryptedFile = EncryptedFile.Builder(
        File(directory, fileToRead),
        context,
        masterKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
    ).build()

    val contents = encryptedFile.bufferedReader().useLines { lines ->
        lines.fold("") { working, line ->
            "$working\n$line"
        }
    }
    

자바

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
    String masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

    String fileToRead = "my_sensitive_data.txt";
    ByteArrayOutputStream byteStream;

    EncryptedFile encryptedFile = new EncryptedFile.Builder(
            File(directory, fileToRead),
            context,
            masterKeyAlias,
            EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
    ).build();

    StringBuffer stringBuffer = new StringBuffer();
    try (BufferedReader reader =
                 new BufferedReader(new FileReader(encryptedFile))) {

        String line = reader.readLine();
        while (line != null) {
            stringBuffer.append(line).append('\n');
            line = reader.readLine();
        }
    } catch (IOException e) {
        // Error occurred opening raw file for reading.
    } finally {
        String contents = stringBuffer.toString();
    }
    

또는 openFileInput(name)을 호출하여 파일 이름을 전달할 수 있습니다.

fileList()를 호출하여 모든 앱의 파일 이름 배열을 얻을 수 있습니다.

디렉터리 열기

다음 메서드를 사용하여 내부 파일 시스템의 디렉터리를 열 수 있습니다.

getFilesDir()
앱과 고유하게 연결된 파일 시스템의 디렉터리를 표시하는 File 개체를 반환합니다.
getDir(name, mode)
앱의 고유한 파일 시스템 디렉터리 내에 새 디렉터리를 생성하거나 기존 디렉터리를 엽니다. 이 새 디렉터리는 getFilesDir()에서 제공한 디렉터리 안에 표시됩니다.
getCacheDir()
앱과 고유하게 연결된 파일 시스템의 캐시 디렉터리를 표시하는 File 개체를 반환합니다. 이 디렉터리는 임시 파일용이며 정기적으로 지워야 합니다. 디스크 공간이 부족해지면 시스템에서 디렉터리의 파일을 삭제할 수 있으므로 캐시 파일을 읽기 전에 원하는 파일이 있는지 확인하세요.

이러한 디렉터리 중 하나에 새 파일을 생성하는 데 File() 생성자를 사용할 수 있습니다. 이런 방법으로 내부 저장소 디렉터리를 지정하는 위의 메서드 중 하나에 의해 제공된 File 개체를 전달할 수 있습니다. 예를 들면 다음과 같습니다.

Kotlin

    val directory = context.filesDir
    val file = File(directory, filename)
    

자바

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

파일 삭제

앱에서 더 이상 필요하지 않은 파일은 항상 삭제해야 합니다. 파일을 삭제하는 가장 간단한 방법은 다음과 같이 File 개체의 delete()를 호출하는 것입니다.

Kotlin

    myFile.delete()
    

자바

    myFile.delete();
    

파일이 내부 저장소에 저장되어 있으면 다음과 같이 deleteFile()을 호출하여 파일을 찾아 삭제하도록 Context에 요청할 수도 있습니다.

Kotlin

    myContext.deleteFile(fileName)
    

자바

    myContext.deleteFile(fileName);
    

참고: 사용자가 앱을 제거하면 Android 시스템이 다음을 삭제합니다.

  • 개발자가 내부 저장소에 저장한 모든 파일
  • 개발자가 getExternalFilesDir()을 사용해 외부 저장소에 저장한 모든 파일

하지만 getCacheDir()을 사용해 생성된 모든 캐시 파일은 정기적으로 수동으로 삭제하고 더 이상 필요하지 않은 기타 파일도 정기적으로 삭제해야 합니다.

추가 리소스

기기의 저장소에 파일을 저장하는 방법에 관한 자세한 내용은 다음 리소스를 참조하세요.

Codelab