初心者のためのM Permissions入門


device-2015-11-10-025609今回はAndroid 6.0(Marshmallow)から導入された新しいパーミッションモデルを紹介します。M Permissionsと呼ばれているこの新しい仕組みではパーミッション(機能へのアクセス権限)の仕様が改善されており、アプリが利用するタイミングで機能利用の許可を求めるパーミッションモデルを採用しています。

今まではPlayストアからアプリをインストールするときに一括で権限を得るという、開発者にとってわかりやすい権限の仕組みでした。一方、利用者には、過剰な権限を要求されていたり、どのタイミングで使われているかわからないなど不安な点もありました。

M Permissionsでは利用時に許可ダイアログをだすかたちに改められています。この仕組はAndroid 6.0から導入されていますが、Android Studioでビルドに利用するSDKで挙動が変わる、似たようなAPIを使い分けるなければならない、など癖も多い機能です。

  • targetSdkVersionが23以上: M Permissionを利用する(Android 6.0のAPI Level 23にあわせています)
  • targetSdkVersionが22以下:インストール時に全権限を確認する(旧来の手法)

ここでは、今後の開発を踏まえ最新のSDKを使った場合(targetSdkVersion:23)について重要なメソッドとシーケンスに触れながらM Permissionsを解説します。

  • checkSelfPermission:権限の取得状況を確認する
  • requestPermissions:権限の許可ダイアログを表示する
  • onRequestPermissionsResult:許可ダイアログの承認結果を受け取る(許可・不許可)
  • shouldShowRequestPermissionRationale:許可ダイアログの再表示判定(永続的に不許可設定の場合、falseが返却される)

パーミッションはどうしても難易度が高くなるテーマですが、初学者であれば個々のAPIや仕組みの理解を深めるまえに、サンプルコードを動かしてみてください。実際に動きをみることで理解度が高まるほか、雛形として使うことで一旦、パーミッションのことは横に置きつつカメラなど利用したい機能の理解に注力できます。

サンプルコードの解説は続きからどうぞ。

M Permissionsのシーケンスを知る

基本的な処理シーケンスは次のとおりです(@sys1yagiさんよりアクティビティ図の転載を許可いただきました。ありがとうございます。また@sys1yagiさんのブログには、M Permissionsについて踏み込んだ解説もあるので参考にどうぞ)。次の図で赤枠で囲んだ部分が今回の記事で対象となる処理手順です。

MPermission

アクティビティ図では次の1~4の手順のあとに、さらに不許可だった場合の再リクエストが続きますが、まずはここまでを確認してみましょう。

  1. PermissionChecker.checkSelfPermissionメソッドで現在の権限の有無を確認する
  2. ActivityCompat.shouldShowRequestPermissionRationaleメソッドで許可ダイアログの表示を切り替える
  3. requestPermissionsメソッドで権限付与をリクエストする
  4. onRequestPermissionsResultメソッドでリクエスト結果を受け取る

ここからは上記手順ごと「REQUEST PERMISSION」ボタンを押下するとカメラ権限を取得しようとするサンプルアプリを考えてみましょう。

1.2.ボタンを押すと権限をリクエストできるサンプルアプリ

device-2015-11-10-025556
M Permissionsは、マニフェストファイルに該当のuse-permission要素がないと正常に動きません。サンプルをダウンロードして利用する場合、またカメラ権限ではなくてコンタクトなど別の権限で試す場合は、先にAndroidManifest.xmlにパーミッションを追加するようにしてください。

■AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.techbooster.sample.permissionbasic" >

    <uses-permission android:name="android.permission.CAMERA" />

    <application
        android:allowBackup="true"
    ...省略...

次にActivity中のREQUEST PERMISSIONボタンを押したときの処理です。
PermissionChecker.checkSelfPermissionメソッドで権限をチェックして権限をもっていればカメラの起動処理(TODO部分)を行います。

device-2015-11-10-025840

■src/MainActivity.java

public class MainActivity extends Activity {

    private static final String TAG = "M Permission";
    private int REQUEST_CODE_CAMERA_PERMISSION = 0x01;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button req = (Button) findViewById(R.id.requestButton);
        req.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // パーミッションを持っているか確認する
                if (PermissionChecker.checkSelfPermission(
                        MainActivity.this, Manifest.permission.CAMERA)
                        != PackageManager.PERMISSION_GRANTED) {
                    // パーミッションをリクエストする
                    requestCameraPermission();
                    return;
                }
                Log.d(TAG, "checkSelfPermission: GRANTED");
                Toast.makeText(MainActivity.this, "パーミッションは取得済みです!やり直す場合はアプリをアンインストールするか設定から権限を再設定してください",
                        Toast.LENGTH_LONG).show();
                // TODO : Access
            }
        });
    }
    ...省略...
}

PermissionChecker.checkSelfPermissionメソッドと同じように権限をチェックする実装は、Activity(API Level 23以上)、ActivityCompatなど複数ありますが上記であげたような基本シーケンスを全て実装する場合、PermissionChecker.checkSelfPermissionがおすすめです。

3.パーミッションのリクエスト処理

device-2015-11-10-025609

パーミッションのリクエストはshouldShowRequestPermissionRationaleメソッドと権限をリクエストして許可ダイアログを表示するrequestPermissionsメソッドの組み合わせで実行します。

■src/MainActivity.java

    // Permission handling for Android 6.0
    private void requestCameraPermission() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.CAMERA)) {

            Log.d(TAG, "shouldShowRequestPermissionRationale:追加説明");
            // 権限チェックした結果、持っていない場合はダイアログを出す
            new AlertDialog.Builder(this)
                    .setTitle("パーミッションの追加説明")
                    .setMessage("このアプリで写真を撮るにはパーミッションが必要です")
                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            ActivityCompat.requestPermissions(MainActivity.this,
                                    new String[]{Manifest.permission.CAMERA},
                                    REQUEST_CODE_CAMERA_PERMISSION);
                        }
                    })
                    .create()
                    .show();
            return;
        }

        // 権限を取得する
        ActivityCompat.requestPermissions(this, new String[]{
                        Manifest.permission.CAMERA
                },
                REQUEST_CODE_CAMERA_PERMISSION);
        return;
    }

ActivityCompat.requestPermissionsメソッドは、権限の許可ダイアログを表示します。メソッドの途中でActivityCompat.shouldShowRequestPermissionRationaleメソッドを使った分岐処理が存在していますが、このメソッドはユーザーが過去に「今後は確認しない(継続的に不許可)」を選んでいればfalseを返却します。

サンプルコードでは、falseの場合はそのままrequestPermissionsメソッドを実行してるものの呼び出し先(後述のonRequestPermissionsResultメソッド)では、PERMISSION_DENIED(権限がないという意味)が返却されています。この場合は権限の取得を諦め、機能を制限するかエラー処理を行う必要があります。

また手元でサンプルプロジェクトを動かせる環境であれば「許可しない」を選んだ後、もう一度REQUEST PERMISSIONボタンを押してみてください。次のようにダイアログが表示されるはずです。

device-2015-11-10-025636

shouldShowRequestPermissionRationaleメソッドでの権限チェックのあと、サンプルコードではAlertDialogによりパーミッションの取得を追加説明するダイアログを表示して確認しています。
2回目でメッセージを追加できる仕様は、一見回りくどいようにみえますが権限をもらう最後の機会ですので、必要な権限について十分な説明するべきでしょう(カメラのような分かりやすい機能であれば「なぜ必要か=カメラがないと撮影すら出来ない」という分かりやすさがありますが電話帳などは個人情報を多く含む警戒されてしかるべき内容です)。

4.リクエスト(許可ダイアログ)の結果を受け取る

さきほどはActivityCompat.requestPermissionsメソッドによる許可ダイアログのリクエストを行いました。M Permissionでは、許可ダイアログはAndroid OS(フレームワーク)が表示するため、アプリケーションからはアクセスできません。
そのかわりにコールバックとしてonRequestPermissionsResultメソッドが用意されており、権限を許可しない/許可するを受け取れます。
権限があればカメラへのアクセスを行い(次のサンプルではTODO化されている場所が該当の場所です)、不許可であれば更に、shouldShowRequestPermissionRationaleメソッドでエラーダイアログの種類を変更しています。

冒頭のアクティビティ図では、下の部分にあたります(赤い四角下部)。

■src/MainActivity.java

@Override
public void onRequestPermissionsResult(int requestCode,
                             String permissions[], int[] grantResults) {

  if (requestCode == REQUEST_CODE_CAMERA_PERMISSION) {
    if (grantResults.length != 1 ||
        grantResults[0] != PackageManager.PERMISSION_GRANTED) {
        Log.d(TAG, "onRequestPermissionsResult:DENYED");

      if (ActivityCompat.shouldShowRequestPermissionRationale(this,
          Manifest.permission.CAMERA)) {
        Log.d(TAG, "[show error]");
        new AlertDialog.Builder(this)
            .setTitle("パーミッション取得エラー")
            .setMessage("再試行する場合は、再度Requestボタンを押してください")
            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialogInterface, int i) {
                  // サンプルのため、今回はもう一度操作をはさんでいますが
                  // ここでrequestCameraPermissionメソッドの実行でもよい
                }
            })
            .create()
            .show();

      } else {
        Log.d(TAG, "[show app settings guide]");
        new AlertDialog.Builder(this)
           .setTitle("パーミッション取得エラー")
           .setMessage("今後は許可しないが選択されました。アプリ設定>権限をチェックしてください(権限をON/OFFすることで状態はリセットされます)")
           .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialogInterface, int i) {
                 openSettings();
              }
           })
           .create()
           .show();
      }
    } else {
      Log.d(TAG, "onRequestPermissionsResult:GRANTED");
      // TODO 許可されたのでカメラにアクセスする
    }
  } else {
      super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  }
}

onRequestPermissionsResultメソッドでは、Permissionの許可ダイアログの結果、「許可しない」「許可」を受け取ります。「許可」のケースは、わかりやすくrequestCodeを確認したのち41,42行目のようにカメラにアクセスします。
「許可しない」場合はActivityCompat.shouldShowRequestPermissionRationaleメソッドにより更に分岐します。

device-2015-11-10-025722 device-2015-11-10-025733
今後は確認しない(許可しない) パーミッション取得エラー

onRequestPermissionsResultメソッドの中では、shouldShowRequestPermissionRationaleの分岐により、再度リクエストが可能であれば、REQUESTボタンに誘導します。上図のように今後は許可しない(永続的に許可しない)が選択されている場合、アプリからは権限の変更ができなくなるため、パーミッション取得エラーとしてアプリ設定へ誘導します。

5.許可が得られない場合:アプリ設定へ誘導する

アプリ設定への誘導は、サンプルコードではopenSettingsメソッドが行っています。
■src/MainActivity.java

    private void openSettings() {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        //Fragmentの場合はgetContext().getPackageName()
        Uri uri = Uri.fromParts("package", getPackageName(), null);
        intent.setData(uri);
        startActivity(intent);
    }

openSettingsメソッドのなかではアプリの詳細設定へのインテントを生成しています。

device-2015-11-10-025754 device-2015-11-10-025819
アプリ情報の許可 権限を確認

アプリ情報の中にある許可という項目をクリックすると権限を確認できます。しかし、このような操作は利用者自身で行うため難易度が高く、この操作が必要になる前に権限についてきちんと説明しておくほうがよいでしょう。

ここまでM Permissionsのシーケンスを確認しました。複雑さはあるものの新しいパーミッションモデルとして定型的に使える内容です。実際にはActivityだけでなくFragmentでの実装なども必要になってきますが、利用にあたって重要なことは本稿で述べたとおりです。知識としてまとめておくと便利でしょう。

以上、おつかれさまでした

関連する記事:

No Comments