2009年11月25日水曜日

はじめての Wikipedia に携帯 IPアドレス一覧

携帯向けのサイトを作っていると、アクセス元の IPアドレスで携帯かそうでないかを判断する仕組みを導入することがあります。

いろいろ試行錯誤してこんなページを Wikipedia に作っちゃいました。
http://ja.wikipedia.org/wiki/Mobile:GatewayIP


(以下 Wikipedia にページを作った経緯など)

IPアドレスリストは各キャリアがそれぞれのサイトで公開しています。

docomo(NTTドコモ)
http://www.nttdocomo.co.jp/service/imode/make/content/ip/

SoftBank(ソフトバンク)
http://creation.mb.softbank.jp/web/web_ip.html

au(エーユー)
http://www.au.kddi.com/ezfactory/tec/spec/ezsava_ip.html
「※本情報はEZサーバ以外のホストによる上記表のIPアドレスでのアクセスがないことを保証するものではありません。」とか書いてあるのであまり使っちゃいけないかもしれないですね。。

かなり前もって情報は公開されるようなので普段気にしていればいいのですが、つい忘れてしまいがちです。(au は妙にコピペしずらいし)

そういう情報をまとめているサイトが無いか、プログラムから利用しやすいウェブサービスは無いかと探しますが、なかなか見つかりません。

プログラマなんで、各キャリアのサイトをスクレイピングして CIDR 形式のリストを抽出することが思いつきます。

自分で書く前に情報を探すと Net::CIDR::MobileJP という CPAN モジュールを使った素晴らしい情報に出会います。
http://dsas.blog.klab.org/archives/51117561.html

早速試してみましたが、最近 SoftBank の URL や構造が変わったようで情報が取れません。
そこで Net::CIDR::MobileJP とそのプラグインのソースを見てみると数行の洗練されたコードが見つかります。

これなら参考にして作れると思いますが、待てよと。
この行為はいろんな人が同じことやってるんだろうなーとか、またキャリアのサイトの構造が変わったらやだなーとか。

やっぱり公共性の高いスクレイピングできるまとめページがほしい。
というわけで Wikipedia に恐る恐るページを作っちゃいました。

決してスクレイピングしやすくは無いですが、ひとまず。。

2009年11月19日木曜日

LDReader 記事詳細の WebView をちょっと修正したり

LDReader 0.0.9 をリリースしました。
・記事詳細の WebView でリンクをクリックしたときにダイアログ表示
・ProgressDialog の表示後の処理を修正

記事詳細の WebView でリンクをクリックしたときにダイアログ表示

記事詳細では間違ってリンクをクリックしてしまってブラウザが起動してしまい、非常にストレスを感じていましたが確認用のダイアログを表示することで少し軽減されるようにしました。ソースはこちら。
// NOTE: WebView#setWebViewClient を使用
bodyView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, final String url) {
// NOTE: 設定でリンクを無効にした場合はそのまま終了
if (ReaderPreferences.isDisableItemLinks(getApplicationContext())) {
return true;
}
// NOTE: ダイアログ表示
new AlertDialog.Builder(ItemActivity.this)
.setTitle(R.string.msg_confirm_browse)
.setMessage(url)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// NOTE: OK がおされたら Intent を発行
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
}).show();
return true;
}
});

WebViewClient#shouldOverrideUrlLoading は true を返すことで、WebView の標準を動きをオーバーライドすることができます。

ProgressDialog の表示後の処理を修正

ProgressDialog を表示後に Handler#post を使っていた箇所をすべて Thead を使うようにしました。
実はここ、最初は Thread だったんですが Handler は post の他に postDelayed という遅延実行のメソッドも持っており、Thread のような動きをするので、「あ。こっちでいいのか」とつい思い込みをしてました。

意図しているのは
   プログレスバー表示

重い処理

終了

だったんですが、Handler#post を使うと
   重い処理

プログレスバー表示(一瞬)

終了

になっちゃいます。

ここに詳しい解説がのってました!
http://www.adamrocker.com/blog/261/what-is-the-handler-in-android.html

// NOTE: 修正前
final Handler handler = new Handler();
final ProgressDialog dialog = new ProgressDialog(this);
dialog.show();
handler.post(new Runnable() {
public void run() {
// NOTE: ここで遅い処理
// (some code)
// NOTE: 終わったら ProgressDialog を閉じる
handler.post(new Runnable() {
public void run() {
dialog.dismiss();
}
});
}
});

// NOTE: 修正後
final Handler handler = new Handler();
final ProgressDialog dialog = new ProgressDialog(this);
dialog.show();
new Thread() {
public void run() {
// NOTE: ここで遅い処理
// (some code)
// NOTE: 終わったら ProgressDialog を閉じる
handler.post(new Runnable() {
public void run() {
dialog.dismiss();
}
});
}
}.start();

2009年11月16日月曜日

LDReader フィード一覧の ListView, ListAdapter にハマる

ども。こっそり version 0.0.8 までバージョンアップしました。

[0.0.6] ListView とデータベースとの不整合でたまに例外発生する問題を解消したつもりが失敗。
[0.0.7] android.widget.CursorAdapter を使うことで修正、等
[0.0.8] フィード一覧からピン一覧を開くと、背景が真っ暗になる問題を修正。


[0.0.7] での主な変更

version 0.0.7 以前では、操作していると次のような例外がたまに発生しました。
java.lang.IllegalStateException:
The content of the adapter has changed but ListView did not receive a notification.
Make sure the content of your adapter is not modified from a background thread, but only from the UI thread.

この例外はデータベースのレコード数と ListView のレコード数に違いがあるときに発生します。
LDReader はバックエンドのサービスでレコードを挿入したりしているので自前で色々していたのですが、付け焼刃ではどうしてもこの例外を抑えることができませんでした。
java.lang.Object
↳ android.widget.BaseAdapter
↳ android.widget.CursorAdapter
↳ android.widget.ResourceCursorAdapter

BaseAdapter を継承してゴニョゴニョしていたのですが、探してみると CursorAdapter といクラスがあるではありませんか!

ソースをのぞいて見るとデータベースの更新通知を受け取ってクエリを再発行し、ListView との不整合を解消しているようです。さらに ResourceCursorAdapter では、行ごとの View をリソースを使ってカスタマイズしている場合にピッタリなようです。ApiDemos を見ながら四苦八苦して作ったのですが、まだまだ知らないことがたくさんありそうです。

ちゅうわけで主な変更はこちら。SubscriptionActivity (diff r5)


[0.0.8] での主な変更

Cursor#deactivate を自前でやっていたのですが、Activity#managedQuery を使っていれば deactivate と requery は勝手にやってくれるようです。削除しました。

ちゅうわけで主な変更はこちら。SubscriptionActivity (diff r6)

2009年11月15日日曜日

LDReader ピンに対応しました

関連記事

ども。LDReader をピンに対応させて ver 0.0.5 としました。このブログにコメントいただいちゃったので^ ^
以下スナップショットです。



ちょと見づらいですが、記事タイトル右にピンアイコンがあります。このアイコンをクリックするかメニューから「Pin」を選択するとピンをつけたり外したりできます。

ピンは本家同様に一覧でみることができ、ブラウザで閲覧することができます。
その他、記事 1件の URL をクリップボードにコピーしたり、全件の URL リストをメールで送れるようにしときました。

簡単なバージョンアップですが、今回は少し悩んだ点があります。

単純に実装するとすれば、ピン操作は常にサーバに対して追加や削除を実行し、一覧は最新をサーバから持ってくれば良いです。しかし、これだと地下鉄のようなオフライン環境ではピンを追加することができません。

そこで次のような pin テーブルを作成して、ユーザの操作をまず action として保存し、それから通信を行うようにしました。追加の場合は 1:ACTION_ADD, 削除の場合は 2:ACTION_REMOVE, サーバから取得した場合は 0:ACTION_NONE とします。
create table if not exists pin (
_id integer primary key,
uri text,
title text,
action integer, -- 0:ACTION_NONE, 1:ACTION_ADD, 2:ACTION_REMOVE
created_time integer
)

ピンの追加はロジックはこんな感じ。
public boolean pinAdd(String uri, String title)
throws IOException, ReaderException {
if (!isLogined()) {
login();
}
try {
ContentResolver cr = this.context.getContentResolver();
// NOTE: 事前に uri が重複するピンアクションを削除
cr.delete(Pin.CONTENT_URI, Pin._URI + " = ? and " + Pin._ACTION
+ " > " + Pin.ACTION_NONE, new String[]{uri});
// NOTE: ピンアクションを追加。
ContentValues values = new ContentValues();
values.put(Pin._URI, uri);
values.put(Pin._TITLE, title);
values.put(Pin._ACTION, Pin.ACTION_ADD);
values.put(Pin._CREATED_TIME, (long) (System.currentTimeMillis() / 1000));
Uri pinUri = cr.insert(Pin.CONTENT_URI, values);
// NOTE: 端末がオフラインの場合はここで終了
if (!isConnected()) {
return true;
}
// NOTE: サーバと http 通信。失敗したら IOException などがスローされる。
boolean success = this.client.pinAdd(uri, title);
if (success) {
// NOTE: 通信が成功したら既存の重複ピンを削除して、
// 1:ACTION_ADD を 2:ACTION_NONE に更新。
cr.delete(Pin.CONTENT_URI, Pin._URI + " = ? and " + Pin._ACTION
+ " = " + Pin.ACTION_NONE, new String[]{uri});
values.put(Pin._ACTION, Pin.ACTION_NONE);
cr.update(pinUri, values, null, null);
}
return success;
} catch (ParseException e) {
throw new ReaderException("json parse error", e);
}
}

このままでは、端末がオフラインのときには 1:ACTION_ADD と 2:ACTION_REMOVE が溜まります。
そこで回線がオンラインになったら実行される処理にピンの同期処理を追加します。
public int syncPins() throws IOException, ReaderException {
if (!isLogined()) {
login();
}
ContentResolver cr = this.context.getContentResolver();
// NOTE: 1:ACTION_ADD と 2:ACTION_REMOVE の検索。
String where = Pin._ACTION + " > " + Pin.ACTION_NONE;
String order = Pin._ID + " asc";
Pin.FilterCursor cursor = new Pin.FilterCursor(
cr.query(Pin.CONTENT_URI, null, where, null, null));
try {
while (cursor.moveToNext()) {
// NOTE: 順番にピンアクションを再現
Pin pin = cursor.getPin();
if (pin.getAction() == Pin.ACTION_ADD) {
pinAdd(pin.getUri(), pin.getTitle());
} else {
pinRemove(pin.getUri());
}
Uri uri = ContentUris.withAppendedId(Pin.CONTENT_URI, pin.getId());
cr.delete(uri, null, null);
}
} finally {
cursor.close();
}
// NOTE: サーバからピン一覧を全件取得。
PinsHandler pinsHandler = new PinsHandler();
try {
this.client.handlePinAll(pinsHandler);
} catch (ParseException e) {
throw new ReaderException("json parse error", e);
}
return pinsHandler.counter;
}

関連するソースはこのへん。
@see org.jarx.android.livedoor.reader.Pin
@see org.jarx.android.livedoor.reader.PinActivity
@see org.jarx.android.livedoor.reader.ReaderManager

このピンの実装によってずいぶんと使い勝手が向上したように感じます。ありがとうございます。
次はウィジェットを実装予定です。

2009年11月11日水曜日

LDReader の画面遷移

前記事

LDReader の画面遷移は少し変かも知れません。

現在の画面遷移は
フィード一覧 → 未読の最新の記事詳細 → (未読がなければ)既読の最新の記事詳細
となっています。

普通にやるなら
フィード一覧 → 記事一覧 → 記事詳細
ですかね。

記事一覧画面が無いです。個人的な需要の ”とにかく未読をチェックしたい”で作り始めたのでこうなっちゃってますが、どうでしょう?もし使いづらいとかコメントたくさんもらったら変えたいと思います。

フィード一覧画面:


記事詳細画面:

android アプリ LDReader を マーケットに公開しました

android で動く LDReader という livedoor Reader クライアントを作ってみました。

androlib.com: マーケット紹介ページ
code.google.com: Google Code プロジェクトページ

今のとこ 30件ぐらいダウンロードがあります。(紹介ページの星 4つは、つい自分で押してしまいましたw)

この LDReader については何回か書いていきたいなと思います。

LDReader とは:
定期的にサーバと通信して端末内に情報を保存し、オフラインで RSS の内容を閲覧できるのが特徴です。端末で独自に既読/未読の管理を行います。表示形式はフラットのみです。

作った経緯:
android を入手して最初にほしいなと思ったアプリケーションで、探しても良さそうなのが見つけられなかったためです。地下鉄での移動が多いのでオフラインでも更新をチェックできることを重要視しました。

Google Code でソースコードを公開した理由:
android の SDK やその特性は少しクセがあって作るのに結構苦労しました。つまづいた点の紹介をしたかったのと、ここはこれでいいのか?という疑問を持ったままリリースしたのでツッコミがほしかったのが一番の理由です。
オープンソース版の FastLadder もあるし、作ったのものをオープンにしてみたかったのもありますね^^

追加で考えている機能:
・未読数と最新記事の何件かが表示されるウィジェットに対応する。
・記事検索ができるように。
・キャッシュ記事のクリア。
・表示形式を livedoor Reader 本家と同じく、フォルダやレートに対応する。
・FastLadder に対応する。(fastladder.org と任意のドメイン)
・ピンをつけれるようにする。
・レートをつけれるようにする。
・android ではあまり見ないフィードを隠す。

開発元は株式会社トライキャッチとしました。
パッケージのドメインは org.jarx.android を使ってます。オープンソースモノはこのドメインを使っていこうと思ってます。そのうちウェブページも充実させないと。。

2009年10月28日水曜日

XML の Preference で Intent 呼び出し

ちょっとハマったのでメモ
    <PreferenceScreen
android:title="[タイトル]"
android:summary="[サマリ]">
<intent android:action="android.intent.action.VIEW"
android:targetPackage="[パッケージ名]"
android:targetClass="[パッケージ名].[クラス名]" />
</PreferenceScreen>

2009年8月14日金曜日

android ソース入手。(CentOS 5.3 yum git-1.5.4)

簡単なことですが、すぐ忘れるのでメモ投稿です。

android ソースを入手したくて repo コマンド実行しようとすると、git 1.5.4 以上が必要なんですって。
repo init -u  git://android.git.kernel.org/platform/manifest.git
fatal: git 1.5.4 or later required


私は yum(rpm)派なんですが、rpmforge には 1.5.2 しかなくてどうしようと思ってたら git 本家に yum リポジトリがあったのでそれを使いました。
git は 1.5.4 の最新と思われるものを指定してます。
wget http://kernel.org/pub/software/scm/git/RPMS/git.repo -O /etc/yum.repos.d/git.repo
yum install git-1.5.4.6-1


あとは repo というスクリプトをダウンロードして
mkdir ~/bin
wget http://android.git.kernel.org/repo -O ~/bin/repo
chmod 755 ~/bin/repo


git config で user.email, user.name を設定して、repo init、repo sync でソースコードのダウンロードがはじまります。
git config --global user.email "メアド"
git config --global user.name "名前"
repo init -u git://android.git.kernel.org/platform/manifest.git
repo sync

2009年7月3日金曜日

論理削除とユニークキー制約

ども。久々に時間ができたのでぼちぼち日記書いてみようかなと。
技術ネタですが。。

データベース設計を行うとき、論理削除を採用することが多いです。
論理削除用のカラムとしては boolean 型もしくは小さい数値型を使います。
create table item (
id int not null,
code varchar(40) not null,
name varchar(240) not null,
removed boolean not null default false,
constraint item_pk primary key (id) /* ,
constraint item_code_uk unique key (code) 本当はユニークキー制約をつけたいが。。 */
);

item(商品)テーブルに一意な code(商品コード)という人が認識しやすい番号を任意に設定できるテーブルを設計するとき、ユニークキー制約をつけてしまうと一度論理削除した商品コードが使いまわせない問題が発生します。仕方なくデータベース側のユニークキー制約は外していました。

このテーブルを使うアプリケーション側では、挿入、更新前にユニークキーチェックを行いますが、アプリケーションサーバが複数台あったり、バッチ処理が平行して実行される可能性があったりでなかなか厳密な整合性はとりづらいです。

最近になってふと、論理削除用のカラムをユニークキー制約に含めてインクリメントすればいいじゃんと思いつきました。そして、主キーが 1つあるタイプならばインクリメントとかしなくてそのまま使えるなーと。
create table item (
id int not null, -- 0 以上の主キー
code varchar(40) not null,
name varchar(240) not null,
removed int not null default 0,
constraint item_pk primary key (id) ,
constraint item_code_uk unique key (code, removed)
);

論理削除するときは、
update item set removed = id where id = ?

通常の検索時には where 句に removed = 0 を指定します。
select * from item where removed = 0

まだ実践で使ってないですが多分大丈夫なはず。
いやースッキリした。

2009年4月6日月曜日

起業して1年

1年が早いです。去年の4月に起業してから過去お付き合いのある会社、知人から仕事をいただいて何とかやってこれました。ありがとうございます。

今年は大きな仕事を受注し、社員を増やしたいと思います。

2009年1月20日火曜日

Java のフレームワークどうしましょ

あけましておめでとうございます。

次の受託開発案件で Java を使うことになりそうなのでフレームワークの比較検討しています。

私が今まで使ったことのあるフレームワークは
・Struts 1、2系 → EC など
・Tapestry 3系 → 業務システム
・Click Framework → 自社サービス(openidea.jp)
の 3つです。

一言にフレームワークといっても色々な役割がありますが、私は次の 4つが主要な機能だと考えます。
・URI マッピング
・フォームのバリデーション
・テンプレート
・データベースアクセス

表にするとこんな感じでしょうか。かなり昔の記憶で書いているものあって、正確でなかったり、当時スキル不足や調べ切れなかったものもありますが。。


URI マッピングバリデーションテンプレートデータベースアクセス
Strutsサーブレットフィルタ *.do 標準独自JSP 2.0 標準なし
Tapestryサーブレットを /app などにマッピング独自独自 + ognl 形式なし
Click Frameworkサーブレットフィルタ *.htm 変更不可独自Velocity 標準Cayenne、Spring サポート


この 3つはどれもぴったりハマることなく、気に入らないところは色々いじって使ってました。それぞれの使った感想を簡単にあげます。

Struts:
・設定ファイルが面倒。設定ファイルにして便利だったことはほとんどない。
・*Form, *Action とたくさんのクラスができて管理が面倒。
・学習は比較的容易?情報がたくさんある。

Tapestry:
・業務システムで採用したのが間違いだったかもしれないが、複雑な画面になるとイベントの制御がかなり難解になる。
・カスタムコンポーネントを作り始めると面白い。
・ページ制御側の学習コストは非常に高くつく。HTML テンプレートはスッキリ。

Click Framework:
・拡張子 *.htm が固定で変更できないのが痛い。
・学習は容易。

ほとんどのフレームワークはテンプレート部分や、データベースアクセスは他のプロダクトに変更することが可能です。実はここの選定が肝だったりします。別途テンプレートやデータベースアクセス部分のことは書きたいなと思います。

これらの今まで使ってきたフレームワークの追加調査に加え、Wicket も調査したいです。Spring Framework、S2 など DIコンテナを含むものは今回はパスしようと思います。

ここ 2、3年は Perl、PHP 案件がほとんどだったので忘れていましたが、Java 案件は
→ 新しいフレームワークの選定
→ 開発スタートしてから新しいフレームワークの細かい部分の調査ハマる
で遅延しがちなので要注意ですねー。