- 投稿日:2020-01-25T21:42:26+09:00
【Android】はじめてのCoroutines
アプリ開発歴( =プログラミング歴)がもうすぐで1年になります
@iTakahiroです。現在はiOS・Androidの二刀流を目指して日々奮闘中です!
Qiitaでは、Androidの基本的なアーキテクチャであるMVVMに関する記事をシリーズ化して投稿し、1つのごくごく簡単なアプリを更新していくことでアウトプットしています。これまでの記事 :
ソースコード
今回はMVVMの実装からは少し距離を置き、
Coroutines
について記事を書きます。
本記事では、Coroutines:JetBrains公式ガイド, coroutines:JetBrains公式サンプルプロジェクト の内容を掻い摘んで取り上げています。
公式ドキュメントは英語ですが詳しい手順やメソッドの説明などが記載されているため、もっと詳しい説明が必要な場合は参照してみてください。また、ご指摘や感想などもコメントいただけると嬉しいです。
アジェンダ
- はじめに:Corutines
やその他の非同期処理について
- 導入方法:Coroutines
の導入方法
- 各種キーワード:各種機能の説明と実装
- まとめはじめに
Coroutinesとは
Coroutines
とは、Kotlinを開発したJetBrains社によって提供されている非同期処理ライブラリで、軽量な非同期処理を扱います。
非同期処理の実装はGoogleが提供するThreadやAsyncTaskなどのクラスで実装することも可能ですが、以下の特徴・理由を考慮し、アーキテクチャ学習用のアプリではCoroutines
を採用しました。
- 非同期処理ごとにクラスを作成する必要がない。
そのため
- より安全でエラーが起こりにくい
- 作成したクラスが残ってしまうことによってリークを起こすことがない
Thread
やAsyncTask
を使用する場合、作成したクラスからライフサイクルが終了したActivityやViewModel内のメソッドを呼び出されリークするリスクが少なからず存在する- コードが簡潔
- メモリのコストが小さい
Thread, AsyncTaskとは
ThreadとAsyncTaskに関しても、簡単に説明をしておきます。
Thread
は、単純に新しいスレッドを作成するクラスです。
そもそもスレッドの概念が分からん! という方はこちらのGoogle公式ドキュメントを参照したり「スレッド」で検索したりしてみてください。
このThread
クラスは単純に別のスレッドを作成するだけのものであるため、処理の順序づけや実行後に値を返す処理を実装する際はコードが煩雑になりがちです。
そこで、これらの不便な点を解消し非同期処理を容易にするために、AsyncTask
というクラスがGoogleにより用意されています。
AsyncTask
は、文字通りAsync(非同期 ⇄ 同期: sync)のTask、つまり非同期処理を実装するためのクラスです。
これにより、容易に非同期処理(処理Aを行う前に処理A'を行っておき、処理Aで出た結果を基にUIスレッド(メインスレッド)で処理Bを実行する、などの処理)を実装することができます。
詳しくは、AsyncTaskを参照してみてください。導入方法
では早速、
Coroutines
を実装していきます。
まずappレベルのbuild.gradle
にて、依存関係を記述します。build.gradledependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3' // 最新版(2020.1.19) }また、Projectレベルの
buil.gradle
にて、Kotlinのバージョンが最新になっていることを確認します。
最新でなくてもいいと思われますが、バージョンによってはCoroutines
を使用できない場合があります。
Coroutines
を使用できない場合は、sync
時やファイルにて各種Coroutines
のコンポーネントをimport
するときなどでエラーが出ると思われます(知らんけど)。指定のバージョンに設定するか最新バージョンに書き換え、sync
してみましょう。build.gradlebuildscript { ext.kotlin_version = '1.3.61' // 最新版(2020.1.19) }各種キーワード
Coroutines
で提供されているコンポーネントをキーワードごとにまとめます。CoroutineScope
CoroutineScopeは、すべてのCoroutine Builder(後述する
launch
など)の土台となるインターフェースです。
このCoroutineScope
を継承したスコープ(GlobalScopeなど)を用いて非同期処理を実装することになります。
例:fun launchTest() { println("処理1") GlobalScope.launch { println("処理2") // Thread.sleep(1000)の間に実行される } Thread.sleep(1000) // 実行中の全スレッドを1秒止める println("処理3") }結果:
処理1 処理2 処理3
Thread.sleep(1000)
を挿入しているのは、GlobalScope.launch
内の処理が完了してから処理3
を実行・出力させるためです。
このようにスコープ内の処理が完了するまでスコープ外の処理を待機させることはよくあることです。
今回はThread.sleep(1000)
により、運良く1000(ms)の内にGlobalScope.launch
内の処理が完了しましたが、全ての場合でそうなるとは限りません。
また、無駄な時間も発生するためこの実装は完全ではありません。でも安心してください。
Coroutines
では、以下のように完全に処理が完了するのを待ってくれる優しい機能が提供されています!
これらについては後述します。
- runBlocking
- withContext
先に述べておくと、
withContext
はsuspending function(後述します。後述が多くてすみません! )であるため、coroutine内(例えば、GlobalScope.launch{ //ここ}
)からしか呼ぶことができません。そんな彼らは、coroutine内で処理を止めたいときに使用されます。(
少し余談ですがCoroutineScope
はActivity
やViewModel
などの明確なLife Cycleを持ったクラス内で実装されるべきです。なぜなら、coroutineをキャンセルするタイミングを明確にすることができるからです。そのタイミングは親のクラスが破棄されるタイミング、Activity
であればonDestroy
メソッドなどでよいでしょう。(CoroutineScope).cancel()
を呼び出しておけば問題ないと思われます。
)さて、ここで"後述"で先延ばしにしていた説明を行うことにします。
launch
launchは、実行中のスレッドを止めない新たなcoroutineを作成するビルダーで、
Job
クラスを返すことができます。
launch
は先ほどの例のように、何らかのCoroutineScope
(例ではGlobalScope
)を基に生成され、ラムダ式({}
)内の処理が1つのcoroutineとして実行されます。
Job
クラスを返すとはどういうことかというと、launch
をJob
クラスでインスタンス化したり、Job
が提供するcancel
やjoin
などのメソッドを使用したりできるということです。
また、インスタンス化することで可読性の向上に繋がる可能性もあります(Coroutines
では軽量な処理が実行されるべきなので、そもそも煩雑な処理は実行しないようにするのが望ましいと思われますが)。Job
Jobとは、ライフサイクルを持つbackground "job"のことで、
Job
が完了したときやcancel()
が呼び出されたときにキャンセルされます。
launch
などで生成されます。ここで、
Job
の4つのメソッドの内2つを紹介します。join()
Job
が完了するまでcoroutineを一時中断してくれる、suspending function(後述)です。
例:fun launchTest { GlobalScope.launch { val job = launch { println("あ〜あ、ちょっと待ってよぉ〜") } job.join() println("待ったよ!") } Thread.sleep(1000) // 実行中の全スレッドを1秒止める println("終わり") }結果:
あ〜あ、ちょっと待ってよぉ〜 待ったよ! 終わりcancel()
cancel
を呼び出すことでJob
をキャンセルすることができます。
問題があった場合は、エラーメッセージが出力されます。
例:GlobalScope.launch { val job = launch { for (index in 1..10) { // 1秒ごとに"あ〜あ、ちょっと待ってよぉ〜"と歌う delay(1000) println("あ〜あ、ちょっと待ってよぉ〜") } } // 3秒以上は待てない delay(3000) job.cancel() println("待ちくたびれたよ!") }結果:
あ〜あ、ちょっと待ってよぉ〜 あ〜あ、ちょっと待ってよぉ〜 あ〜あ、ちょっと待ってよぉ〜 待ちくたびれたよ!全文:
fun launchTest() { println("始め!") GlobalScope.launch { val job = launch { for (index in 1..10) { // 1秒ごとに"あ〜あ、ちょっと待ってよぉ〜"と歌う delay(1000) println("あ〜あ、ちょっと待ってよぉ〜") } } // 3秒以上は待てない delay(3000) job.cancel() println("待ちくたびれたよ!") } Thread.sleep(10000) println("終わり!") }処理を一時中断する
Kotlinでは非同期処理実装のため、suspending functionという処理をsuspendする(一時停止する)メソッドが提供されています。
また、実行中の処理を一時中断するための機構は以下の2つがあります。
- runBlocking
- withContext
runBlocking
は、UIスレッドなど実行中のすべての処理を一時中断するために、
withContext
は、coroutines内の処理を一時中断するために使用されます。
それらの詳細を以下で説明します。suspending function
suspending functionとは、このメソッド内の処理が終わるまで実行中の処理を一時停止させることができるようなメソッドです。
Kotlin
独自のメソッドのタイプで、Coroutines
を実装する際に使用されます。
suspend fun
と記述することで定義できます。一時停止するとはいえ、なんでもかんでも停止できるわけではなく、coroutine内においてのみ停止できます。
そのため、suspend fun
で定義されたメソッドはcoroutine内(GlobalScope.launch{ //ここ}
など)からのみ呼び出すことができます。runBlocking
runBlocking
とは、その名の通り実行中の処理を止める機能です。
ラムダ内の処理が完了するまで、現在実行中のスレッド(coroutineによるものではないUIスレッドなど)を一旦止めることができます。これはcoroutineと他のスレッドとの橋渡し役となるよう設計されており、coroutine内では使用されません。
例:fun runBlockingTest() = runBlocking { println("処理1") GlobalScope.launch { println("処理2") // delay(3000)の間に実行される } // Thread.sleep(1000) // 実行中の全スレッドを1秒止める delay(3000) // coroutine(ここではrunBlocking)内の処理のみ3秒止める println("処理3") }実行例:
println("runBlocking、始めるで〜") runBlockingTest() println("runBlocking、終わったで〜")結果:
runBlocking、始めるで〜 処理1 処理2 処理3 runBlocking、終わったで〜
runBlocking
内の処理が完了するのをちゃんと待ってくれました!!
優しい!withContext
withContext
は、coroutine内でCoroutineContext
というContextを新たに提供し、このwithContext
内の処理が完了するまでcoroutine内の処理を一時中断する機能が備わっています。
言うなれば、with "Another" Contextですかね。(
引数として指定するCoroutineContext
は、CoroutineDispatcherを用いて提供されます。
このCoroutineDispatcher
により、以下3種類のCoroutineContext
を実装することができます。
- Dispatchers.Default:標準の
CoroutineContext
が提供される。バックグラウンド処理で共通のスレッド・プールを使用するため、CPUを消費するような計算処理を含む場合に最適です。- Dispatchers.IO:I/Oの処理を中断させたい場合に使われます。
- Dispatchers.Unconfined:あまり使われるべきではないようです。
)
先ほどの
runBlockingTest
内のdelay
メソッドをwithContext
で置き換えた例を記載します。例:
@Test fun runBlockingTest() = runBlocking { println("処理1") withContext(Dispatchers.Default) { println("処理2") // Thread.sleep(1000)の間に実行される } // Thread.sleep(1000) // 実行中の全スレッドを1秒止める // delay(3000) // coroutine(ここではrunBlocking)内の処理のみ3秒止める println("処理3") }実行例:
println("runBlocking、始めるで〜") runBlockingTest() println("runBlocking、終わったで〜")結果:
runBlocking、始めるで〜 処理1 処理2 処理3 runBlocking、終わったで〜この通り、
runBlockingTest
内の処理(処理1, 2, 3)が順番通りに実行されました!!まとめ
簡単に概要をまとめます。
Coroutines
とは、軽量なスレッドを提供するKotlin独自の非同期処理ライブラリです。
ラムダ式一つでスレッドを用意し、非同期処理を実装することができます。
runBlocking
やwithContext
など、実行中のスレッドを一時中断するための機能も簡単に実装することができます。
非同期処理を簡潔に安全に実装できる便利な機構ではないでしょうか。今回は、Kotlinでの非同期処理で最適と思われる
Coroutines
について記事を書きました。
この情報量でおおよその機能や性質を理解し使用することができるのではないか、と思います。
知らんけど。参考資料
- Coroutines:JetBrains公式ガイド
- coroutines:JetBrains公式サンプルプロジェクト
- Thread:Google公式リファレンス
- AsyncTask:Google公式リファレンス
- CoroutineScope:JetBrains公式リファレンス
- launch:JetBrains公式リファレンス
- Job:JetBrains公式リファレンス
- CoroutineDispatcher:JetBrains公式リファレンス
- 投稿日:2020-01-25T17:45:31+09:00
ReamBrowserでAndroid実機のDBを参照する方法(Android:実機端末,PC:Mac)
AndroidをUSBデバッグ接続する
ここは省きます。
ググればすぐでできます!
https://developer.android.com/studio/command-line/adb?hl=jaAndroid SDKのディレクトリを開く
Android SDK Location に記述してあるパスをターミナルで開く
Andorid Studio → Preferences → Android SDK → Android SDK Locationcd /Users/ユーザー名/Library/Android/sdk
adbが存在するパスへ移動
cd ./platform-tools
Android実機の実行環境を開く
adbを実行し、android実機の実行環境を開く
./adb shellrealmファイルを抽出
ローカルストレージを見る
run-as パッケージ名 //上のパッケージわからない場合は以下で調べる pm list packages | grep パッケージ名
realmが保存されているディレクトリへ移動
cd ./files
外部ストレージへコピー
※ここハマりました。
SDカードないからできねえええええええええって思ってたんですけど
sdcardのシンボリックリンクが内部ストレージ(/storage/self/primary)になってました。
なのでSDカードマウントできないどうしようと発狂しなくてよいです。//sdcardを調査して出た結果 ls -ltra lrwxrwxrwx 1 root root 21 1970-09-02 19:46 sdcard -> /storage/self/primary外部ストレージ(SDカードではなく内部ストレージ)へコピー
cat default.realm > /sdcard/Download/export_db.realm exitAndroid実機からPCへコピー
./adb pull /sdcard/Download/export_db.realm /Users/ユーザー名/Documents外部ストレージにコピーしたごみを削除
./adb shell rm /sdcard/Download/export_db.realm
RealmBrowserで開く
RealmBrowserを開く →
Open Realm File
/Users/ユーザー名/Documents/export_db.realm
を開く無事見れましたね!!!
- 投稿日:2020-01-25T13:28:01+09:00
再起動したあと自動的にadb tcpipを有効化する (root化必要)
モチベ
scrcpyするときにいちいちUSBつないでtcpipするのめんどくさい
環境
Nexus 5 無印 (タッチスクリーンが壊れてる)
Android 6.0.1 Mashmallow手順
- adb shell
- su
- setprop persist.adb.tcp.port 5555
- 端末再起動して adb shell getprop で persist.adb.tcp.port があればOK
ソース
This will make it persistent:
setprop persist.adb.tcp.port 5555
// If one of these properties is set, also listen on that port // If one of the properties isn't set and we couldn't listen on usb, // listen on the default port. property_get("service.adb.tcp.port", value, ""); if (!value[0]) { property_get("persist.adb.tcp.port", value, ""); } if (sscanf(value, "%d", &port) == 1 && port > 0) { printf("using port=%d\n", port); // listen on TCP port specified by service.adb.tcp.port property local_init(port); } else if (!usb) { // listen on default port local_init(DEFAULT_ADB_LOCAL_TRANSPORT_PORT); }To answer your 2 last questions, according to several sources (1, 2, 3), thoses are stored in /data/property/. I couldn't find an offical reference about this location though.
For you, that would be in /data/property/persist.adb.tcp.port.
余談
- https://forum.xda-developers.com/showthread.php?t=635102 によると service.adb.tcp.port でも行けるみたいな感じだが再起動したら消えてしまった
ssh root@192.168.3.109 -L 5555:localhost:5037
みたいな感じでadbdへSSH Port Forwardingするのは妙案だと思った。これならrootなくても行ける?だれか試して。今回scrcpyはなるべくオーバーヘッド下げたいので正攻法でやった。
- 投稿日:2020-01-25T09:30:59+09:00
自作アプリで、Intentを使用してGmailアプリに、CC, BCC, 件名, 本文が渡せない。
問題
私の自作アプリでは、Intentという内部処理を使用して、Gmailアプリに、CC, BCC, 件名, 本文を渡しています。ある日突然、Gmailアプリが、CC, BCC, 件名, 本文を受け付けてくれなくなりました。
問題発生時のコード(Kotlin)
// メールアドレス設定 val uri = Uri.parse("mailto:" + mailAddressTO) // 件名、本文をメールツールに渡す val intent = Intent(Intent.ACTION_SENDTO, uri) intent.putExtra(Intent.EXTRA_CC, mailAddressCC) intent.putExtra(Intent.EXTRA_BCC, mailAddressBCC) intent.putExtra(Intent.EXTRA_SUBJECT, subject) intent.putExtra(Intent.EXTRA_TEXT, mailText) startActivity(context, intent, null)解決策
val intent = Intent(Intent.ACTION_SENDTO) intent.data = Uri.parse("mailto:") intent.putExtra(Intent.EXTRA_EMAIL, mailAddressTO) intent.putExtra(Intent.EXTRA_CC, mailAddressCC) intent.putExtra(Intent.EXTRA_BCC, mailAddressBCC) intent.putExtra(Intent.EXTRA_SUBJECT, subject) intent.putExtra(Intent.EXTRA_TEXT, mailText) context.startActivity(intent)参考リンク
https://stackoverflow.com/questions/8701634/send-email-intent
https://qiita.com/kitagry/items/e3b77061c31e388d152f
https://developer.android.com/guide/components/intents-common?authuser=1&hl=ja#Email