web-dev-qa-db-ja.com

Android Marshmallow(API 23)のランタイムパーミッションモデルでパーミッションを同期的にリクエストできますか?

次のようなメソッドがあるとします:

_public boolean saveFile (Url url, String content) {

   // save the file, this can be done a lot of different ways, but
   // basically the point is...

   return save_was_successful;
}
_

アプリ全体で、外部ストレージにファイルを保存する場合は、次のようにします...

_if (saveFile(external_storage_url, "this is a test")) {
   // yay, success!
} else { // notify the user something was wrong or handle the error }
_

これは簡単な例ですので、UIのブロック、例外の適切な処理などについては、私のケースには触れないでください。ファイルの保存が気に入らない場合は、getContact()またはgetPhoneState()または何でも。ポイントは、いくつかの値を返す許可が必要な操作であり、アプリ全体で使用されることです。

Android <= Lollipopで、ユーザーが_Android.permission.WRITE_EXTERNAL_STORAGE_またはその他をインストールし、付与することに同意した場合、すべてが適切です。

ただし、新しいMarshmallow(API 23) 実行時アクセス許可モデル では、ファイルを外部ストレージに保存する前に、(1)アクセス許可が付与されているかどうかを確認する必要があります。 そうでない場合 、おそらく(2) リクエストの根拠を示すシステムがそれが良い考えだと思う場合 )トーストなどで(3)ダイアログを介して許可を与えるようユーザーに依頼し、基本的に座ってコールバックを待つ...

(あなたのアプリは周りに座って待っています...)

(4)ユーザーがダイアログに最終的に応答すると、 onRequestPermissionsResult() メソッドが起動し、コードは(5)WHICHをふるいにかける必要があります permission 実際に 応答する 、ユーザーがyesまたはnoと言ったかどうか(私の知る限り、「no」と「no and二度と聞かない」を処理する方法はありません)、(6)彼らが何を理解するプログラムがfinally(7)先に進んでそのことを行えるように、許可を求めるプロセス全体を促した最初の場所で達成しようとしていました。

ユーザーがステップ(6)で何をしようとしていたかを知るには、特別な code ( "permissions request response")を事前に渡しておく必要があります。 permission request (camera/contact/etc。)しかし、同じことを考えると、「具体的には、許可を求める必要があることに気付いたときにやろうとしていたこと」のコードのように思えます許可/グループはコード内で複数の目的に使用される可能性があるため、許可を取得した後、このコードを使用して適切な場所に実行を戻す必要があります。

私はこれがどのように機能するのかを完全に誤解している可能性があります-それで私が大丈夫かどうかを教えてください-しかし、より大きなポイントは私が本当に非同期の「ユーザーが応答するのを待つ」部分があるため、前述のsaveFile()メソッドを使用して上記のすべてを実行することを検討してください。私が考えたアイデアはかなりハッキーで間違いなく間違っています。

今日の Android Developer Podcast は、すぐ近くに同期ソリューションが存在する可能性があることを示唆しており、Android St​​udio。それでも、実行時の許可プロセスがsaveFile()などに押し込まれる可能性があります。

_public boolean saveFile(Url url, String content) {
   //   this next line will check for the permission, ask the user
   //   for permission if required, maybe even handle the rationale
   //   situation
   if (!checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        R.string.permission_storage_rationale))
       {
           return false; // or throw an exception or whatever
       } else {

   // try to save the file

   return save_was_successful;

   }
}
_

したがって、上記のcheckPermission()は、ユーザーが権限を持たず、許可も拒否した場合に失敗します。おそらくcheckPermission()の周りのループを使用して、最大3回または何かを尋ねることができます。または、メソッドによって正気なdont-be-annoying-policyが処理された場合はもっと良いでしょう。

そのようなことは可能ですか?望ましい?そのようなソリューションはUIスレッドをブロックしますか?ポッドキャストからは、Googlemayがこのような解決策を手に入れたように聞こえましたが、何かがあるかどうか、コンビニエンスクラス、パターン、何かなどについて考えたいと思いました-すべての許可が必要な操作をリファクタリングしなければならない全員が関与するわけではありません。

長い質問には申し訳ありませんが、できるだけ完全になりたいと思いました。私は答えを空中に送ります。ありがとう!


Update:上記のポッドキャストのトランスクリプトです。

41:2 について聞いてください。この議論では:

大まかな転写:

Tor Norbye(ツールチーム): "だから、開発者にとって多くの作業が必要とは思えません。しかし、問題の一部は、これらが同期呼び出しではないということですよね。アクティビティの記述方法を実際に変更して、コールバックを持たせる必要があります。つまり、ステートマシンのようなものです。この状態では、あなたは "

Poiesz(プロダクトマネージャー): "Ah-考えていた-同期応答のオプションがあるかもしれない-"

ノービー:「ああ、それは物を作るだろう-

Poiesz:「私は内部で人々と話すことができます。同期についての議論を思い出しますが、見つけることができます。

Norbye: "ええ。実際、ツールに組み込むべきでしょう。簡単なリファクタリングが必要な場合..."

次に、ツールでアノテーションを使用してアクセス許可が必要なAPIを決定する方法について説明します(現在のところ、これはそれほど優れたIMOでは機能しません)。また、未チェックの「危険な」 "メソッド呼び出し:

ノービー:「... Mを使用している場合は、「ねえ、実際にこの許可を確認していますか、それともセキュリティ例外をキャッチしていますか?」 、「ここで許可をリクエストするためにおそらく何かする必要がある」と言うでしょう。しかし、私はあなたが「CHING!」に行くことができる簡単な修正を持っていることを望みますそして、それは尋ねるためにすべての適切なものを挿入しますが、私が見たときに物事が戻った方法は、これは多くのものを再構築する必要がありました-インターフェースとコールバックを追加し、フローを変更し、私たちはできませんでした。一時的なものまたは永続的なものとしての簡単な同期モード、それは[素晴らしい]でしょう。 "

41
fattire

マシュマロの時点で、私の理解では、あなたはできないということです。

アプリで同じ問題を解決する必要がありました。以下がその方法です。

  1. リファクタリング:何らかの種類の許可に依存するすべてのコードのチャンクを独自のメソッドに移動します。
  2. リファクタリングの追加:各メソッドのトリガーを特定し(アクティビティの開始、コントロールのタップなど)、同じトリガーがある場合はメソッドをグループ化します。 (2つのメソッドが同じトリガーを持ち、同じ許可セットを必要とする場合、それらをマージすることを検討してください。)
  3. さらにリファクタリング:以前に呼び出された新しいメソッドに依存するものを見つけ、これらのメソッドがいつ呼び出されるかについて柔軟であることを確認します:メソッド自体に追加するか、少なくともメソッドが以前に呼び出されていない場合は例外をスローせず、メソッドが呼び出されるとすぐに期待どおりに動作することを確認してください。
  4. リクエストコード:これらのメソッド(またはそのグループ)のそれぞれについて、リクエストコードとして使用される整数定数を定義します。
  5. 権限の確認と要求:これらの各メソッド/メソッドのグループを次のコードにラップします。

_if (ContextCompat.checkSelfPermission(this, Manifest.permission.SOME_PERMISSION) == PackageManager.PERMISSION_GRANTED)
    doStuffThatRequiresPermission();
else
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SOME_PERMISSION}, Const.PERM_REQUEST_DO_STUFF_THAT_REQUIRES_PERMISSION);
_
  1. 応答の処理:許可を要求するすべてのActivityonRequestPermissionsResult()の実装を記述します。要求された許可が付与されたかどうかを確認し、要求コードを使用して、呼び出す必要のあるメソッドを判別します。

APIは、実行時のアクセス許可リクエストにActivityを必要とすることに注意してください。非対話型のコンポーネント(Serviceなど)がある場合は、 Android Marshmallow のサービスから権限をリクエストする方法)をご覧ください。基本的に、最も簡単な方法は通知を表示し、Activityを表示することです。これは、実行時のアクセス許可ダイアログを表示するだけです Here 私のアプリでこれに取り組みました。

10
user149408

簡単な答え:いいえ、今日は同期操作はありません。操作を完了する前に適切な権限があるかどうかを確認する必要があります。最後のオプションとして、セキュリティ例外のtry/catchブロックを配置できます。 catchブロックでは、権限の問題が原因で操作が失敗したことをユーザーに通知できます。さらに、別のポイントがあります。許可が取り消されると、アプリはメインアクティビティから再起動しないため、onResume()でも許可を確認する必要があります。

2
greywolf82

明示的にブロック(およびビジー待機)する必要なく、または別の「ブートローダー」アクティビティを必要とせずに、「同期性」の問題を解決した方法を次に示します。 Iスプラッシュ画面のアクティビティを次のようにリファクタリングしました

update:より完全な例は こちら にあります


note:requestPermissions() AP​​IがstartActivityForResult()を呼び出すため

_public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
}
_

メインのビュー作成ロジックがOnCreate()からOnCreate2()に移動され、OnCreate()が権限チェックを処理するようになりました。 RequestPermissions()を呼び出す必要がある場合、関連するOnRequestPermissionsResult()はこのアクティビティを再開します(元のバンドルのコピーを転送します)。


_[Activity(Label = "MarshmellowActivated",
    MainLauncher = true,      
    Theme = "@style/Theme.Transparent",
    Icon = "@drawable/icon"        
    ////////////////////////////////////////////////////////////////
    // THIS PREVENTS OnRequestPermissionsResult() from being called
    //NoHistory = true
)]
public class MarshmellowActivated : Activity
{
    private const int Android_PERMISSION_REQUEST_CODE__SDCARD = 112;
    private Bundle _savedInstanceState;

    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
    {
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode)
        {
            case Android_PERMISSION_REQUEST_CODE__SDCARD:
                if (grantResults.Length > 0 && grantResults[0] == Permission.Granted)
                {       
                    Intent restartThisActivityIntent = new Intent(this, this.GetType());
                    if (_savedInstanceState != null)
                    {
                        // *ref1: Forward bundle from one intent to another
                        restartThisActivityIntent.PutExtras(_savedInstanceState);
                    }
                    StartActivity(restartThisActivityIntent);
                }
                else
                {                   
                    throw new Exception("SD Card Write Access: Denied.");
                }
                break;
        }
    }

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Android v6 requires explicit permission granting from user at runtime for extra sweet security goodness
        Permission extStoragePerm = ApplicationContext.CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);

        //if(extStoragePerm == Permission.Denied)
        if (extStoragePerm != Permission.Granted)
        {
            _savedInstanceState = savedInstanceState;
            // **calls startActivityForResult()**
            RequestPermissions(new[] { Android.Manifest.Permission.WriteExternalStorage }, Android_PERMISSION_REQUEST_CODE__SDCARD);
        }
        else
        {
            OnCreate2(savedInstanceState);
        }
    }

    private void OnCreate2(Bundle savedInstanceState)
    {
        //...
    }
}
_

ref1:あるインテントから別のインテントへのバンドルの転送

note:これは、より多くのパーミッションを一般的に処理するためにリファクタリングできます。現在、SDカードの書き込み許可のみを処理します。これにより、適切なロジックを十分に明確に伝えることができます。

1
samis

ですから、私の例で使用されている_Android.permission.WRITE_EXTERNAL_STORAGE_許可に関して、私自身の質問に具体的に答えるのは嫌ですが、一体何なのでしょう。

ファイルの読み取りおよび/または書き込みについては、実際には、許可を求めてから確認することを完全に回避する方法があります。したがって、上記のフロー全体をバイパスします。このようにして、例として示したsaveFile (Url url, String content)メソッドは引き続き同期して機能します。

API 19+で動作すると考えられるソリューションは、DocumentsProviderを「仲介者」として機能させることで_WRITE_EXTERNAL_STORAGE_権限の必要性を排除し、アプリの代わりにユーザーに「特定のファイルへの書き込み」(つまり「ファイルピッカー」)、ユーザーがファイルを選択する(または新しいファイル名を入力する)と、アプリはそのUriに対する書き込み許可を魔法のように付与されます。明確に許可しています。

「公式」_WRITE_EXTERNAL_STORAGE_許可は必要ありません。

このような権限の借用の方法は Storage Access Framework の一部であり、これについての議論はIan LakeのBig Android BBQで行われました。 ここにビデオがあります 呼ばれますストレージの許可を忘れる:共有と共同作業の代替手段 _WRITE_EXTERNAL_STORAGE_パーミッション要件。

これは、すべての場合の同期/非同期アクセス許可の問題を完全に解決するわけではありませんが、あらゆる種類の外部ドキュメント、またはプロバイダー(gDrive、Box.net、Dropboxなど)によって提供されるものについても、チェックアウトする価値のあるソリューション。

1
fattire

次のようなブロッキングヘルパーメソッドを追加できます。

@TargetApi(23) 
public static void checkForPermissionsMAndAboveBlocking(Activity act) {
    Log.i(Prefs.TAG, "checkForPermissions() called");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        // Here, thisActivity is the current activity
        if (act.checkSelfPermission(
                Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {


            // No explanation needed, we can request the permission.
            act.requestPermissions(
                    new String[]{
                          Manifest.permission.WRITE_EXTERNAL_STORAGE
                    },
                    0);

            while (true) {
                if (act.checkSelfPermission(
                        Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        == PackageManager.PERMISSION_GRANTED) {

                    Log.i(Prefs.TAG, "Got permissions, exiting block loop");
                    break;
                }
                Log.i(Prefs.TAG, "Sleeping, waiting for permissions");
                try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            }

        }
        // permission already granted
        else {
            Log.i(Prefs.TAG, "permission already granted");
        }
    }
    else {
        Log.i(Prefs.TAG, "Below M, permissions not via code");
    }

}
0
Eli