android 새 Lollipop API를 사용하여 모든 SD 카드에 액세스하는 방법




1 Answers

이것은 Uris를 경로로 변환하는 방법을 찾지 못하거나 (그 반대의 경우도 마찬가지입니다) 일반 경로에 대한 액세스를 제한합니다. 또한 현재 SD 카드에 액세스 할 수 있는지 확인하는 방법을 모르므로 사용자에게 읽기 / 쓰기 권한을 요청할시기를 모르겠습니다.

API 19 (KitKat) 비공개 Android 클래스 StorageVolume을 사용하여 리플렉션을 통해 질문에 대한 답변을 얻을 수 있습니다.

public static Map<String, String> getSecondaryMountedVolumesMap(Context context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    Object[] volumes;

    StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
    Method getVolumeListMethod = sm.getClass().getMethod("getVolumeList");
    volumes = (Object[])getVolumeListMethod.invoke(sm);

    Map<String, String> volumesMap = new HashMap<>();
    for (Object volume : volumes) {
        Method getStateMethod = volume.getClass().getMethod("getState");
        String mState = (String) getStateMethod.invoke(volume);

        Method isPrimaryMethod = volume.getClass().getMethod("isPrimary");
        boolean mPrimary = (Boolean) isPrimaryMethod.invoke(volume);

        if (!mPrimary && mState.equals("mounted")) {
            Method getPathMethod = volume.getClass().getMethod("getPath");
            String mPath = (String) getPathMethod.invoke(volume);

            Method getUuidMethod = volume.getClass().getMethod("getUuid");
            String mUuid = (String) getUuidMethod.invoke(volume);

            if (mUuid != null && mPath != null)
                volumesMap.put(mUuid, mPath);
        }
    }
    return volumesMap;
}

이 방법은 USB OTG를 포함하여 마운트되지 않은 모든 기본 저장소를 제공하고 DocumentUri를 실제 경로로 변환하는 데 도움이되는 경로에 UUID를 매핑합니다.

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static String convertToPath(Uri treeUri, Context context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    String documentId = DocumentsContract.getTreeDocumentId(treeUri);
    if (documentId != null){
        String[] split = documentId.split(":");
        String uuid = null;
        if (split.length > 0)
            uuid = split[0];
        String pathToVolume = null;
        Map<String, String> volumesMap = getSecondaryMountedVolumesMap(context);
        if (volumesMap != null && uuid != null)
            pathToVolume = volumesMap.get(uuid);
        if (pathToVolume != null) {
            String pathInsideOfVolume = split.length == 2 ? IFile.SEPARATOR + split[1] : "";
            return pathToVolume + pathInsideOfVolume;
        }
    }
    return null;
}

Google은 추악한 SAF를 처리하는 데 더 좋은 방법을 제공하지 않습니다.

편집 : 이 접근법 안드로이드 6.0 쓸모없는 것 같아요 ...

android file uri android-sdcard documentfile

배경

Lollipop부터는 앱에서 실제 SD 카드에 액세스 할 수 있습니다 (Kitkat에서 액세스 할 수 없으며 공식적으로 지원되지 않았지만 이전 버전에서 지원됨).

문제

SD 카드를 지원하는 Lollipop 장치를 보는 것은 매우 드물기 때문에 에뮬레이터에 SD 카드 지원을 에뮬레이션 할 수있는 기능이 없기 때문에 테스트하는 데 꽤 오래 걸렸습니다. .

어쨌든 일반 파일 클래스를 사용하여 SD 카드에 액세스하는 대신 (일단 권한을 얻었 으면) DocumentFile을 사용하여 Uris를 사용해야합니다.

이것은 Uris를 경로로 변환하는 방법을 찾지 못하거나 (그 반대의 경우도 마찬가지입니다) 일반 경로에 대한 액세스를 제한합니다. 또한 현재 SD 카드에 액세스 할 수 있는지 확인하는 방법을 모르므로 사용자에게 읽기 / 쓰기 권한을 요청할시기를 모르겠습니다.

내가 시도한 것

현재이 방법으로 모든 SD 카드에 대한 경로를 얻습니다.

  /**
   * returns a list of all available sd cards paths, or null if not found.
   *
   * @param includePrimaryExternalStorage set to true if you wish to also include the path of the primary external storage
   */
  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  public static List<String> getExternalStoragePaths(final Context context,final boolean includePrimaryExternalStorage)
    {
    final File primaryExternalStorageDirectory=Environment.getExternalStorageDirectory();
    final List<String> result=new ArrayList<>();
    final File[] externalCacheDirs=ContextCompat.getExternalCacheDirs(context);
    if(externalCacheDirs==null||externalCacheDirs.length==0)
      return result;
    if(externalCacheDirs.length==1)
      {
      if(externalCacheDirs[0]==null)
        return result;
      final String storageState=EnvironmentCompat.getStorageState(externalCacheDirs[0]);
      if(!Environment.MEDIA_MOUNTED.equals(storageState))
        return result;
      if(!includePrimaryExternalStorage&&VERSION.SDK_INT>=VERSION_CODES.HONEYCOMB&&Environment.isExternalStorageEmulated())
        return result;
      }
    if(includePrimaryExternalStorage||externalCacheDirs.length==1)
      {
      if(primaryExternalStorageDirectory!=null)
        result.add(primaryExternalStorageDirectory.getAbsolutePath());
      else
        result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0]));
      }
    for(int i=1;i<externalCacheDirs.length;++i)
      {
      final File file=externalCacheDirs[i];
      if(file==null)
        continue;
      final String storageState=EnvironmentCompat.getStorageState(file);
      if(Environment.MEDIA_MOUNTED.equals(storageState))
        result.add(getRootOfInnerSdCardFolder(externalCacheDirs[i]));
      }
    return result;
    }


private static String getRootOfInnerSdCardFolder(File file)
  {
  if(file==null)
    return null;
  final long totalSpace=file.getTotalSpace();
  while(true)
    {
    final File parentFile=file.getParentFile();
    if(parentFile==null||parentFile.getTotalSpace()!=totalSpace)
      return file.getAbsolutePath();
    file=parentFile;
    }
  }

이것이 제가 도달 할 수있는 Uris를 확인하는 방법입니다.

final List<UriPermission> persistedUriPermissions=getContentResolver().getPersistedUriPermissions();

다음은 SD 카드에 액세스하는 방법입니다.

startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE),42);

public void onActivityResult(int requestCode,int resultCode,Intent resultData)
  {
  if(resultCode!=RESULT_OK)
    return;
  Uri treeUri=resultData.getData();
  DocumentFile pickedDir=DocumentFile.fromTreeUri(this,treeUri);
  grantUriPermission(getPackageName(),treeUri,Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  getContentResolver().takePersistableUriPermission(treeUri,Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  }

질문들

  1. 현재의 SD 카드에 액세스 할 수 있는지 확인하는 것이 가능합니까? 그렇지 않은 경우 사용자에게 허가를 받도록 요청할 수 있습니까?

  2. DocumentFile uris와 실제 경로를 변환하는 공식적인 방법이 있습니까? 이 답변을 찾았지만 내 경우에는 추락했습니다. 게다가 해킹으로 보입니다.

  3. 특정 경로에 대한 사용자의 허가를 요청할 수 있습니까? 어쩌면 "예 / 아니오를 받아들나요?"라는 대화 만 표시 할 수도 있습니다.

  4. 권한이 부여되면 DocumentFile API 대신 일반 File API를 사용할 수 있습니까?

  5. 파일 / 파일 경로가 주어지면 액세스 권한 (이전에 부여되었는지 확인)이나 루트 경로를 요청할 수 있습니까?

  6. 에뮬레이터에 SD 카드를 만들 수 있습니까? 현재 "SD 카드"가 언급되어 있지만 기본 외부 저장소로 작동하며 새로운 API를 사용해 보려고 보조 외부 저장소를 사용하여 테스트하고 싶습니다.

그 질문 중 일부는 다른 질문에 답하는 데 많은 도움이된다고 생각합니다.




Related