なら@はてなブログ

福岡で働くスマートフォンエンジニア(おっさん)のブログ。更新頻度がとにかく低いのが悩み。

画面の外からメニューをニュッと表示させたい

iPhoneAndroidのアプリを両方作ってると、AndroidのUI設計・開発のやりづらさが身にしみます。


今回は、画面下のボタン(というかタブをイメージ)をタップすると、下からメニューがニュッと出てくる処理の実装について。まあ、イベントはタップじゃなくてフリックでもいいと思うんですが、今回は主にレイアウトの作り方の話。
一般的によく使いそうですが、ぐぐってみてもあんまり情報がありませんでした(日本語で調べてるからでしょうかね?)


もちろん、デフォルトでメニューキーに関連付けたイベントでメニュー出せば同じ動きになりますが、それだとメニューがかなり味気ない見た目になって、レイアウトの自由度が非常に低いんですね。


上記の要件から、僕は単純に「画面の下にメニューのViewを待機させておいて、イベント拾ったら上に動かせばいいんじゃね?」と考えたんですが、なかなか一筋縄ではいかず。
結果的に、上記の仕様で実装はできたのですが、ちょいとハマりそうなところを。


下から画面にフェードインさせるViewの、直接の親ViewがRelativeLayoutの場合、うまくいきません。
どうやら、RelativeLayoutは、そもそも自分の領域外にあるやつが入ってきても描画してくれない模様。

なので、下記レイアウトでLinearLayoutを上に動かしても、もともと表示されているボタン部分だけがむなしく上に動きます。
f:id:narazoro:20101024030313p:image


で、僕のやり方が最適解だとは思っていませんが、ぱっと思いつくひとつの解法としては、LinearLayoutの中身を動かす方法があります。
XMLの構造(親子関係)は上と同じですが、配置場所と動かす対象が違います。
f:id:narazoro:20101024031154p:image

これで、アニメーション対象をボタンと隠しViewに設定して上に動かしてやれば、目的の動きが実現できます。



で、ここでの注意点。かなり有名な話ですが。
Animationを使ってViewを動かした場合、確かに見た目は動いているのですが、タッチイベント等を拾える領域は動いていません
要するに、Animationを使って画面外からメニューを動かしてきても、メニュータップ等のイベント領域は画面外に残ったままなのです

じゃあ、どうするかというと、Animationが終わったタイミング(AnimationListenerで拾えます)で、実際に隠しメニューの座標を画面内に変更する。
または、下から出てくるViewは演出のためのダミーと割り切って、実際にタップさせるViewは最初から画面内に配置しておいてHiddenにしておく、等の対応で行けると思います。
・・・“思います”と書いてるのは、まだ自分で実際にやってないからですが。。。


本日はこんな感じで。
しかし、週末というのに家でコーディングとか、やっぱワーカホリックかもしれませんねえ。。。

AppleKnight BasicDriveDelegate に参加してきました

10月から福岡に戻ってきまして、今後はこっちがメインになるので、地元のイベントやエンジニアの寄合には顔を出していこうかなー? と。
まあ、月に1、2回くらいの割合で東京出張になりそうな悪寒はしていますが。


さてさて、AppleKnightっつーのは、福岡のiPhone開発者・デザイナーさんのコミュニティです。
だいたいこういうのはF社の人が中心になってますな。いつもお世話になっております。


で、「19:00にAIP Cafeだから18:40には会社出ますよ!」って言ってんのにさ、それでも19:00まで打ち合わせが入ってそれが時間オーバーするという。
もうね、日本人は会議開始時間には厳しいくせに、終了時間には甘いというかメリハリがないというか。ダメだなあ。それは会議だけじゃなくて仕事全体に対してもそうね。


グチはさておき。


ということで、遅れて参加。LT終わっとるし。
なにげに、発表者少なかったら飛び入りでなんかしゃべろうという野望もあったのですがw。


で、せっかくなんでブレストでもしようみたいな流れで、今回は「Coolな時計アプリを考えてみる」というもの。
いろいろ出た案の中では、「あれ? これアプリ出したら売れるんじゃね?」と言えるものもあり。
やはり人が集まると何かが出てきますね。うん。


あとは酒飲みながらゆるーい感じでくっちゃべってました。
えーとね、福岡近辺でiPhone開発やってる人(特に個人)は、参加してみるといいですよ? 人脈重要。



例によってまとまりのない記事ですが、こんな感じで。リンク貼っとこう。

AppleKnight(アップルナイト)は、iPhone/iPod Touch, iPad のアプリケーションに興味をお持ちの方々の情報交換を目的とする、福岡を拠点としたコミュニティです。

しかし、このブログ訪問者少ないっつーか、ほぼ絶海の孤島なのでリンク貼って意味あるかなーw?
たまにGoogle検索からたどり着く人がいるくらいのようです。始めたばっかりやしね。

SQLiteでINSERTが激しく遅い件

さて、表題そのまんまです。
で、残念ながら高度なチューニングなんかの話ではなく、おそらく基本的な話です。けど、知らないとハマるかも。


とあるAndroidアプリの開発で、テーブルに20万件ほどのレコードを登録しないといけなくなりました。
で、Androidアプリで使うDBはSQLiteなので、SQLiteでテーブルつくってINSERTをループさせる方法で実装。


くっそ遅い!!


INSERT終わるまで1時間以上かかるんですわ。


で、ちょっとググってみたところ、SQLiteのINSERTメソッド、内部でトランザクション管理してるみたいです。
なので、以下のようにやっちゃうと20万回トランザクションを開始→コミットってやってて、そのコストが激しくバカにならない。

SQLiteDatabase dbh;    // 適当にインスタンスを生成しておく

for (int i = 0 ; i < 200000 ; i++) {
    // 適当にINSERT
    dbh.insert(table, nullColumnHack, values);
}


まあ、ここまで書いた時点で解決策は出てますが、トランザクション管理を明示的にやります。

SQLiteDatabase dbh;

// トランザクション開始
dbh.beginTransaction();
for (int i = 0 ; i < 200000 ; i++) {
    // 適当にINSERT
    dbh.insert(table, nullColumnHack, values);
}
// コミットしてトランザクション終了
dbh.setTransactionSuccessful();
dbh.endTransaction();

件数が多いと、めっさ速くなります。
というか、そもそもが遅すぎただけなんですが。


Oracleさんとかだと、大量のundoためた状態でコミットするとけっこうな時間を取られますが、SQLiteは上記の20万件程度ならコミットも一瞬です。
内部実装ってどうなってんだろ? SQLiteは普通にファイルに保存してるはずだから、トランザクションコミットするたびにファイル開いて書き込んでFlushしてるのかなー?

gdd2010に行ってきました

今年はちょうどいいタイミングで東京出張だったので、gdd2010に参加。
本職プログラマなので、当然スーパーハカー枠で。

前日の勤務が長引いて朝5:00とかまで働いてたので、午前の基調講演は豪快に寝倒し。だめだわー。

午後からはAndroidセッションに張り付いてました。
まとめ記事とかはもっと上手にまとめてくれてる人がいるので省略(訳:めんどくさい)。

とりあえず、Tim Brayの話が面白かった。さすがや。
あれですね、同時通訳がけっこうへぼかったのもありますが、英語の講演は英語のまま聴いたほうが絶対に面白いです。


と、相変わらず中身のない雑記。

SIGKILLの後でアプリが動かなくなる件

さてさて、iPhoneアプリ開発の話でございます。

これまでも開発してるアプリのテスト中に何度かアプリが起動しなくなる(というか入力を受け付けなくなる)現象が起こってました。

で、その状況が

  1. Xcodeからアプリを実行させる(command + r)
  2. アプリを終了させる
  3. ホームボタン2度押しでマルチタスクリストを出して、裏に入ったプロセスを切る(コンソールにSIGKILLの表示)
  4. もう1度アプリを起動する(普通にアイコンをタップ)
  5. アプリが動かない
  6. アプリを終了する
  7. ホーム画面が真っ黒

って感じで、「うわー、こんなバグかかえたままリリースなんかできねえ」と思って(当たり前ですね)、いろいろテストしてたんですが、どうやらこれ、MacとUSB接続した状態でプロセス切るとたまに起こるみたいです。で、USB接続せずに、普通にホーム画面からアプリを立ち上げると、同じ操作をしてもまったく再現しない。

という、盲点でした。
うん、実際、今日この症状が出たアプリなんか、作り始めたばっかりでiAd表示してる程度の処理しかしてなかったんで、そんな致命的なエラーが出る場所なんか思いつかなかったんですよ。ハマったハマった。

うーん、ひょっとして、EXC_BAD_ACCESS調査用に

NSZombieEnabled=YES

とかに設定してるのも関係してるんじゃろか。
ZombieプロセスがiPhoneに残りっぱなしになっちゃうのかな?

AppStoreに2本目のiPhoneアプリを登録したの巻

2本目はGPSを使って似非ARっぽく情報を表示させるアプリ。

今のところ、ARを実現する方法って、やっぱGPSベースかタグ認識ですかねえ。

で、このアプリ、いろいろがんばって処理を詰め込んだ結果、iPhone3GSだとメモリ食い過ぎる子に育ってしまい。いや、動くんですけどね、メモリが厳しくなるとバックグラウンドからの復帰時にカメラが準備できないままなんです。ARでカメラ表示できないのはきつい。
カメラ作り直しとかいろいろ試してみたんでが、UIImagePickerControllerってけっこうクセがあって、なかなかうまくいかず。

もう開き直ってiPhone4専用にしようと。
けど、システム的にiPhone4以外にインストールさせないってのがわからないのです。
iOS4以上っていう指定はできるんですけどね。

で、ターゲットの設定で、アーキテクチャをarmv7だけ指定して、せめてiPhone3Gとかには入れないようにしてみたんですが、それやると、iTunes Connectにバイナリをアップロードした後で、ステータスが「Invalid Binary」になってしまいます。
最初は別の理由があるんだろうと思ってましたが、どうやら上記が原因らしく。アーキテクチャをarmv6, armv7両方指定してやるとめでたく登録完了。


ま、バックグラウンドで動いて位置情報を更新しまくるアプリなんで、「バッテリー食い過ぎ」でリジェクトされる可能性が高いんですけどね。
まあ、なにごとも勉強。

Android Marketにアプリケーションを登録してみたの巻

そのまんまです。
AppStoreへの登録に比べたらずいぶん簡単でしたー。

とはいえ、ハマりどころもあったので記録。

手順はだいたい公式サイトに載ってます。
Android Developers

まずアプリケーションへの署名。これはEclipse+ADTで開発してたら簡単。
まあ、よっぽどCUIが好きな人かよっぽどのドMじゃない限り、Eclipse使って開発してるだろうから省略。注意点は、証明書の有効期限を2033年10月以降に設定することくらい。

で、アップロードについても簡単。
作成したパッケージを選択してアップロード。それだけ。説明文は、英語の説明文を書くのがしんどいくらいですね。英語についてはがんばってとしか言えないです。

唯一のハマりどころ。
Log。
「ソース コード内に Log メソッドへの呼び出しがある場合は、無効にします」

で、これどうなんでしょうね? いちおうLogクラスのリファレンスみたら、こう書いているわけです。

Verbose should never be compiled into an application except during development. Debug logs are compiled in but stripped at runtime. Error, warning and info logs are always kept.

「VERBOSEは開発用ビルドの時しかコンパイルされないよ。DEBUGはコンパイルされるけど、実行時にはじかれるよ」

おいらは、基本デバッグ用のログはDEBUG、Exceptionをcatchした時に、処理継続する場合はWARN、アプリを異常終了させるときはERRORと自分内ルールを決めてるので(つか、当たり前や)、たぶん大丈夫なんだろうと思いつつ、初めてのAndroidアプリ登録だったので(会社名義だし)、デバッグログをコメントアウト。普通にandroid.util.Logのimport文をコメントアウトして、赤くなったところをコメントアウト。あほくさい。

で、みなさん似たようなことやってるようで、ひとつのソリューションとしては、isDebuggable の値を参照してtrueならログ出す、falseならログ出さない、というLogラッパーを作ることみたい。


まあ、次に個人で出すときはGoogleさんの言う事を信じてDEBUGはそのまま書いておこう。


ちなみに、登録するアプリに対して、登録時にGoogleさんはノーチェックです。
たぶん、説明文に「うんこうんこうんこー」とか書いても掲載されると思います。試してないけどw。