2010年9月23日木曜日

デジカメ忘れたし泊まりがけツーリングにテントが必要か考えた

今週、遅めの夏休みというか早めの秋休みを頂いております。

で、週頭にちょびっとツーリングに行きました。
本当はこんなステキな景色が!!
とか
そんな景色を背景に、ほら俺のバイク!!
とかいう内容のものを書こうかと思いましたが、

デジカメ忘れる

という予想外の出来事に遭遇したため、それは諦めました。



さて、既にバイクに乗っている方はともかく、まだ免許持っていない人に本当にバイクは勧められるのか。
また、泊まりがけで行くにしても、テント張ってまでのツーリングは勧められるのかを考えてみました。



バイクの魅力に関しては…正直個人差があるので、その点でオススメするしないは誰かに任せるとして、
私の思うバイクのメリット・デメリットを考えました。

・メリット
 車と比較して、税金等が安いぐらい?

・デメリット
 雨降ると辛い
 コケたら痛い(人も財布も)
 夏熱くて冬寒い
 バイクというだけで毛嫌いする人が居る
 ていうか楽するなら確実に車の後部座席とかタクシーとか…

結論:
 興味のない人には勧められない。
 まあ、原付位なら…ねぇ…


私個人が思うに、興味ない人にオススメできる部分は無いってこと。
某漫画で「乗ってみなきゃワカンネ」的なこと言ってますが、どうせ乗ったってわかんない人はわかんないままです。




さて、バイク持っていたらとりあえず乗りたくなるはずです。
免許取ったばかりや、バイク買ったばかりならなおさら。

んで、そのうち日帰りじゃ行けないところへ行きたくなるはずです。
その際に旅館やホテルに泊まるのもいいけど…テントはどうだろう?

ってことで泊まりで行くツーリングにテント等は必要か?
テントのような一応どこでも寝られる設備があることによる個人的なメリット・デメリットを…

・メリット
 ツーリングルートを変更しやすい
 →宿泊地を決めていると、そこまでは行かなければならなくなる…

 疲れたら、すぐ休める
 (周囲の目を気にしなければ)結構どこでも寝れる
 (お高いキャンプ場でなければ)宿泊費が圧倒的に安い!
  →道の駅や公園、強者なら本当にどこでも…

・デメリット
 荷物が増える
 →テント・寝袋・マットetc
 設営や撤収がやや面倒
 →雨の日は最低


ぐらいでしょうか?
お金が無いけどロングツーリングしたければ欲しいところですね。
まあ、どこでも寝れますが、ある程度の常識の範囲内で寝てください。
そして撤収時にゴミは片付けましょう。
なお、世の中にはテントも(゚⊿゚)イラネって人が居ますが…寝袋だけとかだと雨降ると悲惨ww


ちなみにこんな感じのものを持っていると疲労回復が全然違います。
一度ハマると銀マットには戻れません。
各社から一杯出てますし、銀マットよりもコンパクト。オススメ。
銀マットより当然高いけどな!




ついでにツーリングクッカー持ってると、バイクを見ながらお酒飲んで飯食って…



そんなわけで個人的にはバイクでテント持ってツーリングって結構好きです。
というか、ここまでの結論として好き嫌いと興味ある無しだけだと思ってます。
なので、これで興味を持った人はとりあえず好きなバイク買って、適当に旅に出てください。
もしかすると人生が大きく狂う変わるかもしれません。

くれぐれも実行は自己責任でお願いいたします。




2010年8月22日日曜日

HIPSっていう中華H.I.Dを付けてみた

ヤフオクでよく出ているHIDを買ってバンディット1250Fに付けてみました。
バイク用で5,500円という破格!!

まあ、届いてみて箱を開けて説明書を読んだんですが、多分バイク用という括りで開発しているわけではなさそうです。

この製品に関してはぶっちゃけ車用を買っても同じでしょう。
どうせなら調光可能なものにすればよかったかな…



とりあえずメーターを外します。


ロービームの蓋を外して観察。
う~んやっぱり加工が必要です。

こちらのサイトを参考に加工しました。
ちなみにロービームの蓋の内径は43mm
スペーサーの内径は上記サイトと同様23mm
私はアルミ板(1mm)で作りました
まあ、上記サイトほど綺麗に作ってませんけどね…
一番時間がかかる作業がこのカプラ加工。
あとは取り付け場所が決まっていて、空焚き後の待ち時間を除けば30分位で終わる気がする。

てかGSXとおんなじ蓋っぽいですね。
この蓋を加工する際は、このカプラーを再利用出来るように穴空けしました。


ちなみに端子は刺さっているだけなので簡単に外せます。


電源を元々のカプラーから引っ張るための線は邪魔なので切り離す。
この後、再度ハンダ付けして熱収縮でくるんで使います。


空焚き。
説明書には何も書いてませんでしたが、HID取り付け作業をしているサイトでは大体やっているので。
一応へんな煙も出なかったので1、2分ほど。


結局全部外した。
う~ん空間しか無い。
けどさすがに52パイの車用の水温計とか入れられないかな…


バラスト(右)を付属していたステーに両面テープとネジで止める。
配線についてるなんかのケース(左)もステーに両面テープとタイラップで固定。


ここに付けてみた。
ここまで来たら、元のカプラー(蓋にくっついていた奴も使用)から電源を取って、あとはバラスト・バーナーと接続して終了。
ワザワザ販売者がつけたと思われる、バッテリーから電源を取る為の配線等は無視。
だってハロゲンの時の純正配線で十分容量的にも問題ないし…


以下、比較

まずは未点灯


純正品ハロゲンランプ


今回のHID(6000K)


純正品ハロゲンランプ


今回のHID(6000K)


色温度の違いはわかりますが、明るさに関しては画像じゃよくわかんね。
取り付け後、近所のレッドバロンに加工した蓋の注文に行きました(なんかあったら純正状態に戻せるように)
そのさい走行していて何処と無く明るいかな~ぐらいには感じました。
そもそもそんな暗い場所走ってないからなぁ…
だれか比較用にノーマルバンディット1250F持ってきてくださいww

まあ、これで20W省エネ化ですwww

2010年8月15日日曜日

省エネ化とか

現状

  • 消費電力増加分
    • ETC
    • LED(フロントオレンジ、リア緑 フロントがウインカーとカウルの中のせいで目立たん…)
  • 消費電力減少分
    • テール&ストップLED化
    • ポジションLED化
    • ナンバー灯LED化
  • ほか
    • Desire取り付け用ステー増設
    • 電源確保用のシガーソケット増設
結果としてはテール・ポジション*2・ナンバー灯がそれぞれ5Wで20W
ストップが21W
だったのが、
常備系が計1W未満
ストップが(確か)1Wちょっと
になりました。(いずれも13Vで実測値)
う~む省エネ
テープLEDが一本辺り100mA(緑90mA オレンジ110mA(実測)だった)の電流を使用しているので、一本辺り13*0.1で1.3W
4本で5W
と、言うことはあと8本足してもまだお釣りが…
その前にウィンカーをLED化したいですけど、ICリレーがメンドイなぁ…




2010年8月12日木曜日

イタリアにイラッとした

バンディット1250Fのオプション(キャリア)が届いて喜んでました。

が、その喜びは直ぐ怒りに。


ボルトナット、全く合わない。

もう笑えるくらい合わない。

購入したバイク屋さんに「ボルト系は同じ規格のものをホームセンターで揃えたほうが楽だよ~」
とは言われていましたが、ここまでとは…

ってか楽というか、合わないってなに?
トップ・サイドキャリア合わせ六万位するんだけどネジすら作れないのか?
しかもこれ一応純正オプションだよね?

検品って言葉知ってるか??

イタリア人真面目に仕事しろ

いや、GIVI製だけど純正オプションだし文句はスズキに言うべきか??

これから買うっていう人はそうそう居ないと思いますが、部品番号晒しておきます。


99000-990D7-37T(トップケースキャリア)
99000-990D7-065(サイドケースキャリア)


なお、トップケースキャリアにはバーエンドが付いてきました。
これ自体は問題なかったんですが、もともと付いていたバーエンドが…

アクセル側は問題なかったんですが、クラッチ側がネジ回してもバーエンドごと回るだけ…
余りにおかしいのでよく見たら微妙に曲がってる。
いや、転倒もしてなければ当然新車から弄ってない部品なんですけど…
もうどうしろと…



2010/08/14 追記
ちなみにダメダメだったもの
・M6のナット全部(トップケース、サイドキャリア合わせて4つ)
・トップケースのサイドキャリア取り付け用ネジ穴(M8)半分(左側2箇所がネジ切り必要でした)
もちろん、なんか取り付け位置が微妙~な感じなのはデフォですね…
なんかmade in japan で素敵なやつないのかな?
なおバーエンドは気合で外しました。

2010年7月3日土曜日

いらっしゃいませ Bandit1250FA

買っちゃった。
勢いだけで。
ていうか外見だけで買っちゃったような感じ。
実車も見ずに契約しましたwww
契約が6/20の納車が6/28だったかな?
後悔はしていない!

画像はDesireで撮影したものを縮小しただけ。



後悔はしてないけど、次の点は気に入らなかったwww

  • 現行の大型の癖に、テールとかウインカーとか電球(作りが安い…)
  • シート下が全くない。ETCの搭載すら怪しい今日この頃。(ZRXを見習え!!)
  • どうやら日本国内では不人気車のようだ(社外パーツあるのかな?)
なんとも言えない点
  • ラムエア用のダクトだと思ったら飾りだった…(いや、加工し放題と考えればプラス??)
まあ、まだ慣らしの段階で走る部分等々に関しては現状では感想なし。
第一96のZRX1100と比較してもねぇ…
現状の個人的な感想としては、あと10万高くていいから細かいところをもっと作りこんで欲しかったですね。
とりあえずLEDテールの作成から始めたいと思います。

え、作りが安いのはsuzukiだから仕方が無い?

Desire入手!


じゃじゃ~ん、というほどのものでもありませんが、Desire入手しました。

会社名義で。





desire





主な比較対象がHT-03Aなので、比較するのも申し訳ない感じですが、非常に良い感じ。

そのほか会社に転がっているXperiaと比較しても断然Desireですね。

ただ、ハードの作り込みは中の人曰くXperiaのほうがずっと良く出来ており、何かのベンチマークではXperia >> Desireらしいです。

もしかすると、いつ来るかわからないアップデートでXperiaの評価が凄く上がるかもしれません。

ただ、普通の使い勝手とか考えると、やっぱりXperiaという選択肢は無いように思えます。

もし現状でAndroid端末が欲しいならDesire一択ですね。

もしくは次の製品を待つ!

スマートフォンが欲しいならiPhoneなのかな…

まあ、私林檎製品買う気ないのであれですけど…





そういえばXperiaのドライバってダウンロードできるようになったんかな?

4月末の時は公式から消えていた気がするけど、現状はどうなんでしょ?
ちなみにDesireの場合は、
HTC SyncってのをPCにインストールし、DesireをPCに繋いで、DesireからHTC sync を選択すればインストールされます。(XP sp3で確認)

ただ、なんかDesire側は接続出来なかったよ!とか言ってきますが、ドライバはインストールされるようです。

インストール後はADBを通じてちゃんと認識されていました(eclipseにて確認)





え?あいえすぜろいち?なにそのメガネケース?




2010年6月19日土曜日

ListViewのヘッダ幅を固定してみる(C++/CLI & win32)

なんかやっぱり流行りというかAndroid関連での検索が引っかかってくる今日この頃。
そんな中 windowsformアプリ関連(C++/CLI)のことをちょこっと。しかも今更な内容。
正直Windowsアプリはwin32APIでゴリゴリに書く人で、.Netってな~に?って状態ですので、間違ってても責任取りません。
ってかむしろこういう方法あるよ!、みたいな素敵な指摘が欲しいです。

内容としては ListView header幅固定 を C++/CLIにwin32APIを混ぜて実現したもの
ちなみにヘッダの境界線にマウスが来た際にマウスカーソルが変更されるのも阻止。
まとめると

  • ListViewのヘッダ幅がマウスで弄れないこと
  • ヘッダの境界線上でマウスカーソルが変わらないこと
なお、会社で確認した動作確認はXP SP3
開発はVS2008 standerd sp1
家での確認は 7 pro のVC++ 2008 Express Edition

スゲー簡単な方法
// stdafx.hに追加
#include "windows.h"
#include "commctrl.h"

// Form1.hの適当なところに追加
// 略
EnableWindow( ListView_GetHeader( reinterpret_cast(this->listView1->Handle.ToInt32()) ), FALSE);
// 略

ぶっちゃけ、 Google先生に「ListView ヘッダ固定」で聞いてTOPに返ってくる内容ですが…。
上記内容をフォームのコンストラクタの中にでも突っ込んでおけばOK。
その辺から拾ってきたのを足しただけですので、ありなのかどうかすら分かりませんが、一応目的は果たせます。

上記の注意点

windows form アプリ的にはあまりwin32APIを使用されたくなさそうです。
環境にもよりますが、多分上記はまずリンカエラーとなるでしょう。
これもググれば答えが返ってきますが、一応方法を。

vs2008 standerd sp1 の場合は、うろ覚えですが、
・プロジェクト → プロパティ → C/C++ → 全般 → 追加のインクルードなんたら → 親からなんたら にチェック
で通るようになるかと。
何でも2005位からformアプリ作成時の初期設定では、win32用のライブラリが軒並み外されているとかで、自分で設定が必要らしいです。
もちろん個別に必要なライブラリをリンクしてもいいんですけど。

VC++ 2008 Express Edition
上記と同じ方法で行けると思いきや、駄目でした。
そこで取った方法は次の方法。

  • Win32コンソールアプリを作成
  • プロジェクト → プロパティ → リンカ → コマンドライン からlibファイル名をコピー
  • Formアプリの同じ場所にコピーした内容を貼付け
超強引ですがこれで行けますwww

ちなみにコピーした内容はこれ

kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib

不要なものも居るけどその辺は無視。

ちょっと面倒な方法

お作法的にいいのかどうかは全く分かりませんが、上記にたどり着いて気がついたのは
「余裕でwin32APIガンガン使えるじゃん!」ってことです。

ウィンドウハンドル取得できれば結構やりたい放題ですよね。
さて、上記のようにHeaderを無効化することで目的を果たすと、楽は楽なのですがこんな要望が来たらアウト。
・ヘッダ部分のクリックはしたい!
更に、私が確認した環境では上記方法ではヘッダ部分をクリックすると、何故か一行目のアイテムがクリックされたと認識することも発覚。
というわけで、ListViewもヘッダ部分もサブクラス化してしまへ。

//****************
// Form1.hとか
//****************

HWND hList = reinterpret_cast(this->listView1->Handle.ToInt32());
HWND hHeader = ListView_GetHeader( hList);

OrgList = (WNDPROC)GetWindowLongPtr( hList, GWLP_WNDPROC);
SetWindowLongPtr( hList, GWLP_WNDPROC, (LONG_PTR)ListViewSubWindowProc);

OrgListHeader = (WNDPROC)GetWindowLongPtr( hHeader, GWLP_WNDPROC);
SetWindowLongPtr( hHeader, GWLP_WNDPROC, (LONG_PTR)ListViewHeaderSubWindowProc);


//****************
// hoge.cpp/h 色々略
//****************

WNDPROC OrgList, OrgListHeader;


LRESULT CALLBACK    ListViewHeaderSubWindowProc( HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
 switch(msg){
 case WM_SETCURSOR:
  return TRUE;
 }
 return (CallWindowProc(OrgListHeader, hWnd, msg, wp, lp));
}

LRESULT CALLBACK    ListViewSubWindowProc( HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
 NMHEADER* pnmhdr;
 switch(msg){
 case WM_NOTIFY:
  pnmhdr = (NMHEADER*)lp;

  switch(pnmhdr->hdr.code){
  case HDN_ENDTRACK:
  case HDN_BEGINTRACK:
  case HDN_TRACK:
  case HDN_DIVIDERDBLCLICKW:
   return TRUE;
  }
  break;
 }
 return (CallWindowProc(OrgList, hWnd, msg, wp, lp));
}

上記のソースもその辺探せば幾らでも出てきます。

  • Header部分でカーソルを変更させない
  • カラム幅をいじらせない
それぞれを自前で実装したというか、本来の実装へ渡さないだけですが。

ちなみにヘッダをクリックできるとか出来ないとかは、ListView::HeaderStyleで変更出来ますので、 まあ、わざわざwin32APIを使用するまででもないでしょう。

なお、恐らくこの段階ではまたもエラーになると思います。
コンパイル時に「 /clr:pure または /clr:safe と共にコンパイルされた関数に対する呼び出し規約 '__stdcall ' が無効です」というエラーがでると思います。
その指摘に従って
・プロジェクト → プロパティ → 共通言語ランタイム サポート
を /clr:pure から /clr へ変更することで、コンパイルが通るようになります。

また、Vistaから「HDS_NOSIZING」なるスタイルが追加されたようなのですが、 もしかするとこれらの実装(サブクラス化)をしなくても、このスタイル設定一発で終わる可能性も大ですね。
ま、めんどくて確認していませんし、そもそも私の場合XPで動作することが前提ですから。
誰か結果を教えてください。

ってか、こんな実装ありなんだろうか…
ぶっちゃけ何処がC++/CLIなんだか、普通にwin32じゃん…
C++/CLI 使いの方教えてください。
むしろ一番目に書くべきな気もしますが、貼り付けたコンソールのウィンドウハンドルって結構簡単に取れるよ!ってことが言いたいだけです。

2010年5月22日土曜日

今更ながらDesireを見てきた

最近全く家でプログラム書いてない…
まあ、その分会社で書いているので問題ないですがwww

さて、今日何となく電気屋に行ってフラっと立ち寄ったら
HTC Desire が置いてあり、触れました^^

先日OS2.2もGoogleIOで発表されたので、何処と無く今更感がありますが…


正直な感想

Desire >>>>>>>> Xperia >= HT-03A

です。

XperiaもHT-03Aも(会社名義ですが)保有しています。
もちろんXperiaのハードスペックには全く不満はありません。
CPUだってXperiaとDesireは同じスナップドラゴン。

だけど体感が全然違う、比べるのも失礼なくらい違う。

HT-03Aは既にちょっと前の携帯なので、比較に入れるのはどうかと思いますが、
まあ、メインで使っているので…

個人的に一番使っている(使い慣れている)というのもあり、
Xperia >= HT-03A としました。
正直、普段使うようなアプリでの動作感の差は、XperiaとHT-03Aでそんなに違うとは思えません。

そこを踏まえると、やはりOSのバージョンの差が大きい気がします。
XperiaもわざわざあそこまでOSをカスタマイズするメリットがあるのか気になります。
docomoというキャリアが問題なのか、ソニエリが問題なのか。
PCを買う時だって、(ハードスペックは十分なのに)OSを選択するのに今更98やXPを選ばず、7選ぶでしょ?
iPhone買う時だって今更3G買わずに3GS買うでしょ?(アップル商品買った事ないけど)
1.6を選ぶ理由は無いと思うんですけどねぇ…
なんだか時代遅れですよね。
��もちろんセキュリティ的な問題や、キャリアの基準等あるんでしょうが。)

iPhoneは使ったこと無いので、想像で勝手に比較するとこんな感じでは?

Desire > iPhone 3GS >>>>>>>> Xperia >= HT-03A

Android 2.2 は更に速度向上とのことですので、今後が更に気になります。
HTC EVO 出ないかな~

来月には次世代iPhoneの発表があるはずですし、スマホ市場の行方は気になりますね。



え?IS-01?
なにそのキャベツロイド。

2010年4月18日日曜日

GWはどこへ行こう

あと半月でGWです。
今年もロングツーリングへ行くか、それとも隔週ならぬ隔日とかでサーキットとか…。

って考えていたら仕事に手がつきません。
┐(´∀`)┌ヤレヤレ

当然先月から暖かくなっているので、家でプログラミングなんてこともやってません。
いや、時間があればあんなことやこんなことをテストしたいんですけどね。

それこそ自前センサーロガーを作ってツーリングログ取得とか。

噂によるとGWも凄く寒くなる可能性があるとか無いとか。
もしそうならば、GWはヒキコモリプログラマーかな?

でもやっぱりツーリングが一番。

去年九州に行って見てこれなかった箇所に行くのもいいなぁ。
或いは近場を2泊3日くらいでフラフラもいいなぁ。

というかもうずっと休みならいいなぁ。

無理か…。

2010年3月22日月曜日

都内開花らしい

先週に引き続きトミンモーターランドへ行ってきました。

午後だけ。



2台ほどBコースを28~9秒ぐらいで走る恐ろしい方々がいました。

スタッフの方の話によると最速クラスの人たちは26~7秒らしいですが。

オソロシイ。



そんな私の本日ベストは32.8

32秒切れるようになったら、そろそろ車体を弄ってもいいか?

まあ、お金があればですけど。

キャブくらいは社外品にしたいものですが・・・。



むしろXR100M売って、頑張ってNSF?

う~ん、危険な妄想だ・・・。



とか考えていた帰りの渋滞で、都内でソメイヨシノが開花したことをラジオを聞いて知りました。

もうそんな季節か・・・。


2010年3月20日土曜日

無線機を買ってみた

STANDARDのVX-6を買いました。





今年はバイクと車に無線機を載せたいと思います。

まあ、思うだけなら2年くらい前から思っていましたが。



同じようなやつで

VX-7



とか

VX-8




とかありましたが、まあデュアルで受信する必要も無いし・・・。

お高いし・・・



あ、ちなみに上記の奴らは全てアマ4無線の免許が必要です。

受信Onlyなら問題ないのかな?

もちろん無線開局も必要です。



ここからはそんな無線開局に関する総務省に対しての愚痴ww



アマチュア無線の開局手続きはweb申請できるんですね。

http://www.denpa.soumu.go.jp/public2/list/index.html#self



なんですが、新規登録のフォームにいくら文字を入力しても化ける・・・。

もうなんでかしらんが文字化けする。


結果として

・IEを使用する

・インターネットオプション→詳細タブ→Internet Explorerの設定をリセット

してからじゃないとダメと言うことが判明。



どんなサイトの作りしてるんだか・・・。

まあ、いまどきIEなんて使っていないので、いくら設定をリセットしても個人的には関係ないんですがね。

もうちょっと全うにサイトを作って欲しいものです。

色々とわかりづらいし。



さて、一週間後にIDとpassが届いたら申請の続きができるわけですが、

この調子だと更なるどうでもいい苦労が待っていそうです。


2010年3月14日日曜日

外で遊んできた

今日は実に4ヶ月ぶりくらいに、バイクに全うに乗りました。



行き先はトミンモーターランド、Bコース。

はじめて行きましたがいいですね。

もう少しうちから近ければ言う事なしです。



ちなみに現地についたのが14時過ぎ。

40分位しか走ってませんwww



久しぶりのミニバイクに、新品のタイヤに、はじめてのコース。



皮むきで糸冬 了..._φ(゚∀゚ )アヒャ




左がヘタクソなので非常によい練習になりそうです。

来週も行こうかしら。


2010年3月13日土曜日

季節の変わり目

抜け毛が凄い!

もちろん猫のです。

自分の髪の毛ではありません(そうだとしたら色々悲しい)



家のあらゆるところに猫の毛が!

服のあらゆるところに猫の毛が!



うちの由緒正しい雑種3匹分ですから仕方がないにしてもねぇ・・・。

ブラッシングしてもしても次から次に・・・。

掃除しても次から次に・・・。



猫アレルギーで花粉症の自分には、スギ花粉で鼻水がでるのか、猫の毛が原因かさっぱりです。



なんかこう画期的なブラッシング方法とかないものか・・・。



2010年3月10日水曜日

DELL studio 1557 がおかしい件について3

最近フリーズもなく比較的快適に使用していましたが・・・。

なんかすっごいカリカリ音がするんですけど。

もうかれこれ5分ほど・・・。

フリーズに続き新たな現象発生です。



まず突然カカカッというような音が発生し、その後ずっとHDDアクセスしているような音が・・・。



正直これからDELL studio 1557 買うか悩んでいる方。

やめた方がいいかも。


これ今のうちにデータバックアップしとかないとまずいかも・・・。



2010年3月7日日曜日

第917回「週末の予定」

週末は天気があぁぁ・・・。

土曜日は本当は2010年走り初めでサーキットへ!

のつもりだったんですが、この天気と寒さじゃねぇ・・・。

ヘタレな私にはむりでした。



本日は予定通り知人の展覧会へ。

メディアアートっていうんですか?なかなか面白かったです。

まさかARToolKitを使用した展示物にお会いするとは思いませんでした。



その帰りにこんなものを買ってしまった・・・。







ご存知(?)ロジクールのマウスです。

トラックボールのやつです。型番はたしかTM-400?

マウスを動かさなくていいと言うのはなんだか不思議な感覚ですが、結構いいかも



ええ、決して衝動買いではありません。

家のマウス古くて使い辛いし、前々から欲しかったし、今年に入ってからまだ全然PC関連買ってないし・・・。



と言い聞かせているのはないしょです。

2010年2月14日日曜日

特定のタップイベント時にMapViewから座標を取得する

意外と試行錯誤したので記録にしておきます。

というワケでまずは特定のタップイベントの取得に関して・・・。
は、以下のサイトを参考にしました。

hoge256blogさん
adakodaさん

上記のサイトを見て、なんも考えずに
・Activity → MapActivity
ぐらいの変更でとりあえずなんとかなるだろう・・・。
ってのがまず甘かった。


public class Main extends MapActivity implements
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener {

 private GestureDetector gesture;
 private MapView mapView;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  protected void onCreate(Bundle icicle) {
   // TODO 自動生成されたメソッド・スタブ
   super.onCreate(icicle);
   setContentView(R.layout.main);

   mapView = (MapView)findViewById(R.id.map);
   mapView.setEnabled(true);
   mapView.setClickable(true);
   mapView.setBuiltInZoomControls(true);
   mapView.setSatellite(false);

   gesture = new GestureDetector(this);

   mapView.invalidate();

  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
   gesture.onTouchEvent(event);
   return super.onTouchEvent(event);
  }
  // 以下onDoubleTapとか軒並み略

いわずともあれです。
MapView上をタップしても「onTouchEvent」にイベントが来ません。
上記には書いていませんが、ところ狭しとログかけまくってます。
MapViewが全て処理してくれているため、当たり前といえば当たり前です。

じゃあ、上記mapViewになんかタッチを処理しそうなリスナーとか登録すりゃいいんじゃね?
と考え、eclipseの力を発揮(?)させ、setOnTouchListenerなるものを発見。
ってことで以下に修正。

public class Main extends MapActivity implements
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener {

 private GestureDetector gesture;
 private MapView mapView;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  protected void onCreate(Bundle icicle) {
   // TODO 自動生成されたメソッド・スタブ
   super.onCreate(icicle);
   setContentView(R.layout.main);

   mapView = (MapView)findViewById(R.id.map);
   mapView.setEnabled(true);
   mapView.setClickable(true);
   mapView.setBuiltInZoomControls(true);
   mapView.setSatellite(false);

   gesture = new GestureDetector(this);

   // 追加
   mapView.setOnTouchListener( new OnTouchListener() {

    public boolean onTouch(View v, MotionEvent event) {
     // TODO 自動生成されたメソッド・スタブ
     gesture.onTouchEvent(event);
     return false;
    }
   });

   mapView.invalidate();

  }

  /* コメントアウト
  @Override
  public boolean onTouchEvent(MotionEvent event) {
   gesture.onTouchEvent(event);
   return super.onTouchEvent(event);
  }
   */
  // 以下onDoubleTapとか軒並み略

結果、一回しかイベント取れないし。
ちょっとやってみたところ、どうやら「onTouch」で一度でもfalseを返すともうダメ。
普通false返したら、superクラスというか、そもそも備えた処理してくれるんじゃないんですか。
自前で処理を全部書いて、全てtrueを返すなら運用出来そうですが、MapViewですよ。
もともとある機能の分まで実装できるかっての。
ていうかできるくらい能力ありゃ苦労しませんよねw

そこで考えたのが、「流石にイベントが来ていない訳ないだろ」ということ。
横取りしちまえwwww ってこの辺の発想はVCのサブクラス化とかから来てるのかな・・・。
ということで次のように修正。

//略

 @Override
 public boolean dispatchTouchEvent(MotionEvent ev) {
  //TODO 自動生成されたメソッド・スタブ
  gesture.onTouchEvent(ev);
  return super.dispatchTouchEvent(ev);
 }

 @Override
 public void onCreate(Bundle savedInstanceState) {

  //略

  /* コメントアウト
  //追加
  mapView.setOnTouchListener( new OnTouchListener() {

   public boolean onTouch(View v, MotionEvent event) {
    //TODO 自動生成されたメソッド・スタブ
    gesture.onTouchEvent(event);
    return false;
   }
  });
   */
  mapView.invalidate();

 }

 //以下onDoubleTapとか軒並み略

あくまでイベントが知りたいだけなので、処理するしないに関わらず、本来の処理に渡しています。
こうすることで

  • イベント取れる
  • MapViewそのものの処理は邪魔しない(はず)
と考えました。

だが幸せは長く続かなかった

とりあえず長押し処理に何かを付け加えましょう。
わかりやすいので長押しした場所を中央に移動するとかにしました。

public void onLongPress(MotionEvent e) {
 // TODO 自動生成されたメソッド・スタブ
 // 以下を追加
 // タップされた画面座標をmapView上の緯度経度に変換
 GeoPoint temp = mapView.getProjection().fromPixels((int)e.getX(), (int)e.getY());
 _log("GeoPoint lat:" + temp.getLatitudeE6() + " long:" + temp.getLongitudeE6());
 mapView.getController().animateTo(temp);
 mapView.postInvalidate();
}

こちらも一見良さそうですが、良く考えるとmapViewに渡している画面座標って、たぶんMapActivityが管理している幅になりますよね。
当然これ動作させると、なんだか長押しした場所がView中央よりやや北側になります。
ちなみに以下のような画面ならなおのこと・・・。

この場合富士山を長押ししたところで、富士山がView中央にくることはない。


じゃあ、あとは何があるかと考えると、
・MapViewを拡張する
くらいかなと思っていたところでこんなのを発見
日本アンドロイドの会
該当ページの一番下の方で「タップされた位置にアイコンを描画する」って項目があるじゃないですか。
しかも後で気がついたんですがAndroidプログラミング入門にもそれっぽいこと書いてあるし・・・。一体どこ読んでんだ?っていわれそうです。
ってことでOverlayを試してみましょう。

public class Main extends MapActivity {

 private MapView mapView;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  protected void onCreate(Bundle icicle) {
   // TODO 自動生成されたメソッド・スタブ
   super.onCreate(icicle);
   setContentView(R.layout.main);

   mapView = (MapView)findViewById(R.id.map);
   mapView.setEnabled(true);
   mapView.setClickable(true);
   mapView.setBuiltInZoomControls(true);
   mapView.setSatellite(false);
   mapView.getOverlays().add( new MyOverlay(mapView));
   mapView.invalidate();

  }

  // 略
 }

 /**
  * Overlayクラス追加
  */
 class MyOverlay extends Overlay implements
 GestureDetector.OnDoubleTapListener,
 GestureDetector.OnGestureListener{

  private GestureDetector gesture = new GestureDetector(this);
  private MapView parent;

  MyOverlay(MapView mapView){
   parent = mapView;
  }

  @Override
  public boolean onTouchEvent(MotionEvent e, MapView mapView) {
   // TODO 自動生成されたメソッド・スタブ
   gesture.onTouchEvent(e);
   return super.onTouchEvent(e, mapView);
  }


  // 略

  public void onLongPress(MotionEvent e) {
   // TODO 自動生成されたメソッド・スタブ
   GeoPoint temp = parent.getProjection().fromPixels((int)e.getX(), (int)e.getY());
   parent.getController().animateTo(temp);
   parent.postInvalidate();

  }

  // 略

 }

これで無事MapView上で長押しした場所が、Viewの真ん中に来ることになりました。
まさかOverlayにこんな使い方があるとは・・・。
てっきり描画だけかと思っていましたし。
いや、正しい使い方かどうかは分かりませんがね。
まあ、考え方によってはイベント処理のみを追加したい場合に、こんなOverlayクラスを追加するだけでできるので、既存の処理への影響が少ない!かもしれないというメリットがありますかね?

2010年2月13日土曜日

暇つぶし


ふと先月のアクセスログ見たら、意外にも月1000PVもあるんですね。

びっくり。良くて一日10件で300PV程度かと思ってましたよ。

ちなみに自分のIPと会社のIPは外しているので、純粋に検索して引っかかった(騙された?)人の数と言えるでしょう。






検索ワードトップは「studio フリーズ」というような感じのワード。

みんな苦労してるんですね。

こちらも一向に改善しないんですけど・・・。

DELLどうにかしろ《゚Д゚》ゴラァァァァァァァァァァァァア!!





なおアクセス第一位は某ソフトハウスさん。

見たことも聞いたこともない会社さんからのアクセスが1割w





もちろん猫とかバイクとかは見てなさそうですが・・・。

一応アンドロイドネタに釣られて来ているようです。

お仕事お疲れ様です。

こんな走り書きのコードで役に立っているのか???





他にも企業さんだと

あのグループやこのグループやそんなグループまで。

まあ、この辺は全部で5%程度のアクセスでしたがw





でも検索してくるってことは、やっぱりアンドロイド系を何かしらやっていると言うことですよね??

まさか会社の端末で個人的なアプリを開発しているなんて奇特なことはしていないですよねw





正直機密に触れない程度に課題というかコメントとか残していっていただければ、暇つぶしに組みますよw

時間があればね・・・。

っていうか「こう書くべき!」って修正してくれると嬉しいんですがwww





いや結構アクセスログって面白いwww






まあ、会社さんからアクセスが有るってことは需要がある、

イコール今のお勤め先が潰れたら(今なら)拾ってくれると言うことでしょうか?





それはそれ、これはこれってことで別問題かなぁ・・・。




2010年2月11日木曜日

不定期更新・我が家のぬこ様2

我が家のヌコ様
やっぱりかわいい^^








よろしければどうぞ。
http://nanashimemorandum.web.fc2.com/neko02.html

買っちゃった

おニューのグローブ。

RSタイチのGP-EVO です。

\23099 高ぇぇぇぇ・・・。

まあ、試着した感じではこれが一番良かったので。


IMG_0813.jpg



ちなみに2輪館=>ナップス(\25000以上、ほぼ定価('A`))で試着してネットで購入。

明細みたら鳩ヶ谷のSOX川口店2Fにお店があるようです。

今年もいっぱい走ろう!




2010年1月24日日曜日

2010年1月23日土曜日

Android用のTwitterクライアントを作ってみる3

以前作った機能をほぼそのままServiceが管理するように作成
その上で

  • 一定時間毎に hometimeline を取得
  • ステータスバーへの通知
  • サービスからのコールバック
みたいな機能をつけました。


一定時間毎に hometimelineを取得
 private static final int GET_TWITTER_HOME_TIMELINE = 1;
 Handler mHandler = new Handler(){
 public void dispatchMessage(Message msg) {
  switch( msg.what){
  case GET_TWITTER_HOME_TIMELINE:
   mHandler.removeMessages(GET_TWITTER_HOME_TIMELINE);
   mHandler.sendEmptyMessageDelayed(GET_TWITTER_HOME_TIMELINE, intervaltime);
   // TODO
  }
 };

至って単純です。
mHandler.sendEmptyMessageDelayedで適当な時間後に自分自身を呼び出すだけです。


ステータスバーへの通知
 NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
 Notification notification = new Notification(R.drawable.icon, getString(R.string.Service_message1), System.currentTimeMillis());
 PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, 呼び出すアクティビティ名.class), 0);
 // 第二引数がアイコン横の太字
 // 第三引数が通知内容(?)
 notification.setLatestEventInfo(this, getString(R.string.app_name), getString(R.string.Service_message1), contentIntent);
 notification.flags = Notification.FLAG_AUTO_CANCEL; // 通知を一度使用したらステータスバーから自動的に消える
 // 第一引数は通知ID
 manager.notify(R.string.app_name, notification); // ステータスバーへの通知

詳細はTaosoftwareさんのブログとか
http://www.taosoftware.co.jp/blog/2009/04/android_notification.html
こちらAndroidの受託開発とかやってるみたいですね。
うちの会社と規模も良く似てるwww
でもAndroid特化(?)というのは羨ましいような・・・。


サービスからのコールバック
 //*** ICallbackListener を宣言するaidlファイル
 interface ICallbackListener {
  void receiveMessage(String message);
 }
 //*** サービス側ソース
 //コールバックアクティビティの管理リスト
 private RemoteCallbackList listeners = new RemoteCallbackList();
 //リスナーの追加
 public void addListener(ICallbackListener listener) throws RemoteException {
  listeners.register(listener);
  //削除はunregister で
 }
 //コールバック内容
 int numListeners = listeners.beginBroadcast(); //listeners.finishBroadcast();までスレッドセーフ
 for( int i = 0; i < numListeners; i++){
  try {
   listeners.getBroadcastItem(i).receiveMessage( "コールバック内容");
  } catch (RemoteException e) {
   Log.e("CallbackService", e.getMessage(), e);
  }
 }
 listeners.finishBroadcast();
 //*** コールバックを受け取るアクティビティのソース
 private ICallbackListener listener = new ICallbackListener.Stub() {
  public void receiveMessage(String message) throws RemoteException {
  // TODO メッセージに対する処理
  }
 };

え~、あ~
あくまで備忘録なんで、足りない部分は脳内補完してください。
コピペだけじゃ動きません。


と、まあこんな感じで作ると
twitter に新着があると

  • アクティビティが起動してない場合は通知バーへ通知
  • アクティビティが起動してたらコールバックを使用して直接描画
みたいなことができます。
ちなみに新着はhometimelineの一番上の発言内容が、以前と異なるかどうかで判断w
なにかいい方法あればいいんですが。
API制限を考えるとhometimelineの取得間隔はそれなりに考えものです。

マニフェスト情報

・サービスを別プロセスにする
<service android:name="クラス名" android:process=":適当な名称">
ちなみにコロンは必須のようです。
ぶっちゃけ別プロセスにしなくても動きますが、本曰くアクティビティと異なるプロセスで動作させることで、
『アクティビティのみを停止させることができ、メモリの節約が期待できます。』
とのことです。
まあ、なんか別プロセスの方がサービスぽいですしね。


新着があると通知が


開くとこんな感じ


通知バーから起動


アクティビティが起動しているあいだは、新着があってもステータスバーへの通知はなし



アイコンがノーマルってのが悲しいですね。
ここまで来るとアイコンを作成する能力が少しでも欲しい・・・。
ちなみに実機で動作させると、認証とかに意外と時間がかかります。
認証中とかに「通信中・・・」みたいなダイアログ出したり、発言が反映したことをアクティビティに伝えるetc細かい機能が欲しくなります。


参考 「Android プログラミング入門」

  • 第二部 3.5.2 ノーティフィケーションを使ってBMIの計算結果を通知する
  • 第二部 第4章 サービス


追記
上記で別プロセスの場合コロンは必須と書きましたが(ていうか本にそう書いてあったが)、正しくは

  • 「同一パッケージ内のコンポーネントしか実行できないプライベートなプロセスとして」別プロセスを作る場合
というような感じのときにはコロンを頭において適当な名前を付けるようです。(と別の章で書いてありました)
プライベートなプロセスとグローバルなプロセスを、アプリによっては使い分ける必要があるんでしょうが、とりあえずそんな高度(?)なアプリはまだ作らないんでその辺は曖昧で( ^ω^)・・・
参考 「Android プログラミング入門」
  • 第四部 1.2.1 プロセスの共有

2010年1月17日日曜日

Android Serviceで困ったこと2

前回のの続き

ええ、マニュアル読めってことで完了します。
何がって、サービスのライフサイクル。
bindService で起動されたサービスと startService で起動されたサービスのライフサイクルが異なることをソース書いて気がつきました。

基本以下のようです。

  • bindService で起動 → bindしているアプリ全てが unbind したら終了
  • startServiceで起動 → stopService or stopSelf で終了
ただし、startService で起動しようとも、bindすることは可能であり、stopを呼んでもbindしているやつがいる場合は終了できません。

なので、とにかく常駐させたい場合はstartServiceで起動するしかなさそうです。
やっぱりマニュアルは読まないとね。
まあ、言い訳するならば、「自分でソース書いてあちこちにログがけして、サイクルを学ぶことには意味がある!」ってことにします。
http://developer.android.com/intl/ja/guide/topics/fundamentals.html

Android Serviceで困ったこと

例によって、マニュアルを読むと解決することが半分。
ソース落として中身をしっかり見れば解決するであろうことが半分。
とりあえず後者に関する内容です。

bindService() 直後にはサービスを利用できない。

具体例としては以下のようなやつはNG
private IService service;
private ServiceConnection conn = new ServiceConnection() {

 public void onServiceConnected(ComponentName arg0, IBinder arg1) {
  // TODO 自動生成されたメソッド・スタブ
  service = IService.Stub.asInterface(arg1);
 }

 public void onServiceDisconnected(ComponentName arg0) {
  // TODO 自動生成されたメソッド・スタブ
  service = null;
 }
};
private void onChk1(){
 Intent intent = new Intent(IService.class.getName());
 bindService(intent, conn, BIND_AUTO_CREATE);
 service.serviceAPI(); // ここで落ちます
}

ようはbindService を呼んですぐにonServiceConnected() まで到達しないということです。
上記のような場合、onChk1 を完全に抜けてからでないと絶対に到達しませんでした。

以下NG集

private void onChk2(){
 Intent intent = new Intent(IService.class.getName());
 bindService(intent, conn, BIND_AUTO_CREATE);

 while( service != null){ // 無限ループに陥る
  wait(1000);
 }
 service.serviceAPI();
}
private void onChk3(){
 Intent intent = new Intent(IService.class.getName());
 bindService(intent, conn, BIND_AUTO_CREATE);

 new Thread(){ // スレッド化しても無駄
  @Override
  public void run() {
   while( service != null){ // 無限ループに陥る
    wait(1000);
   }
   service.serviceAPI();
  }
 }.start();
}

ちなみにbindService と service.serviceAPI を別関数化し、スレッド化して呼んでもダメなものはダメでした。
もちろんonChk3 のようなスレッドの作り方が良くないのかもしれませんが。
その辺、javaに詳しい人なら即座に解決策がでるんでしょうか?

では、なんでこんなことをしようと思ったかと言うと、
・onCreate / onResume 時に サービスから初期値を取得したい
という状況が発生したからです。

一応以下の方法でそれっぽくなりますが、なんかもっと簡単な方法ないものでしょうか。
完全解決という感じではないし。
自分でメッセージループ管理しだすと、VCチックな感じも・・・。
ただ、この面倒な感じは、BREWアプリを想像させるような・・・。
まあBREWなんかと比較したら失礼ですね。あれはマジ終わってる。

onCreate でサービスをバインドし、初期値もサービスから取得っぽく実装
private IService service;
private ServiceConnection conn = new ServiceConnection() {
 // 略
};

private static final int _MY_BIND_SERVICE = 1;
private static final int _MY_INIT_DATA = 2;
private Handler mHandler = new Handler(){
 @Override
 public void dispatchMessage(Message msg) {
  // TODO 自動生成されたメソッド・スタブ
  switch( msg.what){
  case _MY_BIND_SERVICE:
   removeMessages(_MY_BIND_SERVICE);
   _bind();
   break;
  case _MY_INIT_DATA:
   removeMessages(_MY_INIT_DATA);
   _init_data();
   break;
  default:
   super.dispatchMessage(msg);
  }
 }
};
public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 mHandler.sendEmptyMessage(_MY_BIND_SERVICE);
 mHandler.sendEmptyMessage(_MY_INIT_DATA);
}

private void _bind(){
 if( service != null){
  return;
 }
 Intent intent = new Intent(IService.class.getName());
 bindService(intent, conn, BIND_AUTO_CREATE);
}

private void _init_data(){
 if(service == null){
  mHandler.sendEmptyMessage(_MY_BIND_SERVICE);
  mHandler.sendEmptyMessageDelayed(_MY_INIT_DATA, 500);
  return;
 }
 service.serviceAPI(...);
 // 略
}



改めて書くとなんと強引な…。
しかもあくまで「onCreate内でやってるっぽい」だけで、普通にonCreateそのものは終わってますから。
そういう意味では完全解決ではありませんね。
removeMessagesを読んでいるのは、処理が重複しないように念のために呼んでいるだけです。

Android アプリの多重起動禁止

相変わらず、マニュアルとか読まないので今回はじめて知ったこと。

Androidにおけるアプリの多重起動禁止方法
多分ソース的にも回避する方法があるような気がするんですが。

少なくとも簡単に調べた結果、Winアプリみたいにmutex的な排他方法とかは見つかりませんでした。

で、肝心の多重起動禁止方法

  • Activity のLaunchモード(launchMode 属性)を変更する
http://developer.android.com/intl/ja/guide/topics/fundamentals.html

起動モードは

  • standard(default)
  • singleTop
  • singleTask
  • singleInstance
とあり、とりあえず下二つのいずれかを選択しておけば多重起動はせずに済みそうです。

簡単な方法で一応確認

適当なアクティビティを一つ作って

onCreate ~ onDestroy を実装し、Logだけ吐くように設定

でもって以下のように動作させる。

起動→ホームボタン→再度アプリをメニューから起動(ホーム長押しではない)

ログ見ればすぐに分かりますが、

  • standard の場合、onStop のログのあとに onCreate が
  • それ以外の場合、onStop のログのあとに onRestart が
当然前者の場合、「戻るボタン」を押して終了したと思いきや、もう一つが現れます。

後者は「戻るボタン」を押せば、デスクトップが表示されます。

singleTop が多重起動しなかったわけは、上記サイトを確認。

というか、ホームボタン長押し→表示されるアプリ一覧から起動でonPause からの復帰というの、今回初めて知ったし・・・。

多分条件次第ではホームボタン長押しからでも多重起動するんじゃないかな?と思います(複数のActivityを抱えるアプリだとありそう)

いやしかし、みんな開発ガイドとか読んでるんかな?

対象の部分が日本語じゃなかったら絶対読んでない・・・。

ついでに言うとなんとなくしか理解していませんがw


2010/01/17 追記 ぐぐってたらこちらのブログの記事でIntentのCategoryとExtraとFlagの一覧表を作られていました。

Intentで呼び出す場合にフラグを考慮すれば、もちろん条件次第でしょうが多重起動は回避できそうです。

勉強になりました。

気がついていなかった

GoogleIME の64bit版出てるし。

さっぱり気がついていなかったwww

Google.cn 撤退確定情報と同時に気がつきました。

2010年1月10日日曜日

Android用のTwitterクライアントを作ってみる2

前回のアプリをもうちょっとのリッチに・・・
っていうか、あんなのじゃアプリというにもおこがましいですよねw
というわけで、どう考えても必要な機能を追加してみました。

ID/Pass の設定

前回のソースを軸に ID等を設定するActivityを作成し、Intentを使用して呼び出します。
ID等の保管には SharedPreferences を使用するので、一度設定すればアプリがアンインストールされるまで「たぶん」記憶されます。
以下のような感じで作成します。

  • 本体Activity(Main.java)
    • アプリ起動
    • ID/Passが設定されているかを確認
    • されてなければ設定画面を呼び出す
    • ID設定Activityの呼び出しは、menuボタン押下時のオプションメニューに追加
    • 後は前回と同じような感じ
  • ID設定Activity(SetID.java)
    • ID/Passの現在設定を取得しEditTextに設定
    • OKが押されたら、ID等を認証確認し問題なければ設定
    • Cancelが押されたら、現在設定のまま終了(ただし現在設定すらない場合はNG)

layout/main.xml

前回から変更なし


layout/setid.xml

ボタン二つとEditText二つ備えたlayoutを作成すればよし。
android:inputType="textPassword" をEditTextに指定すれば、いかにもパスワード入力っぽくなります。


AndroidManifest.xml

アクティビティを増やしたので、追加したものはしっかり記述する。
忘れると実行時に怒られます。
・<activity android:name=".SetID"></activity>


value/strings.xml

文字列をlayoutで設定した場合は、このxmlを編集します。


SetID.java
package com.omokageru.ak.twitter;

import java.io.IOException;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class SetID extends Activity {

 private String orgid;
 private String orgpass;
 private String tempid;
 private String temppass;

 private EditText idEdit;
 private EditText passEdit;
 private Button saveButton;
 private Button cancelButton;

 /* (非 Javadoc)
  * @see android.app.Activity#onCreate(android.os.Bundle)
  */
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  // TODO 自動生成されたメソッド・スタブ
  super.onCreate(savedInstanceState);
  setContentView(R.layout.setid);

  idEdit = (EditText) findViewById(R.id.setid_edit);
  passEdit = (EditText) findViewById(R.id.setpass_edit);
  saveButton = (Button) findViewById(R.id.button_save);
  cancelButton = (Button) findViewById(R.id.button_cancel);

  // プリファレンスからID/Passを取得
  SharedPreferences preferences = getSharedPreferences( getString(R.string.preferences_name), MODE_PRIVATE);
  orgid = preferences.getString( getString(R.string.preferences_id), "");
  orgpass = preferences.getString( getString(R.string.preferences_pass), "");

  // 初期値の設定
  idEdit.setText(orgid);
  passEdit.setText(orgpass);
  tempid = "";
  temppass = "";

  saveButton.setOnClickListener(new OnClickListener() {

   public void onClick(View v) {
    // TODO 自動生成されたメソッド・スタブ
    tempid = idEdit.getEditableText().toString();
    temppass = passEdit.getEditableText().toString();

    // tempid == null とか tempid == "" だと引っかからなかったので
    if( tempid.length() == 0 || temppass.length() == 0){
     Toast.makeText( SetID.this, "ID / pass の設定がありません", Toast.LENGTH_LONG).show();
     return;
    }

    // id/pass が正しいかを確認
    if( !chkid()){
     Toast.makeText( SetID.this, "ID / pass を見直してください", Toast.LENGTH_LONG).show();
     return;
    }

    // 新しい ID/pass を設定して終了
    SharedPreferences preferences = getSharedPreferences( getString(R.string.preferences_name), MODE_PRIVATE);
    SharedPreferences.Editor editor = preferences.edit();
    editor.putString( getString(R.string.preferences_id), tempid);
    editor.putString( getString(R.string.preferences_pass), temppass);
    editor.commit();
    setResult(RESULT_OK);
    finish();

   }
  });

  cancelButton.setOnClickListener(new OnClickListener() {

   public void onClick(View v) {
    // TODO 自動生成されたメソッド・スタブ
    // cancel といえども、まったく設定のない場合は許可しない
    if( orgid.length() == 0 || orgpass.length() == 0){
     Toast.makeText( SetID.this, "ID / pass の設定がありません", Toast.LENGTH_LONG).show();
     return;
    }
    setResult(RESULT_CANCELED);
    finish();
   }
  });
 }

 private boolean chkid(){
  try {
   DefaultHttpClient httpClient = new DefaultHttpClient();
   Credentials cred = new UsernamePasswordCredentials( tempid, temppass);
   httpClient.getCredentialsProvider().setCredentials( new AuthScope("twitter.com", 80), cred);

   HttpGet get = new HttpGet( "http://twitter.com/account/verify_credentials.json");
   HttpResponse response = httpClient.execute(get);

   // 失敗時の処理
   if(response.getStatusLine().getStatusCode() != HttpStatus.SC_OK){
    return false;
   }

   // 一応セッションを閉じておく
   HttpPost post = new HttpPost( "http://twitter.com/account/end_session.json");
   httpClient.execute(post);

   return true;

  } catch (ClientProtocolException e) {
   // TODO 自動生成された catch ブロック
   Toast.makeText( SetID.this, "予期せぬエラー SetID#chkid ClientProtocolException", Toast.LENGTH_LONG).show();
  } catch (IOException e) {
   // TODO 自動生成された catch ブロック
   Toast.makeText( SetID.this, "予期せぬエラー SetID#chkid IOException", Toast.LENGTH_LONG).show();
  }
  return false;
 }


}

なお、ID/Passの認証のチェックには
http://twitter.com/account/verify_credentials.format
を使用。

「この API は認証情報(BASIC認証で使うユーザ名とパスワードの組み合わせ)が有効かどうかを確認するのに利用することができる」
とありましたので、そのようにしました。

一応認証に成功したら
http://twitter.com/account/end_session.format
を呼び出しています。

TwitterAPIの仕様書を日本語に翻訳してくれている方がいらっしゃいますので、詳細はそちらを
観測気球様 Twitter API 仕様書 (勝手に日本語訳シリーズ)


Main.java
/*
 *
 * HTTPステータスコード(レスポンス)
 * 200 OK:                    成功
 * 304 Not Modified:          新しい情報はない
 * 400 Bad Request:           API の実行回数制限に引っ掛かった、などの理由でリクエストを却下した
 * 401 Not Authorized:        認証失敗
 * 403 Forbidden:             権限がないAPI を実行しようとした(following ではない protected なユーザの情報を取得しようとした、など)
 * 404 Not Found:             存在しない API を実行しようとしたり、存在しないユーザを引数で指定して API を実行しようとした
 * 500 Internal Server Error: Twitter 側で何らかの問題が発生している
 * 502 Bad Gateway:           Twitter のサーバが止まっている、あるいはメンテ中
 * 503 Service Unavailable:   Twitter のサーバの負荷が高すぎて、リクエストを裁き切れない状態になっている
 */

package com.omokageru.ak.twitter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class Main extends Activity {

 private Button updateButton;
 private Button reloadButton;
 private EditText updateEdit;
 private WebView webView;

 private DefaultHttpClient httpClient;
 private String strpass;
 private String strid;


 /* (非 Javadoc)
  * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
  */
 /**
  * オプションメニューの追加
  */
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // TODO 自動生成されたメソッド・スタブ
  super.onCreateOptionsMenu(menu);

  // 第二引数で設定するItemIDで
  // onMenuItemSelected で判定する
  menu.add( 0, 0, 0,R.string.Set_id_pass);
  return true;
 }

 /* (非 Javadoc)
  * @see android.app.Activity#onMenuItemSelected(int, android.view.MenuItem)
  */
 /**
  * 選択されたオプションメニューの動作
  */
 @Override
 public boolean onMenuItemSelected(int featureId, MenuItem item) {
  // TODO 自動生成されたメソッド・スタブ
  super.onMenuItemSelected(featureId, item);

  switch( item.getItemId()){
  case 0: //R.string.Set_id_pass
   GoSetTwitterIDAndPassActivity();
   break;
  }
  return true;
 }

 /* (非 Javadoc)
  * @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
  */
 /**
  * 子アクティビティからの結果を受け取る
  */
 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  // TODO 自動生成されたメソッド・スタブ
//  super.onActivityResult(requestCode, resultCode, data);
  if (resultCode == RESULT_OK) {
   SetTwitterIDAndPass(false);
  }
 }

 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  updateButton = (Button) findViewById(R.id.UpdateButton);

  // ボタン押下時の処理
  updateButton.setOnClickListener(new OnClickListener() {

   public void onClick(View v) {
    // TODO 自動生成されたメソッド・スタブ

    if(!chkid(false)){
     return;
    }

    // 通信を伴うので別スレッドで
    new Thread(){
     @Override
     public void run() {
      // TODO 自動生成されたメソッド・スタブ
      updateButton.setClickable(false);
      reloadButton.setClickable(false);
      String str = updateEdit.getEditableText().toString();
      UpdateTwitter(str);
      reloadButton.setClickable(true);
      updateButton.setClickable(true);
     }
    }.start();
   }
  });

  reloadButton = (Button) findViewById(R.id.ReloadButton);

  reloadButton.setOnClickListener(new OnClickListener() {

   public void onClick(View view) {
    // TODO 自動生成されたメソッド・スタブ

    if(!chkid(false)){
     return;
    }

    // 通信を伴うので別スレッドで
    new Thread(){
     @Override
     public void run() {
      updateButton.setClickable(false);
      reloadButton.setClickable(false);
      GetAndWriteHomeTimeLine();
      reloadButton.setClickable(true);
      updateButton.setClickable(true);
     }
    }.start();
   }
  });

  updateEdit = (EditText) findViewById(R.id.UpdateEdit);
  webView = (WebView) findViewById(R.id.twitter_web);

  SetTwitterIDAndPass(true);
    }

 /**
  * Twitterへつぶやく
  * @param str
  * 投稿内容
  */
 private synchronized void UpdateTwitter( String str){
  try {
   // パラメータstatus=hoge でhogeを発言(必須)
   HttpPost post = new HttpPost( "http://twitter.com/statuses/update.json?status=" + str);
   // 一応これで発言するはず
   HttpResponse response = httpClient.execute(post);

   // 失敗時の処理
   if(response.getStatusLine().getStatusCode() != HttpStatus.SC_OK){
    Toast.makeText( Main.this, "API制限か、Twitter ID/Password が間違っている可能性があります", Toast.LENGTH_LONG).show();
    return;
   }

   // ここからの描画はなぜか利かない・・・
   GetAndWriteHomeTimeLine();

  } catch (ClientProtocolException e) {
   // TODO 自動生成された catch ブロック
   Toast.makeText( Main.this, "予期せぬエラー Main#UpdateTwitter ClientProtocolException", Toast.LENGTH_LONG).show();
  } catch (IOException e) {
   // TODO 自動生成された catch ブロック
   Toast.makeText( Main.this, "予期せぬエラー Main#UpdateTwitter IOException", Toast.LENGTH_LONG).show();
  }

 }

 /**
  * friend_timelineは将来廃止予定らしいので home_timeline を取得し表示。
* http://d.hatena.ne.jp/nowokay/20091030 を参考にしました。 */ private synchronized void GetAndWriteHomeTimeLine(){ try { // webView.clearCache(false); HttpGet get = new HttpGet( "http://twitter.com/statuses/home_timeline.json"); // これで取得するはず HttpResponse response = httpClient.execute(get); // 失敗時の処理 if(response.getStatusLine().getStatusCode() != HttpStatus.SC_OK){ Toast.makeText( Main.this, "API制限か、Twitter ID/Password が間違っている可能性があります", Toast.LENGTH_LONG).show(); return; } //解析と出力 //サーバーからのデータを取得 InputStream is = response.getEntity().getContent(); InputStreamReader isr = new InputStreamReader(is); StringWriter strin = new StringWriter(); BufferedReader buf = new BufferedReader(isr); for(String line; (line = buf.readLine()) != null;){ strin.write(line); } buf.close(); isr.close(); is.close(); //出力準備 StringWriter strout = new StringWriter(); PrintWriter out = new PrintWriter(strout); out.println("<html><head><title>Twitter testapites</title></head>"); out.println("<body>"); //JSONデータからタイムラインを取得してHTMLを生成 JSONTokener token = new JSONTokener(strin.toString()); JSONArray arr = new JSONArray(token); for(int i = 0; i < arr.length(); i++){ JSONObject obj = arr.getJSONObject(i); JSONObject user = obj.getJSONObject("user"); out.println("
"); out.println(""); out.println("" + user.getString("screen_name") + "
" ); out.println(obj.get("text") + "
"); // ここで出力を確認しているが、発言内容が含まれているにもかかわらず、 // update側からはなぜか更新されない。 Log.i("t_test", "" + obj.get("text")); out.println("
"); } out.println("</body></html>"); strin.close(); out.close(); // 二回呼び出すと反映される・・・ // invalidate() の意味は?? webView.loadData(strout.toString(), "text/html", "utf-8"); webView.loadData(strout.toString(), "text/html", "utf-8"); strout.close(); // 描画更新 // 本体スレッドからの呼び出しではない場合に備えpostInvalidateを使用。 // webView.postInvalidate(); // Log.i("t_test", "Invalidate"); } catch (ClientProtocolException e) { // TODO 自動生成された catch ブロック Toast.makeText( Main.this, "予期せぬエラー Main#GetAndWriteHomeTimeLine ClientProtocolException", Toast.LENGTH_LONG).show(); } catch (JSONException e) { // TODO 自動生成された catch ブロック Toast.makeText( Main.this, "予期せぬエラー Main#GetAndWriteHomeTimeLine JSONException", Toast.LENGTH_LONG).show(); } catch (IOException e) { // TODO 自動生成された catch ブロック Toast.makeText( Main.this, "予期せぬエラー Main#GetAndWriteHomeTimeLine IOException", Toast.LENGTH_LONG).show(); } } /** * TwitterのIDとPassを設定する * @param bCreate * 初回起動時はtrueを指定 */ private void SetTwitterIDAndPass( boolean bCreate){ updateButton.setClickable(false); reloadButton.setClickable(false); // プリファレンスからID/Passを取得 SharedPreferences preferences = getSharedPreferences( getString(R.string.preferences_name), MODE_PRIVATE); strid = preferences.getString( getString(R.string.preferences_id), ""); strpass = preferences.getString( getString(R.string.preferences_pass), ""); if(!chkid( bCreate)){ return; } httpClient = new DefaultHttpClient(); Credentials cred = new UsernamePasswordCredentials( strid, strpass); httpClient.getCredentialsProvider().setCredentials( new AuthScope("twitter.com", 80), cred); GetAndWriteHomeTimeLine(); updateButton.setClickable(true); reloadButton.setClickable(true); } /** * Twitter ID/pass が設定されているかを確認 * @param bCreate * 初回起動時はtrueを指定 * @return * boolean値 */ private boolean chkid( boolean bCreate){ if(strid.length() == 0 || strpass.length() == 0){ // 初回起動時にid/passが未設定なら、設定画面を呼び出す if(bCreate){ GoSetTwitterIDAndPassActivity(); } return false; } return true; } /** * Twitter ID/pass を設定するActivityへ移動 */ private void GoSetTwitterIDAndPassActivity(){ try{ Intent intent = new Intent(Main.this, SetID.class); startActivityForResult(intent, 0); } catch( ActivityNotFoundException e){ Toast.makeText( Main.this, "起動先のActivityが見つかりません", Toast.LENGTH_LONG).show(); } } }

前回からちょこちょこっと変更した箇所もありますが、大きくは

  • Intentを使って別のActivityを呼び出す
  • menuボタンを押した時に、オプションメニューが出る
くらいでしょう。

相変わらず、Activityのサイクルを考慮したコードなんか書いていません。
あげく、TwitterAPIの使用回数制限とかも無視しまくり。
流用しよう、なんていう奇特な方が勝手に直してくれることを期待しています。
ってか、いいサンプルあったり、素敵な修正がある場合は教えてくださいw

次は

  • 何分かに一回 home_timeline を取得に行く
  • 変更があったら通知バーに通知する
というようなサービスでも作りますか?

動かすとこんな感じ。

なんかメニュー画面が出るとか、それっぽい感じがします。

初回起動時、IDとpassを設定


とりあえず入力


認証できなかった場合


認証成功すると、前回作成したような画面が


menuボタン押下時
急にそれっぽく見えるw


menuボタンからID設定画面へ
前回設定したidとpasswordが。(もちろんpassword とかは適当なものに打ち直していますよw)


android:inputType="textPassword" を設定している場合。
なんだかそれっぽいw



参考 「Android プログラミング入門」
  • 第二部 3.2.2 サブアクティビティからの応答を受け付ける
  • 第二部 5.1 プリファレンス
  • 第三部 1.3.5 メニュー