Dive into Ofuton

お布団に飛び込もう

TabLayoutでナウいUI

いつの間にか公式でいい感じのタブUIが楽に作れるようになってた。

material.io

Lollipop出始めの頃は全然公式でライブラリとかがなくて、PagerSlidingTabStripとか使ってたけど、今は公式で結構いい感じに提供されてるのね。

material.io

ドキュメントとかの調査は大事だなあと感じる。

いい感じにActionbarをスクロールに追従させるやつ https://guides.codepath.com/android/handling-scrolls-with-coordinatorlayout

API level 21以上(Lollipop以降)ならListViewでもいい感じに出来る。

stackoverflow.com

listView.isNestedScrollingEnabled = true

SwipeRefreshLayoutを使うと、ListViewが空っぽのときスクロールできない。EmptyViewをセットしておくと動いた。

AndroidStudio+Kotlinのアップデートで詰まった話

環境


2ヶ月ぶりにAndroidStudioを起動したら、Kotlinのバージョンが1.1.2-3から、1.1.3-2に上がっていたので上げてみたところビルドが通らなくなった。 どうやら自分以外にも遭遇している人がいたみたい。 stackoverflow.com

で、回答にこういうことが書いてあった。 gist.github.com

don’t use kapt { generateStubs true } } at all, it will not work with kapt3

書いてあったのでこれ削除した。

ついでにbuildtoolsやらsupportlibraryやらのバージョンも上げてみたら、supportlibraryがないよと怒られた。

developer.android.com

いつの間にやらSDK toolsからインストールするんじゃなくてmavenから落っことしてくるスタイルになったみたい。

allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    }
}

多分これで動いた。(他にもガチャガチャしたのでそれはちゃんと覚えてない)

diff --git build.gradle build.gradle
index dfc6deb..be0b64b 100644
--- build.gradle
+++ build.gradle
@@ -1,12 +1,12 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 
 buildscript {
-    ext.kotlin_version = '1.1.2-3'
+    ext.kotlin_version = '1.1.3-2'
     repositories {
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.3.1'
+        classpath 'com.android.tools.build:gradle:2.3.3'
         classpath 'com.google.gms:google-services:3.0.0'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 
@@ -18,6 +18,9 @@ buildscript {
 allprojects {
     repositories {
         jcenter()
+        maven {
+            url "https://maven.google.com"
+        }
     }
 }
 
diff --git mobile/build.gradle mobile/build.gradle
index 1397a1a..722bab0 100644
--- mobile/build.gradle
+++ mobile/build.gradle
@@ -2,16 +2,17 @@ apply plugin: 'com.android.application'
 apply plugin: 'kotlin-android'
 
 android {
-    compileSdkVersion 25
-    buildToolsVersion "25.0.3"
+    compileSdkVersion 26
+    buildToolsVersion "26.0.0"
     defaultConfig {
         applicationId "com.crakac.ofutodon"
         minSdkVersion 21
-        targetSdkVersion 25
+        targetSdkVersion 26
         versionCode 1
         versionName "1.0"
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
 
@@ -47,32 +48,27 @@ dependencies {
     androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
         exclude group: 'com.android.support', module: 'support-annotations'
     })
-    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
-    compile 'com.android.support:appcompat-v7:25.3.1'
-    compile 'com.android.support:support-v4:25.3.1'
-    compile 'com.android.support:design:25.3.1'
-    compile 'com.android.support:support-annotations:25.3.1'
-    compile 'com.android.support.constraint:constraint-layout:1.0.2'
+    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+    compile 'com.android.support:appcompat-v7:26.0.0-beta2'
+    compile 'com.android.support:support-v4:26.0.0-beta2'
+    compile 'com.android.support:design:26.0.0-beta2'
+    compile 'com.android.support:support-annotations:26.0.0-beta2'
-    compile 'com.jakewharton:butterknife:8.5.1'
+    compile 'com.jakewharton:butterknife:8.7.0'
-    annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
-    kapt 'com.jakewharton:butterknife-compiler:8.5.1'
+    annotationProcessor 'com.jakewharton:butterknife-compiler:8.7.0'
+    kapt 'com.jakewharton:butterknife-compiler:8.7.0'
 }
 
-kapt {
-    generateStubs = true
-}
-
 repositories {
     mavenCentral()
 }

SNSに投稿した時間を可視化したい

Why

  • 最近はやりのマストドンにかなり時間を取られているので、なんとかして自分の時間を取り戻したい。
  • 睡眠時間を間接的に記録したい
  • なんか作りたい
  • GitHubの草みたいにしたい

How

進捗

概ね出来てきた。 f:id:crakac:20170628230146p:plain

GitHubっぽい表は、cal-heatmapを使用

ActiveRecordで、1分毎の投稿を集計して返す

Status.select("count(*) as toots, date_trunc('min', tooted_at) as t").where(user_id: id).where(tooted_at: start..stop).group('t')

↑の結果をJSONにして、ブラウザなりに返す

render json: stats.map{|s| [s.t.to_i, s.toots]}.to_h

困りどころ

cal-heatmapが表示用のJSONを取得するとき、こういう感じのパラメータが飛んでくるが、

start=2017-06-27T13:00:00.000Z&stop=2017-06-28T12:59:00.000Z

上述のselect文だと最後のマス目のcount()が合わない。2017-06-28T12:59:00.000Zからの一分間で投稿されていた場合はカウントできなくなるから。

毎回countするよりは、集計結果を別テーブルにして、ユーザーがSNSへの投稿を取得するのと同じタイミングで更新していくのがいいんだろうなという気持ちではある。

今読んでるもの

PostgreSQLのEXPLAINの読み方https://www.postgresql.jp/document/8.3/html/using-explain.html

さくらのVPS(Ubuntu16.04LTS)を借りてからSSHのポートを変更してローカルから接続するまで

TL;DR:

  • sshdがListenするポートは複数指定できるので、まずは追加してチェックしてから22番を削除するとよい
  • ufwは使わずiptablesだけでやる
    • というかufwでポートを開けようとしても開いてなさそう
  • iptablesの設定はサーバー再起動すると消えるのでなんとかする
    • iptables-persistentをインストールする(要検証)
    • /etc/iptables/iptables.rulesを直接いじる(本当にこれで大丈夫なのか?)

iptablesについてはiptables-persistentの方がいいと思います。インストールするだけみたいなので。

参照したところ

iptablesの設定方法 – さくらのサポート情報
iptablesによるパケットフィルタリング

コマンド群

# 作業用ユーザ作成
adduser USER_NAME

# sudo出来るようにする
gpasswd -a USER_NAME sudo

# ssh ポート変更、rootログイン不可、パスワードログイン不可などにする
vi /etc/ssh/sshd_config
# 設定反映
service sshd restart #bash補完だとsshdじゃなくてsshしか出なかったのはなんでだろう。

# ufwは止める
ufw disable

# iptables (さくらのヘルプより。あくまで参考例と但し書があるが、とりあえず使う)
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
iptables -A INPUT -p tcp ! --syn -m state --state NEW -j DROP
iptables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport $YOUR_SSH_PORT -j ACCEPT
iptables -I INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -P INPUT DROP
iptables -P OUTPUT ACCEPT

# iptablesの設定確認(今回の環境だと再起動で消し飛ぶのでなんとかする。)
iptables-save 

# 以下のどちらか
apt-get install iptables-persistent(未検証)
vi /etc/iptables/iptables.rules #iptables-saveで出てきた内容 or 上記iptablesコマンド群を書く

# 作業用ユーザssh公開鍵をおく
su USER_NAME #sshでログインしたいユーザに切り替える
cd
mkdir .ssh
echo "PUBLIC_KEY" > .ssh/authorized_keys
chmod 600 .ssh/authorized_keys
chmod 700 .ssh
exit

# rebooooooooooooooot!!!!!
reboot

これでたぶんいけるとおもいます。

以下おまけ

こういう記事は前にも書いてて 、そんなに変わってないやろバリバリーと同じように設定してたらハマったと言う話。 4年経ってると変わるんだね…。

環境

ローカル MacBook Pro (Retina, 13-inch, Mid 2014)

さくらのVPS
ゾーン 石狩第1ゾーン
メモリ 512 MB
ストレージ SSD 20 GB
CPU 1コア
お値段 685円/月

メモリ1GBあたりの値段は1GB, 2GBの方が安いけど今は性能は必要ないし困ったときに上げられるのがVPSの利点なのであまり考えないことにしよう。浮いたお金(281円)

なぜVPS

  • マストドンが流行っているから
  • そろそろサーバーがほしいから
  • rootが取れるから
  • 最悪管理画面からリセットできるから
  • やろうと思えばなんでも出来るから
  • 勉強にちょうどいいから
  • 色々作って公開したいから

色々理由はあるけど、時間的、金銭的、精神的余裕を最近取り戻してきたというのと、SoftSkillsという本を読んだことがきっかけ(日本語版Amazonリンク)。

VPSを借りる

さくらのVPSは先に書いたとおり、4年前にも借りたことがあった。 当時全く不満がなかったと記憶しているので、今回もさくらもVPSにした。 また、さくらの人がMastodon会議に出ていることと、世界最大のMastodonインスタンスmstdn.jpの足回りを支えているということを聞いてさくらインターネットに好意を持ったから。人間は感情的な生き物だからしかたないね。

OSインストー

OSはCentOS6/7、Ubuntu14.04LTS, 16.04LTS、その他いくつか選べるようになっていた。どちらかでなければいけない理由もないのでちょっとだけ馴染みがあるUbuntuにした。 ボタンポチポチするだけでOSのインストールが終わるVPS素晴らしい。

WebコンソールからSSHの設定をする

さくらのVPS(Ubuntu16.0.4LTS)の管理ユーザの名前が、rootではなくubuntuであると気付くまでにムダに時間がかかってしまった。 思い込み、視野狭窄、こういうものがバグを取りづらくしているんだなと身をもって学んだ。

疲れた

こういう記事見つけた。なんでこないだは見つからなかったの…もうだめ眠いよパトラッシュ… iptables の設定を保持する(iptables-save と iptables-restore)

下書き供養 楽天ハッカソンに言ってきた話(2014.11.01/02)

久しぶりにブログ書くか〜〜〜という気持ちがSoftSkillsを読んで湧いてきたので、書こうとしたらいつだかの下書きが出てきた。

楽天ハッカソンではMatzさんの話があって、最近Mastodon関係でも話を聞くことがあった。
なんかタイミングもいいなと思ったので、だいたいそのままで公開。

以下本文

11/1, 2日にあった楽天ハッカソンに行ってきた。

ちょっと振り返る

準備

当日になって0からスタートはやべえよなってことで、本検索APIAndroidから使えるかどうか試すところまでやった。

初日

9:30〜21:00というスケジュールをみて、10分前くらいに着けばいいかと思って楽天カフェを目指してみたら、9:30開場10:00開始だったようで、少し暇を持て余すことになった。
前日アルバイト先でリリースしたアプリに不具合が見つかったようで対応してくれというメッセージが飛んできており道中原因調査していたという話はおいておこう。

開場に到着して、名札に名前を書いたんだけど、ちょうど前にいた集団がみんなローマ字で書いていたので私もそれに倣ってローマ字で書いた。さすがグローバル企業楽天主催のハッカソンだと思っていたら、実はその集団は留学生だっただけで、別に英語で書く意味はなかった。

プロとアマの話

最初にまつもとゆきひろさんのお話があった。
スポーツの世界ではほぼ間違いなくプロが勝つが、プログラムの世界は少し違っているということだった。
多くのお金と人と時間をかけたプロダクトが必ずしも良くなるわけではなく、少人数でささっと作ったものが広まるということがよくあるということ。
人が増えるほど、コミュニケーションコストがかかり、柔軟性がなくなり、開発が進まなくなる。
周りからの期待が大きいほど、冒険ができなくなる。
この辺りの話はイノベーションのジレンマという言葉に集約されるのだろうか?

いま世界中で使われているものは、世に出た当初は「おもちゃ」扱いされていたということ。
「おもちゃ」と言われたら勝ち。

批判してくれる人に感謝しつつ、開発し続けよう。

チームビルディング

当日はチームビルディングの時間があった。
まず、どんなアイデアがあるのか、どういう人と組みたいのかというのを一人ずつ発表していき、その後の時間で組みたい人と話し合いながらチームを組むというもの。チームが決まり次第一緒に昼食を食べながら、アイデアを更に煮詰めていくようだ。

一人で開発する気満々だったんだけど、周りがどんどんチームを組んでご飯に行くのをみて若干不安を覚える。

気づいたら、ハッカソンに誘ってくれた友人と、koboで何かしたいと言っていた人と、楽天で公開されている全API叩くと言っていた人と、私のアイデアで優勝ですって人の5人だけになっていた。

進行係の人に、とりあえず飯に行ってこい会場を追い出され、近くのパスタ屋に入ることにした。
気づいたら全APIを叩くと言っていた人が消えていた。他のチームに吸収されたようだ。

結局、優勝アイデアを持ってる人も別のチームになったため、残り物の3人でチームを組むことになった。
チームOTS、結成の瞬間である。

何をするか

koboを使って、簡単に書籍のリコメンドができたらいいなと思っていた。
私は、最近話題になっていた、顔写真をから年齢、性別、感情などがわかるReKognizeというサービスを使って、本をおすすめ出来ないかなということを考えていた。
お昼一緒に食べてる時に、座席の空席状況kobo使って分かるといいよねっていうアイデアが出てきたりもした。

今回チームの3人で得意なものが、私→Android、ほか2名→サーバー側だったのでうまいこと分担する必要があった。
座席状況は店内のkobo以外からも見れる必要があるのでサーバーが必須だとして、顔の写真を撮って本をおすすめするところは頑張ればAndroidですべて完結するのだ。それをすると、そう、俺が死ぬ。

なので、顔写真を撮ってReKognizeに投げるところはAndroidでやり、返ってきた情報を元にクエリを作って本をおすすめする部分はサーバー側でやることになった。

分担をまとめると、

私:Android(顔写真)
友人;サーバー(書籍リコメンド)
メンバー:サーバー(空席状況)

となって、作業量的にもちょうどいい感じっぽくなった。

開発タイム 1日目

開発中はひたすらもくもくと開発していた。
カメラの部分については、ちょうどバイト先でカメラアプリを作っているのもあり、ハマりどころにはことごとくハマっていたおかげでかなりスムーズに実装できた。
21時までに、顔写真取得して、ReKognizeに投げて、結果のJSONを書籍リコメンド用サーバーに投げるところまでいって、会場をあとにした。
おやつが最高だった。

開発タイム @ごみばこアジト

友人のおうちに泊まり込みで更に開発。
残念ながらkoboを持ち出せなかったので、自分のAndroidスマホデバッグしつつ開発していた。
koboとは内部的に持っている画像の向きが違っているせいでReKognitionでなかなか顔を検出できなかったり、
koboだと動かなかったAndroidのFaceDetectionListenerがちゃんと動いたりしてわけがわからなかった。
謎のテンションだったので、基本的に思考が口からダダ漏れだった気がする。

んで、顔認識したらその位置に矩形を表示しようとしてたらティンときた。

「顔認識できれば目の位置に楽天カード合成すりゃ楽天カードマンいけるんじゃね?」

というわけで、矩形表示ができるようになったら、楽天カードマンの実装を始めた。

「あー、動かん」
「なにこれ、動かん」
「あー」
「あーーーー」
「動いたアアアアアアァアアァッ」
「なんかずれてる」
「ヨッシャ」
楽天カードマーーーンwwww」

僕の方はだいたいこういう感じ。
友人の方は、感情やら年齢やらを元にうまいことクエリを作っていい感じの検索結果になるよう頑張ってました。

手巻きタバコとお酒おいしかったです。ありがとうございました。

2日目

2日目は、発表があるので15:30頃に開発終了だったため、あまりコーディングに割ける時間がなかった。
発表に向けたスライドをちょいちょい作りつつ、サーバー側との通信部分を実装した。
あと、本日のおすすめコーヒーという機能を友人が作ったので、それをクライアント側で表示できるようにした。

最後の方で、座席のところに顔写真載せるようにしたら面白いんじゃないかというアイデアが出て、ぎりぎりまで開発していた。
座席表の写真を楽天カードマンにしてやりたかったが、残念ながらできなかった。マジで残念。

ちなみに2日目もおやつが最高だった。

発表

いよいよ発表。
Android端末上のアプリをプロジェクターに映すので、設備の都合上一番最初の発表になった。
結果としてこれは結構ラッキーだったのではないかなって思う。

持ち時間が5分と短いこともあり、スライドは何故作ったのかというモチベーションについて、各画面のスクショと簡単な説明、アプリの構成というシンプルなものにした。

デモの部分では、せっかくメンバーが作ってくれた機能の一部を忘れるというポカをやらかしてしまった。
指摘してくれてありがとうございました。ごめんなさい。
Chromeで表示してバックグラウンドに残しておいて準備はしていたんだけどやはり人間焦るといけない。

最後に40秒くらい時間が余ったので、楽天カードマンモードを見せることができた。

楽天カードマンが表示された瞬間、会場が最高に盛り上がった。
インカメラ&縦画面のプレビューに綺麗にカード重ねるため費やした一晩の苦労が報われた。
最高。

結果発表

何か一つだけでも賞を取れてたらいいなあと思っていたら、一番最初のAPI賞で呼ばれた。
やった!賞取ったぞ!という気持ちと、API賞止まりか、という気持ちが半分ずつくらいな感じでした。
それから少し後に、kobo賞の発表があった。
kobo賞は別チームで良さそうなアプリがあったので、受賞はそのチームだろうと思っていた。
そしたらなんと司会の人が自分たちのチーム名を呼ぶじゃあありませんか!
これにはビビった。まさか2つも賞を取るとは…!
そして副賞はkobo arc7 HD。Androidの入ってるハイスペックなkobo。中身はTegra3でメモリ1GBなので、初代Nexus7と同じくらい、なのかな。
タブレットは前から欲しかったのでとても嬉しかった。

その後いくつか部門賞の発表が続き、最後に優秀賞、最優秀賞の発表。
wktk

他部門受賞がありということに気付いてしまったので、結構期待しながらドラムロールを聞いていた。

ドコドコドコドコドコドコ

\ジャン/

呼ばれたァアッァッァッァアァアアアアアァ

優秀賞きましたわー。

感動しました。

そしてなにより驚きました。

まさか3つも賞をいただけるなんて思ってなかったですもん。


========

下書きはここで終わってた。
読み返しながら当時のことを思い出して懐かしくなった。
ハッカソンに出るにあたって目標を決めていたんだけど、それについて全く触れていなかったので書く。

動くものを作る

これはハッカソンに出るからには絶対の目標だった。
デモがない発表は嫌なので。
多少不具合があってもとにかく動かせという気持ちでコーディングしていた。

就活

就活用のネタが欲しいなというのがそれなりにあった。
学部の頃にも就活をしたが、そのときは胸を張って言える成果物が全然なかったので苦い思いをした。
入賞すれば自信になるだろうし、出来なくても参加して人脈が出来たらラッキー、と考えていた。
結果としては入賞、しかも二部門+優秀賞なので、とても自身になった。

懇親会でも、心なしか声をかけてくれる人が多かったような、そんな気がした。
とは言え結局、その場で会った人たちと就活で接点はなかった。
自分からメールする勇気がなかったし、懇親会でもあまり話せなかった。

ネタを仕込む

これはちょっと最初から目標にしていたか?と言われると微妙なところ(夜中作業中に思いついただけなので)
だけどなんとなく、真面目なだけの発表だとつまらないよな、という気持ちはあった。

ハッカソンはこの一回しか参加したことがない。
振り返っているとまた行ってみたくなってきた。

BitConverterでGetBytesするよりポインタ使ったほうがメモリに優しい

ykimisaki.hatenablog.jp

byte[]に何かを変換するとき、BitConverter.GetBytesを使わない方がパフォーマンス出る。

GetBytesの中身を見ると、(当然といえば当然だけど)内部で引数の型の大きさ分だけbyte配列をnewしているため、ポインタを使って適当に用意したバッファを使い回した方がメモリに優しい。

GetBytes - http://referencesource.microsoft.com/#mscorlib/system/bitconverter.cs,9108fa2d0b37805b,references

[System.Security.SecuritySafeCritical]  // auto-generated
public unsafe static byte[] GetBytes(int value)
{
    Contract.Ensures(Contract.Result<byte[]>() != null);
    Contract.Ensures(Contract.Result<byte[]>().Length == 4);

    byte[] bytes = new byte[4];
    fixed(byte* b = bytes)
        *((int*)b) = value;
    return bytes;
}

GetBytesを呼んだ回数だけbyte配列もnewされるため、適当にバッファ用意してそこにbyte[]に変換したものをコピーするとよい。
一番上のリンクだと、byte[1024]のバッファに、int[100]をまとめて突っ込んでいた。要点はこんな感じ。

byte[] buf = byte[4];

unsafe{
    fixed(byte* b = &buf[0]) // bufの先頭アドレスのポインタを作成
        *((int*)b) = value;// ポインタに値を入れる(bufに入る)
}

追記

UnityのmonoとBitConverterが微妙にコードが違ったのでこっち(mono/BitConverter.cs at Mono2.12.x-Unity4.x · Unity-Technologies/mono · GitHub)見たほうがいいかも

Android版IL2CPPがなんか遅い

Unity5.2からIL2CPPがAndroidでも使えるようになったみたい。
tsubakit1.hateblo.jp

Unity5.4.0b17時点でも、

IL2CPP on Android is experimental and unsupported

と書いてあるので、まだ実験段階な模様。

ほーん、どれぐらい早くなるんやー?と思って、Mathf.Atan2や、Vector3.DotなどをNexus5xで5,000万回実行して見たら、Atan2は2割くらい早くなって、Dotは逆に2割ほど遅くなった。

生成されるcppファイル読めばわかるのかな…