ちょっとH!なホッテントリビューワー Hotentor をリリースしました。
タイトルは釣りです。
最近ギョームの方でAndroid書いていたりするので、頭の整理も兼ねてAndroidアプリを作ってみました。
スクショはこんなです。それにしても錦織くんすごいですね。同い年とは思えんです。
H!なのはアイコンだけですごめんなさい。
というわけで、Hotentorという、ホッテントリビューワーを作ってみました。
画面全体を使ったダイナミックなUIが特徴的で、Android版Pressoは一向に出る気配がないし、公式アプリは飽きてきたし、という方にオススメです。
また、コメント閲覧、ブックマーク、ウェブページ表示といった基本的な機能も備えています*1。
サポートしているOSバージョンはAndroid 4.0.3以降です*2。動作確認に、P-04Dという化石のような端末*3を使っているため、画像を多く使っている割に、動作は軽快だと思われます。
余談
ネットワーク通信とかAsyncTaskLoaderとかを一度整理しておきたいなと思い、適当にRSSリーダーを作り始めたのですが、やってみたら意外と形になったので、公開に至ったという感じです。
帰宅後の数時間でちまちまコーディングし、デイリービルドを実機にインストール。通勤時に、実機テストを兼ねて実際に使いつつ、UIとか機能とか考えたりしてました。
開発期間に関しては、あまり意識していませんでしたが、ソースコード見ると8月29日って日付が見当たるので、10日程度のようです。
技術的な話
Android開発には、言語にするには多すぎる細かいノウハウがたくさんあるのですが、出来る範囲内でざっくり共有したいと思います。
開発環境
- PC:MacBook Pro Rethina, 13-inch, Late 2013, OS X 10.9.4
- IDE:Android Studio 0.8.6
- ギョームでもEclipse+ADT→Android Studioの流れは確実に来てます。Intelli Jはリファクタ機能が超強力で、これなしのJava開発は考えられないレベル。
- 実機端末:P-04D
- エミュレータ:Genymotion
- とにかく早い。登録とかは少し面倒。個人利用はフリー。
- デザインツール:Keynote, Sketch3, Pixelmator
- 素材:iconfinder
デザイン
素材作成
どう作るのがベストなのか未だに分かってないのですが、とりあえずx基準で作成し、書き出しの設定で各種サイズを一気に書き出すようにしました。
また、素材はちょいちょい修正が入る可能性があるので、最初はxだけをresフォルダ下に配置して、最終段階で残りを配置するというフローが良さそうです。
UI/UX
ブクマ数表示する部分の文字に若干影つけてるとか、最上部のエントリでない場合にエントリーリストの上に少しスペース空けているとか。
開発
ライブラリ
- Volley
- Google製ネットワークライブラリ。
- jsoup
- HTMLパーサー。
- Joda-Time
- Picasso
- Square製ネットワーク画像ライブラリ。Android版SDWebImage的なもの。神。
- Otto
- Square製EventBusライブラリ。GuavaのEventBusのfork。
Squareのライブラリは素晴らしいので要チェック。
Singleton vs. static method
これはJavaの書き方の問題ですが、私は割りとSingletonではなくstatic methodをガンガン使う派です。
純粋なOOP的には原則Singleton使うのが正解なのだと思いますが、継承*4する予定が無いなら、static methodの方が記述量減るし、いいんじゃないかなぁと思っています。
Utilityクラス
まぁこれもOOP原理主義の人には怒られそうな話ですが、この程度の小規模なアプリなら、過剰な抽象化せずとも、とりあえずUtilityクラス作ってみるでいいんじゃないかなと思います*5。
一部のUtilityをGistにアップしたのでリンク貼っておきます。
Appクラス
Androidあるある「アプリケーションコンテキストを取得したい」。
アプリケーションコンテキストは、Activity#getApplicationContextで取得できますが、それよりも、Applicationを継承したクラスを用意するのが良いと思います。
今回作成したAppクラス。
package jp.hateblo.shin.hotentor; import android.app.Application; import android.content.Context; import com.android.volley.RequestQueue; import com.android.volley.toolbox.Volley; import com.squareup.otto.Bus; /** * Created by shin on 2014/08/30. */ public class App extends Application { private static Context sAppContext; private static RequestQueue sRequestQueue; private static Bus sMainBus; @Override public void onCreate() { super.onCreate(); sAppContext = getApplicationContext(); sRequestQueue = Volley.newRequestQueue(sAppContext); sMainBus = new Bus(); } public static Context getAppContext() { return sAppContext; } public static RequestQueue getRequestQueue() { return sRequestQueue; } public static Bus getMainBus() { return sMainBus; } }
AndroidManifest.xmlの設定も忘れずに。
<?xml version="1.0" encoding="utf-8"?> <manifest ...> <application ... android:name=".App"> ... </application> </manifest>
フラグメント間通信
Hotentorでは、メインのViewPagerと右ドロワーのListViewが状態を共有しているのですが、それぞれ違うフラグメントに属しています。そのため、何らかのフラグメント間通信が必要となります。
一般的に、フラグメント間通信を実現するには、Activityを経由するか、グローバル変数を使う方法がありますが、Activityと密結合になるのは少し気持ち悪いのと、グローバル変数はまぁ避けたいかな、ということで今回はEventBusでやってみました。
結果として、Event受信側でUIがセットアップされていないのに更新しようとしてヌルポというのが起きがちということ以外は、今のところ問題なさそうです。とはいえ、ベストとも言い難いのでもう少しいい方法を模索したい所ですが。
AsyncTask vs. AsyncTaskLoader
昔からあるAsyncTaskと、API Level 11で導入されたAsyncTaskLoader、ググってみるとAsyncTaskを使うの止めてAsyncTaskLoader使いましょうという情報がちらほら。AsyncTaskはdeprecatedだみたいな話も見つかります。
が、色々調査した結果、これらは使い分けるものであるという結論に至りました。基準は以下の通りです。
- AsyncTaskは非同期で永続化処理を行うのに適している。
- AsyncTaskLoaderはActivity/Fragment内のビューに表示するデータを読み込むのに最適。
- 逆に永続化処理をバックグラウンドにするのにAsyncTaskLoaderは適していません。というのも、AsyncTaskLoaderは処理した結果をキャッシュすることを意図とした設計になっているためです*7。
Hotentorでは、以下のような使い方をしています(言葉で書くとアレですが)。
- エントリ一覧を表示するFragment#onCreateでgetLoaderManager().initLoader。
- Fragment#onCreateLoaderでLoaderクラスをnewしてreturn。
- Loader#onStartLoadingで、キャッシュ*8を確認。あればdeliverResult、無いならforceLoad。
- LoaderのloadInBackgroundに非同期処理したい同期的な処理を記述。結果をメンバ変数にキャッシュ。Fragment#onLoadFinishedへ。
- Fragment#onLoadFinishedで非同期処理の結果を描画。もし結果がnullだったらネットからデータを取得し、その後getLoaderManager().restartLoader。
- 強制リロードしたいときはgetLoaderManager().restartLoader。
Drawableの色変更
右ドロワーでは、アクティブのエントリを枠で囲んでいます。
これが意外と面倒でした。色の数だけ、枠線のdrawableを作るというのはナンセンスなのでプログラム側で何とかしたいものです。
で色々悩んだ結果、Drawable#setColorFilterが使えることが分かりました。こんな感じです。
Drawable bg = getContext().getResources().getDrawable(R.drawable.bg_sublist_item).mutate(); bg.setColorFilter(categoryColor, PorterDuff.Mode.SRC_IN); ViewUtil.setViewBackground(borderView, bg);
テスト
大変申し訳無いことにテスト0行、テストカバレッジ0%という輝かしい結果を残しています。まぁ規模ちっちゃいしモデル層が無いので許してください。
ちなみに、Androidでテスト書くなら、エミュレータ不要且つJUnit4が使えるRobolectricがオススメです。会社のAndroid Studioでは依存順の問題で色々面倒な事が起きたのですが、家のPCでは今のところそういった問題は発生してないです*9。
アプリ公開
署名済みAPK
KeyのAliasは何を入れてもOK。
Proguard
"app/build.gradle"を編集。
android { ... buildTypes { release { runProguard true // ← proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
"app/proguard-rules.pro"を編集。
今回はこんな感じです。
-dontwarn com.squareup.okhttp.** -dontwarn org.joda.time.** -keepclassmembers class ** { @com.squareup.otto.Subscribe public *; @com.squareup.otto.Produce public *; } -keeppackagenames org.jsoup.nodes -keep public class org.joda.time.** { public protected *; }
Proguardして困るのはリフレクション使っている部分なわけですが、問題なのはサードパーティのライブラリで、どこで使われているか把握するのが結構難しいです。
今回は、署名済みAPKを生成→adb intall
で実機へ送信*10→動作確認→落ちる→ログ見てググる→公式の設定があれば利用、無いなら適当に書く、という感じでリストを作成しました。
開発者登録
アプリ公開にはGoogle Playのデベロッパー登録が必要です。
クレカで25$のお布施が必要ですが、Appleの年額7800円に比べれば、ちょろいものですね。
*1:が、これら全てインテントで外部アプリへ丸投げする方式をとっているので、良く言えばAndroidらしいアプリ、ぶっちゃけた表現をすればRSSを表示しているだけのガワアプリです。
*2:Android 2.xは実機端末を持っていないので切りました。もう2.xは切っていいよね?
*3:パナソニックの今は無きELUGAの最初期の端末(正確にはELUGAブランドではないらしい)。バッテリーを致命的なまでに犠牲にすることで、厚さ7.8mm、重量103gという小型・軽量化を実現。2.3から4.0のアップデートが危ぶまれるレベルの端末で、度重なるアップデート延期には呆れたものです。
*4:だいたいSingletonの継承は考えないといけないこと多いし面倒。 http://www.hyuki.com/techinfo/singleton.html
*5:ゆーてもApache Commons LangにしてもGuavaにしても巨大なUtilityの塊なので、こういうの使わないなら、それなりにUtlityっぽいクラスが出来るのは仕方がない気もしますが
*6:GCDの直列キューの考え方と同様
*7:あくまでも"Loader"ですしね
*8:Loader側でメンバ変数に格納
*9:Android Studioのバージョンの問題もあるかも
*10:Android Studioだとリリースビルドを実機へ直接インストールする方法が無い