×
Crocus
공부한 내용을 정리하는 블로그로 시작한
Crocus는 2014년 1월 14일 부터 시작하여
현재 월 6만명, 총 1,426,999명의 방문자 수를 기록하고 있습니다.
Donation
이제 많은 사용자들이 이용하는 만큼
더 다양한 서비스 개발/제공을 위해 후원금을 모금하고자 합니다.
후원을 해주시는 분들은 Donators 명단에 성명, 후원금을 기입해드리며
Crocus 블로그가 아닌 다른 곳에 정리해둔 저만의 내용을 공유해 드리고자 합니다.
Account
예금주 : 고관우
신한은행 : 110-334-866541
카카오뱅크 : 3333-01-7888060

👉 후원 페이지 바로가기 Donators
익명 : 5000원(Crocus응원합니다.)
강현대 : 5000원(busyhuman)
익명 : 5000원(알고리즘 학습러)

Android Q OS 이전에는 아래와 같은 형태로 Storage가 관리되었다.

 

 

내부 저장소

개별 앱만 접근 가능

 

외부 저장소

외부 저장소 권한이 있으면 누구나 접근이 가능

  - READ_EXTERNAL_STORAGE 

  - WRITE_EXTERNAL_STORAGE 

 

 

 

 

 

 

 

 

 

 

 

하지만 Q OS 이후에는 Storage 관리가 아래와 같이 변경되었다.

 

샌드박스 저장공간

  - 개별 앱만 접근 가능

  - 읽고 쓰기 위한 별도의 권한 필요 없음

  - Context.getExternalFilesDir(...)

  - 앱 삭제 시 함께 삭제됨

  - 앨범 아트, 썸네일 등 다른 앱과 공유할 필요가 없는 미디어 파일은 샌드박스 공간을 활용

 

 

 

 

 

 

 

 

 

 

 

공용 저장공간

  - MediaStore.Audio

  - MediaStore.Video

  - MediaStore.Images

  - 별도 권한 없이 파일 생성 가능

  - 외부 저장소 권한으로 다른 앱이 생성한 파일 접근 가능

 

 

 

 

 

 

 

 

 

다운로드 저장공간

  - MediaStore.Downloads

  - 별도 권한 없이 파일 생성 가능

  - 시스템 파일 선택기를 통해서만 다른 앱이 생성한 파일 접근 가능

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Scoped mode

원활한 Migration을 위해 Scoped mode 적용 여부를 선택할 수 있는 플래그를 Manifest.xml파일에서 아래와 같이 제공하고 있다.

<application android:requestLegacyExternalStorage="true">

 

다음 Android 버전(Android 11 / R)에서는 Target SDK 버전과 관계없이 모든 앱에 Scoped mode를 적용될 예정이다.

단, 개발 앱의 Scoped mode 여부는 앱 설치 시 정해지며, 이후 변경되지 않는다.

 

 

다음 코드는 Android Q 이전에서 이미지 파일을 저장하는 예제다.

private void beforeQ() {
    String strFolderPath = Environment.getExternalStorageDirectory().getAbsolutePath() + CAPTURE_PATH;
    File folder = new File(strFolderPath);
    if (!folder.exists()) {
        folder.mkdirs();
    }

    OutputStream out = null;
    String strFilePath = strFolderPath + "/";

    try {
        File fileCacheItem = new File(strFilePath + fileName);
        fileCacheItem.createNewFile();
        out = new FileOutputStream(fileCacheItem);
        // 비트맵을 png로 변환
        bmp.compress(Bitmap.CompressFormat.PNG, 100, out);
        // 미디어 스캐닝을 하여 삭제,추가 정보를 알린다.
        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(fileCacheItem)));
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (out != null) {
                out.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

 

 

다음 코드는 Android Q 이후에서 이미지 파일을 저장하는 예제다.

private void afterQ() {
      ContentValues values = new ContentValues();
      values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
      values.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
      // 파일을 write중이라면 다른곳에서 데이터요구를 무시하겠다는 의미입니다.
      values.put(MediaStore.Images.Media.IS_PENDING, 1);

      ContentResolver contentResolver = getContentResolver();
      Uri collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
      // ContentResolver을 통해 insert를 해주고 해당 insert가 되는 위치의 Uri를 리턴받는다.
      // 이후로는 해당 Uri를 통해 파일 관리를 해줄 수 있다.
      Uri item = contentResolver.insert(collection, values);

      try {
          // Uri(item)의 위치에 파일을 생성해준다.
          ParcelFileDescriptor pdf = contentResolver.openFileDescriptor(item, "w", null);
          if (pdf == null) {

          } else {
              InputStream inputStream = getImageInputStram();
              byte[] strToByte = getBytes(inputStream);
              FileOutputStream fos = new FileOutputStream(pdf.getFileDescriptor());
              fos.write(strToByte);
              fos.close();
              inputStream.close();
              pdf.close();
              contentResolver.update(item, values, null, null);
          }
      } catch (FileNotFoundException e) {
          e.printStackTrace();
      } catch (IOException e) {
          e.printStackTrace();
      }
      values.clear();
      // 파일을 모두 write하고 다른곳에서 사용할 수 있도록 0으로 업데이트를 해줍니다.
      values.put(MediaStore.Images.Media.IS_PENDING, 0);
      contentResolver.update(item, values, null, null);
  }
private InputStream getImageInputStram() {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    bmp.compress(Bitmap.CompressFormat.PNG, 100, bytes);
    byte[] bitmapData = bytes.toByteArray();
    ByteArrayInputStream bs = new ByteArrayInputStream(bitmapData);

    return bs;
}

public byte[] getBytes(InputStream inputStream) throws IOException {
    ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
    int bufferSize = 1024;
    byte[] buffer = new byte[bufferSize];

    int len = 0;
    while ((len = inputStream.read(buffer)) != -1) {
        byteBuffer.write(buffer, 0, len);
    }
    return byteBuffer.toByteArray();
}

 

 

출처

 

https://hjiee.tistory.com/entry/Android-MediaStore%EB%A1%9C-%ED%99%94%EB%A9%B4-%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0-Android-Q

  1. txxbro 2020.09.03 16:47

    좋은 글 잘 읽었습니다 !

    근데 궁금한 점이 위에 예시에서 bmp 변수(아마 Bitmap 객체일 듯 싶네요)가 선언이 안되어 있는 것으로 보이는데
    해당 변수는 복사할 Bitmap을 의미하는 것이 맞나요 ?