◆PROCESSING 逆引きリファレンス
カテゴリー:スマホ(AndroidMode)
公開領域にアクセスするには(AndroidMode編)
【概要】
PROCESSINGにAndroidMode を導入する事で、PROCESSINGで開発したプログラムをAndroid端末上で動かす事ができるようになります。
AndroidModeの導入については「PROCESSINGをAndroid端末で動かすには(4.0版)」記事を参照してください。
AndroidスマホにはNAND型のフラッシュメモリを利用したストレージが搭載されている事が多いようですが、Androidのストレージ領域は
- 内部領域(getFilesDirectory)
- 外部領域(getExternalFilesDirectory)
- 公開領域(getExternalStoragePublicDirectory)
と呼ばれる3つの領域から構成されています※。()内はこの領域にアクセスする代表的なAPIです。
※実際には、これにキャッシュ領域が加わります。
(画像URL:illust-AC 様:shinya さん、acworks さん)
本記事では、このうち公開領域と呼ばれるエリアにアクセスする方法について紹介します。
内部領域と外部領域にアクセスする方法については「ストレージのファイルをアクセスするには(AndroidMode編)」記事を参照してください。
公開領域は、アプリケーションが他のアプリケーションと情報を共有するための領域です。(必ずではありませんが)この領域はSDカード上に確保されることがあります。
当然ですが「公開領域」には個人情報や、勝手に読み書きされては困るファイルは置いてはいけません。
公開領域にアクセスするには特別な権限が必要となります。またこの領域はAndroid 6.0以降、セキュリティの観点からアクセスが厳しく制限されるようになっています。
例えば読み込みを行うのであれば、マニフェストファイルに
1 |
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> |
を追記する必要があります。
書き込みを行うのであれば
1 |
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
を追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.mslabo.processingsample1"> <!-- 外部ストレージへのアクセス権 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> |
上記はマニフェストファイルに外部ストレージへのアクセス権を追記した例です。書き込み権限を与えると、読み取りも可能になる事に注意してください。
標準エディタで開発している場合は Androidメニューから「Sketch Permissions」を選択して開くダイアログボックスで WRITE_EXTERNAL_STORAGE などにチェックをつけてください。
またAndroid6.0以降では、公開領域へファイルを書き込んだり、フォルダを作成するには、マニフェストファイルにパーミッションを記述するだけでは不十分となりました。
とくに古い参考書やブログの記事では、マニフェストファイルにパーミッションを記述するだけでアクセスできるように書いてあるものがあります(当時はウソでは無かった:汗)ので、注意してください。
また公開領域は、外部領域と同じく「必ず読み書きできる」とは限りません。公開領域がSDカードなどである場合、ユーザによって取り外される事があるからです。
より慎重にプログラミングするなら、 Environment がもつ getExternalStorageState() メソッドで、公開領域の状態を確認してから操作するのが良いでしょう。
Android 6.0 以降で、公開領域にアクセスする最もシンプルなケースでは
- アクセスしたい領域へのパス、またはFileオブジェクトを得る
- アクセス可能か検査する
- 必要な権限があるか確認する
- 権限がない場合は、権限を要求する
- 権限の要求結果を受け取って処理する
という流れになります。
また必要に応じて「なぜ権限が必要なのか説明する」手順を加えることが推奨されています。
【詳細】
ファイルオブジェクトを取得する
公開領域は、Environment がもつ getExternalStoragePublicDirectory() メソッドでFileオブジェクトが取得可能です。
.
公開領域へのFileオブジェクト取得File file = Environment . getExternalStoragePublicDirectory( String type ) ;
file : 公開領域の Fileオブジェクト
type : 領域を識別するための名前。null は不可
type には、Environmentクラスが持つ以下の定数の何れか、または任意のフォルダ名を指定します。
DIRECTORY_MUSIC
DIRECTORY_PODCASTS
DIRECTORY_RINGTONES
DIRECTORY_ALARMS
DIRECTORY_NOTIFICATIONS
DIRECTORY_PICTURES
DIRECTORY_MOVIES
DIRECTORY_DOWNLOADS
DIRECTORY_DCIM
DIRECTORY_DOCUMENTS
各定数ごとに、公開領域のしかるべき File オブジェクトが戻されます。
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 |
import android.os.Environment; /** * PROCESSING 公開領域パス取得Sample * @auther MSLABO * @version 1.1 2018/7 */ final int FONT_SIZE = 14; File publicExt; void setup(){ //横向き固定 orientation(LANDSCAPE); //文字サイズ、色、アライメント指定 textSize(FONT_SIZE * displayDensity); textAlign(LEFT, TOP); fill(0); } void draw(){ background(255); //公開領域のパス取得 publicExt = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); text(publicExt.getPath(), 0, 0); noLoop(); } |
<出力例>
上記例は、公開領域のDCIM領域(写真などを格納するフォルダ)に関するパスを取得して表示するものです。
権限があるか確認する
checkSelfPermission() メソッドで、必要な権限を該当アプリケーションが持っているかどうかが確認できます。
.
権限を確認するint check = ContextCompat . checkSelfPermission (Context context, String permission);
int check = act . checkSelfPermission (String permission);
check : 権限の有無。 PERMISSION_GRANTED、PERMISSION_DENIED の何れか
context : アプリケーションコンテキスト
permission : 確認したい権限名
act : Activityインスタンス
permission には、確認したい権限名を指定します。権限名には Manifest.permission クラスが持つ定数を利用します。
例えば、外部ストレージへのアクセス権なら「Manifest.permission.WRITE_EXTERNAL_STORAGE」です。
下記は、外部ストレージへのアクセス権があるか検査するサンプルです。
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 |
import android.Manifest; import android.content.pm.PackageManager; import android.app.Activity; import android.content.Context; /** * PROCESSING 権限チェックSample * @auther MSLABO * @version 1.0 2018/8 */ Activity act; Context con; boolean havePermission; final int FONT_SIZE= 16; void setup(){ //文字の設定 textAlign(LEFT, TOP); textSize(FONT_SIZE * displayDensity); fill(0); //ActivityとContextの取得 act = getActivity(); con = act.getApplicationContext(); //ストレージへのアクセス権があるか確認する if ( con.checkSelfPermission( Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { //権限なし havePermission = false; } else { //権限あり havePermission = true; } } void draw(){ background(255); if( havePermission ){ text("ストレージアクセス権限あり",0,0); } else { text("ストレージアクセス権限なし",0,0); } } |
実は checkSelfPermissionメソッドは、ContextCompat クラス、PermissionChecker クラス、Context クラスにもあります。ややこしいですね(汗)。
PROCESSINGをAndroidStudioで開発するなら何れのメソッドも利用可能ですが、ContextCompat クラスのものを使えば良いかと思います。
ただし標準エディタで開発する場合は、標準エディタが android.support.v4 ライブラリを import できないため、Context がもつ checkSelfPermissionメソッド一択となります。
それぞれ細かい動作が異なるのですが、違いが知りたい方は以下のブログ等が参考になります。私も大いに参考とさせていただきました。ありがとうございます。
権限を要求する
権限がない場合は、必要な権限をユーザに要求する必要があります。
権限の要求には requestPermissions() メソッドを利用しますが、このメソッドもActivityCompat クラスとActivity クラスの両方にあります。
.
void act . requestPermissions (String[] permissions, int requestCode);
permission : 確認したい権限名
requestCode : 一意に決めた任意の数値
act : Activityインスタンス
checkSelfPermissionと同じように、PROCESSINGをAndroidStudioで開発するなら、ActivityCompat クラスのものを使えば良いでしょう。
標準エディタで開発する場合は、Activity がもつ requestPermissions メソッド一択となります。
permission には、取得したい権限名を指定します。権限名には Manifest.permission クラスが持つ定数を利用しますが、こちらは文字列配列となっています。
なぜ配列かといえば、1回の命令で複数の権限を要求できるからです。
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 |
import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; import processing.core.PApplet; /** * PROCESSING 権限要求 Sample * @auther MSLABO * @version 1.0 2018/8 */ public class Sketch extends PApplet { final int FONT_SIZE = 16; final int MY_PERMISSIONS_REQUEST_CODE = 200; Activity act; Context con; boolean getGrant; @Override public void settings() { fullScreen(); } @Override public void onResume() { super.onResume(); act = getActivity(); con = act.getApplicationContext(); } @Override public void setup() { //文字設定 textAlign(LEFT, TOP); textSize(FONT_SIZE * displayDensity); fill(0); //権限があるか検査する if (con.checkSelfPermission( Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { //権限がないので要求する act.requestPermissions( new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_CODE); } else { //権限がある getGrant = true; } } @Override public void draw(){ background(255); if(getGrant){ text("権限が与えられました", 0, 0); } else { text("権限がありません", 0, 0); } } } |
上記は外部ストレージへのアクセス権を要求する例です。
アプリケーションを起動して、最初にrequestPermissions() を発行すると、以下のようなダイアログBOXが表示されて権限の要求が行われます。
表示されるメッセージは、要求する権限により異なります。
ここで「許可」または「許可しない」を選択すると、onRequestPermissionsResult にrequestCodeで与えたコードとともに、選択結果がパラメータとして渡されます。
onRequestPermissionsResult では、権限の許可・非許可に応じて適切な処理を行うことになるでしょう。
最初のアプリケーション起動で権限が与えらなかった場合、2度目にアプリケーションの起動を行って requestPermissions() を発行すると、今度は以下のようなダイアログBOXが表示されます。
1度目とは違い「今後は確認しない」というチェックBOXがあります。
「今後は確認しない&許可しない」を選択した場合、以降はrequestPermissions()を発行しても、再び問い合わせメッセージが表示される事はなく、要求した権限が無い状態となります。
注意としては、2018/8月現在 requestPermissions()をPROCESSINGのAndroidMode標準の仮想端末で実行すると、プログラムが異常終了する事です(汗)。
同じコーディングをAndroidStudioから、普通の(AndroidStudioの仮想端末マネージャ経由で作成した)仮想端末で実行すると、正常に動作します。AndroidMode標準の仮想端末の不具合かとは思いますが、標準エディタでプログラムをテストする方は注意してください。
「今後は確認しない&許可しない」を選択された後、再度権限を要求するダイアログボックスを表示するには
設定→アプリと通知→アプリの権限→ストレージ で表示されるストレージ権限一覧から、目的のアプリケーションを探し出して、右横のスィッチをON→OFFと切り替えます。
もちろんこの操作でスィッチをON=権限付与 状態のままにしたら、以降は checkSelfPermission() で「権限あり」が戻ってきます。
権限の要求結果を受け取る
requestPermissions()の結果は、onRequestPermissionsResult で受け取ります。
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 |
//権限要求結果を受け取る @Override public void onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) { //結果を処理する switch (requestCode) { case MY_PERMISSIONS_REQUEST_CODE: //ストレージアクセス権の要求結果を処理する if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //権限が付与された getGrant = true; } else { //権限付与が拒否された getGrant = false; } break; default: super.onRequestPermissionsResult(requestCode, permissions, grantResults); break; } return; } |
上記はonRequestPermissionsResult で権限要求結果を受け取る例です。
requestCodeには、requestPermissionsに与えたコードが渡ってくるので、どのrequestPermissionsの結果なのかを判定可能です。
permissionsには、ユーザによって処理された権限名が渡ってきます。grantResultsはユーザの選択結果です。
それぞれ配列なのは、requestPermissionsが複数の権限を一度に要求できるためです。
権限取得理由を説明する
より親切に作るなら、権限を要求するときには、なぜその権限が必要なのかユーザに丁寧に説明すべきでしょう。
カメラアプリがカメラアクセス権を要求するのは自然ですが、カメラアプリが電話帳へのアクセス権を要求するとなれば、違和感がありますよね?
そんな場合は、「なぜその権限が必要なのか」ちゃんと説明すべきです。
Androidにはこのような利用を想定して、shouldShowRequestPermissionRationaleメソッドが用意されています。
shouldShowRequestPermissionRationaleメソッドは、該当アプリが初めて権限を要求する時はFalseを返しますが、2度目以降の権限要求時で、かつ「今後は確認しない&許可しない」で拒否されていない場合は、Trueを返します。
このメソッドが True を返したときは、「少なくとも1回以上、権限の要求を拒否された状態」だが、まだ「今後は確認しない&許可しない=2度と聞くんじゃねーよ」が選ばれたわけではない状態だという事です。
なので、親切丁寧に「権限が必要な理由」を説明する必要があると判断します(笑)。
.
boolean ret = act . shouldShowRequestPermissionRationale(String permissions);
permission : 確認したい権限名
ret : False 初回または永久拒否状態。True 2回目以降で問い合わせ可能状態
act : Activityインスタンス
このメソッドも ActivityCompat 版 と Activity 版があります。言うまでもなく、標準エディタでコーディングする人は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 |
@Override public void setup() { //文字設定 textAlign(LEFT, TOP); textSize(FONT_SIZE * displayDensity); fill(0); //権限があるか検査する if (con.checkSelfPermission( Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { //2回目以降の権限要求か確認する if (act.shouldShowRequestPermissionRationale( Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // 2回目以降の権限要求なので(つまり少なくとも1回は拒否られた:汗) // なぜ権限が必要か説明した上で、あらためて権限を要求する // (**注意**) // これはSampleなので、簡易的にダイアログBOXを表示していますが // 実際は下記の方法でダイアログBOXを表示してはいけません。 // ちゃんと作るなら、ダイアログBOXの表示は DialogFragment を // 使いましょう。 act.runOnUiThread(new Runnable() { @Override public void run() { new AlertDialog.Builder(act) .setTitle("お願い") .setMessage("ストレージへのアクセス権が必要なのでござる。" + "\n頼む、権限を与えてくれ。") .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick( DialogInterface dialog, int which) { // OKボタン押下時、権限を要求する act.requestPermissions( new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_CODE); } }) .show(); } }); } else { //1回目の権限要求、もしくは「今後は確認しない」で拒否られている。 //1回目である可能性も考慮し、無駄かもしれないが //とりあえず権限要求してみる act.requestPermissions( new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_CODE); } } else { //権限がある getGrant = true; } } |
上記は先程のサンプルに、shouldShowRequestPermissionRationaleを組み込んだ例です。
この例はサンプルなので、ちょっと乱暴な方法でダイアログボックスを表示していますが、このダイアログボックス表示方法は非推奨です。ちゃんとしたアプリでは真似してはいけません。
メッセージの内容は・・・まぁ気にしないでください。ええ。サンプルですので。(汗)。
【関連記事】
サンプルプログラム
ストレージのアクセス権を要求する例:
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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; import android.os.Environment; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import java.io.File; import processing.core.PApplet; /** * PROCESSING 権限要求 Sample * @auther MSLABO * @version 1.1 2018/8 */ public class Sketch extends PApplet { final int FONT_SIZE = 16; final int MY_PERMISSIONS_REQUEST_CODE = 200; final String MYAPPDIR = "MYAPPLICATION"; Activity act; Context con; boolean getGrant; boolean canAccess; boolean writeFile; File publicExt; String[] datas; @Override public void settings() { fullScreen(); } @Override public void onResume() { super.onResume(); act = getActivity(); con = act.getApplicationContext(); } @Override public void setup() { //変数初期化 getGrant = false; //権限なし canAccess = false; //アクセス不能 writeFile = false; //未書き込み //文字設定 textAlign(LEFT, TOP); textSize(FONT_SIZE * displayDensity); fill(0); //書き込み用データ作成 datas = new String[5]; datas[0] = "パーティメンバー"; datas[1] = "戦士"; datas[2] = "盗賊"; datas[3] = "魔女"; datas[4] = "僧侶"; //権限があるか検査する if (ContextCompat.checkSelfPermission(con, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // 権限を要求する ActivityCompat.requestPermissions(act, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_CODE); } else { //権限がある getGrant = true; //アクセス可能か調べる //与えるのはMYAPPDIRではなく、Environment.DIRECTORY_DCIM //とかでも良い canAccess = checkAccess(MYAPPDIR); } } @Override public void draw(){ background(255); if(getGrant){ //権限がある if(canAccess){ text(publicExt.getPath() + "\nにアクセス可能です", 0, 0); //ファイルを作成する if(!writeFile){ saveStrings(publicExt.getPath() + File.separator + "party.txt", datas ); writeFile = true; } } else { //権限があるけど、アクセスできない text("権限が与えられましたが、アクセスできません",0,0); } } else { //権限なし text("権限がありません", 0, 0); } } //権限要求結果を受け取る @Override public void onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) { //結果を処理する switch (requestCode) { case MY_PERMISSIONS_REQUEST_CODE: //ストレージアクセス権の要求結果を処理する if (grantResults.length > 0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){ //権限が付与された getGrant = true; //アクセス可能か調べる //与えるのはMYAPPDIRではなく、Environment.DIRECTORY_DCIM //とかでも良い canAccess = checkAccess(MYAPPDIR); } else { //権限付与が拒否された getGrant = false; } break; default: super.onRequestPermissionsResult(requestCode, permissions, grantResults); break; } return; } //dirPath にアクセスできるか調べる処理 boolean checkAccess(String dirPath){ //公開領域の MYAPPLICATION フォルダObjectを得る publicExt = Environment.getExternalStoragePublicDirectory( dirPath); //アクセスできるか調べる String status = Environment.getExternalStorageState(publicExt); if( status.contains(Environment.MEDIA_MOUNTED) ){ //アクセス可能なので、フォルダがあるか確認する if( !publicExt.exists()){ //フォルダが無いので作成する publicExt.mkdirs(); } //アクセス可能 return true; } //アクセスできない return false; } } |
詳細で細切れに説明した内容をまとめたサンプルです。
公開領域は、必ずアクセスできるとは限らないし、フォルダがあるとも限らないため、必要な処理を行っています。
本サンプルでは公開領域にオリジナルのフォルダ(MYAPPLICATION)を作成し、そこに勇者のパーティメンバーを書き込んでいます。
本ページで利用しているアイコン画像は、下記サイト様より拝借しております。各画像の著作権は、それぞれのサイト様および作者にあります。