반응형

오늘은 삽질을 하다가 찾아낸 방법을 공유하려고 씁니다

 

진짜 ㅋㅋㅋ 내용들때문에 진짜 삽질을 어마어마하게 했네요 ㅋㅋㅋㅋ 

(인터넷도 잘 서핑을 해야됨 진짜... ㅋㅋ )

 

 

자 간단하게 먼저 설명 시작!!

 

일단 안드로이드 API29 즉 안드로이드 10버전 부터는 저장소 접근 방식이 변경되어 이전 방식처럼 바로 접근은 불가함

 

 

파일 Read/Write

Android 9 이하 (API 28 이하) Permission 획득 후 접근 가능
- WRITE_EXTERNAL_STORAGE
- READ_EXTERNAL_STORAGE
Android 10 이상 (API 29 이상) Scoped storage 정책 적용

Android 10 에서는 예외사항으로 속성값 적용 시 9버전처럼 사용가능(Target Build SDK가 29일 경우, 30일 경우는 해당 속성값 무시)
-> <application
      ....
      // 아래부분 추가
      android:requestLegacyExternalStorage="true"
    >

Private 영역은 권한 요청없이 생성, 삭제 가능
Public 영역은 MediaStore 함수를 이용하여 생성, 삭제 등 가능
반응형

 

일단 Storage Scope 에 대한 내용은 아래 링크 참조바랍니다.

[구글 공식내용]

 

Android 11의 저장소 업데이트  |  Android 개발자  |  Android Developers

Android 11의 저장소 업데이트 Android 11(API 수준 30)에서는 플랫폼을 한층 더 강화하여 외부 저장소의 앱 및 사용자 데이터를 더욱 안전하게 보호합니다. 이 버전에는 미디어의 원시 파일 경로 액세

developer.android.com

 

 

자 그러면 Private 영역, Public 영역은 뭐가 있는지 봅시다

 

이때까진 

// 루트경로
String path = Environment.getExternalStorageDirectory().getPath();

위 처럼 루트 경로에서 "패키지명" 폴더 를 만들어서 작업을 많이 했다면.... 이젠 불가....

 

공용 폴더 Path 정리

//공용 DCIM 폴더
String DCIM_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_DCIM).getPath();

//공용 다운로드 폴더
String Download_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS).getPath();

//공용 음악 폴더
String Music_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_MUSIC).getPath();

//공용 팟캐스트 폴더
String Podcast_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_PODCASTS).getPath();

//공용 벨소리 폴더
String Ring_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_RINGTONES).getPath();

//공용 알람 폴더
String Alarm_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_ALARMS).getPath();

//공용 알림음 폴더
String Noti_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_NOTIFICATIONS).getPath();

//공용 사진 폴더
String Picture_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES).getPath();

//공용 영상 폴더
String Movie_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_MOVIES).getPath();

//공용 문서 폴더
String Document_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOCUMENTS).getPath();

//공용 스크린샷 폴더
String Screenshot_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_SCREENSHOTS).getPath();

//공용 오디오북 폴더
String Audio_Path = Environment.getExternalStoragePublicDirectory(DIRECTORY_AUDIOBOOKS).getPath();

 

Private 영역 Path 정리

// 내부폴더 Path : /Android/data/패키지명/files/

//내부 DCIM 폴더
String DCIM_Path = context.getExternalFilesDir(DIRECTORY_DCIM).getPath();

//내부 다운로드 폴더
String Download_Path = context.getExternalFilesDir(DIRECTORY_DOWNLOADS).getPath();

//내부 음악 폴더
String Music_Path = context.getExternalFilesDir(DIRECTORY_MUSIC).getPath();

//내부 팟캐스트 폴더
String Podcast_Path = context.getExternalFilesDir(DIRECTORY_PODCASTS).getPath();

//내부 벨소리 폴더
String Ring_Path = context.getExternalFilesDir(DIRECTORY_RINGTONES).getPath();

//내부 알람 폴더
String Alarm_Path = context.getExternalFilesDir(DIRECTORY_ALARMS).getPath();

//내부 알림음 폴더
String Noti_Path = context.getExternalFilesDir(DIRECTORY_NOTIFICATIONS).getPath();

//내부 사진 폴더
String Picture_Path = context.getExternalFilesDir(DIRECTORY_PICTURES).getPath();

//내부 영상 폴더
String Movie_Path = context.getExternalFilesDir(DIRECTORY_MOVIES).getPath();

//내부 문서 폴더
String Document_Path = context.getExternalFilesDir(DIRECTORY_DOCUMENTS).getPath();

//내부 스크린샷 폴더
String Screenshot_Path = context.getExternalFilesDir(DIRECTORY_SCREENSHOTS).getPath();

//내부 오디오북 폴더
String Audio_Path = context.getExternalFilesDir(DIRECTORY_AUDIOBOOKS).getPath();

 

내부폴더를 생성하고 사진, 영상 파일 생성 후 갤러리에 표출하기위해 

// 특정 사진을 갤러리에 동기화
sendBroadcast(new Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)) );

동기화를 실행.... 그러나 실패....

 

왜????  Private 영역은 애시당초 Android 폴더 내에 .nomedia 라는 파일이 자동생성 되어있어 갤러리에 동기화가 되질 않는다....하아.... 

 

그래서 DCIM 폴더에 파일을 옮겨야 하는 일이 생겼는데... 

 

그런데... 여기서 공용 폴더에 파일을 옮기려고 했더니 문제가 생겼다.

 

 

 

그냥 옮기면 안되고 MediaStore 함수를 이용하여 보내야 하는 상황이 발생!!!

 

ㅋㅋㅋㅋㅋ 그럼 MediaStore 를 또 알아봐야함 ㅋㅋ 끝이없네 ㅋㅋ 

 

간단하게 함수먼저 똭!!

 

사진파일 이동

   /**
     * @param context : Context
     * @param file : 원본 파일 정보
     * @param SavePath : 옮길 폴더 디렉토리  (ex : DCIM/TEST/사진저장)
     */
    public static void ImageFileMove(Context context, File file, String SavePath){
        try {
            Log.e("ImageFileMove", file.getName());
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                ContentValues values = new ContentValues();
                values.put(MediaStore.Images.Media.TITLE, file.getName());
                values.put(MediaStore.Images.Media.DISPLAY_NAME, file.getName());
                values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
                values.put(MediaStore.Images.Media.RELATIVE_PATH, SavePath);
                values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

                Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
                OutputStream fos = context.getContentResolver().openOutputStream(uri);

                Bitmap bmp = BitmapFactory.decodeFile(file.getPath() );
                bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);

                fos.flush();
                fos.close();
            } else {
                String dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString() + File.separator + SavePath;
                File dir_file = new File(dir);
                if(!dir_file.exists()){
                    dir_file.mkdirs();
                }

                File imgfile = new File(dir_file, file.getName());
                FileOutputStream os = new FileOutputStream(imgfile);
                Bitmap bmp = BitmapFactory.decodeFile(file.getPath() );
                bmp.compress(Bitmap.CompressFormat.JPEG, 100, os);
                os.flush();
                os.close();

                ContentValues values = new ContentValues();
                values.put(MediaStore.Images.Media.TITLE, file.getName());
                values.put(MediaStore.Images.Media.DISPLAY_NAME, file.getName());
                values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
                values.put(MediaStore.Images.Media.BUCKET_ID, file.getName());
                values.put(MediaStore.Images.Media.DATA, imgfile.getAbsolutePath());
                values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

                context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

 

이거면 로컬에 생성한 사진파일을 DCIM 에 특정폴더로 이동하여 갤러리에 갱신시킬 수 있다 ㅎㅎ 

설명은 편하게 툭툭 쓰지만 삽질을 쫌 했음.... 후우.... 

 

속성값 중에 특히 중요한건 아래내용이다.

속성값 설명
DISPLAY_NAME 저장할 파일 이름
DATE_TAKEN 파일생성 시간
DATA 원본파일 정보
MIME_TYPE 파일 저장 형식
RELATIVE_PATH 저장할 폴더 경로

 

위에 함수를 사용하면 사진파일 이동은 별 문제 없이 가능할 것이다.

 

그럼....영상파일 이동은?!!

 

함수 또 나옵니다~~~~

 

영상파일 이동

    /**
     * @param context : Context
     * @param file : 원본 파일 정보
     * @param SavePath : 옮길 폴더 디렉토리  (ex : DCIM/TEST/영상저장)
     */
    public static void VideoFileMove(Context context, File file, String SavePath){
        try {
            Log.e("VideoFileMove", file.getName());
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                ContentValues values = new ContentValues();
                values.put(MediaStore.Video.Media.TITLE, file.getName());
                values.put(MediaStore.Video.Media.DISPLAY_NAME, file.getName());
                values.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
                values.put(MediaStore.Video.Media.RELATIVE_PATH, SavePath);
                values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");

                Uri uri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
                ParcelFileDescriptor pdf = context.getContentResolver().openFileDescriptor(uri, "w", null);
                if(pdf == null){
                    Log.e("asdf", "null");
                }
                else{
                    FileOutputStream fos = new FileOutputStream(pdf.getFileDescriptor());
                    fos.write(FileUtils.readFileToByteArray(file));
                    fos.close();
                    pdf.close();
                    context.getContentResolver().update(uri, values, null, null);
                }
            } else {
                MOVE_FILE(context, MP4_Path, file.getName(), MP4_Path_orig);
            }
            Sub_FileDelete(file, 81);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void MOVE_FILE(Context context, String inputPath, String inputFile, String outputPath){
        InputStream in = null;
        OutputStream out = null;
        try{
            File dir = new File(outputPath);
            Log.e("dir", dir.getPath());
            if(!dir.exists()){
                dir.mkdirs();
            }

            Log.e("MOVE_FILE", outputPath + "/" + inputFile + "______" + dir.getPath());

            in = new FileInputStream(inputPath + inputFile);
            out = new FileOutputStream(outputPath + "/" + inputFile);

            byte[] buffer = new byte[1024];
            int read;
            while((read = in.read(buffer)) != -1){
                out.write(buffer, 0, read);
            }
            in.close();
            in = null;

            out.flush();
            out.close();
            out = null;

            new File(inputPath + inputFile).delete();

            File tmp_file = new File(outputPath + "/" + inputFile);
            context.sendBroadcast(new Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(tmp_file)) );
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
SMALL

 

이러면 영상파일 이동도 완료 ㅎㅎㅎ 

 

기본적인 내용은 사진 이동하고 비슷함 ㅎㅎ 

 

안드로이드 8, 9, 10, 11 까지 시험 진행 완료된 함수이니 사용해도 무방할 것임 ㅎㅎ 

 

 

 

여기까지 하고 나니 DCIM 폴더에 저장한 사진 리스트를 받아와서 삭제해야하는 경우가 생김!!!

(산넘어 산.... 역시 가볍게 끝내주질 않음....)

 

그래서 DCIM 폴더에 접근해서 전체 파일 정보를 읽어오는

File dir = new File(path);
File[] fileList = dir.listFiles();

했지만... 결과는 Null !!!!!!! 왜?!!! 

 

그럼 아래처럼 파일 존재 확인은???

File file = new File(path);
Log.e("file", "Result : " + file.exists());

가능하다.... 

 

음.... 그래서 다시 검색 시작.....

근데 거의 대다수 사이트에서 똑같이 말하는 내용이 있다.....

아래처럼 WRITE_EXTERNAL_STORAGE Permission 에서 maxSdkVersion 을 28로 설정하라고.....

<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28"/>

 

아래처럼 maxSdkVersion 을 29로 설정하면 10버전에서 파일 리스트 호출이 정상적으로 동작함.....

<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="29"/>

 

누구냐 도대체.... 28로 설정하라해서 삽질하게 만든 사람들은.....하아.....

 

오늘의 삽질은 여기까지.... ㅎㅎ 

 

다 필요한 함수는 아니겠지만 누군가에겐 도움이 되길 바라면서 오늘도 이만 ㅎㅎ 

반응형

+ Recent posts