PEEE802.11

半端者

Androidアプリでstaticフィールドは絶対ではない

Androidアプリで、Contextをどこからでも参照する方法として以下のようなコードがよく紹介されている。

  • staticでApplication Contextを保持するクラス
public class ContextHolder {
    private static Context mContext;

    public static void setContext(Context context) {
        mContext = context;
    }

    public static Context getContext() {
        return mContext;
    }
}
  • アプリのプロセス起動時に実行されるApplicationクラスのonCreate()でContextをセット
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ContextHolder.setContext(this);
    }
}

あとはContextHolder.getContext()で任意のクラスからContextを取得できる、というわけだ。

しかし、実はこのコードは問題を孕んでいる。

Android StudioでContextをstaticフィールドに突っ込むと「メモリリークするよ!」と警告が出るが、もっと深刻な問題は知らぬ間にnullになることである。

ご存知の通り、Androidはメモリが逼迫するとバックグラウンドにあるActivityやらServiceやらを殺していく。殺されるのはそういったAndroid特有のオブジェクトだけではなく、アプリのライフサイクルとは無縁の上記ContextHolderのようなオブジェクトも例外では無い…ように感じる。というのも、実際に上記のようにstaticで参照を保持しているフィールドにアクセスするアプリで、ぬるぽでクラッシュしたレポートがコンスタントに上がってきているので。クラスがUnloadされると、次回必要になった際に再Loadされる。その時に初期値を設定していないstaticフィールドはnullで初期化される。そこで本来はApplicationクラスのonCreate()でContextを設定したいところが、プロセスまでは殺されていないためにApplicationクラスのonCreate()が再度呼ばれず、staticフィールドはnullのままになってしまうのである。

以下にちらっと書いてあるが、アプリをロードしたClassLoaderごとGCされることがあると示唆されている。

developer.android.com

The class references, field IDs, and method IDs are guaranteed valid until the class is unloaded. Classes are only unloaded if all classes associated with a ClassLoader can be garbage collected, which is rare but will not be impossible in Android.

別の例として、以下のようにApplicationクラス内にstaticで持つコードも見かける。さすがにApplicationクラスはUnloadされないと思うし、されたとしても再Load時にonCreate()が呼ばれるはずなので、この場合はおそらく問題ないと思う。

public class MyApplication extends Application {
    private static Context mContext;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = this;
    }

    public static Context getContext() {
        return mContext;
    }
}

ClassLoaderごとUnloadされる状況をあえて作り出すのは容易ではなさそうなので、実際に動作確認したわけではなくあくまで予測ですが…

IPv6をIPoEで使う + IPv4をDS-Liteで使う

自宅のブロードバンド環境は、auとの契約の関係もあり長らくJCOMであった。東京在住で、遅い時で2Mbpsほど、速い時で15Mbpsほど出ていたのでそこまで不満に感じていなかった。しかし、そろそろ契約更新月なのと、Wi-Fiルータを新調して11acにしようか考えていたところで、一新してしまうことにした。

さて、他の多くのISPもそうだが、So-netも例に漏れず回線が混雑する夜間は1Mbpsほどしか速度が出ない。空いてる時間帯でも20Mbpsすらなかなかいかない。この原因はウェブ上各所で説明されているが、IPv4 PPPoE認証がボトルネックになっているためである。以下のページが詳しい。

techlog.iij.ad.jp

VDSL環境だと、IPoEを使うと理論上は電話線の速度上限である100Mbpsまでは出るはずである。 早速、混んでる時間帯のPPPoEでの速度を覚えておき、So-netに連絡してIPoEを使いたい旨申し出る。 So-netとしては大々的に宣伝しているオプションではないし、IPoEに変えて改善するかはわからないため(それ以外の原因で遅いユーザもいるらしい)、現在の速度と状況について簡単にヒアリングされる。言うだけで機械的に処理してくれるわけではない。

IPoEが解放されたら、ONUWi-Fiルータを再起動する。Wi-FiルータはAUTOモードに設定して、インターネット@スタートが設定されていればただ再起動するだけでよい。ただし、IPv6パススルーはOFFにする(デフォルトOFF)。ここで、PPPoE接続の設定を無効化すると(Internet -> PPPoE -> PPPoE接続先リスト に有効な接続先があれば、「接続先の編集」ボタンを押して無効にするか削除する)、DS-Lite (IPv4 over IPv6)が設定される。

再起動後以下のようにルータのステータスが表示されればOK。

  • 動作モード
    • ルータモード
  • Internet
  • IPv6
    • IPv6接続方法: インターネット@スタートを行う
    • IPv6接続状態: NDプロキシ
    • グローバルアドレス: (IPv6アドレスが表示)

以下のサイトで、IPv6の接続性があればIPv6 IPoEで繋がっている。ISPSo-netではなくINTERNET MULTIFEEDと表示されれば、DS-Liteが有効になっている。

test-ipv6.com

IPv4 DS-Liteでの速度測定結果。VDSLのポテンシャルを限界近くまで引き出している。

www.speedtest.net

IPoEはともかく、DS-LiteはSo-netが案内していないオプションであり、現状はルータの設定次第で勝手に繋がってしまった、というものである。いつ使えなくなるかわからないのでご利用は自己責任で。

転職して1ヶ月経ったので振り返ってみる

基本的に前職同様ソフトウェア開発なので大きく戸惑うことはないのだが、ところどころ違う点が見えてきた。

プロジェクトの進め方

前職だとリーダーがスケジュールを決めて、各マイルストーンまでにやるべきことを洗い出し、時間かけて準備することや不確定要素が多くリスクが見えにくいタスクから先に取り組んで進捗を定期的に確認したり、そういった作業がプロジェクトの初期にあって、それらがミーティングを通じてテキパキと決定されていった。現職は、プロジェクト規模や期間が相対的に小さいこともあり、リーダーやPMがテキパキ決めるというよりはみんなで話し合いながら柔軟に決めていくという感じ。ミーティングもややゆるい感じで、前職でははっきりしたミーティングの目的がなければ時間節約のためにキャンセルとか、議論が停滞したら宿題にしてサクサク先に進めてその後メールで議論を進めるとかしていたが、それもない。そもそもミーティングの数が少なかったり、時間が切迫しているわけでもないというのが大きいのだろうか。

コミュニケーション

Slackでチーム内でゆるい感じのコミュニケーション。前職はプロジェクト関連の状況報告や運営方針など諸々のメールが一日100通は来ていたが、今は一日10通来るかどうか。その分開発に集中できて最高である。

チーム体制

前職はアーキテクト的な人だけがコードを承認できたが、今はみんな対等な立場でレビューし合う感じ。本を読んで勉強している人が多いし、チーム全員がちゃんとコードが書けるエンジニアという印象を受けている。

休みを気軽に取れる雰囲気で、土日に繋げてそれなりの連休も問題ない感じ。若干のドライさは感じるものの、少なくとも心理的安全性は確保されていそう。

あまり変わらないところ

服装や勤務制度の自由さ、役職を振りかざす人がいないことなど。

結論

現状満足。

前職の感想

よかった点

  • 旧態依然とした日本企業の悪い風習や文化がなく自由な雰囲気であるため居心地は良い
    • 服装は自由。社内ではみんな「さん」付けで呼び、役職を振りかざす人はいない。ウェブ上でハンコリレーだの冗長な会議だの飲み会強制参加やアルハラパワハラだの劣悪な労働環境の内情が散見されるが、都市伝説なのでは?と思うほど無縁だった。また、退職にあたって今まで一緒に仕事したたくさんの人に挨拶に行った時、誰もが温かい言葉をかけてくれたのには本当に救われた。
  • 英語を業務で使う機会が多く良い経験になる
    • "Janglish"だからとバカにされることはない。むしろ何も話さない方が問題視される。いろいろな国の英語に触れられるので(むしろネイティヴがほぼいない)、発音はネイティヴ並みじゃなくても通じるし聞き手も理解しようという雰囲気が良い。
  • コンシューマ製品を作っていたためダイレクトにユーザや世間の反応がわかる
    • 自分が開発した製品を街で見かけたりネットで評判になったりする。良くも悪くも。

よくなかった点

  • 協力会社に開発を委託することが多い
    • 個人の志向の問題とも思うが、社員は他チームとの連絡役とタスクとリソースの管理に時間をとられる。もっと開発現場で働きたい自分にはちょっと辛かった。
  • リソースとタスク量のアンバランス
    • タスクは増えどもリソースは増えず。効率的に業務を回す工夫は現場に委ねられるも現状の業務を回すので精一杯。
    • 開発スケジュールはバッファが少なく、リソースもバッファがないため、トラブルは残業でカバー。トラブルはそれなりの規模のプロジェクトには付き物なので、残業前提でプロジェクトを進めていることになる。
  • 分業化によってコミュニケーションコストが高い
    • 誰に聞いていいかわからないことがよくあり、隙間領域のバグ票などは押し付け合いになりがち。
    • 分業化に伴って階層化が進み、中間層は仕事やメッセージのバケツリレーをするだけになる。

こうやって書き出してみると、人月ビジネスとその管理業務が自分の方向性に合わないことと、 人材をコストとみなしてコスト削減の号令を聞き続けるのに疲れたのと、 常に負荷オーバーで楽になる未来が見えなかったいうところだろうか。 技術的バックグラウンドは必要とは言え、開発する余裕がなく調整作業ばかりの毎日で、日に日に違和感ばかりを溜め込んでいた。 もちろん目的やビジネス的成功なき開発は会社では意味をなさないし、できるだけ固定費を削減して利鞘を確保しようというのは正しい。 しかしそれを達成しようとした結果、従業員のモチベーションや心身の健康といった数値化しにくいものを下げてしまうリスクは認識されるべきだと思う。

転職活動をするにあたってコーディング面接対策をしていて、改めて自分の技量の未熟さを痛感したけど、 それでもやはりエンジニアとして調整だけではなく開発にもっともっと時間を割きたいという思いは変わらない。

晴れて転職した先の職場はチームみんながちゃんとコーディングしてるエンジニアという様相で、組織がよりフラットで、以前よりも開発に時間を割けそうである。 初めての技術領域なので慣れないことも多いが、指導を受けながら勉強して一刻も早くチームに貢献できるようになりたい。

USBバルク転送の挙動(ZLP: Zero Length Packet)

USB機器のデバイスドライバ書いてる人なら当たり前の話だとは思うんだけど、専門外の自分が調べる機会があったのでメモ。

USBの転送モードはいくつかあって、そのうちの一つにバルク転送というものがある。大容量のデータをやりとりするのに適した転送モードで、記憶装置とのデータのやりとりに使われたりする。このバルク転送を使ってPC(ホスト側)から携帯端末(デバイス側)にデータを送っていたのだけど、あるサイズのデータを送ると、デバイスが応答を停止してしまう現象が起きた。あるサイズというのは、512バイト、1024バイト、1536バイト、…つまり512バイトの倍数である。

最初は、受け取ったデータを処理をするためにデバイス側に追加した自分のコードが悪さしてるのでは?と思ったのだが、いろいろ検索してみるとどうも違うらしい。

まず、USB2.0 High Speedバルク転送のデバイスのエンドポイントのバッファサイズは512バイトである。そして、USB 2.0 の仕様の5.8.3でバルク転送について以下のように書かれている。

endpoint must always transmit data payloads with a data field less than or equal to the endpoint’s reported wMaxPacketSize value. When a bulk IRP involves more data than can fit in one maximum-sized data payload, all data payloads are required to be maximum size except for the last data payload, which will contain the remaining data. A bulk transfer is complete when the endpoint does one of the following:

  • Has transferred exactly the amount of data expected.
  • Transfers a packet with a payload size less than wMaxPacketSize or transfers a zero-length packet

When a bulk transfer is complete, the Host Controller retires the current IRP and advances to the next IRP.

つまり、バッファサイズより大きなデータを送る際はバッファサイズいっぱいまで詰めるだけ詰めて分割して送り、期待するデータ量を送り終わったら、もしくはバッファサイズより小さいパケットが来たら転送が終了する、ということらしい。

ここで曲者なのが期待するデータ量をどうやって知るかということ。USBバルク転送の仕様では、これから送るデータ量をやりとりする方法は記述されていない。ネット上を調べた感じ、どうもデバイスやドライバの実装依存のようなのだ。自分が調べていたドライバやデバイスには、特にそういう実装がないように見える。つまり、送る/受け取るデータ量を事前に知ることができない。ということで、データ転送を終了する条件はただ一つ、バッファサイズより小さいパケットが来ること、になる。自分が直面した問題は、バッファサイズ = 512バイトと同じパケットを送ると、デバイスが転送の途中だと判断し、いつまでも次のパケットを待っている状態になっていた、ということだった。

この問題を解決するための一般的な方法が、上記引用にも書いてあるzero-length packet (ZLP)である。ホスト側が、全部のデータを送り終わり、かつ送ったデータがデバイス側のバッファサイズの倍数だったら、データペイロードが空のパケットを送り、デバイスに転送が終了したことを知らせる。この実装だと、デバイス側がどれだけデータが送られてくるかを知らなくてもよい。

自分が担当しているのはデバイス側の実装だけなので、ホスト側のドライバを書いているチームに実装してもらわなければならない… デバイス側はAndroid Linuxのコードを流用してるから、そのままシンプルにキープしておきたいなあ。

ちょっと古くてUSB2.0までしか載ってないけど、以下の書籍で調べた。訳書だけど自然な日本語でわかりやすかった。

USB 2.0とUSB On-The-Goを含むカスタムUSBデバイス開発のすべて USBコンプリート[第3版]

USB 2.0とUSB On-The-Goを含むカスタムUSBデバイス開発のすべて USBコンプリート[第3版]

職場を離れて一人で考える時間について

自分は基本的には職場で仕事に集中して家ではリラックスしたいタイプで、オンとオフはけっこうはっきりしている。仕事モードの時は、仕事話とその延長の雑談であればいいが、あまりに離れたプライベートな話題などは話に入っていけなかったりする。コンテキストスイッチが切り替わらないのだ。また、仕事終わりにすぐに職場外の人と会うと、中途半端に仕事モードのままなので会話を楽しめなかったりする。

家に帰って嫁さんと話しているときは完全にオフモードなのだが、嫁さんが先に寝たりお風呂に入っていたりして一人になると、その日の仕事で抱えている問題について思いを巡らせてしまう。ただしこの時は仕事モードオンで考えるわけではなく、限りなくオフに近い状態で考える。すると、職場で集中している時とは別のアプローチで物事を進めるヒントを掴んだりして、次の日出社したら一歩前進した状態から仕事を始められることがある。

仕事が単純作業でなければ、こういうオフの時間は大切だということを実感する。オンとオフで使用する思考回路が違うのだろうか。家でも仕事のことを考えているというのは気が休まる時間がないと思う人もいるだろうが、無意識に考え出してしまうので自分はこういう性分で仕方がないと思っている。子どもができればそんなこと考えるヒマもなくなって生活が変わるのだろうが、仕事のこと以外でも一人でリラックスして何かに思いを巡らせる時間は大事にしていきたいと思う。

SwitchPreferenceでSwitchの状態を変更する

単純なことのようだけどなんかパッと見つからなかったのでメモ。

setChecked(boolean checked) でON/OFFできる。

SwitchPreference sp = (SwitchPreference)findPreference("hoge_switch");
sp.setChecked(true);