본문 바로가기

Android/Function

[안드로이드] 외부 저장소에 데이터 파일 저장하기(쓰기) ― SAF(Storage Access Framework)로 파일 쓰기

728x90
반응형

이전의 안드로이드 외부 저장소 접근 후 파일을 읽고 쓰는 예제를 따라하다보면, 잘 안되는것을 느끼실 겁니다.

저 또한 시도해봤는데 첫째로, 최상위 경로를 가지고 오는 메서드인 getExternalStorageDirectory()를 더이상 사용할 수 없다는 점 입니다.

예제를 따라하다보면 코드에 Environment.getExternalStorageDirectory() is deprecated 라는 문구가 뜰 것입니다.

그래서 getExternalFilesDir로 메서드를 변경하였지만 이 매서드는 개별 앱공간의 경로이므로 사용자가 접근이 힘들고, 앱 삭제시 삭제될 수 있어 보였습니다.

알아보니 이전의 파일의 읽고 쓰는 방식의 보안을 염려하여 다른 앱이 우리 앱에 소속된 파일들을 볼 수 없도록 업데이트 되었습니다.

Android 10이상에서는 외부저장소에 대해 Scoped storage 모드로 동작하게 됩니다.

이제 기존 외부저장소의 공통 저장 공간이 모두 사라지고 개별 앱공간이 격리되어, 다른 앱의 파일에 직접 접근할 수 없습니다.

Scoped Storage Mode에서는 파일 경로만으로 접근하는 것이 불가능하고, 시스템 파일 선택기를 열어 사용자가 직접 파일을 선택하게 해야 접근이 가능합니다.

그래서 이제 외부 저장소에 접근해 파일을 읽고 쓰기 위해선 MediaStoreSAF(Storage Access Framework)를 이용해야 합니다. 테스트 해보니 무리는 없었습니다.

사진, 비디오, 오디오 파일등은 MediaStore를 사용하고, Downloads 파일에 저장되는 documents(txt,pdf)나 기타 파일을 읽고 쓰기 위해 SAF를 사용하면 됩니다.

또한 다른 앱 파일을 접근할 때에만 READ/WRITE_EXTERNAL_STORAGE 퍼미션이 있으면 됩니다. 자세한 사항은 참고문서를 통해 확인해주세요.

 

 

파일 쓰기(문서 만들기) 예제 코드


원하는 데이터 파일을 쓰고 저장하기 위하여 먼저 파일을 생성하였습니다.

private int WRITE_REQUEST_CODE = 43;
    
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void StartRecord(){
  try {
     long now = System.currentTimeMillis();
     Date date = new Date(now);
     @SuppressLint("SimpleDateFormat") SimpleDateFormat sdfNow 
        = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      String formatDate = sdfNow.format(date);


    /**
    * SAF 파일 편집
    * */
    String fileName = formatDate+".txt";

    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("text/plain" );
    intent.putExtra(Intent.EXTRA_TITLE,fileName);

     startActivityForResult(intent, WRITE_REQUEST_CODE);
   } catch (Exception e) {
      e.printStackTrace();
   }

}
  • ACTION_CREATE_DOCUMENT 인텐트를 실행하여 사용자가 원하는 폴더를 선택하여 파일을 생성하게 됩니다. 만약 파일을 불러 읽어올 것이라면 ACTION_OPEN_DOCUMENT를 사용하면 됩니다.
  • ACTION_OPENABLE 카테고리를 인텐트에 추가하면 열 수 있는 문서만 표시 됩니다.
  • setType을 통해 필터링도 가능합니다. "text/plain" 뿐 아니라 png타입의 image를 보고 싶다면 "image/png"를 넣어주면 됩니다. 타입 상관없이 보여지고 싶다면 "image/*""*/*"를 넣으면 됩니다.
  • Intent.EXTRA_TITLE을 통해 저장될 파일명을 입력할 수 있습니다. 물론 입력된 파일명을 사용자가 직접 수정할 수 있습니다.

 

파일 생성후 onActivityResult가 호출 됩니다. 여기서 URI를 추출하여 사용하면 됩니다. 

 

private ParcelFileDescriptor pfd;
private FileOutputStream fileOutputStream;
    
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
   super.onActivityResult(requestCode,resultCode,data);
   if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        Uri uri = data.getData();
        addText(uri);
    }
}
    
public void addText(Uri uri){
    try {
        pfd = this.getContentResolver().openFileDescriptor(uri, "w");
        fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());

     } catch (FileNotFoundException e) {
        e.printStackTrace();
     }
}

addText를 통해 추출한 URI를 이용하여 파일을 쓰기위한 stream을 열었습니다.

 

이제 파일을 쓰고, stream을 닫아 파일쓰기를 끝내면됩니다.

/**
* 이 메서드를 통해 기록
*/
public void putString(String st) throws IOException {
        if(fileOutputStream!=null) fileOutputStream.write(st.getBytes());
 }

 

 

 public void FinishRecord() throws IOException {
    Toast.makeText(getApplicationContext(), "저장되었습니다.", Toast.LENGTH_LONG).show();
    fileOutputStream.close();
    pfd.close();

}

 

제가 사용한 코드는 단지 파일을 생성하여 쓰기만 한 코드입니다.

응용하여 파일을 읽거나, 이어 쓰기도 할 수 있습니다

아래 참고문서를 참고해주세요.

이어서 내부저장소 파일을 외부저장소로 옮기는 예제도 포스팅 하였습니다.

[Android/Function] - [안드로이드] 내부저장소에 저장된 파일 외부저장소로 옮기기/복사하기 (SAF) ― Copy file from the internal to the external storage

 

참고문서

 

 

 

 

728x90
반응형