Flutterで開発していると激重になる現象

Flutterで開発しているとやたらPCが重たくなり、調べてみるとsimdiskimagedというプロセスのCPU使用率が上がっていることに気づきました。

Flutterの開発環境は、Intel Mac、VSCode、FlutterSDK 3.24.xといった環境です。直近やったことと言えば、OSとXCodeのバージョンアップ。

FlutterのSDKをダウングレードしたり、XCodeを再インストールしてみましたが解決せず。ググってもあまり似たような症状の人は見つかりません。

いろいろ試していると下記のようにiPhoneのシミュレーターを一度起動させると改善しました。

open -a Simulator

おそらくXCodeをバージョンアップしてから一度もXCode経由でシミュレーターを起動していなかったせいで、simdiskimagedのプロセスが異常を起こしていたのかもしれません。

「収支管理」アプリをリリース

収支管理アプリは、収支を簡単に管理できる家計簿のようなアプリです。余計な機能はできるだけ取り除き、必要な機能のみに絞ることでシンプルなアプリに仕上げました。

機能は、カレンダーによる収支の一覧画面、通帳のように収支を一覧で確認できる画面、グラフによる収支の推移がわかる画面、バックアップ機能やタグ仕訳など。

iOS版のダウンロードはこちらから

Android版のダウンロードはこちらから


Flutterを使用したため、iOSとAndroid両方一度に開発することができました。iOSとAndroidで若干挙動が異なるところはありましたが、各プラットフォームでネイティブ開発していたこともあり、それほど苦労することもなくリリースできました。

まず、初めてFlutterを使ってみた感想は、ライブラリが充実していてUIの構築がとても簡単にできること、欲しいと思ったライブラリが既に用意されているので、爆速で開発できる点です。そして、ホットリロードによりコードの更新がリアルタイムに反映できる点もすばらしいと思いました。

アーキテクチャは、レイヤードアーキテクチャを採用しました。これは、UI層→アプリケーション層→ドメイン層→インフラストラクチャ層のように各層で、ロジックを分離する方法です。UI層からインフラストラクチャ層のように依存関係を一方向にすることで、各層の影響を限定的にすることが可能です。

例えば、インフラ層にデータベース入出力するロジックを実装した場合で考えます。テスト時だけデータベースを書き換えたいというケースにおいて、データベースの入出力するロジックをDIで入れ替えるということが簡単にできるわけです。

今回使用したライブラリなどは、状態管理にRiverpod、SQLiteへのアクセスdrift、リリース後の管理にFirebase、サブスクリプションの管理にRevenueCatを使用しました。

初めてサブスクリプションの実装にあたって、Firebaseで処理するか迷いましたが、iOSとAndroid両方に対応するのが少し面倒だったことや、サーバ側でデータをなるべく持ちたくなかったこともあり、RevenueCatを使うことにしました。

RevenueCatはサブスクリプション以外のアプリ内課金を管理するのにも便利そうです。月間$2,500までは無料で使えます。(それ以降は1%の手数料とのこと)

Flutterの開発部門の人員削減の話など出ているのが少し不安なところです。あとは、ライブラリについても更新されていないパッケージなども見かけますので、メリットとデメリットを考えて採用するのがよさそうです。

適時開示情報のAndroidアプリをリリース

iOSでリリースしていた適時開示情報アプリのAndroid版をリリースしました。

Google Playからのダウンロードはこちらから

適時開示情報アプリは、TDnet・EDINETの適時開示情報をほぼリアルタイムでプッシュ通知で配信するサービスです。適時開示情報以外にも決算予定日のプッシュ通知も可能です。

アプリからは過去3年間の適時開示情報の検索や、決算予定日をカレンダーで見やすい一覧表示で確認できます。また、ウォッチ機能でお気に入りの株式銘柄の適時開示情報を一覧で確認することもできます。


今回、Android版はKotlinとJetpack Composeを利用し、ネイティブアプリとして開発しました。先行リリースしていたiOS版と同等の機能とUIにすることを目標として取り掛かりました。

iOS版ではマテリアルデザインを採用していたため、UIの構築もスムーズに進めることができました。KotlinはSwift似ている部分が多いため、言語についてもそれほど問題となりませんでした。Androidはさまざまなメーカーが端末を販売しているため、レイアウトまわりなど苦戦しそうなイメージでしたが、Jetpack Composeがその部分を上手いこと吸収してくれるため、想像より作りやすく感じました。

アーキテクチャは、さまざまありますが今回は初めてということもあり、無難にMVVMの構成とし、使用したライブラリなどは、Hilt、Retrofit、Firebase、Glance(ウィジェット部分)としました。iOSでの開発と違い、当然使用しているライブラリやアーキテクチャも違うため、いろいろ勉強になる部分もありとても新鮮でした。

iOS、Androidと別々の言語でネイティブアプリとして開発してしまうと、管理がとても大変になりますので、今後はFlutterやCompose Multiplatformも取り入れていきたいところです。

SSL化しました

Let’s EncryptがようやくPublic Beta Programになったので、SSL化してみました。
IdenTrust社のクロスルート証明書であるため、ほとんどのブラウザに対応しているのもいいですね。
無料で個人のサイトでも手軽にSSL化できるようになったので積極的に使っていきたいです。

証明書の発行方法もGitからダウンロードしたスクリプトを実行するだけというのもいまどきな感じ。
DebianならApacheなどへの導入もすべて自動で出来るようですが、CentOSではすべて自動化は無理なようです。近いうちに正式リリースされると思うので、そのときには自動化できるようになっていることでしょう。

証明書の有効期限は90日で、60日毎の更新を推奨しているようです。これはリリース後も変わりません。
期限を短くすることで秘密鍵の危殆化や誤発行による被害を抑えられるというメリットがあるようです。発行はスクリプトによる自動化させることで更新はたいした手間でなくなるとのこと。なかなか新しい試みですね。

しばらく使ってみて様子をみていこうと思います。

Let’s Encrypt
Let’s Encrypt 総合ポータル

類似画像を検索する方法を調べみた その1

Google画像検索のような類似画像を検索する方法を調べてみた。

ぱっと思いつくのは、色や形状などの特徴量を比較し、近似しているものを調べていく方法だ。
この手の情報やライブラリは、けっこう出回っているので、一番お手軽にできそうだ。

他のアプローチとしては、特徴量が近似していないものを探しいくことで、類似性を判定する方法もあるそうだ。

画像同士をつきあわせて類似性を判定するのではなく、彼らは問題をまったく別の角度から捉えた。彼らの方法では、ターゲットの画像(A)を大量のランダムな画像と比較して、それらと当の画像との、もっとも著しい違いを記録する(Ra)。そして、もう一つのターゲット画像(B)に対しても同じ記録を作成する(Rb)。この、RaとRbがほぼ同じなら、画像AとBは類似性が高いだろう。

重要なのは'類似'ではなく'違い'だ–まったく新しい着想に基づく高精度の画像検索アルゴリズム | TechCrunch Japan

画像検索とは

テキストキーワードを用いる方法を、TBIR(Text Based Image Retrieval)、
画像特徴を用いる方法を、CBIR(Content Based Image Retrieval)と呼ぶみたいだ。

TBIRは、画像にキーワードを紐付けて、そのキーワードに類似している画像を検索する方法とのこと。
今回はCBIRについて調べていきたいと思う。

形状特徴を利用する方法

検索していると「モノクロ画像検索のための形状特徴」という論文を発見した。
SobelやCannyフィルタを使ってエッジの抽出をおこなった画像を、3×3に分割し、各領域のエッジ割合を比較する、という方法のようだ。

気になったのがaHash(AverageHash)という方法だ。

この手法は「Perceptual Hash」という、「比較可能なハッシュ」を生成するための一手法です。

一般的にMD5SHA1などのハッシュ値は、1バイトでもデータが違えば、まったく違うハッシュ値を返してきますが、「Perceptual Hash」は似たようなデータには似たようなハッシュ値を返してきます。

簡単な画像の類似度計算手法「Average Hash」 » Untitled Blog

どんな方法だろうと見てみると、グレースケール化した画像を8×8ピクセルに縮小化し、色の濃淡でビット列を作成する、ということらしい。

「Average Hash」は以下のような手順で生成することができます。

画像のサイズを縮小 (ブログによれば8×8に縮小)
色をグレースケールにする
画像の各ビクセルを使って色の平均値を計算
それぞれのピクセルで色の濃淡を調べ、その色が平均値よりも濃い場合は1を、薄い場合は0を設定する
結果的に8×8=64ビットのビット列ができあがる

pHashというものもあるらしく、DCT(離散コサイン変換)を使っているらしい。
こちらはライブラリ化しており、下記のサイトからダウンロードできるが、ライセンスはGPLとのこと。
pHash.org: Home of pHash, the open source perceptual hash library

libpuzzle

libpuzzleというライブラリが有名なようで、使っている記事がたくさんヒットした。
PHPから簡単に使えるようになっているらしく、BSDライセンスで扱いやすそうだ。
ラッパーを作れば他の言語からも利用できると思う。
Libpuzzle – A library to find similar pictures

しかもけっこう速そうだ。

なお、うちのCoreSolo(初代Intel Mac mini)な環境では、
シグネチャの生成x100画像で大体1分~1分半。
近似度判定x10000回で大体2~3秒だったので、
シグネチャをどっかに保存しておけば、10万画像の線形比較くらいなら許容範囲ではないかと思われる。

激ニコぷんぷん丸のソースコード公開

先日リリースしたiPhoneアプリ「激ニコぷんぷん丸」のソースコードをGitHubにアップした。
特にいいアイデアも浮かばなかったので、多少インパクトがあって実績になりそうなものを作った。
が…色々迷走した挙句、よく分からないアプリが完成した。
使い道がないのでソースコードをGitHubにアップすることにした。
テストコードも書いてなく、あまりいいコードではないけど何かの参考になれば…。

Pinterestのようなインタフェースで、ツイッター上に流れているニコ動を表示するアプリ。
ツイッター上の動画情報は、ツイッターのストリームAPIを使ってリアルタイムに取得している。
クライアント部はObjective-C、サーバ部はRails3(Unicorn + NginX) + MySQL + Redisで出来ている。

とりあえず、クライアントアプリだけ。
pontago/objc-NicoTwi · GitHub

iOS7対応

面倒くさがりなのでブログを書く頻度が少なかったのだが、なるべく細かい記事でも書くことにした。
iOS7がリリースしてしばらく経つのだが、ようやくひと通りiOS7対応が終わった。
後手後手の対応で、アップデートが遅くなってしまいスミマセン。

今回はメジャーアップデートということもあり、大幅なUI変更が加わった。
流行のフラットUIに変わったことで、iOS6以前と両方サポートするアプリは少し大変かもしれない。

ステータスバーが一体化した

一番やっかいな問題が、ステータスバーの一体化だ。
ステータスバーをアプリから操作(色の変更や透過、表示・非表示)出来るようになったため、UINavigationBarなどと違和感なく一体化させることが可能になった。

今まではステータスバーを除いた座標から取得出来ていたのだが、iOS7からはステータスバーの20px分が含まれなくなった。このせいでデザインが崩れてしまうアプリがけっこうあると思う。

手っ取り早いのは、UIWindowをステータスバー分ずらしてしまうことだ。こうすることでiOS6以前と同じように処理することができる。

    if ([[UIDevice currentDevice].systemVersion floatValue] >= 7.0f) {
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
self.window.clipsToBounds = YES;
self.window.frame = CGRectMake(0, 20.0f, self.window.frame.size.width, self.window.frame.size.height - 20.0f);
self.window.bounds = CGRectMake(0, 20.0f, self.window.frame.size.width, self.window.frame.size.height);
}

UINavigationBarのbarTintColorやtintColorを変更することで、ステータスバーの色も変わる。

Objective-C – iOS7でナビゲーションバーやステータスバーの文字色を変える – Qiita [キータ]

UINavigationBarの戻るが変わった

前のビューに戻るときのボタンが「<」みたいなのになった。
そのため、backBarButtonItemなどにイメージ付きのUIBarButtonItemを設定すると、「<」分ズレて表示がおかしくなる。

僕は手っ取り早く、backBarButtonItemではなく、leftBarButtonItemに設定してしまった。
具体的には、UINavigationControllerを派生したクラスを作成し、pushViewControllerをオーバーロードする。
その中で、viewControllersをカウントし、0以上の場合(一つ以上ビューがプッシュされている)に、戻るボタンを設定する。例えば以下のようなコードになる。

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
if ([self.viewControllers count] > 0) {
viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]
initWithImage:[UIImage imageNamed:@"back.png"] style:UIBarButtonItemStylePlain
target:self action:@selector(popViewControllerAnimated_)];
}
[super pushViewController:viewController animated:animated];
}
- (void)popViewControllerAnimated_ {
[self popViewControllerAnimated:YES];
}

カスタムUIBarButtonItemのアイコン色が反映されない

iOS7になってから、ボタンに設定した画像がtinColor(青色とか)で塗りつぶされるようになった。
UIBarButtonItem内にUIButtonをカスタムビューとして設定していたのだが、tintColorで塗りつぶされなかった。
どうやら、UIButtonの種類が、UIButtonTypeSystemまたは、UIButtonTypeRoundedRectの場合にのみtinColorで塗りつぶしされるようだ。

EZ-NET: iOS 7 では標準ボタンの画像が tintColor で塗りつぶされる : iPhone プログラミング

UITableViewのセル背景色が反映されない

UITableViewCellの背景色は、デフォルトで白?になったようだ。
変更する方法は、tableView:willDisplayCell:forRowAtIndexPath:か、tableView:cellForRowAtIndexPath:のどちらかから、backgroundColorをclearColor(もしくは好きな色)に変更するといい。

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.backgroundColor = [UIColor clearColor];
}

疲れたのでこのへんで。

PHPからCOMを使う

WindowsでIEを制御するためにCOMを使ってみた。
PHPからCOMを使うのは簡単で、「php.ini」からCOMを読み込むように設定すればいいみたいだ。
PHP 5.3.15 / 5.4.5 以前は、デフォルトで読み込まれるらしい。

PHP: インストール手順 – Manual

# php.ini
[COM_DOT_NET]
extension=php_com_dotnet.dll

使いたいコンポーネント名を引数に指定して、COMオブジェクトを生成するだけで使える。
navigate関数で指定ページに移動したあとは、DOMを操作するだけだ。

ページ表示後の待機処理

navigate関数呼び出し後、表示が完了するまで待機するのだけど、いくつか方法がある。
busyプロパティを見て判断する方法が一番簡単なようだ。
しかし、この方法だとDOMが構築される前にループを抜けてしまうため、
DOM要素にアクセスするとエラーになる場合がある。

while ($com_ie->busy) {
com_message_pump(3000);
}

もう一つは、com_event_sink関数で読み込み完了のコールバック関数を指定する方法だ。

class IEEventSinker {
var $completed = false;
var $url = '';
function DocumentComplete(&$dom, $url) {
if ($url == $this->url) {
$this->completed = true;
}
}
function OnQuit() {
$this->completed = false;
}
}
$com_ie = new COM('InternetExplorer.Application', null, CP_UTF8);
$sink = new IEEventSinker();
com_event_sink($com_ie, $sink, 'DWebBrowserEvents2');
while (!$sink->completed) {
com_message_pump(3000);
}

この方法だと読み込み完了のイベントを処理できるので安全らしい。
実際に試したところ、後者の方法でもエラーになることがあった…。
com_message_pump関数を使って、処理待ち状態のメッセージを処理しながらループする。引数の値は、タイムアウトミリ秒だ。0を指定した場合、処理待ちメッセージがなければすぐにFALSEを返す。

ブラウザのビジー状態を判定するための,より良い方法 (WSHでIEを自動操作する際,COMのアプリケーションイベントを利用する) – 主に言語とシステム開発に関して

Yahoo検索を自動化する

Yahoo検索にアクセスしてキーワードを入力後、Submitするものを作ってみた。

<html>sample code.
<?php
class IEEventSinker {
var $completed = false;
var $url = '';
function DocumentComplete(&$dom, $url) {
if ($url == $this->url) {
$this->completed = true;
}
}
function OnQuit() {
$this->completed = false;
}
}
$com_ie = new COM('InternetExplorer.Application', null, CP_UTF8);
$com_ie->visible = true;
$sink = new IEEventSinker();
com_event_sink($com_ie, $sink, 'DWebBrowserEvents2');
$sink->url = 'http://search.yahoo.co.jp/';
$com_ie->navigate($sink->url);
$st = time();
while (!$sink->completed) {
com_message_pump(3000);
if ((time() - $st) >= 5) break;
}
$com_ie->document->forms(0)->p->value = 'php';
$com_ie->document->forms(0)->submit(null);

フォームのClickとSubmitが動かない

最初はWindowsXP上のIE7で試していたのだけど、Windows8上のIE10で上手く動作しない問題が起きた。
フォームの入力は正常にできるのだけど、ClickやSubmitが動作しない。

ググりまくったところ、下記のページを発見し、引数にNULLを指定したところ無事に動作した。
ちなみにIE9以降で起こる問題みたいだ。

Japanese user list of the Ruby programming language ()

参考コード
pontago/php-IECom · GitHub

タクトスイッチを使う

温度計にモード切り替え(最高温度、最低温度)を付けたかったので、タクトスイッチの使い方を調べることにした。
まずは、マイコンを使わずにタクトスイッチを使って、LEDを点灯・消灯させる。
かなり苦戦したのだけど、いろいろな人に教えてもらって何とか完成させることができた。

タクトスイッチの構造

タクトスイッチは押すとカチッと手応えのある押しボタンだ。押している間だけ電流が流れるらしい。
足は4本付いていて、押すことで2本の足が接続され電流が流れるという、単純な仕組みのようだ。

下記のページを参考にさせてもらった。
arduino使い方:スイッチの入/切でLEDを点灯

タクトスイッチを使ってLEDを操作

押している間だけLEDを点灯させる回路を作ってみることにした。
回路は下記のページを参考に組んでみた。

スイッチ回路 – Hinemos amo!

f:id:happytar0:20130729232254p:plain

マイコンを使ってタクトスイッチを操作

続いて、マイコンを使ってLEDを点灯させてみることにした。

PD3にLEDを接続、PD4にタクトスイッチのアノードを接続した。
PD4は入力になるため、DDRDレジスタでビット4を0にし、PD3は出力で使うため、ビット3を1にする。
また、内部プルアップを有効にするために、PORTDレジスタのビット3を1にする必要がある。

これは、スイッチがどこにも接続されていない場合、電圧が不安定になり0なのか1なのか分からなくなってしまうためだ。
それを防ぐためにスイッチとの間に抵抗をはさむ必要があり、これをプルアップ抵抗という。
AVRの場合、プルアップ抵抗を内蔵しているので、上記のようにPORTDレジスタのビットを立てると、内部プルアップ抵抗が有効になる。

あとは、bit_is_clear関数でPD4が0になっているかチェックし、LEDの点灯させるだけだ。
bit_is_clear関数は、第一引数にチェックするレジスタ(PORTBやPORTD)、第二引数にチェックするビット番号を指定する。
0になっているとtrueが返ってくる。同様にbit_is_set関数は、1になっているとtrueが返ってくるようだ。
この関数を使えば、簡潔に記述することができる。

この回路はそのものズバリのものが、下記ページに書いてあったので参考にさせてもらった。
LED点灯 ( IO入出力 )
top

長押しを判定する

スイッチのオン・オフの他に、長押しを実装してみることにした。
モード切り替えはスイッチの短押しを使って、最高温度や最低温度は長押しでリセット出来るようにするためだ。

長押しを判定するためにタイマ割り込みを使うことにした。しかし、とてもむずかしく長い道のりとなった…。
短押しと、長押しを判断するのがこんなにむずかしいとは思わなかった。
普段何気なく使うことが多いのだけど、単純な仕組みでも一から作るとなるとむずかしいもんだ。

回路図は下記のようになった。
f:id:happytar0:20130729234821p:plain

タイマ割り込みを使う

タイマ割り込みについては説明しているサイトがたくさんあるので割愛する。
下記のサイトが詳しく説明されていて参考になった。
http://d.hatena.ne.jp/hijouguchi/20100620/1276997802

簡単に説明すると、カウンタ用レジスタ(TCNT0)があって、一定周期毎にこのレジスタの値がカウントアップされていく。
レジスタがオーバーフローしたり、設定した値に到達すると割り込みハンドラが呼び出されるというものだ。

大事なのは、クロック数と分周比から何秒ごとに割り込みハンドラが呼び出されるのかを考えることだと思う。
今回は、ATTINY2313を8MHzで動かしていて、分周比を1024に設定した。(他にも256や8など設定できる)
分周比は、「TCCR0B」レジスタで設定する。分周比1024なので、0b00000101となった。

8000000(8MHz) / 1024(分周比) = 7812.5Hz

となるので、1秒間に7812回動作するとうことだ。

1(秒) / 7812(Hz) = 約0.000128秒 = 0.128ms(ミリ秒)

0.128ms毎にカウントアップされていくことになる。
今回はカウンタ用レジスタがオーバーフローしたときに割り込みハンドラを呼び出すようにした。
カウンタ用レジスタは8ビットなので、256になるとオーバーフローすることになる。

0.128(ms) * 256 = 32.768ms

約32ms毎に割り込みハンドラが呼び出されるようだ。

タイマ割り込みの実行

タイマ割り込みを使うためには、「interrupt.h」をインクルードする。
TCCR0Bレジスタ、TIMSKレジスタを設定した後、sei関数を呼び出すことで実行される。
sei関数は、全割り込みを有効にさせる関数だ。逆にcei関数は、全割り込みを無効にさせる関数になる。

割り込みハンドラは、ISRマクロを使って設定する。
最初ISRは関数だと思ったのだけど、よくよく見てみるとマクロになっていた。
第一引数に使うハンドラ名を入れる。
今回は、TIMER0のオーバーフローということで以下のようになった。

ISR(TIMER0_OVF_vect) {
}

長押しをどのように判断するか

短押しと長押しを区別させるのに苦労した。
32ms毎にスイッチが押されているか判断し、3秒以上押されてい場合に、長押しと判断させることにした。
3秒未満でスイッチを離した場合は、短押しとなるようにした。

押した場合に判定させてしまうと、短押しと長押しのイベントが同時に起きてしまい、動作がおかしくなってしまう。
なので、長押しと区別させるために、短押しの場合、リリース時(スイッチを離した時)に判定させることで上手くいった。

また、長押しされている時間を調べるために変数にハンドラ呼び出された回数を保存することにしたのだけど、ずっと長押ししているとカウンタ変数がオーバーフローしてしまい誤動作するということがあった。
カウンタ変数を大きいものして一時しのぎすることも出来るのだけど、今回は「長押し判定時間の3秒を越えた場合、カウントアップさせない」ことで対応した。

今回のコードもGitHubにアップした。
pontago/avr-TactSwitchTest · GitHub

参考サイト
[AVRexample] Timer0 オーバーフロー | 花夢電科雑多猫
無機物の週末 超☆ゆとり的電子工作 その2!
http://d.hatena.ne.jp/hijouguchi/20100620/1276997802
http://www.hokutodenshi.co.jp/PUPPYSupportPage/ensyu/timer/timer2.html
AVR timer(1) | stastaka's Blog
タイマ/カウンタ1を使う
[example] 外部割り込み – 花夢電科雑多猫 マニュアル&メモ
つくろぐ 技術系