web-dev-qa-db-ja.com

実行時にWRITE_EXTERNAL_STORAGEパーミッションをリクエストして許可しても、現在のセッションには影響しません

これは、_Manifest.permission.WRITE_EXTERNAL_STORAGE_許可を要求するときの Android Marshmallowで導入された実行時許可 の新しいモデルに関するものです。

手短に言えば、私が経験しているのは、_Manifest.permission.WRITE_EXTERNAL_STORAGE_許可をリクエストした場合(およびユーザーが許可した場合)、アプリは外部ストレージディレクトリから読み書きできません破壊して再起動するまでアプリ

これは私がやっている/経験していることです:

私のアプリは次の状態から始まります:

_ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
_

これは、外部ストレージにアクセスする権限がありません。

次に、Googleが説明するようにManifest.permission.WRITE_EXTERNAL_STORAGEの許可をリクエストします

_private void requestWriteExternalStoragePermission() {
    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
        new AlertDialog.Builder(this)
                .setTitle("Inform and request")
                .setMessage("You need to enable permissions, bla bla bla")
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ActivityCompat.requestPermissions(MendeleyActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RC_PERMISSION_WRITE_EXTERNAL_STORAGE);
                    }
                })
                .show();
    } else {
        ActivityCompat.requestPermissions(MendeleyActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, RC_PERMISSION_WRITE_EXTERNAL_STORAGE);
    }
}
_

ユーザーが許可を許可すると、onRequestPermissionsResultが呼び出されます。

_@Override
public void onRequestPermissionsResult(int requestCode,  String permissions[], int[] grantResults) {
    switch (requestCode) {
        case RC_PERMISSION_WRITE_EXTERNAL_STORAGE: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0 && PackageManager.PERMISSION_GRANTED
                // allowed 
            } else {
                // denied
            }
            break;
        }
    }
}
_

allowedブロックが実行され、ユーザーがアクセス許可を付与したことが確認されます。

この後すぐに、アプリを破棄して再度開かなければ、外部ストレージへのアクセス許可はまだありません。すなわち:

_hasWriteExternalStoragePermission();                  // returns true
Environment.getExternalStorageDirectory().canRead();  // RETURNS FALSE!!
Environment.getExternalStorageDirectory().canWrite(); // RETURNS FALSE!!
_

だから、Androidランタイムは私にパーミッションがあると思っているようですが、ファイルシステムはそうではありません...実際、Environment.getExternalStorageDirectory()にアクセスしようとすると例外がスローされます:

_Android.system.ErrnoException: open failed: EACCES (Permission denied)
at libcore.io.Posix.open(Native Method)
at libcore.io.BlockGuardOs.open(BlockGuardOs.Java:186)
at libcore.io.IoBridge.open(IoBridge.Java:438)
at Java.io.FileOutputStream.<init>(FileOutputStream.Java:87) 
at Java.io.FileOutputStream.<init>(FileOutputStream.Java:72) 
_

私が今アプリを破壊して再度開くと、振る舞いは本来のようになり、外部ストレージフォルダで読み書きできるようになります。

誰もこれを経験していますか?

私は1つの公式エミュレータを使用しています:

  • 最新のAndroid 6.0(API 23)API 23、Rev 1。
  • Intel x86 Atom System Image、API 23、Rev 1を実行するエミュレーター.

アプリを次のように作成します。

_Android {
    compileSdkVersion 23
    buildToolsVersion "22.0.1"

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 23
    }
...
}
_

誰かがこれを確認し、バグを開く必要があると思うのは私だけではないが、そのようなコア機能がSDKでバグになる可能性は低いと思うので、何か間違ったことをしていると思います。

25
GaRRaPeTa

http://developer.Android.com/reference/Android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE

APIレベル19以降、getExternalFilesDir(String)およびgetExternalCacheDir()によって返されるアプリケーション固有のディレクトリ内のファイルの読み取り/書き込みには、このアクセス許可は必要ありません。

「ランタイム許可要求」は、明らかに19を超えるAPIレベル23で開始されるため、getExternalFilesDir()が指すフォルダーの外部のデータにアクセスする場合を除き、許可は必要ありません。したがって、これはエミュレータのバグだと思います。

実行時のアクセス許可の要求をサポートしないレベル19より下のターゲットでは、マニフェストのアクセス許可を調整するだけで機能します。

7
crazii

同じ問題がありました。これは大きな問題のようです。外部ストレージへの書き込み許可を変更すると、このプロセスのGIDが変更されます(Linux側)。 IDを変更するには、プロセスを再起動する必要があります。次回アプリを開くと、新しいgroupIDが設定され、許可が付与されます。

要するに、私は恐れていますこれはエミュレータのバグではなく、実際にはLinuxとAndroidの大きな問題です。

アプリを最初に実行したときに許可を求め、許可が次のように与えられたときに再起動することで、これを「解決」しました。

PackageManager packageManager = getPackageManager();
                    Intent intent = packageManager.getLaunchIntentForPackage(getPackageName());
                    ComponentName componentName = intent.getComponent();
                    Intent mainIntent = IntentCompat.makeRestartActivityTask(componentName);
                    startActivity(mainIntent);
                    System.exit(0);

バックグラウンドで実行されているサービス(別のプロセスIDを持つ)を作成し、許可を与えることを試みることができます。そうすれば、完全なアプリではなく、サービスを再起動するだけで済みます。マイナス面では、これにより作業が増える可能性があります。

お役に立てれば。

---編集---

ユーザーM66B( https://stackoverflow.com/a/32473449/1565635 )は、関連するGIDのリストを見つけました。詳細については、こちらをご覧ください: https://Android.googlesource.com/platform/frameworks/base/+/master/data/etc/platform.xml

5
Tobias Reich