Android 6.0のRuntime Permissionに対応する

2015/10/4 26785hit

Android 6.0ではRuntime Permissionという概念が取り入れられました。
それまでのアプリでは動作に問題が出ることが有ります。

Runtime Permissionとは

Androidではカメラやネットワークなど一部の機能を使用するには、ManifestにPermissionを記載して、使用することを明示的に宣言しないと使えない機能があります。
これにより、ユーザーはそのアプリがどのような機能を使用しているかを把握することが出来ます。

PermissionはAndroid6.0未満では全てインストール前に確認するという方式が取られていました。
ユーザーがPermissionを許可しなかった場合アプリをインストール出来ません。
しかし、この方式はいくつかの問題を含んでいます。
  • 多くのユーザーはインストール前にそのアプリがどのPermissionをどうやって使うのか把握しづらく、ユーザーはPermissionに何が宣言されていようとそれが妥当であるか判断できない。
  • 一度許可したアプリは永続的に許可され続けるのでユーザーによるPermissionチェックが適切に働きにくい。
  • Permissionが変更されるとユーザーの手動アップデートが必要となるため、Permissionを追加したバージョンアップでアップデートがなかなか行ってもらえなくなったり、それを防ぐために予め片っ端からPermissionを宣言しておくなどということが発生していた。

それらを解決するためにAndroid6.0ではRuntime Permissionという仕組みが取り入れられました。
Runtime Permissionではカメラやアドレス帳へのアクセスと言ったリスクの高いPermissionについてインストール時には許可を求めず、アプリ実行中にアプリ内で許可を求めます。

これにより、Permissionを必要になったタイミングで求めることができるので、ユーザーが何故そのPermissionが必要なのか理解しやすくなり、ユーザーはあとから、そのアプリがどのような機能を使っているかを見ることが出来てPermissionを変更することも出来ます。
Permisionを追加しても、自動的にアップデートが行われるようになり、古いアプリを使い続けることが減ると思われます。

Runtime Permissionの動き

Runtime Permissionがどのように動くは、TargetAPIの指定により変わってきます。
TargetAPIが22以前の場合は従来のアプリのようにインストール時にPermissionの一覧が表示されユーザーが全てのPermissionを許可することでインストールが可能となります。
これにより、インストール直後はアプリが使う全てのPermissionが許可 状態となっています。

TargetAPIが23以上の場合は標準的なPermissionについては今までどおりインストール時に行われますが、センシティブなPermissionについてはインストール時にはチェックされず、そのままアプリがインストールされます。
TargetAPIが22以前の場合と違い、この時点ではセンシティブなPermissionは拒否状態となっています。


Target SDKが22以前だったとしてもインストール時に許可を求めることで初期値が許可になるだけという点に注意してください。

target SDKに関係なくAndroid6.0ではインストール後個別にPermissionを拒否することができます。
その場合対応していないアプリでは例外で落ちてしまいます。(Permissionを拒否するときに警告ダイアログは出ます。)
そのため、Runtime Permissionの対応が面倒なのでTargetSDKを22のままにしておくというのはあまり良い対応ではないです。

Target SDKが22以下のアプリにたいしてPermissionを拒否した時に表示されるダイアログ。
警告はしてくれるがそれに従うかはユーザーの自由。
「このアプリはAndroidの以前のバージョンを対象としています。権限を許可しないと、意図したとおりに動作しなくなる可能性があります。」

Runtime PermissionはAndroid6.0以降でのみ動作します。
Android5.1以下ではtargetSDKのバージョンによらずすべてのPermissionが従来通りインストール前に確認されます。

いつ許可を求めるか

アプリがそのPermissionをどの程度必要としているかによって許可を求めるタイミングを変えることで利便性を改善できます。

Permissionが無いとアプリが成立しない場合

アプリを開始時にPermissionの許可取得リクエストを実行します。
インストール後にもPermissionを拒否できるので、実行時のチェックも必要ですが、いざ利用したいという時に個別にパラパラと許可を求めると、ユーザーにとって煩わしく感じるおそれがあるため最初の段階で許可を求めます。
例えば画像編集アプリであればストレージへのアクセスは必要なので起動時にPermissionを求めます。

Permissionが無くてもアプリが成立する場合

アプリの一部機能だけでPermissionを使用している場合など、Permissionがなくてもアプリとして成立するのであれば、起動時にはPermissionの要求を行わずに、Permissionが必要な機能を使用する段階で許可を求めるダイアログを表示します。
例えば画像編集アプリの場合、カメラへのアクセスは、写真を撮って加工したい人には必要ですが、既に撮った写真を加工したい人にとっては不要です。
このような場合はカメラ機能を押したタイミングでPermissionを求めるようにすることで、カメラ機能を使わないユーザーに不要な気を使わせる必要がなくなります。

ユーザーのアクションを主体とせずにPermissionが必要な場合

SMSの受信のようにユーザーがアクションしていないタイミングで必要となるPermissionは、たとえPermissionがなくてもアプリが成立したとしても、Permissionを求めるタイミングが他にないため起動時に許可を求めます。

どうやって許可を求めるか

そのPermissionを要求する理由が明確かによって許可の求め方を変えます。

ユーザーがPermissionを必要とする理由が明確である場合

単にPermissionを要求するダイアログを表示します。
カメラボタンを押した時にカメラのPermissionをリクエストするのはユーザーにとってわかりやすく、単にPermissionを要求するダイアログで事足ります。

Permissionを必要とする理由がわかりにくい場合

Permissionが必要な理由を説明します。
例えばユーザー認証を行うためにSMSとの自動連携を行っている場合、SMSへアクセスできるPermissionが必要ですが、ユーザーはSMSへのアクセス権が何故必要なのか理解できない可能性があります。
このような場合にはユーザー認証にSMSを使用しており、SMSへのアクセスに許可しないと正しく認証ができない旨を伝えて許可を求めます。

拒否されたら

拒否された場合にどのように振る舞うかは、いくつか考えられます。

Permissionがないとアプリの本質的な機能を使用することができなくなる場合

Permissionの許可を求める画面を全面に表示し、それ以上先に進めなくします。
これによりユーザーに強くPermissionを求めることが出来ます。

Permissionがなくてもアプリとして機能するが、Permissionが拒否されることがユーザーの一時的な気の迷いだと思える場合

再度該当の機能を実行するタイミングで、なぜそのPermissionが必要なのかを説明を表示して、再度承諾を求めるダイアログを表示します。
ユーザーはすぐにPermissionを有効にすることが出来ます。

Permissionがなくてもアプリとして機能し、かつユーザーが明示的にPermissionを拒否し続けると思われる場合

対象のボタンを無効化して、拒否された機能を使用できなくします。
これにより、ユーザーは2度とPermissionを求めるダイアログでイライラしなくて良くなります。
この場合、ユーザーが設定から明示的にアプリに許可を付け直す必要があります。

Runtime Permissionの対象となるPermission

Runtime Permissionの対象となるPermissionは次のとおりです。
READ_CALENDAR カレンダーの読み込み
WRITE_CALENDAR カレンダーの書き込み
CAMERA カメラ機能
READ_CONTACTS 連絡先の読み込み
WRITE_CONTACTS 連絡先の書き込み
GET_ACCOUNTS ユーザーが使用しているアカウントの取得
ACCESS_FINE_LOCATION 詳細な位置
ACCESS_COARSE_LOCATION 大まかな位置
RECORD_AUDIO オーディオの録音(マイク)
READ_PHONE_STATE 電話の状態を取得する
CALL_PHONE 電話をかける(直接発信するのではなく通話アプリをIntentで呼び出す場合は不要)
READ_CALL_LOG 通話履歴を取得する
WRITE_CALL_LOG 通話履歴を書き込む
ADD_VOICEMAIL ボイスメールを追加する
USE_SIP SIP( Session Initiation Protocol)を使用する
PROCESS_OUTGOING_CALLS 通話発信時のIntentを補足する
BODY_SENSORS 心拍数などの身体センサーを使用する
SEND_SMS SMSを送信する
RECEIVE_SMS SMSを受信する
READ_SMS SMSを読む
RECEIVE_WAP_PUSH WAP(Wireless Application Protocol)PUSHを受信する
RECEIVE_MMS MMSを受信する
READ_EXTERNAL_STORAGE 外部ストレージから読み込む
WRITE_EXTERNAL_STORAGE 外部ストレージに書き出す

上記のPermissionを使っている場合Android6.0以上の端末ではRuntime Permission扱いとなり、対応が必須となります。(Target SDKを22以下とすることで、とりあえず動かし続けることは可能です。 しかしながら、その場合ユーザーが明示的にPermissionを拒否すると例外で落ちます。)

Permissionとどう付き合うか

Permissionを許可しないユーザー

今回の変更でPermissionに対するユーザーの考え方が変わってくる可能性があります。
これまではインストール時に漠然と表示していたため多くのユーザーがPermissionを意識せずに許可していました。
一方で今後は明確にユーザーを説得する必要が出てくるので、かなりの割合でPermissionを許可しないユーザーが出てくると思います。

Permissionを減らす

まず第一に利用するPermissionを減らす事を考えます。
例えば電話を発信する場合、アプリ内から直接発信する場合はPermissionが必要ですが、番号がセットされた発信画面を表示するまでならPermissionは必要ありません。
ユーザーが明示的に通話ボタンを押す必要が出てきますが、むしろそちらのほうがユーザーフレンドリーといえる場合もあるはずです。
Intentが必要な電話発信

Uri phoneNumber = Uri.parse(“tel:0123456789″);
Intent callIntent = new Intent(Intent.ACTION_CALL,phoneNumber);
startActivity(callIntent);


Intentを必要としない電話発信

Uri phoneNumber = Uri.parse(“tel:0123456789″);
Intent callIntent = new Intent(Intent.ACTION_DIAL,phoneNumber);
startActivity(callIntent);

同様に、現在地を表示する場合にはPermissionが必要ですが地図アプリをIntentで開けばPermissionが必要なくなります。

許可されない状態で動作する

Permissionを許可しないユーザーもユーザーとして取り込むにはPermissionが許可されていない状態で出来るだけ多くの機能が動作できるような作りにする必要があります。

対応方法

対応に必要な手順は3つあります。
1.Permissionが許可されているか確認する。
2.Permissionが許可されていない場合許可を求める。
3.Permissionが許可・拒否された時の処理を追加する。

1.Permissionが許可されているか確認する。

Permissionの許可を求めるにはContextCompat#checkSelfPermission()を使用します。
第1引数にContext,第2引数に確認したいPermissionを表す文字列を渡します。
Permissionを表す文字列は android.Manifest.permissionから選択します。存在しないPermissionを指定すると実行時例外が発生するため注意してください。

通常はManifest.permission.CAMERAのようにManifestをimportして指定することが出来ますが、GCMを使っている場合には別のManifestクラスが自動生成されるため明示的にandroid.Manifestをimportするか、android.Manifest.permission.CAMERAのように完全修飾子を指定します。

戻り値はint型で、許可されている場合は、PackageManager.PERMISSION_GRANTED
拒否されている場合はandroid.content.pm.PackageManager.PERMISSION_DENIEDが返ります。

CAMERAが許可されているかを確認する場合の例

if (ContextCompat.checkSelfPermission(
this,android.Manifest.permission.CAMERA)== PackageManager.PERMISSION_GRANTED){
// 許可されている時の処理
}else{
// 拒否されている時の処理
}

2.Permissionが許可されていない場合許可を求める。

許可されていないことが分かった場合は ActivityCompat#shouldShowRequestPermissionRationale()を呼びます。
このメソッドはユーザーがPermissionを明示的に拒否したかどうかを返します。
引数はContextCompat#checkSelfPermission()と同様に、Contextと確認するPermissionを表す文字列です。
ユーザーが明示的に拒否した場合はtrue、ユーザーがまだ許可していない場合はfalseが返ります。
trueが帰ってきた場合、ユーザーにPermissionが何故必要なのか説明して説得して再度許可を求めるか、ボタンを無効にするなどの処理を行います。
falseが帰ってきた場合、ユーザーはまだ判断を行っていないので許可を求めるダイアログを表示します。

許可を求めるには
ActivityCompat#requestPermissions()
を使用します。
引数は
第1引数にContext
第2引数にPermissionを表す文字列の配列
第3引数にコールバックで受け取るint型の数字(任意の数字)
を指定します。

第2引数がcheckSelfPermission()やshouldShowRequestPermissionRationale()と違って配列なのに注意してください。
複数の文字列を渡すことでPermissionの許可を一度に求めることが出来ます。
ここで指定するPermissionはAndroidManifestに宣言したPermissionでなくてはいけません。
もしAndroidManifestに宣言した以外のPermissionを指定すると
「問題が発生したため、パッケージインストーラーを終了します。」というわかりにくいエラーが表示されアプリが終了します。


Cameraが許可されていない場合、ユーザーが明示的に拒否したかどうかで処理を分ける例

if (ContextCompat.checkSelfPermission(
this,android.Manifest.permission.CAMERA)== PackageManager.PERMISSION_GRANTED){
// 許可されている時の処理
}else{
//許可されていない時の処理
if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.CAMERA)) {
//拒否された時 Permissionが必要な理由を表示して再度許可を求めたり、機能を無効にしたりします。
} else {
//まだ許可を求める前の時、許可を求めるダイアログを表示します。
ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.CAMERA}, 0);

}
}


Permissionがない場合このようなダイアログが表示されます。
「写真の撮影と動画の記録を許可しますか?」
複数のPermissionを指定していた場合は1/2のように許可するPermissionがいくつあるかも表示されます。

3.Permissionが許可・拒否された時の処理を追加する

ダイアログでPermissionが許可・拒否された時に処理を行うにはActivityあるいはFragmentのonRequestPermissionsResult()をOverrideします。
引数として、
第1引数にActivityCompat#requestPermissions()の第3引数で指定した値
第2引数にActivityCompat#requestPermissions()の第2引数で指定した値
第3引数に第2引数と1対1になるかたちでユーザーがPermmisionを許可したかどうかがintで渡されます。

後はそれに合わせて、許可された場合は後続の処理を行い、拒否された場合はボタンを無効にするとか、説得するためのメッセージを表示するなど必要な処理を行います。

カメラのPermissionが許可されたかどうかを確認する例

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case 0: { //ActivityCompat#requestPermissions()の第2引数で指定した値
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//許可された場合の処理
}else{
//拒否された場合の処理
}
break;
}
}
}


これによりRuntime Permissionに対応できます。

Android6.0について

Runtime PermissionはAndroidのバージョンアップ史上、最も多くのAPKに影響を与えるバージョンアップであるように思います。
targetAPKを変更しないことで暫定回避はできるものの、例外で落ちるリスクを考えると、できるだけ早めに対応しておくべきでしょう。


Android6.0の正式版は今週中にも配信予定です。

Androidといえば正式版のアップデートが発表されても、すぐに対応するのは開発者向けのNexusだけで、それ以外の端末のアップデートは遅れる印象でしたが、Android5.0からDeveloper Previewが配信されるようになり、正式版から採用への間隔が短くなっています。
(噂レベルですが、HTCは今月中に6.0対応のスマートフォンを発売すると言われています。)


もし、対象のPermissionを使っているアプリが既に存在する場合は早めに対応を行ってテストしてください。

前:正しいMacBook Air/Pro用ACアダプターケーブルの巻き方 次:既存アプリに影響するAndroid 6.0での変更点

関連キーワード

[Android][Java][IT]

コメントを投稿する

名前URI
コメント