はじめに
Android初心者(自分含む)がつまづく点のひとつだと思われる、Android 6.0 Marshmallow以降でのパーミッション管理ですが、皆さんどのように書いておられるでしょうか。
すでに定型化できていればよいのですが、新しく実装する場合は今回紹介するPermissionsDispatcherが導入も簡単、かつ分かりやすく記述できて良さそうでした。
(今回例示するコードはJavaですが、最新の3系ではKotlinもフルサポートとのことです)
環境
Android Studio 3.0.1
 JRE: 1.8.0_152-release-915-b08 x86_64
 Mac OS X 10.13.3
導入
appモジュールのbuild.gradleを編集します。最新バージョンは公式のgithubを確認してください。
 なお、上記のリンク先で書かれている
exclude module: "support-v13"
のくだりは、Fragmentのサポートが不要な場合の記述なので、パーミッション周りの処理をFragmentでも行いたい場合は下記でOKです。
| 1 2 3 4 5 6 | dependencies {     ...     compile 'com.github.hotchemi:permissionsdispatcher:3.1.0'     annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:3.1.0' } | 
Activityでの実装例
アプリ内でカメラ機能のパーミッションが必要なケースを想定します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | package ... import ... import permissions.dispatcher.*; @RuntimePermissions // 必須 public class MainActivity extends AppCompatActivity implements View.OnClickListener{     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);         findViewById(R.id.use_camera).setOnClickListener(this);     }     @Override     public void onClick(View view) {         MainActivityPermissionsDispatcher.useCameraWithPermissionCheck(this);     }     @Override     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {         super.onRequestPermissionsResult(requestCode, permissions, grantResults);         // 自動生成されるクラスにパーミッション管理を移譲する         MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);     }     @NeedsPermission(Manifest.permission.CAMERA) // 必須     public void useCamera() {         // カメラを使う処理を記述する         Camera camera = null;         try {             camera = Camera.open();         } catch (Exception e) {             e.printStackTrace();         }         ...     }     @OnPermissionDenied(Manifest.permission.CAMERA) // オプション     public void onCameraDenied() {         Toast.makeText(this, "カメラ機能を利用できません。", Toast.LENGTH_LONG).show();     }     @OnShowRationale(Manifest.permission.CAMERA) // オプション     public void showRationaleForCamera(PermissionRequest request) {         showRationaleDialog("カメラ機能を利用するためには、権限の許可が必要です。", request);     }     @OnNeverAskAgain(Manifest.permission.CAMERA) // オプション     public void ifNeverAskAgain() {         Toast.makeText(this, "設定画面からカメラ機能の利用を許可してください。", Toast.LENGTH_LONG).show();     }     private void showRationaleDialog(String msg, final PermissionRequest request) {         new AlertDialog.Builder(this)                 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {                     @Override                     public void onClick(@NonNull DialogInterface dialog, int which) {                         request.proceed();                     }                 })                 .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {                     @Override                     public void onClick(@NonNull DialogInterface dialog, int which) {                         request.cancel();                     }                 })                 .setCancelable(false)                 .setMessage(msg)                 .show();     } } | 
@RuntimePermissions(必須)
PermissionsDispatcherが動作するために必要なアノテーションです。
 ActivityまたはFragmentのクラス宣言に付けます。
| 1 2 3 | @RuntimePermissions public class MainActivity extends AppCompatActivity implements View.OnClickListener{     ... | 
PermissionsDispatcherに処理を移譲する
アノテーションを記述することによって自動生成されるクラスに、パーミッションの取り回しを移譲します。
 最初はMainActivityPermissionsDispatcherが解決できず赤文字になるかもしれませんが、その場合は
Build
> 
Rebuild Project
を実行してください。
| 1 2 3 4 5 6 7 8 9 10 11 | @Override public void onClick(View view) {     MainActivityPermissionsDispatcher.useCameraWithPermissionCheck(this); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {     super.onRequestPermissionsResult(requestCode, permissions, grantResults);     // 自動生成されるクラスにパーミッション管理を移譲する     MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults); } | 
@NeedsPermission(必須)
パーミッションを要求するダイアログで「許可」を選択した時、あるいは既に許可されている場合の処理です。
 (カメラの場合であれば
Camera.open()
など、パーミッションが無いとExceptionが発生するようなコードです)
| 1 2 3 4 5 6 7 8 9 10 | @NeedsPermission(Manifest.permission.CAMERA) public void useCamera() {     // カメラを使う処理を記述する     Camera camera = null;     try {         camera = Camera.open();     } catch (Exception e) {         e.printStackTrace();     } } | 
ここまでは必ず記述することになる正常系のコードです。
 以降はパーミッションの要求が拒否された場合の処理になります。
 (optionalですが、実装しないということはあまり無いと思います)
@OnPermissionDenied(オプション)
「許可しない」が選択された時の処理です。
| 1 2 3 4 | @OnPermissionDenied(Manifest.permission.CAMERA) public void onCameraDenied() {     Toast.makeText(this, "カメラ機能を利用できません。", Toast.LENGTH_LONG).show(); } | 
@OnShowRationale(オプション)
パーミッション要求が拒否されている場合に、許可が必要な理由を表示します。
 
request.proceed()
でパーミッション要求のダイアログが再表示されます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @OnShowRationale(Manifest.permission.CAMERA) public void showRationaleForCamera(PermissionRequest request) {     showRationaleDialog("カメラ機能を利用するためには、権限の許可が必要です。", request); } private void showRationaleDialog(String msg, final PermissionRequest request) {     new AlertDialog.Builder(this)             .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {                 @Override                 public void onClick(@NonNull DialogInterface dialog, int which) {                     request.proceed();                 }             })             .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {                 @Override                 public void onClick(@NonNull DialogInterface dialog, int which) {                     request.cancel();                 }             })             .setCancelable(false)             .setMessage(msg)             .show(); } | 
@OnNeverAskAgain(オプション)
パーミッション要求が一度拒否されると、二回目以降は「今後表示しない」というチェックボックスがダイアログに表示されるようになります。これをチェックして「許可しない」を選択すると、以降はアプリ内で権限を取得できなくなります。
この状態になると、アプリ内でできることは無くなりますので、ユーザーに手動で設定画面から許可してもらうよう促すしかありません。
 設定画面への遷移処理をIntentで実装するケースもあると思います。
| 1 2 3 4 | @OnNeverAskAgain(Manifest.permission.CAMERA) public void ifNeverAskAgain() {     Toast.makeText(this, "設定画面からカメラ機能の利用を許可してください。", Toast.LENGTH_LONG).show(); } | 
さいごに
いかがでしたでしょうか。
Marshmallow以前・以後をまたいでAndroidに関わっている方であれば、パーミッション周りについてはご自身の実装パターンをお持ちかもしれませんが、アノテーションを使ってスッキリ記述できるのは中々良いのではないでしょうか。
また、そうでない方にとっても「何を実装しなければいけないのか」「何をユーザーに伝える必要があるのか」が、コードを読むだけで理解しやすく、おすすめできると思います。
 
  
 
 
 
