Android

[안드로이드 Q]안드로이드 11 미디어 저장소 접근에 대한 처리

안드로이드 10이나 안드로이드 11 부터는 저장소 접근방식이 변경된다. 그렇기에 기존 방식으로는 오디오나 비디오 그리고 사진 파일에 접근할 수 없다. 그 처리 방법에 대해 알아보자

우선 메니페스트파일에 android.permission.WRITE_EXTERNAL_STORAGE 권한에 대해 maxSdkVersion를 28로 설정한다.앱이 범위 지정 저장소를 사용하면 Android 9(API 수준 28) 이하를 실행하는 기기에 한해서만 저장소 관련 권한을 요청하도록 해야하기 때문이다. 안드로이드 10이나 11버전에 권한 요청을 사용자에게 허용하도록 권유해서 처리한다고 해도 적용되지않는다.

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

다음은 권한을 요청하는 백앤드코드의 변경이다. 사용자에게 권한 요청을 요청할 때 READ_EXTERNAL_STORAGE 권한에 대한 허용만 요청 처리한다.

        if (Build.VERSION.SDK_INT < 23) {  //마시멜로우 이전버전은 권한 방법 기존방법 적용

        } else {// 마시멜로 버전 이상 확인
             requestUserPermission();
        }
        
        ...........
        
    private void requestUserPermission() {
        try {
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P){
                if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {  //권한 허용상태인지 체크

                    //  
                    ActivityCompat.requestPermissions(MainActivity.this
                    	, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}
                        , MY_PERMISSION_REQUEST_STORAGE2);
                } else {
                    // 허용일 경우에 해당 
                    
                }
            }else{

                if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
                        || ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
                ) {  //권한 허용상태인지 체크

                    ActivityCompat.requestPermissions(MainActivity.this
                    , new String[]{Manifest.permission.READ_EXTERNAL_STORAGE
                    , Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSION_REQUEST_STORAGE);
                } else {
                    // 허용일 경우에 해당 
                }
            }

        }catch (Exception e){
             
        }
    }        
        

onRequestPermissionResult()메소드 부분에서 역시 분기를 하였다.

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if(requestCode != -1) {
            switch (requestCode) {
                case MY_PERMISSION_REQUEST_STORAGE2: //안드로이드 P 이후의 권헌 처리
                    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                         
                    } else {
                        this.callGuideDialogUsingTimer(getResources().getString(R.string.info_info_text), getResources().getString(R.string.info_auth_text), 7000);
                    }
                    break;
                case MY_PERMISSION_REQUEST_STORAGE:
                    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
                        // 허용 
                    } else {
                        //비허용
                        checkPermissions();
                    }
                    break;
                default:
                    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }
    }            
    
    
    
    @TargetApi(Build.VERSION_CODES.M)
    private void checkPermissions() {
        boolean writeExternalStorageRationale = ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
        int hasWriteExternalStoragePermission = ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);

        if ( hasWriteExternalStoragePermission == PackageManager.PERMISSION_DENIED
                && writeExternalStorageRationale)
            //showDialogForPermission("앱을 실행하려면 퍼미션을 허가하셔야합니다.");
            this.callGuideDialogUsingTimer(getResources().getString(R.string.info_info_text), getResources().getString(R.string.info_auth_text), 7000);
        else if ( hasWriteExternalStoragePermission == PackageManager.PERMISSION_DENIED
                && !writeExternalStorageRationale)
            showDialog();
        else if ( hasWriteExternalStoragePermission == PackageManager.PERMISSION_GRANTED ) {
           //
        }
    }    
    
    
    
    
    public void showDialog(){
        try {
            if (!MainActivity.this.isFinishing()) {
                final Dialog guideDialog = new Dialog(MainActivity.this);
                guideDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
                guideDialog.setContentView(R.layout.activity_question_dialog);
                guideDialog.getWindow().setBackgroundDrawable(new ColorDrawable(0));        //Android: how to create a transparent dialog-themed activity
                //guideDialog.setCanceledOnTouchOutside(false);
                //guideDialog.setCancelable(false);
                //String title = getResources().getString(R.string.ic_action_name2);
                //String msg = getResources().getString(R.string.txt_8);

                TextView txtContent = (TextView) guideDialog.findViewById(R.id.txtContent);
                //TextView txtTitle = (TextView) guideDialog.findViewById(R.id.txtTitle);
                //txtTitle.setText(title);
                txtContent.setText(getResources().getString(R.string.info_auth_text));


                Button buttonCancel = (Button) guideDialog.findViewById(R.id.buttonCancel);
                buttonCancel.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (v.getId() == R.id.buttonCancel) {
                            if (!MainActivity.this.isFinishing() && guideDialog != null && guideDialog.isShowing()) {
                                guideDialog.dismiss();
                            }
                        }
                    }
                });

                Button buttonConfirm = (Button) guideDialog.findViewById(R.id.buttonConfirm);
                buttonConfirm.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (v.getId() == R.id.buttonConfirm) {
                            if (!MainActivity.this.isFinishing() && guideDialog != null && guideDialog.isShowing()) {
                                guideDialog.dismiss();
                            }
                            try {
                                Intent appDetail = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                                        Uri.parse("package:" + getPackageName()));
                                appDetail.addCategory(Intent.CATEGORY_DEFAULT);
                                appDetail.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                startActivity(appDetail);
                            }catch (Exception e){
                                //e.printStackTrace();
                            }
                        }
                    }
                });

                if(!MainActivity.this.isFinishing() && guideDialog != null && !guideDialog.isShowing()) {
                    guideDialog.show();
                }

            }
        }catch (Exception e){

        }

    }

권한 요청 부분은 끝났다. 이제 미디어스토어에 접근해보자. SdkVersion 29 부터는 MediaStore.Audio.Media.DATA 가 deprecated 되었다. 접근 방식이 바뀐것이다. ContentUris클래스의 withAppendedId()메소드를사용하여 Uri정보를 획득한다.

       List<MusicVo> songsList = new ArrayList<MusicVo>();
       
       String[] mCursorCols = new String[]{
                MediaStore.Audio.Media._ID,
                MediaStore.Audio.Media.ARTIST,
                MediaStore.Audio.Media.TITLE,
                MediaStore.Audio.Media.ALBUM,
                //MediaStore.Audio.Media.DATA,
                MediaStore.Audio.Media.ALBUM_ID
        };

        Cursor cur = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
        	, mCursorCols, null, null, null);

        if(cur != null && cur.getCount() > 0 && cur.moveToFirst() ) {
            String title, artist, album, data;
            int idColumn = cur.getColumnIndex(MediaStore.Audio.Media._ID);  
            int titleColumn = cur.getColumnIndex(MediaStore.Audio.Media.TITLE);
            int artistColumn = cur.getColumnIndex(MediaStore.Audio.Media.ARTIST);
            int albumColumn = cur.getColumnIndex(MediaStore.Audio.Media.ALBUM);
            //int dataColumn = cur.getColumnIndex(MediaStore.Audio.Media.DATA);  
            
            try {
                cur.moveToFirst();
                do {
                    title = cur.getString(titleColumn);
                    artist = cur.getString(artistColumn);
                    album = cur.getString(albumColumn);
                    data = cur.getString(dataColumn);
                    long audioId  = cur.getLong(idColumn);
                    long albumIds = cur.getLong(cur.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));
                    
                    Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
                    Uri sAlbumArtUri = ContentUris.withAppendedId(sArtworkUri, albumIds);
                    Uri externalUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                    Uri contentUri = ContentUris.withAppendedId(externalUri,audioId);
                   
                    Log.e("TAG", "contentUri = " + contentUri + " id :" + audioId  + "        _contentUri.getPath() :" +contentUri.getPath() +"       checkURIResource : " +FileUtil.checkURIResource(context,contentUri));

                    MusicVo musicvo = new MusicVo((artist == null ? "" : artist), (title == null ? "" : title), (album == null ? "" : album), null, (data == null ? "" : data), sAlbumArtUri, contentUri);
                    songsList.add(musicvo);

                } while (cur.moveToNext());
            } catch (IllegalStateException ee) {
                ee.printStackTrace();
            }catch (SecurityException se){
                songsList = new ArrayList<MusicVo>();
            }catch(Exception e){
                songsList = new ArrayList<MusicVo>();
            }finally {
                if (cur != null) {
                    cur.close();
                }
            }
        }
        if (cur != null) {
            cur.close();
        }            
public class MusicVo {
    private String gasu;
    private String jemok;
    private String albumName;
    private Drawable drawimage;
    private String data;
    //private int albumId;
    private Uri imgUri ;
    private Uri contentUri ;
    private FileDescriptor getFileDescriptor;
    
    public MusicVo(String _gasu, String _jemok, String _albumName, Drawable _image, String _data, Uri _imgUri,  Uri _contentUri){
        this.gasu = _gasu;
        this.jemok = _jemok;
        this.albumName = _albumName;
        this.drawimage = _image;
        this.data = _data;
        this.imgUri = _imgUri;
        this.contentUri = _contentUri;
    }    
    
    ...... getter, setter 생략

필요하다면 SharedPreferences에 노래 절대 경로 Uri의 getPath()정보를 저장해둔다.

//SharedPreferences 에 저장
SharedPreferences sp = context.getSharedPreferences("PREF", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString(ALARM_SONG_PATH,vo.getContentUri().getPath());
editor.putString(ALARM_SONG_NAME, vo.getGasu() + " - " + vo.getJemok() ); 
editor.apply();

노래를 재생시켜보자

MediaPlayer mplay = null;
mplay = new MediaPlayer();

mplay.reset(); 
mplay.setDataSource(MusicActivity.this, musicVo.getContentUri());
mplay.prepare();
mplay.start();

두번째 노래를 재생시키는 방법이다. SharedPreferences에 저장해두었던 Uri 값을 가져와서 처리하는 방법이다.

 private void musicPlay(){
   SharedPreferences sp = getSharedPreferences("PREF", Activity.MODE_PRIVATE);
   String uriStr = "content://media" + sp.getString(ALARM_SONG_PATH, ""); 
   Uri contentUri = Uri.parse(uriStr);
   if (checkURIResource(getApplicationContext(), contentUri)) {
      if (mp3 == null) {
          mp3 = new MediaPlayer();
          mp3.setDataSource(getApplicationContext(), contentUri);
          mp3.prepare();
          mp3.start();
      }
   } 
 }
 
 
     public  boolean checkURIResource(Context context, Uri uri) {
        Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
        return (cursor != null && cursor.moveToFirst());
    }

Leave a Reply

error: Content is protected !!