AWS Lambda で NoClassDefFoundError
やろうとしたこと
AWS Lambda に jar をアップロードして実行したかった。
問題
jarを作成してAWSのコンソールにアップロードして実行すると、ローカルで作成した自作のモジュールのクラスが NoClassDefFoundError
。
環境
IntelliJ IDEA 2018.2.6 (Community Edition)
macOS 10.14.2
gradle-4.8-all
kotlin 1.2.71
やったこと
AWS ドキュメント - .zip デプロイパッケージの作成 (Java) や
を参考に、以下のようなタスクを定義して ./gradlew :app:build
。
task buildZip(type: Zip) { from compileJava from compileKotlin from processResources into('lib') { from configurations.runtime from configurations.compileClasspath } } build.dependsOn buildZip
これでうまくいくはずだけど、AWSコンソールから実行すると NoClassDefFoundError
と言われる。
jarをunzipしてlibを覗いてみると…
ローカルの自作モジュールがjarになってない!
原因
ローカルの自作モジュールに java-library
プラグインを入れていたこと。
plugins { ... id 'java-library' // ←これ }
とりあえずこれを消したらちゃんとjarを作ってくれてうまく実行できました。
AWS Lambda実践ガイド (impress top gear)
- 作者: 大澤文孝
- 出版社/メーカー: インプレス
- 発売日: 2017/10/16
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
ndk-buildにパラメータを渡す
コマンドラインから外部引数を渡したい場合は、 ndk-build -e
で指定することでパラメータを渡すことができる。
$ ndk-build -e HOGE=HOGE MAGE=MAGE
これで Android.mk から $(HOGE)
とか $(MAGE)
とかで値を利用できる。
- 作者: 出村成和
- 出版社/メーカー: 秀和システム
- 発売日: 2012/12/19
- メディア: 単行本
- クリック: 1回
- この商品を含むブログ (5件) を見る
dependenciesの@aarの正体
build.gradleでしばしば見かけるこの記法
dependencies { implementation "jp.co.mst.android:awsome:1.0.0@aar" }
この @aar
という記法は アーティファクトオンリー記法 といい、以下に説明があります。
アーティファクトオンリー記法は、指定した拡張子のファイルのみをダウンロードする、というモジュール依存関係を作成します。ディスクリプタがあっても無視されます。
ライブラリの依存関係を無視する
例えば、 RxAndroid のbuild.gradleを覗いてみますと
dependencies { api 'io.reactivex.rxjava2:rxjava:2.2.0' ... }
とあるように、RxAndroidはRxJavaに依存しています。
なので、アプリからRxAndroidをgradleで取り込むと勝手にRxJavaがついて来るのですが、RxAndroidを取り込むときに以下のように @aar
をつけると、RxAndroidのaarだけ持ってきて、RxJavaは取り込まれなくなります。
dependencies { implementation 'io.reactivex.rxjava2:rxandroid:2.1.0@aar' }
何が嬉しいの?
あんまり積極的にお目にかかる機会もないとは思いますが、この記法のミソは
ディスクリプタがあっても無視されます。
というところでしょうか。
ディスクリプタというのは、mavenリポジトリ上で依存関係を管理するファイルで、RxAndroidの場合は rxandroid-2.1.0.pom がそれに当たります。
たとえば、何らかのとてつもない事情があってmavenリポジトリ上のディスクリプタが壊れてしまい、ライブラリの依存関係の解決ができなくなってしまったが、自分には治す権限がないけどaarだけ取り急ぎ使いたい!といったものすごく込み入った事情がある場合なんかには重宝されるかもしれません。
あるいは、ライセンス上取り込んじゃまずいライブラリの依存関係だけが何故か記述されてしまっているような罠に掛かりそうになったときなどにも使えるかもしれません。
実際には、多分 aarをローカルから読み込む - Learn to Live and Live to Learn などの記事にあるように、ローカルのパスからディスクリプタを使わないでaarを指定して取り込むときに使うことが大多数なのかなという気はしますが、社内リポジトリやMaven Central Repository などで公開されているライブラリを @aar
付きで取り込んでるコードをどこかで見かけることがありましたら、そこには並々ならぬ事情があるのかもしれません。
アプリの依存関係とライブラリの依存関係がかぶってるときは@aarつけて取り込んだほうがいいの?
依存関係がかぶっていてライブラリのバージョンの競合が発生するような場合は、バージョンの競合を解決するための戦略が別途ありますので、@aar
をつけることが最適な解決策になるケースは限られているでしょう(デフォルトではより新しいバージョンが採用されるようになってます)。
ちなみに、今回例に出したRxAndroidは、RxAndroidのバージョンによらず常に最新のRxJavaをアプリ側で取り込むことを推奨しています。
Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築
- 作者: 綿引琢磨,須江信洋,林政利,今井勝信
- 出版社/メーカー: 翔泳社
- 発売日: 2014/11/05
- メディア: 大型本
- この商品を含むブログ (6件) を見る
再生時間をLongから mm:ss 形式に変換する
やりたいこと
Android の MediaPlayer などで Long で取れる duration を、2:50 のように 「分:秒」の形式の文字列にする。
joda-timeを使えば簡単
LocalTime を使うのもよいが、API Level 26以上を要求されるので、 joda-time-android を使う。
// build.gradle dependencies { implementation 'net.danlew:android.joda:x.x.x.x' }
val format = DateTimeFormat.forPattern("mm:ss") val str = format.print(duration)
雨が降りそうになったらGoogle Homeに教えてもらえるようにした
やったこと
雨が降りそうになったらGoogle Homeが「雨が降りそうです」と教えてくれるようにした。
環境
ハードウェア
- Raspberry Pi 3 Model B+ element14
- Google Home mini
ミドルウェア
- raspbian 9.4
- node.js 9.11.1
- google-home-notifier 1.2.0
WebAPI
環境づくり
Raspberry Pi のセットアップ
以下の記事を参考にOSのインストールからsshでの接続まで。
MacユーザがRaspberry Pi2をセットアップする-1 | scribble warehouse
node.jsはこちらの記事を参考に9.11.1を入れた。
第三回 Raspberry Pi 3に最新のNode.jsをインストールする
$ apt-get update $ apt-get install -y nodejs npm $ npm cache clean $ npm install n -g $ n 9.11.1
Google Homeを能動的に喋らせる
Google Homeを喋らせるにはgoogle-home-notifierを使うのが楽とのことで、以下の記事を参考にセットアップ。
Google Home開発入門 / google-home-notifier解説
$ npm init $ npm install google-home-notifier
dns_sd.h がありません的なエラーが出たので以下を実行。
$ sudo apt-get install libavahi-compat-libdnssd-dev
天気の取得
天気取得APIはいくつかあるが、以下の要件を満たせていて手軽に叩けたので今回は YOLP(地図):気象情報API - Yahoo!デベロッパーネットワーク を使う。
API自体シンプルでcurlサンプルもあったのが嬉しい。
- 緯度・経度で指定できる
- 現在の天気が取れる
- 少しあとの天気予報が取れる
nodeの環境変数
APIの呼び出しにYahoo!のAppIDが必要だったので、以下の記事を参考に dotenv をインストール。
Nodeプロジェクトで環境依存の設定の管理方法
$ npm install dotenv --save
.envは忘れず.gitignoreへ…。
せっかくなのでgitで晒すのは憚られる以下の情報も.envに書いた。
コード
Github
GitHub - ergooo/GoogleHomeWeatherNotifier: 雨が振りそうになったらGoogleHomeに喋ってもらう
javascript初心者なのでいろいろ有り得ないところがあるかもしれませんが…。
GoogleHomeを喋らせる
// GoogleHome.js module.exports = class GoogleHome { constructor(ipAddress) { this._ipAddress = ipAddress } tell(message) { const googlehome = require('google-home-notifier') const language = 'ja'; googlehome.device('Google-Home', language); googlehome.ip(this._ipAddress); googlehome.notify(message, function(res) { console.log(res); }); } }
ほぼサンプルそのまま。IPアドレスとメッセージを受け取って喋らせる。
天気取得
// WeatherApi.js module.exports = class WeatherApi { constructor(appId) { this._appId = appId } /** * @param {WeatherApi~RequestCallback} callback */ request(latitude, longitude, callback) { const request = require('request'); const url = this._buildUrl(latitude, longitude, this._appId) console.log('url: ' + url) const options = { url: url, method: 'GET' } request(options, function (error, response, body) { if (!error && response.statusCode == 200) { const json = JSON.parse(body); callback(error, json.Feature[0].Property.WeatherList.Weather) } else { callback(error, null) } }) } _buildUrl(latitude, longitude, appId) { return 'https://map.yahooapis.jp/weather/V1/place?' + 'output=json' + '&coordinates=' + longitude + ',' + latitude + '&appid=' + appId } }
javascriptでHTTPするのは require('http')
する方法と requeire('request')
する方法があるようだったが、後者のほうがナウくて楽なようだったので後者で。
JSONのパーズは何も考えなくてもやってくれるので非常に手早く書ける。
天気をチェックして喋らせる
// WeatherCheckerMain.js module.exports = class WeatherCheckerMain { static check() { require('dotenv').config() const GoogleHome = require('./GoogleHome') const googleHome = new GoogleHome(process.env.NODE_GOOGLE_HOME_IP_ADDRESS) const WeatherApi = require('./WeatherApi') const weatherApi = new WeatherApi(process.env.NODE_YAHOO_APP_ID) weatherApi.request(process.env.NODE_LATITUDE, process.env.NODE_LONGITUDE, function(error, weathers) { if(!error) { console.log('request ok.') const current = weathers[0] const next = weathers[1] if(current.Rainfall == 0 && next.Rainfall > 0) { googleHome.tell("雨が振りそうです。") } } else { console.log(error) } }) } }
定期実行させやすそうだったのでメインの処理もクラスにしてみた。 Yahoo!のAPIでは、現在とその後10分おきの降水予報が取れたので、現在降水0で10分後降水0以上なら通知するようにした。
定期実行
node.jsのいろいろなモジュール14 – node-cronでcron的にプログラムを実行する | Developers.IO こちらの記事を参考にほぼコピペで。タイムゾーン部分だけエラーになったので直した。
$ npm install cron time
// cron.js const cronJob = require('cron').CronJob; const cronTime = "00 00,10,20,30,40,50 00,01,09-23 * * *"; const job = new cronJob({ cronTime: cronTime , onTick: function() { console.log('onTick!'); let Main = require('./WeatherCheckerMain') Main.check() } , onComplete: function() { console.log('onComplete!') } , start: true , timeZone: "Asia/Tokyo" }) job.start();
実行
pm2 でcron.jsを実行するようにした。
$ npm install pm2 -g $ pm2 start cron.js
感想
なんだかんだ1ヶ月ぐらいかかるかと思ったけどほぼコピペでトラブルなく行けたので1日でできてしまった。 nodeは手軽でサクッとできるというとても良い印象が得られた。 まだ雨が降ってないので本当にちゃんと動くかわからないけど、とりあえず満足。
Raspberry Pi3 コンプリートスターターキット (Standard 16G)
- 出版社/メーカー: TechShare
- メディア: エレクトロニクス
- この商品を含むブログを見る
java libraryのテストからresourcesが見えない
環境
Android Studio 2.3.1
やりたいこと
Android StudioでJava Libraryのモジュールを作成し、テストコードからリソースファイルを読み込みたい。
うまくいかない
リソースの読み込みはgetResouce()すれば取れるはずなのだが、main/resources/にファイルを配置してもどういうわけか結果はnullになる。
build/resourcesに対象のリソースファイルが生成されてるので、ビルドはうまくいっている。
URL url = getClass().getClassLoader().getResource("trades.json"); System.out.println(url);
結果
null
解決
build.gradleに以下を追加
sourceSets { test { output.resourcesDir = output.classesDir } }