fkm blog

software開発に関することを書いていきます

TypeScript + Firebase + npmでCannot find moduleと言われた話

結論から言うとしょーもない話だったんですが。

Firebaseのライブラリは <script> で読み込む方法は紹介されてるけど、 npm でインストールしてES6のimportで使う方法はあっさりとしか書いてなかったりする。

まずはインストール

$ npm install --save firebase

そして main.ts あたりでimport

import * as firebase from "firebase";

これで問題ないはずだが、VSCodeCannot find module firebase と赤線を引いてくる。

答えは tsconfig.json にあった。

/* Module Resolution Options */
// "moduleResolution": "node" ←コメントアウトされてる

次のようにコメントを外してあげると、エラーは出なくなった。JSONなのでエントリー前後のカンマに注意。

/* Module Resolution Options */
"moduleResolution": "node"

Google I/O 報告会 in 福岡 2019

これに参加して、Android QとAndroid Studioに関してお話してきました。

gdg.connpass.com

福岡のI/O報告会はここ3年ほどは参加してるけど、参加者がとにかく若いのが特徴的。「コードなんて書きませんよガハハ」的なおじさんほぼいない感じ。

来年はもっと福岡からのI/O参加者、増えるといいなぁ

Google I/O 2019: What's New in Architecture Components

youtu.be

2年前のI/Oで、Archtecture Componentとして LifecycleRoom が発表された。その後ページングが追加され、去年のI/OではナビゲーションとWorkManagerが追加された。

プロの開発者の70%が、これらのうち1つ以上をアプリにいれて使っている。

開発サイクル

まず、EAPで一部の開発者に向けて公開し、みんなに公開していいと判断したタイミングでアルファ版になる。改善の結果、ベータ版でAPIが固定される。その後stableになり、次のアルファ版フェーズが始まる。

f:id:fkm:20190602131629p:plain

Kotlin first

ktxを出すだけでなく、Kotlinしか使えない機能も。APIのデザインもkotlinにあわせる。

Data Binding

ライブラリの中でも古い方のData Binding、Android Studio 3.5でコンパイルが早くなった(20%ほど)。

インクリメンタルなアノテーションプロセッシングを使いたい場合は、次の行を build.gradle に追加する。

android.databinding.incremental = true

また、クラス生成がライブになった。例えばレイアウトXMLでidを追加したとき、即座にクラス側も変更されるようになった。

リファクタリングにも対応した。例えばレイアウトXMLであるクラスのメソッドを呼んでいたとする。今まではそのメソッド名をリファクタリング機能で変更したとき、レイアウトXML側は変わらなかったがAndroid Studio 3.5から変更されるようになった。

Data Bindingでのエラーもよりわかりやすくなった。

View Binding

Android Studio 3.6で登場予定(もうCanaryが出てるので触ってみよう)。

  • Gradle pluginでBinding classを生成するよ
  • 100% コンパイルタイムsafety
  • Android Studioへのインテグレーションばっちり(要検証
  • Data Bindingとのコンパチ

ViewModelとSavedState

ViewModelはあくまでメモリに情報を保存するので、プロセスが止まるようなケースだと情報がなくなってしまう。一方SavedStateはシステム側に情報を退避させるので、中断+プロセス断からの再開でも情報を復元できる。メモリをたくさん消費するようなオブジェクトはViewModelに、Viewの状態(チェックがついているかどうかや、スクロール位置とか)はSavedStateに。

ここで登場するのが SavedStateHandle、これはViewModelのコンストラクタで受け取り、ViewModelの中で使う。SavedStateHandle は単なるmap-likeオブジェクト。

val handle: SavedStateHandle

// 読み込み
val myValue: Int = handle.get("key")

// 書き込み
handle.put("key", newValue)

LiveData も扱える。mutableになる。

val liveData = handle.getLiveDate("key")
liveData.observe(lifecycleOwner) { value -> ... }
liveData.value = newValue

ViewModelの初期化は ktx 使うと次のようにかける。

val userViewMode: UserViewModel by viewModels()

WorkManager

オンデマンドでの初期化はアルファ版のWorkManagerで使える。初期化は今まではアプリの先頭だったが、これからは必要とする時点で初期化できる。

カスタムのApplicationクラスを次のようにする。

class MyApp: Application(), Configuration.Provider {
    override fun getWorkManagerConfiguration(): Configuration {
        return Configuration.Builder()
            // 設定をここに書く
            .build()
    }
}

使う時は、 getInstance()context を渡す。

WorkManager.getInstance(context).enqueue(...)

Android M以前の端末用に、Google Play Servicesとの連携も用意中(coming soon)

アプリを強制停止した時などの動作は、実は端末間によって差がある。この差をなんとかしようとしてるみたい。

WorkManagerのテストにRobolectricが使えるようになった。

Workの単体テストはアルファ版で使える。テスト用のワーカーを TestWorkerBuilderTestListenalbeWorkerBuilder を使って作る。そして doWork()startWork() を呼んでテストする。

val request = OneTimeWorkRequestBuilder<MyWorker>.build()
val worker = TestWorkerBuilder.from(context, request, executor).build()

val result = worker.doWork()
assertThat(result, `is`(Result.success()))
...

Listenableなワーカーは、こんな感じ。

val request = OneTimeWorkRequestBuilder<MyWorker>.build()
val worker = TestListenableWorkerBuilder.from(context, request).build()

val result = worker.startWork().get()
assertThat(result, `is`(Result.success()))
...

WorkManagerのForeground service対応はやっている最中。

Room

Room 2.1から、DAOのメソッドに suspend をつけることができるようになった。

全文検索にも対応した。Entityに @Fts4 をつけるだけ。そしてDAOでは次のように MATCH が使える。

@Dao
interface SongDAO {
    @Query("""
        SELECT * 
        FROM Song
        WHERE Song MATCH :query
    """)
    fun searchSongs(query: String): List<Song>
}

データベースのViewにも対応した。

@DatabaseView("""
    SELECT
        Album *,
        count(song_id) AS num_of_songs,
        sum(song_elapsed_time) as total_time
    FROM Album
    JOIN AlbumSongRef ON (album_id = ref_alubm_id)
    JOIN Song ON (ref_song_id = song_id)
    GROUP BY album_id
""")
data class AlbumItem(
    @Embedded
    val album: Album
    @ColumnName("num_of_songs)
    val numOfSongs: Int
    @ColumnName("total_time")
    val totalTime: Long
)

RxJavaも対応。DAOの戻り値の型に CompletableSingle<Int> とかが使えるように。

Paging

今後の課題。

  • ネットワークエラーへの対応
  • ヘッダーとフッター

Navigation

次期バージョンでは、 - ViewModelスコープの対応。 - URIでの遷移。 - 遷移先にダイアログ - dynamic featureの対応

udacityに最新のAndroidアプリ開発コースができてる。https://www.udacity.com/course/ud9012

Google I/O 2019: What's New in Kotlin on Android, 2 Years In

youtu.be

KotlinがAndroidの公式言語になって2年。決定自体は3.5年前ほど前。コミュニティとかに聞いてもKotlinが好まれていた。

Kotlin foundation

Kotlin言語を保護/推進/高度にするためにJetbrainとGoogleによって作られた。ボードメンバーは - 2人がJetbrain - 2人がGoogle - 1人がそれ以外

言語の設計。また言語の破壊的変更のコントロールとかも。より詳しいことは

https://kotlinlang.org/foundation/kotlin-foundation.html で。

ここからはデモ。

when に関しては、次のように when 内部でのみ有効な書き方がある。 responsewhen の中だけ有効。

when (val response = calculateROI(years)) {
    50 -> println("Too long")
    5 -> println("Bargain")
    else -> println("Can't handle $response")
}

ある機能やクラスが実験的(experimental)であるというアノテーションが作れる。例えばNewAPIですよーというアノテーション

@Experimental
annotation class NewAPI

@NewAPI
class MicroserviceAPI

fun main() {
    val microserviceAPI = MicroserviceAPI() // コンパイルエラー : これは実験的なAPI
}

警告にしたい場合は、レベルをつけてwarningにする。

@Experimental(level = Experimental.Level.WARNING)
annotation class NewAPI

次はcontractについて。通常、nullチェックをした場合、その後はnon-nullとして扱うことができる。この機能をスマートキャストと言う。

fun printLength(s: String?) {
    if (s != null) {
        println(s.length)
    }
}

ここで、次のように != ではなく、メソッドで判別した場合はどうなるだろう?

fun String?.notNull(): Boolean {
    return this != null
}

fun printLength(s: String?) {
    if (s.notNull()) {
        println(s.length) // コンパイルエラー。sはnullable
    }
}

これだと嬉しくない。ここで登場するのがcontract。 true を返した場合、 this はnullでないよとお知らせしている。

import kotlin.contratcs.contract

fun String?.notNull(): Boolean {
    contract {
        returns(true) implies (this@notNull != null)
    }
    return this != null
}

fun printLength(s: String?) {
    if (s.notNull()) {
        println(s.length) // 今回はコンパイルエラーにならない。
    }
}

しかしこのcontractはexperimentalなAPIなので、そのままでは使えない。なので notNull() にexperimentalなAPI使ってますよのアノテーションをつける。

import kotlin.contracts.ExperimentalContracts
import kotlin.contratcs.contract

@UseExperimental(ExperimentalContracts::class)
fun String?.notNull(): Boolean {
    contract {
        returns(true) implies (this@notNull != null)
    }
    return this != null
}

マルチプラットフォーム

例えばプラットフォーム名といった、各プラットフォーム固有の実装を作りたい場合は expectactual を使う。

共通コード(common)では、 次のように expect を用意し、使う。

expect object Platform {
    val name: String
}

fun hello(): String = "Hello from ${Platform.name}"

もちろん実装は各プラットフォームで作らないといけない。例えばJVM用であれば次のように作る。

actual object Platform {
    actual val name: String = JVM"
}

未実装のプラットフォームがあった場合はコンパイルエラー。

AndroidとKotlin

実際に実行されるのはバイトコードなので、そんなにできることはないけど、デバッグ用の機能がAndroid Q(多分)に入っている。例えばインライン関数とか。

Jetpackのライブラリはコルーチンに対応。詳細は他のセッションにて。

KotlinプラグインAndroid Studioの一部になった(だいぶ前な気もするけど)。なのでAndroid Studioのリリース前に動作するかチェックされている。また、Android特有のリファクタリングにも対応した。例えばレイアウトXMLのファイル名を変更したとき、 R.layout.xxxx で参照している部分も変更するとか。

LintもKotlinサポートになった。またテンプレート(例えばフラグメントの追加とか)もKotlinになった。R8もKotlin特有の最適化をやるようになっている。

ドキュメントはKotlinとJavaの両方で見れる。

Googleでは?

Googleの内部で使われているアプリでもKotlinが使われている。例えば地図やオフィスの位置を表示するアプリとか。

GoogleドライブやGoogleホームアプリとかではKotlinが使われている。

直近6ヶ月で見ると、16%のアクティブに開発されているアプリがKotlinを使っている。去年のI/Oからだと5倍に増えている。トップ1000アプリだと44%のアプリが使っている。

Kotlin First

Kotlinを使っての開発にみんな満足している。調査によるとKotlinを使っているエンジニアのほうが25%も幸せであった。

Kotlin Firstとはどういうことか?Javaをやめるわけではなく、一部のもの(オンライントレーニングやサンプル)はKotlin firstで、Javaはベストエフォートになる。またマルチプラットフォームJetpack ComposeはKotlinのみになる。

Switchすべき?プロジェクトの状態による。リリースが1週間後に迫っているようなプロジェクトでは当然だがスイッチすべき時じゃない。次の開発サイクルに入るタイミングで導入してみるのがよさそう。

もちろんKotlin導入にはトレードオフがある。(学習しやすいとはいえ)Kotlinの学習曲線については気にする必要がある。最初Kotlinを書いてみると、JavaっぽいKotlinコードになる。3〜6週間して、ようやくKotlinらしいコードがかけるようになる。

Ongoing Kotlin work

Kotlin everywhere

https://kotl.in/everywhereを見てね

Google I/O 2019: Understand Kotlin Coroutines on Android

Kotlin系いきます。

youtu.be

理想的なコード

こんなコード、書けるといいよね。

val user = fetchUser() // ネットワークからユーザー情報をとってくる
textView.text = user.name

でも、当然ながらUIスレッドでこんなコード書くと、 fetchUser()NetworkOnMainThreadException 投げちゃうので、だめ。

じゃあこうするとどう?

thread {
    val user = fetchUser() // ネットワークからユーザー情報をとってくる
    textView.text = user.name
}

今度は textView への変更で CalledFromWrongThreadException 投げちゃう。

これならどうだ。

fetchUser { user -> 
    textView.text = user.name
}

これならOK。コールバックのリークがなければ。

Subscriptionを使う例

コールバック方式の場合、途中でとめる仕組みがないとアクティビティが停止しても処理が進んじゃう。そのためこんなコードを書くよね。

val subscription = fetchUser { user ->
    textView.text = user.name
}

override fun onStop() {
    super.onStop()
    subscription.cancel()
}

非同期処理が1つならいいけど、これが複数になると、 onStop() がこんな感じになっちゃう。

override fun onStop() {
    super.onStop()
    subscription.cancel()
    subscription2.cancel()
    subscription3.cancel()
    subscription4.cancel()
    subscription5.cancel()
}

これをなんとかしたいので、 Architecture Componentが誕生した。

RxJava way

RxJavaを使う方法もある。

fun fetchUser(): Observable<User> = ...

fetchUser()
    .as(autoDisposable(AndroidLifecycleScopeProvider.from(this))
    .subscribe { user -> 
        textView.text = user.name
    }

LiveData way

LiveDataだと、こんな感じで書ける。

fun fetchUser(): LiveData = ...

fetchUser().observe(viewLifecycleOwner) { user -> textView.text = user.name }

解決済みの問題?

現状、並行性に関する問題の解法は3つ。

  • LiveData: Observable Data holder
  • RxJava: Observable + Scheduler + Observer
  • Coroutines: 中断可能な計算

LiveDataは「いいけど、より完璧なソリューション頼む」、RxJavaは「パワフルだけど、時に間違った使われ方をしたり、オーバーキルすぎたりする」

コルーチンは「ベストっぽいが、エクステンションの充実と学習曲線がなぁ」

Jetpackでは、コルーチンをメインとして、RxJavaはドキュメントでのサポートとするらしい。

コルーチン

コルーチンとは、コールバックスタイルを置き換える、非同期処理をシンプルにしてくれるもの。

例えば、こんなコードがあったとする。

fun loadUser() {
    val user = api.fetchUser()
    show(user)
}

もちろんこのコードは (apiの実装にもよるが)UIスレッドをブロックしてしまったり、クラッシュしたりする。

コールバックスタイルだと、こんな感じ。

fun loadUser() {
    api.fetchUser() { user -> 
        show(user)
    }
}

コルーチンだと、こんな感じになる。

suspend fun loadUser() {
    val user = api.fetchUser()
    show(user)
}

ブロッキングスタイルだけど、UIをブロックしたりしない。

fetchUser() は、こんなメソッドとして定義される。

suspend fun fetchUser() {
    withContext(Dispatchers.IO) {

    }
}

Kotlinでは、Dispatcherとして - Default: 計算用 - IO: ディスクやネットワークアクセス用 - Main: Androidのメイン(UI)スレッド が用意されている。

withContext() はUIスレッドで呼んでも大丈夫。

Kotlinではどうやってる?

※ここは動画みたほうがわかりやすいかも。

suspend な関数が呼ばれると、スタックにマーカーを置く。マーカーを置いてから、通常の関数呼び出しのように呼び出し位置をスタックに積む。そして関数内部で別の suspend な関数が呼ばれると、マーカーまでを避難させて処理を続ける。呼んだ位置がちゃんと退避されているので、Dispatcherでの処理が終わった時点でちゃんとスタックの復元ができるという仕組み。

ライブラリの対応

WorkManager

work-runtine-ktx:2.0.0 ですでにコルーチンサポート済み。こんな感じで使う

class UploadNotesWorker(...): CoroutineWorker(...) {
    override suspend fun doWork(): Result {
        val newNotes = db.queryNewNotes()
        noteService.uploadNotes(newNotes)
        db.markAsSynced(newNotes)
        return Result.success()
    }
} 

LiveData

LiveData:2.2.0-alpha01 で、 liveData が追加された。中身はコルーチンブロックなので suspend 関数を呼ぶことができる。

// user: LiveData<User>
// なおこのままだとメインスレッドでDBを読むので例外になっちゃう
val user = liveData {
    emit(db.load(userId))
}

Dispatcherを指定することもできる。

// user: LiveData<User>
val user = liveData(Dispatchers.ID) {
    emit(db.load(userId))
}

タイムアウトも指定できる。なぜタイムアウトの指定があるのか?それは画面回転にともなうLiveDataのunsubscribeとsubscribeによるもの。画面回転すると、今の画面は使えないのでLiveDataに対してはunsubscribeが行われる。LiveData側からすると、subscribeしてるものがなくなるので処理を中止してよいことになってしまう。そして再度subscribe(通常だと1秒未満)が行われれ、処理が最初から行なわれる という事態が発生してしまう。これではイケてないのでタイムアウトの概念が導入されている。

また、 emit() は何度も呼んでよい。なのでこんな感じのコードが書ける。

val user = liveData {
    emit(db.load(userId)) // まずDBにあるのを返して
    val u = api.fetch(userId) // ネットワークからとってきて
    db.insert(u) // それをDBにいれて
    emit(u) // 最新のものとして返す
}

もし db.load(userId) がRoom経由のLiveDataを返す場合、DBの監視機能がついてるので最後の emit() がいらなくなる(insertした時点で db.load() が返したLiveDataに最新のデータがやってくる)。そんなケースの場合は emitSource() を使う。

val user = liveData {
    emitSource(db.load(userId)) // まずDBにあるのをLiveDataで返して
    val u = api.fetch(userId) // ネットワークからとってきて
    db.insert(u) // それをDBにいれる
}

ViewModel

コルーチンは油断するとリークしてしまう(必要ないのに処理を続けてしまう)ので、Kotlinでは「スコープ」が導入されている。すべてのコルーチンはスコープの中で実行されないといけない。スコープは中で実行しているコルーチンを必要なときに全部キャンセルできる。

ViewModelもコルーチンスコープになった。ViewModeの中で次のようなコードが書ける。

viewModelScope.launch {
    // UIスレッドで実行される
    while(true) {
        delay(1_000)
        writeFile()
    }
}

画面遷移などでViewModelが破棄されるときにコルーチンもキャンセルされる。

Lifecycle

ライフサイクルオーナーもコルーチンスコープとなるよう修正が入った。

LifecycleOwner.lifecycleScope: CoroutineScope

なので、 ActivityFragment の中で次のようなコードが書けるようになった。

fun loadUser() {
    this.lifecycleScope.launch {  
        ...
    }
}

UIに関連した非同期処理がとても書きやすくなる。例えば画面が表示されてから、1秒待ってヒントを出すといった際、こんな感じで書ける。

override fun onStart() {
    super.onStart()
    this.lifecycleScope.launch {
        delay(1_000) // 1秒待って
        showFullHint()
        delay(1_000) // さらに1秒待って
        showSmallHint()
    }
}

画面回転とかでキャンセルされる!というのを忘れてはいけない。画面回転しても継続したほうがいいような処理はViewModelで。

また、lifecycleScope.launch はフラグメントの状態がどうなってるかわからないので、非同期処理後に画面遷移といったコードを書くと、タイミングによっては IllegalStateException が発生しちゃう。そのため、 launchWhenStarted というのが追加されている。

override fun onCreate() {
    super.onCreate()
    this.lifecycleScope.launchWhenStarted {
        val note = userViewModel.loadNote()
        fragmentManager.beginTransaction()...commit()
    }
}

この場合、途中で suspend な関数呼び出しから戻ってきたタイミングで状態が STARTEDになっているかのチェックが入る。PAUSEDとかになっていたら続きの処理は行なわれない。

テストについて

kotlinx-coroutines-test というライブラリがある。まだexperimental。特定のテストツールに依存してるわけじゃないので、JUnit 4とか5とか、お好きなのをどうぞ。

例えばこんなクラスを考えてみよう。

class Repository {
    val liveData = liveData {
        emit(1)
        delay(1_000)
        emit(2)
    }
}

テストコードはどうするか。まずはスコープを作る。

val testDispatcher = TestCoroutineDispatcher()
val testScope = TestCoroutineScope(testDispatcher)

で、beforeとafterに設定を追加。

@Before
fun setup() {
    Dispatchers.setMain(testScope)
}

@After
fun tearDown() {
    Dispatchers.resetMain()
    testScope.cleanupTestCoroutines()
}

テストは、こんな感じで書く。JUnit4のRuleを使ってる。

@get:Rule
val testCoroutineRule = TestCoroutineRule()

@Test
fun test01 = testCoroutineRule.runBlockingTest {
    val subject = repository.liveData
    subject.observeForTesting {
        subject.value shouldEqual 1
        advanceTimeBy(1_000)
        subject.value shouldEqual 2
    }
}

observeForTesting はライブラリで提供されている関数ではなく、こんな感じで自作したもの。

fun <T> LiveData<T>.observeForTesting(
    block: () -> Unit
) {
    val observer = Observer<T> { Unit}
    try {
        observerForever(observer)
        block()        
    } finally {
        removeObserver(observer)
    }
}

Google I/O 2019: What's New in Android Development Tools

youtu.be

Android Studio 3.2

去年のI/Oでは、Android Studio 3.2が発表された。App bundleやナビゲーションエディタなど。

Android Studio 3.3

今年(2019年)1月にAndroid Studio 3.3がリリースされた。プロジェクト作成ウィザードの刷新や、単一variantのsyncなど。

Android Studio 3.4

今年(2019年)4月にAndroid Studio 3.4がリリースされた。リソースマネージャーの登場や、R8(Proguardの新しい版)がデフォルトになるなど。

Android Studio 3.5

Project Marble。IDEの体験を向上させよう。そのために大量のIssueを - System health - Feature Polish - Bug Backlog に分類したよ。

System health

Android Studio起動時に「統計情報をGoogleに送っていい?」と聞かれるが、その情報が使われてるのがここらしい。

有効にしていて、Android Studioがクラッシュすると、クラッシュレポートがGoogleに送信される。

UIのフリーズは注意しておきたい分野。UIスレッドが数秒とまった場合、すべてのスレッドダンプをとってGoogleに送信している。ダッシュボードによると、平均14秒止まり、発生回数が5万件というのがあったりする。

この統計をもとにバグを直したところ、例としてデータバインディングを使っているXMLの編集がとてもはやくなった。

もちろんスポットで直してるだけではない。ちゃんとリグレッションテストもやってる。例えばUIスレッドがネットワークリクエスト投げていたりしたらテストがFailするようにしてる(Javadoc出したりするときに発生しそう)。

次はメモリに関して。IDEがOOMに遭遇したとき、ヒープの統計とかを収集してGoogleに送信している。このような統計から見えてきたことは、多くのユーザーはAndroid Studioをヒープ1.2GBで動かしていたという事実。

なぜ1.2GBをデフォルトにしたか?デフォルトを大きくすると、ローエンドのマシンで起動すらできなくなってしまうから。だからといって小さくしすぎると、通常のプロジェクトを開くときに開けなくなってしまう。

もしマシンのメモリがたっぷりあるのなら、Android Studioが使用するヒープサイズは大きくしよう。

Android Studio 3.5では、「もうちょいAndroid Studioが使用するヒープサイズ大きくする?」のダイアログが出るように。その場で設定を変更して再起動もできる。

Android Studioメモリリークを調べるには、ヒープダンプを取るのが手っ取り早い。でもでかいし、個人情報が含まれるからそれは無理。そのため、OOMが近くと一旦ヒープダンプをとってローカルに保存し、次回起動時にメモリリークを解析してその結果だけを送信するようにしてある。送信後、保存したダンプファイルは消している。送信前にはどんな情報を送るか、プレーンテキストになっているものが確認できる。

このメモリリークの情報から、有名なサードパーティプラグインメモリリークを突き止めた(すげー)。プラグイン開発者にバグレポートしたところ、「数年前からなんかリークしてたっぽいんだけど、わかんなかったんだよねー」とともに、24時間以内に直してくれた。

だいぶ改善されたとはいえ、メモリリークのあるプロダクトを出荷しているという事実にかわりはない。なので特殊なテストランナーを用意し、テストでメモリリークを発見できるようにした。

次にスピードに関して。1つの手は余計なプラグインをいれないこと。でもパフォーマンスのためにプラグイン使うのやめるのはおすすめしない。

高速化のために、アノテーションプロセッシングのインクリメンタル化をしたよ(別セッションにて)

Android Studioのマジョリティは(Macではなく)Windows。ファイルのセパレータが違うといった、Windows特有の問題もあるけど、パフォーマンスに影響があったのがウィルスチェッカー。SDKやプロジェクトのフォルダを除外すると、速度が4倍になった。そのため、SDKやプロジェクトフォルダがウィルスチェッカーに除外されていない場合、ダイアログを出すようにしたよ。盲目的に除外するのではなく、セキュリティとのトレードオフを考えてね。

エミュレーターもまたCPUをよく使う分野。Google Play入りのものだと、アップデートの確認やアプリの最適化などが走って結構CPU使っている。これをなんとかするために、エミュレーターのデフォルトを「バッテリー使用中」に変更したよ。

もう1つの改善は、マイクでずっと待ち受けするのをやめたよ(ホームとかで「お〜け〜ぐ〜ぐる」と呼ぶと下からにょきっとでてくるあの機能)。マイクが必要な場合はその都度ONにして使ってね。

Feature polish

Instant runの代わりにApply changesを作ったよ。機能的にはInstant runとほぼ同じだけど、Android O以降に限定して変なhack使うのをやめてるよ。Instant runの時はホットスワッピングのためのコードを差し込んでたりしたので、逆にクリーンビルドが遅くなってた。Apply changesはOS側のホットスワップの機能を使ってるので、より安定してるよ。

次はGradle。今までGradleはjarとか、ライブラリで使ってるのをどんどんホームディレクトリにキャッシュとして放り込んでたけど、それを自動で削除してくれる機能が入ったよ。でもAndroid Studio的にはキャッシュから読んだほうが早いので、この機能には困った。けどなんとか直した(It's been fixed)。

飛行機の中とか、ネットワークの使用がめっちゃ制限されている環境で開発する人のために、maven.google.com のリポジトリをダウンロードしてオフラインでも使えるようにしたよ。zipを展開して、READMEに書かれているように設定してみてね。

Android Studioをアップデートして、プロジェクトを開いた時に「いろいろアップデートしてね」のメッセージがこれまでは出ていたが、今後は出なくなる。

データバインディングを使ってる人は、いろいろ改善したのでAndroid Studio 3.5を使ってみてね。

Bug backlog

数週間が400以上のバグを直したよ

Gradle plugin

今までは、Android StudioのバージョンをあげるとGradleプラグインのバージョンもあわせてあげる必要があった。今後はその必要はなくなる(あげたほうがいいが)。そのため、プラグインさえあわせていればAndroid Studioのstable版を使おうが、betaを使おうが、かまわない。

デモは動画みてね。他のセッションで紹介されたものがほとんど。

Chrome OS

Chrome 75以上でx86Chrome OSで、Android Studio 3.5が正式サポート。