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 を使ってます。オープンソースモノはこのドメインを使っていこうと思ってます。そのうちウェブページも充実させないと。。