Android Studioで ExecutionException: java.lang.OutOfMemoryError: GC overhead limit exceeded

環境

Android Studio 3.0.1
Build #AI-171.4443003, built on November 10, 2017
JRE: 1.8.0_152-release-915-b08 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Mac OS X 10.12.6

Java 8

現象

とあるプロジェクトにライブラリ追加したりコード書いたりしてビルドしたら以下のエラーが出てビルドできなくなった。

Information:Gradle tasks [clean, :app:assembleStaging]
Error:Uncaught translation error: java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: GC overhead limit exceeded
Error:Uncaught translation error: java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: GC overhead limit exceeded
Error:Uncaught translation error: java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: GC overhead limit exceeded
Error:Uncaught translation error: java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: GC overhead limit exceeded
Error:4 errors; aborting
Error:Execution failed for task ':app:transformClassesWithDexForStaging'.
> com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: com.android.ide.common.process.ProcessException: Error while executing java process with main class com.android.dx.command.Main with arguments {--dex --num-threads=4 --multi-dex --main-dex-list /Users/xxxxxx/dev/xxxxxxx/app/build/intermediates/multi-dex/staging/maindexlist.txt --output /Users/xxxxxxxxxxxxx/app/build/intermediates/transforms/dex/staging/folders/1000/1f/main /Users/xxxxx/dev/xxxxxxxx/app/build/intermediates/transforms/jarMerging/staging/jars/1/1f/combined.jar}
Information:BUILD FAILED in 2m 25s
Information:6 errors
Information:0 warnings
Information:See complete output in console

ビルドで OutOfMemoryError だと...?

そんな重いことしてないはず。

RxJava, RxAndroid, Retrofitライブラリを追加しただけなのに!

ググる

情報でた

stackoverflow.com

解決方法

app/build.gradleに以下を追加してメモリを抑える

android {

    dexOptions {
        javaMaxHeapSize "4g"
    }
}

ビルドしてみると、、、ビルド通った!

ビルド完了!!

めでたしめでたし

PM2でnodejsアプリを動かす

foreverは使ったことあるが、pm2の方が高機能という話を聞いたので使ってみる。

PM2のインストール

グローバルインストールする

npm i -g pm2

pm2コマンドが認識されていればインストールOK

PM2を使う

開始するだけなら

pm2 start jsファイル

--nameで名前つきでスクリプトを起動すると便利

というか、名前がないと操作が不便すぎるので必須だと思う

slack-botという名前で起動する

$ pm2 start main.js --name slack-bot

[PM2] Starting /home/pi/dev/slack-bot/main.js in fork_mode (1 instance)
[PM2] Done.
┌───────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────┬───────────┬──────┬──────────┐
│ App name  │ id │ mode │ pid  │ status │ restart │ uptime │ cpu │ mem       │ user │ watching │
├───────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────┼───────────┼──────┼──────────┤
│ slack-bot │ 0  │ fork │ 1432 │ online │ 0       │ 0s     │ 12% │ 20.0 MB   │ pi   │ disabled │
└───────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────┴───────────┴──────┴──────────┘

再起動

pm2 restart アプリ名

$ pm2 restart slack-bot
Use --update-env to update environment variables
[PM2] Applying action restartProcessId on app [slack-bot](ids: 0)
[PM2] [slack-bot](0) ✓
┌───────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────┬───────────┬──────┬──────────┐
│ App name  │ id │ mode │ pid  │ status │ restart │ uptime │ cpu │ mem       │ user │ watching │
├───────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────┼───────────┼──────┼──────────┤
│ slack-bot │ 0  │ fork │ 1480 │ online │ 1       │ 0s     │ 66% │ 15.1 MB   │ pi   │ disabled │
└───────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────┴───────────┴──────┴──────────┘
 Use `pm2 show <id|name>` to get more details about an app

いけたっぽい

停止する

pm2 stop アプリ名

$ pm2 stop slack-bot
[PM2] Applying action stopProcessId on app [slack-bot](ids: 0)
[PM2] [slack-bot](0) ✓
┌───────────┬────┬──────┬─────┬─────────┬─────────┬────────┬─────┬────────┬──────┬──────────┐
│ App name  │ id │ mode │ pid │ status  │ restart │ uptime │ cpu │ mem    │ user │ watching │
├───────────┼────┼──────┼─────┼─────────┼─────────┼────────┼─────┼────────┼──────┼──────────┤
│ slack-bot │ 0  │ fork │ 0   │ stopped │ 1       │ 0      │ 0%  │ 0 B    │ pi   │ disabled │
└───────────┴────┴──────┴─────┴─────────┴─────────┴────────┴─────┴────────┴──────┴──────────┘
 Use `pm2 show <id|name>` to get more details about an app

stoppedになってる

ログを見る

pm2 logs アプリ名

 $ pm2 logs slack-bot
[TAILING] Tailing last 15 lines for [slack-bot] process (change the value with --lines option)
/home/pi/.pm2/logs/slack-bot-error-0.log last 15 lines:
...

モニタリング

起動しているアプリのログやプロセスの確認ができる。

pm2 monit

見やすくて少しカッコいい

スクリーンショット 2018-04-27 18.41.53.png

設定ファイルを用いた起動指定

YamlJsonで起動オプションを設定ファイルに定義できる。

今回はYamlで作成してみる。

# ファイル名:pm2config.yml(ファイル名は自由です)

name: slack-bot                        # アプリ名
script: main.js                        # スクリプトファイルパス
watch: true                            # フォルダやサブフォルダ内のファイルが変更された場合、アプリは再読み込みされます
log-date-format: "YYYY-MM-DD HH:mm Z"  # ログに日付を追加

設定ファイルで使える項目は以下リンク http://pm2.keymetrics.io/docs/usage/application-declaration/#attributes-available

設定ファイルで起動する

pm2 start pm2.yml
pm2 stop pm2.yml
pm2 restart pm2.yml

参考にしたリンク

「アイデアは考えるな」を読んだ感想

書籍

アイデアは考えるな
面白法人カヤック代表 柳澤大輔
http://amzn.asia/eyJUrs7
  • 会社の本棚にあったので何気なく読んだら、すごく面白くて一気に読み切ってしまった。
  • PM、エンジニア、デザイナー問わず読める本。

イデアは質より量

  • すごいアイデアは誰にでも出せるわけではない、まずはすごくないアイデアをたくさん出そう。
  • すごいアイデアを1つ出すのではなく、すごくないアイデアを10出そう
  • すごくないアイデアを出していけばだんだんとポジティブになり、アイデア出しが面白くなってくる、そこが狙い

なんでも乗っかれ

  • なんでもとにかく目の前に現れたものに悩ますに乗っかろう、自分の判断はあてにならない、自分フィルターを外そう。
  • 乗っかったら面白がれるポイントを探そう(ポジティブに)、視点を変えよう、それが意外な発見につながり、斬新なアイデアに繋がる。
  • ポジティブでなければアイデアはたくさん出せない。
  • イデアとは既存の要素の組み合わせ以外何ものでもない
  • 乗っかるからこそヒントを得てヒットを生み出せる
  • イデアを出すと気には締め切りは絶対に必要、締め切りとは人間の力を目一杯引き出す装置。
  • 情報を集めたほうが格段にアイデアを出しやすい、情報を集めるなら本を読む、ネットで集める、人に会って聞くなど自分にあった方法を見つけろ。

発想法

  • 結果逆算法
    • イデアを出す前に、まず結果をイメージしてどういうアイデアだったらそうなるのかを逆算する、普通の人もぶっとんだアイデアを思いつく
  • マンダラチャート
    • 3*3のマスを使ってアイデアを出していく、アイデア出しの練習にかなり有効
  • イデアの公式
    • 既存のアイデア要素を分割して組み合わせ新しくパターンを作っていく

1人でアイデア出しの練習にはマンダラチャートが良い

ブレスト

  1. とにかくアイデアの量を出す
  2. とにかく相手を否定しない
  3. とにかく相手の意見に乗っかる

ブレストの人数は5〜7人くらいがちょうどいい、それ以上だと発言の量などによる効率が悪い

Android Studio 3.1.1でSyncエラー Data Binding annotation processor version needs to match the Android Gradle Plugin version. You can remove the kapt dependency com.android.databinding:compiler:3.1.0 and Android Gradle Plugin will inject the right version.

タイトルが長すぎるけど気にしない。

Android Studio 3.1.1にアプデしたらSyncエラーが出た。

Data Binding annotation processor version needs to match the Android Gradle Plugin version. You can remove the kapt dependency com.android.databinding:compiler:3.1.0 and Android Gradle Plugin will inject the right version.

日本語訳

データバインディングアノテーションプロセッサのバージョンは、Android Gradle Pluginのバージョンと一致する必要があります。 あなたはkapt依存関係com.android.databinding:compiler:3.1.0を削除することができ、Android Gradle Pluginは適切なバージョンを注入します。

修正方法

メッセージ内容を読むと分かる通り、
以下のapp/build.gradeにあるandroid.databinding:compilerのバージョンをAndroid Gradle PluginのGradleバージョンと合わせれば直る。

Android Gradle PluginのGradleバージョン

    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.1'

3.1.1だね

app/build.gradeにあるandroid.databinding:compiler

kapt 'com.android.databinding:compiler:3.1.0' <- 古い!!

3.1.0かー、ちょっと古いね

こいつを3.1.1にあげよう

kapt 'com.android.databinding:compiler:3.1.1'

これでSyncもビルドも通った。

でも今までkaptもandroid.databinding:compilerもなんとなく使ってきたので、こいつらは一体なんなのか軽く調べた。

kaptとは?

KotlinからJavaのコードを利用可能にするモノらしい。

qiita.com

android.databinding:compilerとは?

以下リンクを見ると、コンパイル時にLayout xmlからBindingのコードを吐き出すものらしい。

sys1yagi.hatenablog.com

つまり?

kapt 'com.android.databinding:compiler とは

DataBindingが定義されたLayout XMLコンパイル時に生成されるコードを、Kotlinで利用可能にするモノのようだ

Androidで処理時間を計測するならSystem#currentTimeMillisよりもSystemClock#uptimeMillis

タイトルまんまの内容。

メソッドの処理時間を計測したかった

Androidで処理時間を計測しようとSystem.currentTimeMillis()を使ってたら、SystemClock.uptimeMillis()のがいいよとアドバイスをもらった。

なぜか?

System.currentTimeMillis()の場合、端末時間をユーザーが変更できたりOSの時刻補正によって現在時間がかわる恐れがあるため

SystemClock.uptimeMillis()は端末ブートしてからの時間を記録してるので、時間に変更が入る心配がない

ただし、SystemClock.uptimeMillis()はディープスリープに入ると止まるため、長い計測を行う場合には注意が必要だ。

参考リンク

Yukiの枝折: 経過時間を求める方法 http://yuki312.blogspot.jp/2011/11/blog-post_30.html

Android KotlinでRealmを使う

環境

資料

Realm公式 Realm: Create reactive mobile apps in a fraction of the time

Realmの導入

プロジェクトのbuild.gradleにrealmを追加

buildscript {
    ext.kotlin_version = '1.2.21'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "io.realm:realm-gradle-plugin:5.0.0" //追加
    }
}

appのbuild.gradleにrealmを追加

ここはrealmを最後の行に書かないとエラーになるはず

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'realm-android' //追加

Realm初期設定

Realm.init

これはアプリ起動中に1回行うだけでいいので、カスタムアプリケーションクラスでやるのがオススメ

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
    }

AndroidManifestにカスタムアプリケーションを登録するのを忘れずに

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="ikemura.com.kotlinrealmmigration"
    >

    <application
+       android:name=".MyApplication" 
        android:allowBackup="true"

Realm使用

インスタンス

いよいよ画面でrealmを使えるようになった

    lateinit var realm: Realm

    private fun initRealm() {
        val realmConfiguration = RealmConfiguration.Builder()
                .deleteRealmIfMigrationNeeded()
                .schemaVersion(0)
                .build()
        realm = Realm.getInstance(realmConfiguration)
    }

Insert

Transaction化しないとIllegalStateExceptionが発生する

        realm.executeTransaction {
            realm.insert(Book(1,"first"))
        }

Read

titleが「first」と一致する値をbookに取得する

        var book = realm.where(Book::class.java)
                .equalTo("title","first")
                .findAll()
        Log.d(TAG,book.toString())

equalTo:カラム名, 値 の比較を行える findAll:クエリ条件を満たすすべてのオブジェクトを同期検索する

Delete

削除もTransaction化しないとIllegalStateExceptionが発生する

        //削除対象を取得
        var book = realm.where(Book::class.java)
                .equalTo("title","first")
                .findAll()
        //削除
        realm.executeTransaction {
            book.deleteAllFromRealm()
        }

nowで作成したAPIをKotlinとRetrofitとRxJavaとJacksonでGET/POSTする

いつもAndroidでRetrofit+RxJavaを使ってAPI通信する処理を書いてるが、今回はKotlinで挑戦してみる。
ついでに、使ったことのないnowもJacksonも触ってみた。

作成するもの

  • nowを使ったnodejs製のAPIモック(express)
  • Androidアプリ(Kotlin/Retrofit/RxJava/Jackson)

環境

API

準備

APIモックをexpressを使ったnodejsで作成し、nowにデプロイしていく。
nowについてはこの記事を参考にした。
以下、作業ログ

// APIモックのプロジェクト準備
$ mkdir try-now && cd try-now
// package.json作成
$ npm init -y
// expressを追加
$ npm i -S express
// apiの処理を書くjsを作成
$ touch index.js

// nowのインストール
$ npm i -G now
// nowのログイン(メールアドレスを入力すると確認メールが届くのでverifyする)
$ now login

実装

index.jsに以下を貼り付ける。
レスポンス変数は適当に作った。

var express = require('express')
var app = express()

app.get('/', function (req, res) {
  const json = {
    status: 1,
    card_status: 10,
    card_type: 'normal',
    card_id: "ca1111",
  }
  res.json(json)
})

app.post('/', function (req, res) {
  const json = {
    status: 2,
    card_status: 20,
    result_code: 1
  }
  res.json(json)
})

app.listen(3000)

ポートは3000を指定してるけど、なくても良い。

これでAPIモックはできあがった。

$ node index.js

としてローカルでも使えるが、今回はnowにデプロイする

デプロイ

nowでデプロイ、超簡単

$ now

https://try-now-kplvmhijbn.now.shというアドレスが表示されたので、これをAPIモックとして利用できる。

このアドレスに対してgetやpostをしていく。

Androidアプリ

使用するライブラリ

app/build.gradleに以下のライブラリ達を追加する

    // Retrofit2
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:converter-jackson:2.3.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

    // RxJava2
    implementation "io.reactivex.rxjava2:rxjava:2.1.9"

    // RxAndroid
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'

    // Jackson
    implementation 'com.fasterxml.jackson.core:jackson-core:2.9.4'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.4'
    implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.4'
    implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.4"

ソース:https://github.com/banbara23/try-now

実装

Retrofitのインタフェースを作成。

戻り値はRxjavaのObservableにする。

interface ApiService {
    @POST("/")
    fun post(): Observable<PostResponse>

    @GET("/")
    fun get(): Observable<GetResponse>
}

APIクライアントを作成していく。

今回、baseUrlにはnowで作成したurlを設定する。

class ApiClient {
    var apiService: ApiService

    init {
        val retrofit: Retrofit = Retrofit.Builder()
                .baseUrl("https://try-now-kplvmhijbn.now.sh")
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(JacksonConverterFactory.create(ObjectMapper()))
                .build()
        apiService = retrofit.create(ApiService::class.java)
    }

    /**
     * GETリクエスト
     */
    fun get(): Observable<GetResponse> =
            apiService.get()

    /**
     * POSTリクエスト
     */
    fun post(): Observable<PostResponse> =
            apiService.post()
}

APIクライアント呼び出しは以下

ラムダ初めて触った、すごい

  // get
        apiClient.post()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(
                        { res -> Log.d(TAG, res.toString()) },
                        { error -> Log.e(TAG, "{$error.message}") },
                        { Log.d(TAG, "post completed") }
                )

  // post
        apiClient.post()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(
                        { res -> Log.d(TAG, res.toString()) },
                        { error -> Log.e(TAG, "{$error.message}") },
                        { Log.d(TAG, "post completed") }
                )

モデルについては、 Response(共通レスポンス)を継承した各モデルが存在するイメージで作成した。

/**
 * 共通レスポンス
 */
open class Response(

        @field:JsonProperty("status") val status: Int = 0,
        @field:JsonProperty("card_status") val cardStatus: Int = 0
)
/**
 * GETレスポンスのモデル
 */
data class GetResponse(
        @field:JsonProperty("card_type") val cardType: String = "",
        @field:JsonProperty("card_id") val cardId: String = ""
) : Response() {
    override fun toString(): String =
            "GetResponse(status='$status' cardStatus='$cardStatus' cardType='$cardType', cardId='$cardId')"
}
/**
 * POSTレスポンスのモデル
 */
data class PostResponse(
        @field:JsonProperty("card_type") val cardType: String = "",
        @field:JsonProperty("card_id") val cardId: String = "",
        @field:JsonProperty("result_code") val resultCode: Int = 0
) : Response() {
    override fun toString(): String =
            "PostResponse(status='$status' cardStatus='$cardStatus' resultCode='$resultCode')"
}

この継承してるあたり、もうちょいうまくできないだろうか。

ビルドして実際に動作させると、Logcatに以下のログが表示されていればGET/POSTが成功している

getのログ
D/AppCompatActivity: GetResponse(status='1' cardStatus='10' cardType='normal', cardId='ca1111')
D/AppCompatActivity: get completed

postのログ
D/AppCompatActivity: PostResponse(status='2' cardStatus='20' resultCode='1')
D/AppCompatActivity: post completed

ソース:https://github.com/banbara23/Kotlin-Jackson-Sample

ハマったところ

Jacksonが@JsonPropertyを認識してくれない

APIレスポンスをJacksonでシリアライズする際、共通レスポンスモデルであるResponse.ktがKotlin用のアノテーションでないとエラーになる。

Jacksonが継承元の変数を@JsonPropertyだと認識してくれなかった。
継承先は@JsonPropertyでも大丈夫なのに…

NG : @JsonProperty
OK : @field:JsonProperty

実際のコード

/**
 * 共通レスポンス
 */
open class Response(
        // NG
        //@JsonProperty("status") val status: Int = 0,
        //@JsonProperty("card_status") val cardStatus: Int = 0

        // OK
        @field:JsonProperty("status") val status: Int = 0,
        @field:JsonProperty("card_status") val cardStatus: Int = 0
)

同様の問題が起きてないか調べたところ、以下のリンクに辿り着き、kotlinアノテーションを使うことで解決できた。 - https://github.com/FasterXML/jackson-module-kotlin/issues/56#issuecomment-321932309 - https://github.com/FasterXML/jackson-module-kotlin/issues/98#issuecomment-352016501

感想

nowが楽すぎてすごい

kotlinだとJavaよりコード量少なくて楽で良い