26

バックグラウンド

Lollipop 以降、アプリは実際の SD カードにアクセスできるようになりました (Kitkat ではアクセスできず、以前のバージョンでは正式にサポートされていませんでしたが)

問題

SD カードをサポートする Lollipop デバイスを目にすることは非常にまれになっており、エミュレーターには SD カード サポートをエミュレートする機能が実際には備わっていないため (または、エミュレートしている?)、それをテストするのにかなりの時間がかかりました。 .

とにかく、通常の File クラスを使用して 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を試して使用するために、セカンダリ外部ストレージを使用してテストしたいと考えています。

これらの質問のいくつかについては、一方が他方に答えるのに大いに役立つと思います。

4

2 に答える 2

3

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 を含む) を提供し、それらの UUID をパスにマップします。これにより、DocumentUri を実際のパスに変換する (およびその逆) のに役立ちます。

@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 を処理するためのより良い方法を提供するつもりはないようです。

編集:このアプローチは Android 6.0 では役に立たないようです...

于 2016-01-10T07:54:19.233 に答える