(LDReader のリリース記事ばっかりですが。。)
LDReader 1.0.5 をリリースしました。主な変更点は以下です。
1. 記事の削除機能を追加
2. バックエンドで記事取り込み中のもっさり感を解消
(ソース差分 r12)
1. 記事の削除機能を追加
・全記事
・未読記事のみ
・特定フィードの全記事
・特定フィードの未読記事のみ
の削除機能を追加しました。
2. バックエンドで記事取り込み中のもっさり感を解消
一覧系の画面では、ResourceCursorAdapter の autoRequery オプションを true に設定していたのですが、記事が 1件挿入されるたびに SELECT文を発行しており、このせいで画面が固まったようになる問題がありました。autoRequery を false にして独自の更新通知を受け取った場合のみに requery するようにしました。
2010年1月31日日曜日
LDReader 1.0.2 パフォーマンス改善と記事一覧画面追加
android の LDReader 1.0.2 をリリースしました。主な変更点はこちら。
・記事一覧画面を追加
・パフォーマンス改善
・バグ修正
(ソース差分)
記事一覧画面について
今までの画面遷移は
フィード一覧 → 記事詳細
だったのですが
フィード一覧 → 記事一覧 → 記事詳細
としました。
記事一覧を作ってみたら今まで無理して記事詳細に詰め込んでいた機能が、以外とすんなりおさまるようになりました。
使用感もこちらの方が自然になったと思います。
パフォーマンス改善
使っていてデータがたまってくると、記事詳細からフィード一覧へ戻るときどうしても遅いことに気づき調査したところ。。
sqlite に対する次の SQL が遅いことが分かりました。
item テーブルのスキーマはこんな感じです。id にアンダーバーがついてるのは android の慣例ですね。
item テーブルの subscription_id と unread にはそれぞれインディクスを貼ってあるのにどーしてこんなに遅いんだろうと。
色々と回り道しつつ、次のように複合インディクスを追加することで解決しました。
使ってくださってる方がいらっしゃったら是非アップグレードをお勧めします^^
・記事一覧画面を追加
・パフォーマンス改善
・バグ修正
(ソース差分)
記事一覧画面について
今までの画面遷移は
フィード一覧 → 記事詳細
だったのですが
フィード一覧 → 記事一覧 → 記事詳細
としました。
記事一覧を作ってみたら今まで無理して記事詳細に詰め込んでいた機能が、以外とすんなりおさまるようになりました。
使用感もこちらの方が自然になったと思います。
パフォーマンス改善
使っていてデータがたまってくると、記事詳細からフィード一覧へ戻るときどうしても遅いことに気づき調査したところ。。
sqlite に対する次の SQL が遅いことが分かりました。
select count(*) from item subscription_id = ? and unread = 1
item テーブルのスキーマはこんな感じです。id にアンダーバーがついてるのは android の慣例ですね。
CREATE TABLE item (
_id integer primary key,
subscription_id integer,
uri text,
title text,
body text,
author text,
unread integer,
created_time integer,
modified_time integer
);
CREATE INDEX idx_item_created_time on item(created_time);
CREATE INDEX idx_item_modified_time on item(modified_time);
CREATE INDEX idx_item_subscription_id on item(subscription_id);
CREATE INDEX idx_item_title on item(title);
CREATE INDEX idx_item_unread on item(unread);
item テーブルの subscription_id と unread にはそれぞれインディクスを貼ってあるのにどーしてこんなに遅いんだろうと。
色々と回り道しつつ、次のように複合インディクスを追加することで解決しました。
CREATE INDEX idx_item_unread_by_sub_id on item(subscription, unread);
使ってくださってる方がいらっしゃったら是非アップグレードをお勧めします^^
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 に恐る恐るページを作っちゃいました。
決してスクレイピングしやすくは無いですが、ひとまず。。
いろいろ試行錯誤してこんなページを 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 でリンクをクリックしたときにダイアログ表示
記事詳細では間違ってリンクをクリックしてしまってブラウザが起動してしまい、非常にストレスを感じていましたが確認用のダイアログを表示することで少し軽減されるようにしました。ソースはこちら。
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
・記事詳細の 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 以前では、操作していると次のような例外がたまに発生しました。
この例外はデータベースのレコード数と ListView のレコード数に違いがあるときに発生します。
LDReader はバックエンドのサービスでレコードを挿入したりしているので自前で色々していたのですが、付け焼刃ではどうしてもこの例外を抑えることができませんでした。
BaseAdapter を継承してゴニョゴニョしていたのですが、探してみると CursorAdapter といクラスがあるではありませんか!
ソースをのぞいて見るとデータベースの更新通知を受け取ってクエリを再発行し、ListView との不整合を解消しているようです。さらに ResourceCursorAdapter では、行ごとの View をリソースを使ってカスタマイズしている場合にピッタリなようです。ApiDemos を見ながら四苦八苦して作ったのですが、まだまだ知らないことがたくさんありそうです。
ちゅうわけで主な変更はこちら。SubscriptionActivity (diff r5)
[0.0.8] での主な変更
Cursor#deactivate を自前でやっていたのですが、Activity#managedQuery を使っていれば deactivate と requery は勝手にやってくれるようです。削除しました。
ちゅうわけで主な変更はこちら。SubscriptionActivity (diff r6)
[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 とします。
ピンの追加はロジックはこんな感じ。
このままでは、端末がオフラインのときには 1:ACTION_ADD と 2:ACTION_REMOVE が溜まります。
そこで回線がオンラインになったら実行される処理にピンの同期処理を追加します。
関連するソースはこのへん。
@see org.jarx.android.livedoor.reader.Pin
@see org.jarx.android.livedoor.reader.PinActivity
@see org.jarx.android.livedoor.reader.ReaderManager
このピンの実装によってずいぶんと使い勝手が向上したように感じます。ありがとうございます。
次はウィジェットを実装予定です。
ども。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日水曜日
登録:
投稿 (Atom)



