概要
先日アシスタントを担当している授業でAndroidアプリの開発を教えていたところ、ある学生さんがGPS機能を利用したアプリを作っていたのですが、Android 6.0でのパーミッション周りの実装に困っており、自分にもあまり知見がなかったのでかなり苦労してしまいました。
またその過程で、そもそものパーミッション(User Permission)の許可の仕組みも伝えなければいけなかったので、どうせならと思い今更ながら得られた知見を記事にまとめてみました。
なのでこの記事では
- Android OSにおけるユーザー権限許可の概要
- Android 6.0以前でのユーザー権限の取得方法
- Android 6.0以降での変更点
- GPSの位置情報を取得するアプリでの実装手順
を解説します。間違いやSDKのアップデートによる差異もあるかもしれませんが、コメント等にて教えていただければ幸いです。またパーミッションの概要の
パーミッションについて
そもそもパーミッション(User Permission)とはなんでしょうか。
それはユーザーがアプリに対して「この情報、この機能を使って何かしてもいいよ」という権限を与える仕組みのことです。
Androidアプリは、端末の持つ様々な情報にアクセスすることができます。機能とは、例えば
- 連絡先
- カメラ
- Wi-Fi
- Bluetooth
- GPS
などのことを指します。
ただし、ユーザーがアプリをインストールした際、何でもかんでもそのアプリにやることを許してしまうとどうなるでしょうか? あるいは、インストールしたアプリがどんな機能にアクセスするのかを、ユーザーに一切知らされないとしたらどうなるでしょう??
開発者は**「電卓と見せかけて、実は連絡先を盗んで業者に垂れ流すアプリ」や「音楽再生アプリと見せかけて、実は勝手にカメラを起動して盗撮するアプリ」**のようなものが作れてしまいます。
そこでアプリを作る側は、上記に示すような機能にアクセスすることを事前に明記し、インストール前にユーザーに伝えなければいけないのです。そしてユーザーはインストール時にこれらの機能へのアクセス(パーミッション)を許可することで初めてアプリがインストールできるようになるのです。
これがパーミッションの仕組みになります。言い換えれば、逆にユーザーへの通知なしに勝手に連絡先やカメラの機能を利用するアプリは作れないようになっているのです。ユーザーが「電卓って書いてあるのになんでカメラへアクセスする必要があるんだろう?」と怪しんでパーミッションを与えなければ、アプリはインストールされません。
Android 5までのパーミッションチェック
ここで先ほど、「パーミッションはインストール時に許可される」と言いました。
しかしこれはOSのバージョンが6.0未満の場合の話です。Android 6.0以降ではアプリを使用中、その機能を使用する段階で初めてチェックされる仕様に変更されています。
以下はAndroid公式の原文になります。
Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app.
訳: Android 6.0 (APIレベル23)からは、ユーザーはインストール時ではなく、アプリを起動した後にPermissionを与えるようになりました。
どういうことかイメージで説明します。まずAndroid 4.xや5.xでは、はユーザー権限の許可はアプリのインストール時にチェックされていました。Google Play Storeなどでアプリをインストールしようとして以下のような画面を見たことがある方も多いと思います。
ユーザーが「同意する」を押すと、アプリへのユーザー権限の許可が与えられかつインストールが開始されていました。つまり**『アプリをインストールしている人=そのアプリのユーザー権限の許可全てを許可している人』**だったわけです。
Android 6からのユーザー権限のチェック
しかしAndroid 6.0 (SDK 23)から、パーミッションを得るタイミングがアプリインストール時ではなくインストール後、その機能に初めてアクセスするタイミングで許可を求める仕様に変更されました。このため、アプリ内でスマホのOSバージョンを判定し6.0以上であればパーミッションを得るためのダイアログを出す必要が出てきました。
軽くまとめると、違いは以下のような図で表されます。
パーミッションを個別に与えることについて
ここでもう一つ重要な点があります。
それはAndroid 5以前では、アプリが複数の機能(例えばカメラとGPSとbluetooth)にアクセスする場合、個別に許可を与えることができず、一括での許可または拒否しかできないということです。例えばGPSを許可してそれ以外は拒否するようなことはできません。
したがって、アプリをインストールしたらユーザー権限の許可全てを許可したとみなされることになるわけです。
一方6.0以降では個別にユーザー権限の許可を与えるかどうかを指定できるようにもなったのです。
なので「カメラへのアクセスは許可するけど連絡先はだめ」というような指定を、ユーザーができるようになりました。
Facebookを例にして
これをもう少し詳しく、ここではFacebookを例に説明します(簡略化のためインターネット接続機能へのアクセスは許可されているものとします)。
Facebookアプリを入れると、友だちになった人のタイムラインを見れますよね。
もしタイムラインを見るだけであれば、Facebookアプリはまだどの機能へもアクセスしていません。
写真のシェア
ここであなたは写真を撮ってシェアしたいと考え、Facebook内からカメラを起動しました。
するとここで初めて、Facebookはカメラ機能へアクセスすることになるのでカメラ機能へのユーザー権限の許可を求めてきます。
そしてあなたが「許可」ボタンを押せば、無事にカメラが起動して写真を取ることができ、逆に「拒否」をした場合には元の画面に戻って何も起こりません。
実際にAndroid 6では、これとほぼ同じ画面が現れます。
連絡先の取得
次にまたタイムラインに戻った時、今度は端末内の連絡先を取り込む機能があることを発見しました。
そこで「連絡先から友達を探す」というボタンを押したところ、やはり同様の画面が現れ、連絡先へアクセスするユーザー権限の許可を求められました。
しかしあなたは「なんか怖いな」と感じたため、これを拒否してしまいました。
その結果あなたはFacebookに対し、カメラ機能へのアクセスは許可し連絡先へのアクセスは許可しなかったことになるのです。
まとめると、
- ユーザーからパーミッションを得るタイミングがインストール前から実行中へ変わった
- ユーザーは複数のパーミッションを求められた場合、一括の許可と拒否から個別の許可と拒否が可能になった
ということになります。
許可が降りない前提で開発する必要性
ではこれを開発者の視点で考えてみましょう。
Android 5まではどんなアプリでも、インストールされた時点で求めた全てのパーミッションに許可が降りたことになっているので、そもそも「この機能に対して許可が降りなかったらどうするか」を考える必要はありませんでした。先程の例で言えばFacebookを入れた時点で、ユーザーはカメラにも連絡先にも許可を出していた事になります。
ところがAndroid 6では**「一部(または全部)の許可が降りないことがある」という前提でアプリを作る必要が出てくるわけです。先程の例で言えば、ユーザーがカメラに許可を出すが連絡先には許可を出さないようなケースではどうするか**を考えて実装する必要があるということです。
このためAndroid 6のSDKにはパーミッションが降りているかどうかをチェックする[checkSelfPermission](http://developer.android.com/intl/ja/reference/android/support/v4/content/ContextCompat.html#checkSelfPermission(android.content.Context, java.lang.String))関数が追加されています。また下りていない場合には**[requestPermissions](http://developer.android.com/intl/ja/reference/android/support/v13/app/FragmentCompat.html#requestPermissions(android.app.Fragment, java.lang.String[], int))関数を使って前述のようなダイアログを出してユーザーに許可を求めることができる**仕組みになったのです。
長くなりましたが、以上がAndroidにおけるユーザー権限の許可の概要と、OSバージョンの違いによる仕組みの差異になります。最後にGPSにアクセスする簡単なアプリを例に、具体的にどのように実装すればいいかを見ていきます。
GPSを利用するサンプルアプリ
以上を踏まえ実装した、簡単なサンプルプログラムをGitHubにて公開したのでこれを元に概要を説明していきます。(詳細はプログラム内のコメントを確認してみてください)
内容は、ボタンを押したら許可を求めるダイアログを表示し、許可した場合はTextView
に座標情報を表示するという簡単なプログラムです。
Androidmanifest.xml
での宣言
まず必要となるパーミッションをAndroidmanifest.xml
内で宣言します。位置情報取得の場合、取得方法に合わせて以下の要素を追加します。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
<!-- (中略) -->
<!-- GPSから位置情報を取得することを許可する -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- ネットワークから位置情報を取得することを許可する -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
GPSが使えない屋内などでは、通信用の電波強度を使って判定する仕組みがあるのですがそれぞれ記述が必要です。
ボタンクリック時の挙動定義
次に適当なActivity
(サンプルプログラムではMainActivity.java)にて、ボタンを表示させ、クリックしたら位置情報の許可を求めます。
この時、Android 6.0以降と5.x以下とで挙動が変わるためif (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {...
のような条件文でOSのバージョンを判定します。
そして6.0以降の場合は、checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
で許可が下りているかをチェックします。checkSelfPermission
の引数にはチェックしたいパーミッションの種類を表す定数を与えます。
なおここでFINE LOCATIONが許可されれば、自動的にCOARSE LOCATIONのパーミッションも許可されるようですのでチェックは前者のみでOKです(参考)。
パーミッションが下りていればLocationManager
インスタンスの取得(initLocationManger()
の呼び出し)、いなければrequestPermissions()
を呼び出して許可を求めるダイアログを出します。
requestPermissions()
の引数には得たいパーミッションの種類を表す定数の配列と、そのパーミッションを識別する値(これは自分で好きに決めて良い)を与えます。
LocationManager
の取得
initLocationManger()
内では何をしているでしょうか。
これはLocationManager
という位置情報を管理するクラスのインスタンスを取得してメンバ変数に保存しているだけです。
// 位置情報を管理するクラス
private LocationManager mLocationManager;
...
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
またmLocationManager.requestLocationUpdates()
には位置情報を更新する条件やされた場合の挙動を定義します。詳細は省略しますが、4番目にLocationListener
のインスタンスを渡して、onLocationChanged()
をオーバーライドしてあげます。
これらをフローチャートにすると以下のようになります。