20200125のAndroidに関する記事は4件です。

【Android】はじめてのCoroutines

アプリ開発歴( =プログラミング歴)がもうすぐで1年になります
@iTakahiroです。

現在はiOS・Androidの二刀流を目指して日々奮闘中です!
Qiitaでは、Androidの基本的なアーキテクチャであるMVVMに関する記事をシリーズ化して投稿し、1つのごくごく簡単なアプリを更新していくことでアウトプットしています。

これまでの記事 :

ソースコード

今回はMVVMの実装からは少し距離を置き、Coroutinesについて記事を書きます。
本記事では、Coroutines:JetBrains公式ガイド, coroutines:JetBrains公式サンプルプロジェクト の内容を掻い摘んで取り上げています。
公式ドキュメントは英語ですが詳しい手順やメソッドの説明などが記載されているため、もっと詳しい説明が必要な場合は参照してみてください。

また、ご指摘や感想などもコメントいただけると嬉しいです。

アジェンダ
- はじめにCorutinesやその他の非同期処理について
- 導入方法Coroutinesの導入方法
- 各種キーワード:各種機能の説明と実装
- まとめ

はじめに

Coroutinesとは

Coroutinesとは、Kotlinを開発したJetBrains社によって提供されている非同期処理ライブラリで、軽量な非同期処理を扱います。
非同期処理の実装はGoogleが提供するThreadAsyncTaskなどのクラスで実装することも可能ですが、以下の特徴・理由を考慮し、アーキテクチャ学習用のアプリではCoroutinesを採用しました。

  • 非同期処理ごとにクラスを作成する必要がない。

そのため

  • より安全でエラーが起こりにくい
  • 作成したクラスが残ってしまうことによってリークを起こすことがない
    • ThreadAsyncTaskを使用する場合、作成したクラスからライフサイクルが終了した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.gradle
dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3' // 最新版(2020.1.19)
}

また、Projectレベルのbuil.gradleにて、Kotlinのバージョンが最新になっていることを確認します。
最新でなくてもいいと思われますが、バージョンによってはCoroutinesを使用できない場合があります。
Coroutinesを使用できない場合は、sync時やファイルにて各種Coroutinesのコンポーネントをimportするときなどでエラーが出ると思われます(知らんけど)。指定のバージョンに設定するか最新バージョンに書き換え、syncしてみましょう。

build.gradle
buildscript {
    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内で処理を止めたいときに使用されます。

(
少し余談ですがCoroutineScopeActivityViewModelなどの明確なLife Cycleを持ったクラス内で実装されるべきです。なぜなら、coroutineをキャンセルするタイミングを明確にすることができるからです。そのタイミングは親のクラスが破棄されるタイミング、ActivityであればonDestroyメソッドなどでよいでしょう。(CoroutineScope).cancel()を呼び出しておけば問題ないと思われます。
)

さて、ここで"後述"で先延ばしにしていた説明を行うことにします。

launch

launchは、実行中のスレッドを止めない新たなcoroutineを作成するビルダーで、Jobクラスを返すことができます。

launchは先ほどの例のように、何らかのCoroutineScope(例ではGlobalScope)を基に生成され、ラムダ式({})内の処理が1つのcoroutineとして実行されます。

Jobクラスを返すとはどういうことかというと、launchJobクラスでインスタンス化したり、Jobが提供するcanceljoinなどのメソッドを使用したりできるということです。
また、インスタンス化することで可読性の向上に繋がる可能性もあります(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独自の非同期処理ライブラリです。
ラムダ式一つでスレッドを用意し、非同期処理を実装することができます。
runBlockingwithContextなど、実行中のスレッドを一時中断するための機能も簡単に実装することができます。
非同期処理を簡潔に安全に実装できる便利な機構ではないでしょうか。

今回は、Kotlinでの非同期処理で最適と思われるCoroutinesについて記事を書きました。
この情報量でおおよその機能や性質を理解し使用することができるのではないか、と思います。
知らんけど。

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReamBrowserでAndroid実機のDBを参照する方法(Android:実機端末,PC:Mac)

AndroidをUSBデバッグ接続する

ここは省きます。
ググればすぐでできます!
https://developer.android.com/studio/command-line/adb?hl=ja

Android SDKのディレクトリを開く

Android SDK Location に記述してあるパスをターミナルで開く
Andorid Studio → Preferences → Android SDK → Android SDK Location

cd /Users/ユーザー名/Library/Android/sdk

adbが存在するパスへ移動

cd ./platform-tools

Android実機の実行環境を開く

adbを実行し、android実機の実行環境を開く

./adb shell

realmファイルを抽出

ローカルストレージを見る

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
exit

Android実機から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 を開く

無事見れましたね!!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

再起動したあと自動的にadb tcpipを有効化する (root化必要)

モチベ

scrcpyするときにいちいちUSBつないでtcpipするのめんどくさい

環境

Nexus 5 無印 (タッチスクリーンが壊れてる)
Android 6.0.1 Mashmallow

手順

  1. adb shell
  2. su
  3. setprop persist.adb.tcp.port 5555
  4. 端末再起動して adb shell getprop で persist.adb.tcp.port があればOK

ソース

https://stackoverflow.com/questions/12251101/how-to-enable-adbd-to-listen-to-a-port-at-boot-time-in-android

This will make it persistent:

setprop persist.adb.tcp.port 5555

https://android.googlesource.com/platform/system/core/+/refs/heads/marshmallow-release/adb/adb_main.cpp

    // 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);
    }

https://android.stackexchange.com/questions/151053/difference-between-system-build-prop-properties-and-setprop-persist-adb-tcp-por

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はなるべくオーバーヘッド下げたいので正攻法でやった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自作アプリで、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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む