Runtime permissionは、アプリがユーザーに要求をして、ユーザーが許可しなければなら受けることができパーミッションを指します。
そのため、どのようなパーミッションが必要なAPIを使用するには、ユーザーに必ず要求をする必要があります。ユーザーが要求を拒否することもできますので、これに対する例外処理も必要です。
Runtime permissionはSDK API 23(Mashmello)からサポートされます。 SDK API 23未満のバージョンでコンパイルした場合、アプリがインストールされたときのパーミッションを取得することができます。したがってAPI 23未満はRuntime permissionを要求する必要がありません。
アプリのインストール時に得るパーミッションはInstall permission呼ばれます。
パーミッションリクエストAPI
ActivityがAppCompatActivityを継承すると、次のようなAPIを使用することができます。
- checkSelfPermission()
- requestPermissions()
- shouldShowRequestPermissionRationale()
このAPIを使用して、ユーザーにパーミッションを要求することができます。
また、ActivityCompatに同じ動作を実行するAPIを使用することができます。
- ActivityCompat.checkSelfPermission()
- ContextCompat.checkSelfPermission()
- ActivityCompat.requestPermissions()
- ActivityCompat.shouldShowRequestPermissionRationale()
今、上記のAPIを使用してRuntime permissionをユーザーに要求して取得する方法を説明します。
プロジェクトの設定
この記事のコードは、Kotlinに作成されました。
プロジェクトのTarget SDK APIは、23以降を使用するようにします。
完成したプロジェクトは、GitHub - RequestRuntimePermissionで確認することができます。
パーミッションを持っていることを確認
checkSelfPermission()
でアプリがパーミッションを持っていることを確認することができます。
次のように確認したいパーミッションを引数として渡すと、 PERMISSION_GRANTED
またはPERMISSION_DENIED
が返されます。
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
// Permission is granted
} else {
// Permission is not granted
}
PERMISSION_GRANTED
が返された場合パーミッションを持っているということで、そうでない場合パーミッションがないことを意味します。
パーミッションを持っている場合はどのような機能が実行されるように実装することができます。
ユーザーにパーミッションリクエスト
requestPermissions()
でユーザーにPermissionを要求することができます。
次のように、まず checkSelfPermission()
でPermissionを持っていることを確認していない場合は requestPermissions()
に要求します。
companion object {
const val PERMISSION_REQUEST_CODE = 1001
}
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
showDialog("Permission granted")
} else {
requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), // 1
PERMISSION_REQUEST_CODE) // 2
}
//1
のように、上記のコメントに表示したものを、以下で詳しく説明しています。
- パーミッションを配列として渡される理由は、このAPIは、いくつかのパーミッションを同時に求めることができるように作られたからです。もし一つのパーミッションのみを要求しても、配列に渡す必要があります。
PERMISSION_REQUEST_CODE
は単なる定数であるが、アクセス許可の要求に応答してイベントが送出されるときに、このcodeに渡されます。数字は何でもしてもされるが、他のイベントと区別されます。
パーミッション要求の結果を受け取る
requestPermissions()
で、ユーザーにパーミッションを要求すると、次のようなポップアップが表示されます。
ユーザーがallowをクリックするか、denyを押すと、その結果は、私のアプリに転送されます。
Frameworkは、結果を渡すときActivityの onRequestPermissionsResult()
を呼び出します。
次は、イベントを受信したとき処理するコードです。
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
when (requestCode) {
PERMISSION_REQUEST_CODE -> { // 1
if (grantResults.isEmpty()) { // 2
throw RuntimeException("Empty permission result")
}
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 3
showDialog("Permission granted")
} else {
if (shouldShowRequestPermissionRationale(
Manifest.permission.ACCESS_FINE_LOCATION)) { // 4
Log.d(TAG, "User declined, but i can still ask for more")
requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
PERMISSION_REQUEST_CODE)
} else {
Log.d(TAG, "User declined and i can't ask")
showDialogToGetPermission() // 5
}
}
}
}
}
private fun showDialogToGetPermission() {
val builder = AlertDialog.Builder(this)
builder.setTitle("Permisisons request")
.setMessage("We need the location permission for some reason. " +
"You need to move on Settings to grant some permissions")
builder.setPositiveButton("OK") { dialogInterface, i ->
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", packageName, null))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent) // 6
}
builder.setNegativeButton("Later") { dialogInterface, i ->
// ignore
}
val dialog = builder.create()
dialog.show()
}
//1
のように、上記のコメントに表示したものを、以下で詳しく説明しています。
- 要求に使用したcodeでイベントがActivityに転送されます。
- grantResultsはemptyになることができません。 emptyなら無視します。
- 複数Permissionを要求することができますので、その結果も配列として渡されます。ここでは1つのパーミッションを要求したため、Index 0の結果を確認するだけです。パーミッションを受け取った場合はどのような機能が実行されるように実装することができます。ここポップアップを離すようにしました。
shouldShowRequestPermissionRationale()
は、ユーザーが__Denyボタンを押したときにtrueを返します。ユーザーがDeny&Do not ask again__ボタンを押すと、falseを返します。 trueが返される場合、ユーザーに再アクセス許可を要求することができます。- しかし、falseが返される場合
requestPermissions
を呼び出しても、要求のポップアップが出ません。パーミッションを受けるユーザはSettingsの内アプリ情報に入ってパーミッションを変更する必要があります。したがって、なぜパーミッションが必要十分説明するポップアップを浮かべ__OK__ボタンを押すと、Settingsに移動できるように実装しました。 ACTION_APPLICATION_DETAILS_SETTINGS
インテントを実行すると、私のアプリの設定画面に移動します。
例の実行結果
requestPermissions()
で、ユーザーにパーミッションを要求すると、次のようなポップアップが表示されます。
__Allow__を押すと、次のようなポップアップが表示されます。
もし__Deny__を押すと、要求のポップアップが繰り返し表示されます。
__Deny&Do not ask again__を押すと、次のようにアプリがパーミッションを必ず得なければならない理由を説明するポップアップが表示されます。
ここで、ユーザは自分のアプリに許可を付与することができます。
Fragmentでパーミッション要求
Fragmentでパーミッションを要求すること、Helper classでパーミッションを要求するように実装することができます。
Fragmentもほとんど同じAPIを呼び出すことができますが、呼び出すことができない場合に ActivityCompat
を利用して呼び出します。
このAPIを呼び出すときに引数としてactivityを渡しヘジュオヤます。
以下は、Fragmentでパーミッションを要求するコードです。
private fun requestRuntimePermissions() {
if (ActivityCompat.checkSelfPermission(requireActivity(), Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
// Permission granted
} else {
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
MainActivity.PERMISSION_REQUEST_CODE
)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
grantResults: IntArray) {
when (requestCode) {
MainActivity.PERMISSION_REQUEST_CODE -> {
if (grantResults.isEmpty()) {
throw RuntimeException("Empty permission result")
}
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(
requireActivity(),
Manifest.permission.ACCESS_FINE_LOCATION)) {
Log.d(MainActivity.TAG, "User declined, but i can still ask for more")
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
MainActivity.PERMISSION_REQUEST_CODE
)
} else {
Log.d(MainActivity.TAG, "User declined and i can't ask")
}
}
}
}
}
もしパーミッションを要求するHelperクラスを作るなら、
onRequestPermissionsResult()
は呼び出されないので、ActivityやFragmentからcallbackされるように実装する必要があります。
プロジェクト全体のコード
プロジェクトのすべてのコードは、GitHub - RequestRuntimePermissionで確認することができます。
参考
Related Posts
- Android - FusedLocationProviderClientに位置情報を取得する
- Android - GPS、Network位置情報を取得する(LocationManager)
- Android - adbコマンドでActivity実行
- アンドロイド - MediaStoreにメディアファイルを保存する方法
- Android - Runtime permissionリクエスト方法と例(kotlin)
- Android11 - Storage(ストレージ)の変更まとめ
- Jetpack Compose - RowとColumn
- Android 13 - 細かいメディアファイルの権限
- Android 13でNotification権限をリクエスト、通知を表示する
- エラー解決:android gradle plugin requires java 11 to run. you are currently using java 1.8.
- Query method parameters should either be a type that can be converted into a database column or a List
- Android - TabLayoutの実装方法(+ ViewPager2)
- Android - adbコマンドで特定のパッケージのプロセスの終了
- Android - adb push、pullでファイルのコピー、ダウンロード
- Android - adbコマンドでsettings value確認、変更、
- Android 12 - IntentFilterのexported明示的な宣言
- Android - adb logcatコマンドでログ出力
- Android - ACTION_BOOT_COMPLETEDイベント受信
- Android - Foreground Service実行
- Android - ファイル入出力の例(Read、Write、内部、外部ストレージ)
- Android - アプリの権限を確認(Permission check)
- Android - adbで実行中のプロセス、スレッドリスト及びメモリ情報の確認
- Android - Broadcast Receiver登録およびイベントの受信方法
- Android - Cleartext HTTP ... not permitted例外解決方法
- Androidのビルドエラー - Calls to Java default methods are prohibited in JVM target 1.6
- アンドロイド - Assetsでファイルを読み取る方法
- アンドロイドのさまざまなNotification種類と実装方法
- アンドロイド - INSTALL_FAILED_TEST_ONLYエラー解決方法
- Android EspressoのCustom Matcher実装方法
- Android Espressoを使用してUIをテストする方法(3)
- アンドロイド - CTS hostsideをgradleで構築する方法
- Androidのアプリのデータフォルダーのパスと内部/外部ストレージ説明
- アンドロイド - 最初のApp作成
- Androidをインストールする方法(Windows)