存在を忘れられる備忘録

あれこれTwitterじゃ流れるから残しときたい書き物用

Android 8.0 Oreo で setOnClickPendingIntent を用いて PendingIntent を呼び出す際の注意点

概要

Android 開発初心者が Widget 開発しようとしたら PendingIntent が虚空に飲まれて困ったので対処しました。 長々と本文がありますが、最後の余談に書いてあることの方が真理で即解決可能な方法です。

注意

筆者は Android 開発初心者で理解が間違っている、不正確な文章、表現が含まれる可能性があるため話半分で読むことをおすすめします。 誤りの指摘、本当の解決法(?)等がございましたらコメントなり Twitter なりでお伝えいただければ幸いです。

導入と動かないサンプルコード

自宅の照明を制御するためにいちいち Google アシスタントに話しかけるのは、引きこもり大学院生の声帯に対してあまりに過酷すぎる問題があります。 というわけで、照明操作のフロントエンドに AndroidWidget を使おうと思ってサクッと作ることにしました。

サクッと、とは言ったものの、今まで Android 開発は GPS ロガーを作るのに触ってみた程度の経験で Widget 開発に関しては全く経験がなかったので爆速で座礁しました。 とりあえず実現したかった機能としては、

  1. Widget をタップ
  2. 照明操作用のエンドポイントにHTTPリクエストを投げる

という機能で、2.の部分を実現する Service を作成して、Widget をタップするとこの Service を起動するという構成が一般的そうです。 HTTP リクエストは後ほど実装するとして、とりあえず Widget タップ → Service 起動の流れを実装することにすると、以下のようなサンプルがヒットします。

Android 8.0での変更

このサンプル、最初は上手く動いていたのですが、つい先日端末を Android 8.0 Oreo にアップデートしたところ動かなくなってしまいました。 そもそも Service の onCreate( ) すら呼ばれていないようで、無言で生成されることもなく死んでいるようです。 色々調査してみると、どうやら Oreo から Service の起動に制約が増えたようです。

Android 8.0 での動作変更点 | Android Developers

要約すると、

  1. バックグラウンドで起動される行儀の悪い Service によるバッテリ消費の問題が深刻化している
  2. Android O では予め許可された特殊な Service を除いてバックグラウンド実行を禁止することに
  3. 許可されない(通常の) Service を実行する際にはフォアグラウンドとして実行する必要がある

今回の Service も許可されない Service のためどこかで差し止められたのだと考えられます。 (変更を加えずに従来通り Service を呼び出しても、数秒は実行されるっぽいので Service の onCreate( ) すら呼ばれていないのは奇妙ですが……)

この変更点は日本語の記事でもいくつか取り上げている例があったのですが、どれも Activity からバックグラウンド実行する Service を呼び出す話で、startService( ) を書き換えるだけでオッケーみたいなことを書いているのですが、Widget では PendingIntent 経由で呼ばれるのでこの方法は使えず全然オッケーではありません。

対処法

ここから行き詰まってインターネットを彷徨い続けることになったのですが、結論から言うと PendingIntent を作成する際に getService( ) から getForegroundService( )に変更する + Service 側で startForeground( ) を呼び出すことで問題は解決しました。 getForegroundService( ) はAPI Level 26で追加されたようなので、場合によってはビルド設定を見直す必要がありそうです。

余談

差し当たってはビルド設定の targetSdkVersion を25にすることでこの変更を逃れられて、当初のサンプルコードを入力することで動作させることが可能なようです。

リンカの気持ちになってOpenCVのメジャーバージョン変更と戦った話

今年の抱負が "アウトプットを増やす" に決定したので早速(1月も終盤)書きます。

背景

現在、諸事情でUbuntu14.04LTS上で作業をしているのですが、当然のように最新のパッケージは降ってきません。 OpenCVも2.4.8がリポジトリ上の最新となっています。 そんな中で3系で新しく導入された、魚眼レンズのカメラキャリブレーションに関する関数が使いたかったので手元でビルドしました。 この時点でかなり疲弊していて、「prefixが/usr/localになってるけど、今日は取りあえず動くか確認して明日整理すればええやろ、インストールや!」などと思ったのが問題のそもそもの発端でした。 無事、魚眼レンズのキャリブレーションを終え、翌日に面倒事を押しつけることに成功し満足げな表情でこの日は作業を終えました。

翌日、また別の作業でビルドを走らせると何やらエラーがでています。 見たところOpenCV周りのエラーっぽかったので心当たりありまくりで、取りあえず3系のOpenCVを消してビルドし直しました。 しかし、エラーは消えません。undefined reference だそうです。 こういった類のエラーが出る時は、大抵ライブラリの指定忘れなのですが、cmakeを使ってこれまで同じようにビルドしてきたのに、今さらこんなエラーが出るのも変な話です。 そんな阿呆なと思いながら、コンパイルコマンドをダンプさせてじっくり眺めてみても、やっぱりきちんと2系のライブラリが漏れなく指定されています。 更に気になる点として、OpenCVに関係する関数が全体的にundefined referenceならよくある話なのですが、今回はごく一部だけがシンボル解決に失敗しているようでした。

ライブラリに含まれるシンボルの調査

というわけで、本当に指定したライブラリに今探しているシンボルがないのか、逆にどんなシンボルならあるのか(型が合ってない可能性)を調べることにしました。 readelf コマンドを使うことで、実行ファイルやライブラリなどのELFバイナリのメタデータを人間が読みやすい(?)形で得ることができます。 シンボルを出力するためにオプション-sを、途中で出力が打ち切られるのを防ぐために-Wを指定して、

readelf -s -W <ELFファイル>

とすると、何やら面妖な記号を含みながらそれっぽいのがズラズラっと出力されると思います。*1

  (前略) _ZN2cv12findContoursERKNS_17_OutputArrayERKNS_12_OutputArrayEiiNS_6Point_IiEE

この記号はC++名前空間や多重定義を実現しながらユニークなシンボル名を作り出すために付加されていて、c++filtなどのコマンドを用いることで以下のようにデコードすることができます。

  (前略) cv::findContours(cv::_OutputArray const&, cv::_OutputArray const&, int, int, cv::Point_<int>)

問題解決

さて、実はさっきから出てきているこのcv::findContoursというのが、今回名前解決がされないと言われている関数の1つでした。 見たところ普通に存在しているようですが……エラー文と比較してみましょう。

undefined reference to `cv::findContours(cv::_InputOutputArray const&, cv::_OutputArray const&, cv::_OutputArray const&, int, int, cv::Point_<int>)'

第一引数がなんか微妙に違う……。 調べてみると、OpenCV2系までは _InputOutputArray_OutputArray の別名だったそうなのですが、3系でそれぞれ別の構造体となったようです。 つまり、ライブラリは2系なので _InputOutputArray は別名定義の解決が行われた _OutputArray としてシンボルが存在しており、リンクしようとしているバイナリの方は3系の残滓が残っている時にコンパイルしたために、独立した _InputOutputArrayとしてシンボルを登録していたようです。 というわけで、一旦バイナリを全てコンパイルし直したらすんなり問題解決しました。

結論

市原仁奈「リンカの気持ちになるですよー!」

*1:readelfも実は3系の結果で、第一引数の部分だけ話の都合で書き換えてあります。手元に2系のバイナリがなかったので……。

心から温まる技術

この記事はcoins Advent Calendar 2016 19日目の記事です。

毎日毎日寒いですね。今日は寒い日々を耐え忍ぶための技術についてです。

coinsで暖を取ると言えば、一般的には計算機をぶん回すことを指すと思います。 徐ろにGentoo Linuxのインストールを始めたり、撮りためたアニメをエンコードするのは冬の恒例行事とも言えるでしょう。 過去のcoins Advent calendarを眺めてみてもこたつをサーバで温める記事があったり、計算機で暖を取ることはもはや目新しいことでもありません。 そこで、今日はちょっと趣向を変えてみようと思います。

迫りくるクリスマス。 どんなに部屋が暖かくても共に過ごす相手がいなければ、心は冷えきったままで生命の温もりが恋しくなるでしょう。 無ければ作るの精神ということで、今日はちょちょっと生命を作り出してしまいましょう。 coinsで生命と言えば?

そう、ライフゲームですね。

ご存じない方のために説明すると、ライフゲームとは碁盤の目状に並んだセルの状態が簡単なルールに従って更新されていく、シミュレーションゲームの一種です。 各々のセルは生死どちらかの状態で、

  1. 死んだセルの周囲8セルの内、生きているセルがちょうど3つの時にそのセルは「生」に変わる
  2. 生きたセルの周囲8セルの内、生きているセルが1つ以下の時にそのセルは死ぬ
  3. 生きたセルの周囲8セルの内、生きているセルが4つ以上の時にそのセルは死ぬ
  4. 上記以外の場合、セルの状態は変化しない

の4つのルールに従ってセルの更新を繰り返し、盤面の変化を眺めて楽しみます。

というわけで、ライフゲームを作っていきます。 ただ単に盤面の状態を液晶ディスプレイに表示するだけだと生命の温もり感に欠けるので、Arduinoを使って8x8のマトリクスLEDに表示しようと思います。 マトリクスLED内部は、

f:id:dj_marble:20161219002125p:plain

(MNA20SR092Gデータシートより引用)

こんな回路をしているので、何も考えずにつなごうとするとArduino Unoではピン数が足りず、金を積んでArduino Megaを買う必要が出てきます。 そんなお金は無いので、今回は8bitのシリパラ変換ICを2つ使ってつなぐことでArduino Unoからでも制御できるようにします。 いい感じにつないだ結果こうなります。

f:id:dj_marble:20161219002938j:plain

後はパパっとArduinoのスケッチを書いてしまいましょう。 マトリクスLEDの点灯には少し工夫が必要で、そのままだと一度に全てのLEDを任意の点灯状態にすることができません。 各行(もしくは列)1つだけに着目すると任意の点灯状態にすることができるので、まず1行だけ表示して、すぐに次の1行だけを表示して……と表示する行を高速で切り替えることで全てのLEDについて任意の点灯状態にすることができます。 ArduinoにはShiftOutという1byteのデータをシリアル出力してくれる便利な関数があるので、これを使うと各行のデータを簡単にシリアル出力することができます。 最終的なスケッチがこちらです。

このスケッチをArduinoに書き込んで完成です。

f:id:dj_marble:20161219013026g:plain

どうですか、生命の温もりを感じられますか。 僕は部屋のあまりの寒さに凍え死にそうです。 とはいえ、もともと箱庭シミュレーションを無心で眺めているのが好きなので楽しいことは楽しいです。

f:id:dj_marble:20161219012517g:plain

完全にただの一発ネタでしたが、ここまでお読みいただきありがとうございました。

matplotlibのcolormapを自分好みにする

散布図とか描くとき色分けしてプロットすると思うんですけど、ちょっとデフォルトのものから変更したかったので調べてみました。途中から飽きて雑にしか理解してないので話半分に読んでください。

ざっくり説明すると、以下のようにRGB各色について点を0から1の範囲で指定すると線形補間してくれて色が決まるそう。1列目が入力の対応位置、2列目と3列目が実際に取る値になります。y=f(x)とすると1列目がxで2,3列目がyに当たると考えれば。yが2つある理由は後述しますが、取りあえずは同じ値を設定しておけば困らないと思います。

cdict1 = {'red':((0.0,0.5,0.5),
                 (0.25,0.0,0.0),
                 (0.5,1.0,1.0),
                 (0.75,0.0,0.0),
                 (1.0,0.5,0.5)),
          'green':((0.0,0.0,0.0),
                   (1.0,0.0,0.0)),
          'blue':((0.0,0.0,0.0),
                  (1.0,0.0,0.0))}

この場合、緑と青は全域で0、赤の値が最小値から最大値にかけて以下のグラフのように変化します。

f:id:dj_marble:20151118213656p:plain

実際に散布図をプロットしたものが以下になります。

f:id:dj_marble:20151118213742p:plain

さて、2列目と3列目が分かれている理由ですが線形補間を行う際、補間したい点を挟むcdictで定義された2点をa,bとしてa[0]<b[0]とすると、a[2]とb[1]を用いて補間されます。説明下手なので自分でも何言ってるかさっぱりですが、以下のように点を指定すると、

cdict2 = {'red':((0.0,1.0,1.0),
                 (0.5,0.0,1.0),
                 (1.0,0.0,0.0)),
          'green':((0.0,0.0,0.0),
                   (1.0,0.0,0.0)),
          'blue':((0.0,0.0,0.0),
                  (1.0,0.0,0.0))}

赤色の値は下図のように補完されます。

f:id:dj_marble:20151118215841p:plain

散布図を描くとこんな感じ。

f:id:dj_marble:20151118220011p:plain

おわかりいただけただろうか。理解力を総動員していただきたい。

もともと欲しかったのはこんなcolormap。

cdict3 = {'red':((0.0,1.0,1.0),
                 (0.5,0.25,0.25),
                 (1.0,1.0,1.0)),
          'green':((0.0,1.0,1.0),
                   (0.5,0.5,0.5),
                   (0.75,1.0,1.0),
                   (1.0,0.25,0.25)),
          'blue':((0.0,1.0,1.0),
                  (0.5,0.75,0.75),
                  (0.75,0.0,0.0),
                  (1.0,0.0,0.0))}

f:id:dj_marble:20151118220446p:plain

気持ち雨量レーダーっぽいかと思います。そうでもないかとも思います。どんなグラフにすればこうなるか理解力が無くてわからなかったので以下のサイトを使いました。

Custom colormaps for Matlab and Matplotlib - Colormap.org

これそのまま使えばええやんとも思った。以下使用したコード。

ファイル保存時にtexを自動コンパイル

タイトル通りです。Twittertex書きながらその場でプレビューしたいっていうpostを見てinotifyとか適当に組み合わせればできそうだなと思ったのでシェルスクリプト4級(?)の腕を駆使してまた一つゴミみたいなスクリプトを生み出してしまいました。

エラーとか全部投げ捨ててるのでflycheckとか入れてない場合リダイレクトしないほうが使いやすいと思います。

Arch linuxにxmonadを導入した

今までWMにはawesomeを使っていたのだけれど、マルチディスプレイ周りの挙動が気に入らなかったのと、じわじわとHaskellを勉強している影響でxmonadに移行した。それで色々メモとして残しておきたかったので久しぶりに筆を執ることに。基本的にArch Wikiを読めばある程度までは動かせるはず。

通知

作業中でもすぐリプライやふぁぼに対応するためにmikutterからの通知を取りこぼすわけにはいかない。awesomeでは標準でnaughtyとかいうのが入っていて特に何をすることもなく使えた気がするのが、xmonadは別で通知サーバーを入れないとダメらしい。Arch Wikiに色々候補が上げられているので適当にそれっぽいものをインストール。

ちなみに色々試した結果、dunstで落ち着いた。

壁紙の設定

デフォルトではあまりに真っ暗な画面が襲いかかってくるので起動できているのかすら確認できない。実際最初は失敗したと勘違いして諦めそうになった。awesomeではテーマを適当にいじれば壁紙を変更できたのが、xmonadではnitrogenなど外部ツールを使って設定するらしい。これでこころぴょんぴょん出来るように。

システムトレイ(xfce4-panelで言う通知エリア)

これもawesomeだと何もする必要がなかったけれど、xmobarはテキストを表示する程度の能力しかない(多分)のでそんな器用なことはできない。うまい方法が見つからなかったのでとりあえずxfce4-panelを使って、透明度を100%にするとそれっぽくなったのでそのまま放置。

gtk3が叛逆を起こす

具体的に言うと、gtk3のアプリケーションだけ周りに異様な空間ができる。gtk3のテーマファイル自体を書き換えたら若干ましになった。/usr/share/themes/にテーマファイルがあるので、使用中のテーマファイルの.window-frameの項目を消し去ったら何とかなる、はず。

他にもあった気がするので思い出したら書き足す。

dokuwikiでバックアップを取ったら生成されるリンクが壊れてた話

完全に備忘録というか、あまりに初歩的すぎて(?)ググっても出てこなかったので。

大学のサークルで情報共有にdokuwikiを用いているのですが、ちょっと色々あって再インストールする羽目になって、元のwikiをディレクトリごとバックアップ取ってデータを新しい方にコピーしたのですが、生成されるページの一部だけリンクが壊れていたんです。

で、結果的に直ったんですが、原因はdokuwikiのドキュメントルートを指定していなかったからでした。明示的に指定しなくても自動で推測してくれるので、元々設定は空欄だったのですが、それがバックアップ側のディレクトリを読みに行ってたのが原因みたいです。