2011年4月11日月曜日

続:Fragmentとandroid-support-v4.jar




気になったので、シーケンスとか描いてみた。

2.3.3 & android-support-v4.jarのシーケンス

3.0のシーケンス

android-support-v4のソースの中身を見てみたが、
どうやら
FragmentActivity内のonPostCreateで、
dispatchActivityCreatedしているのが問題の模様。
http://developer.android.com/guide/topics/fundamentals/fragments.html
には、ActivityのonStartと同じタイミングで
FragmentのonActivityCreatedのあとでのonStartがコールされるはずであり、
これに関しては問題ない模様であるが、
onStart後にコールされるonPostCreatedのタイミングで
またActivityCreatedしようとしてしまっている。
なので、FragmentActivity#onPostCreate内のdispatchActivityCreatedをコメントアウトすれば治りそうな予感。

まあ、それでもActivityの呼び出し元からFragmentのmethodを呼び出すのではなく、
Activityを拡張したFragmentActivityからFragmentのmethodを呼び出しているわけだから
シーケンスのタイミングのずれが発生するのは仕方の無いことか。。。

2011年4月6日水曜日

Fragmentとandroid-support-v4.jar


"SDK"/extra/android/compatibilityにある
FragmentManager.javaを読んだけど
新しいUI部品というよりはユーティリティーの追加のように感じる。
本来のFragmentの実装ではなく
Fragmentとほぼ同じ機能を有した外部ライブラリとして
FragmentActivityとかを実装しているわけだから、それは仕方のないことかも。
サンプルとしては、
FramgnetStackSupport.javaを参考にすればいい。
基本的には、FragmentXXXSupport.javaが外部ライブラリを利用したサンプルになる。

だが、そもそもFragmentを利用するメリットはどこにあるのだろうか?
画面遷移が画面全体の一部の差し替えになることで
かっこいいUIをつくることが出来る。
これは、Viewのadd removeでも出来たことだ。
でも、なんであえてこんなモノがあるのかの理由は
http://android-developers.blogspot.com/2011/02/android-30-fragments-api.html
に書いてある。
XMLを変えて、Activityを一部増やすことで
tablet向けにつくっていたActivityをソースコードをまるまる流用して
二つのActivityに分けることが出来ると言っているわけだ。

ただ、ここで気になってくるのがライフサイクルだ。
compatibilityの外部ライブラリを利用した場合の
FragmentとActivityのライフサイクルと
本来のFragmentとActivityのライフサイクルが一緒なのだろうか?
結論を書くと全然違った!!

ApiDemosにあるFragmentHideShow.javaをほとんどパクって
compatibilityの外部ライブラリを利用して作成したプロジェクトと
3.0の本物のFragmentを利用して作成したプロジェクトを作った。
長いからサンプルコードは貼り付けないが、
FragmentActivityはActivity
FirstFragmentはFragment
SecondFragmentはFragment
で、その各コールバックにEnter/Exitのログを仕込んだ。
Logcatで見えやすいのでログレベルをあえて分けた。

2.3.3 & android-support-v4.jarで作ったFragmentのサンプル


3.0で作ったFragmentのサンプル



結果としては、
2.3.3 & android-support-v4.jarのログ
----------------------------------------
Enter FragmentActivity#onCreate
Enter SecondFragment#onAttach
Exit SecondFragment#onAttach
Enter SecondFragment#onCreate
Exit SecondFragment#onCreate
Enter SecondFragment#onCreateView
Exit SecondFragment#onCreateView
Enter FirstFragment#newInstance
Exit FirstFragment#newInstance
Exit FragmentActivity#onCreate
Enter FragmentActivity#onStart
Enter FirstFragment#onAttach
Exit FirstFragment#onAttach
Enter FirstFragment#onCreate
Exit FirstFragment#onCreate
Enter SecondFragment#onActivityCreated
Exit SecondFragment#onActivityCreated
Enter SecondFragment#onStart
Exit SecondFragment#onStart
Enter FirstFragment#onCreateView
Exit FirstFragment#onCreateView
Enter FirstFragment#onActivityCreated
Exit FirstFragment#onActivityCreated
Enter FirstFragment#onStart
Exit FirstFragment#onStart
Exit FragmentActivity#onStart
Enter SecondFragment#onStop
Exit SecondFragment#onStop
Enter FirstFragment#onStop
Exit FirstFragment#onStop
Enter FragmentActivity#onResume
Exit FragmentActivity#onResume
Enter SecondFragment#onStart
Exit SecondFragment#onStart
Enter SecondFragment#onResume
Exit SecondFragment#onResume
Enter FirstFragment#onStart
Exit FirstFragment#onStart
Enter FirstFragment#onResume
Exit FirstFragment#onResume
----------------------------------------

3.0のログ
----------------------------------------
Enter FragmentActivity#onCreate
Enter SecondFragment#onAttach
Exit SecondFragment#onAttach
Enter SecondFragment#onCreate
Exit SecondFragment#onCreate
Enter SecondFragment#onCreateView
Exit SecondFragment#onCreateView
Enter FirstFragment#newInstance
Exit FirstFragment#newInstance
Exit FragmentActivity#onCreate
Enter FirstFragment#onAttach
Exit FirstFragment#onAttach
Enter FirstFragment#onCreate
Exit FirstFragment#onCreate
Enter FragmentActivity#onStart
Exit FragmentActivity#onStart
Enter SecondFragment#onActivityCreated
Exit SecondFragment#onActivityCreated
Enter SecondFragment#onStart
Exit SecondFragment#onStart
Enter FirstFragment#onCreateView
Exit FirstFragment#onCreateView
Enter FirstFragment#onActivityCreated
Exit FirstFragment#onActivityCreated
Enter FirstFragment#onStart
Exit FirstFragment#onStart
Enter FragmentActivity#onResume
Exit FragmentActivity#onResume
Enter SecondFragment#onResume
Exit SecondFragment#onResume
Enter FirstFragment#onResume
Exit FirstFragment#onResume
----------------------------------------

全然違う。。。
ぱっと見た感じだと
android-support-v4.jarを利用したほうがバグっているような。。。
3.0のソースを見ることが出来ないから、どんな理由でこのような差分が出ているのか
正確なところは解らない。
でも今のところFragmentを3.0以外で利用するのは控えたほうが無難かも。
少なくともそのまま流用は考えないほうがいいと思う。

2011年4月3日日曜日

Google Apps Scriptが使えそう



Google Apps Script(GAS)が気にはなっていたけど、
特に使いたいシーンもないし使っていなかった。
最近、東京電力が電力の利用状況をCSVで公開し始めた。
こいつを利用したアプリをマーケットに公開したが、
東京電力からのデータは一日分だけしか手に入らないし、
最新の情報であっても時間にすれば1時間半以上前の情報になってしまっている。
本当に知りたいのは、30分か1時間後の予測値だと思う。
それを実現するための第1歩として、データの収集がある。
収集しておいて、それを後で利用するためにサーバ立ててDBに入れて、
取り出すためのIF定義して。。。
みたいなことをしなければいけないほど大規模なデータでもない。
1日24要素なので、1年間続けたとしても9000弱の要素しかない。
なので、Google DocsのSpreadSheetに入れればいいし、GASで遊んでみたたいし。。。

GASを利用するためには
といった感じでスクリプトエディタを開いて、
のようなスクリプトを書く。Javascriptで。
初めてJavascript書いた。はずい。。。

そして、自動でデータを集めたいので定期実行するわけだが、
「トリガー」の中のメニューを選ぶと
のような画面になる。上の絵では10分おきにgetTodenDataを実行している。


自動で最新のデータがSpreadSheetに保存出来た。
データが欲しいときは、
ただ見たいなら
https://spreadsheets.google.com/ccc?key=0Arsm5M6qVMHDdEJDZUVTbTg5UWxWbGJ4Rm1ISVF1RHc&hl=en&output=html

CSVでデータが欲しいのであれば
https://spreadsheets.google.com/ccc?key=0Arsm5M6qVMHDdEJDZUVTbTg5UWxWbGJ4Rm1ISVF1RHc&hl=en&output=csv
とすることで誰でもダウンロードできる。


自分は普段スクリプト言語使わないし、
使ったことあるとしてもコピペーしかしたことが無かったけど、
一応、目的のものを作ることが出来た。
書いたのは画像で貼り付けた関数一つだけなので、
それで、定期実行やデータの公開が出来るのは非常に便利だと思う。

2011年3月29日火曜日

シリコンバレーカンファレンスに行って来た。


いまさらではあるが、
今月の初めにシリコンバレーカンファレンスに行って来た。
アメリカにいる最中に地震とかあって、なんだか落ち着かない旅であった。

シリコンバレーは、ソフトウェアとか色々生まれているところではあるが、
会社自体はドコも一緒だなぁ。というのが感想。
ダメなマネージャーはいるし、
開発プロセスがめちゃくちゃだったり、
給料は高そうであるが、出費も多くなるし、
田舎過ぎて経済的な効率はかなり悪そう。
住みやすい場所ではない。

そして、一番印象的だったのは、シリコンバレーにある会社でも
実装を東欧の賃金の安いとこで行っていたことだ。
全部の会社がそうであるわけではないし、
普通に考えれば当たり前だけど、シリコンバレーでも
賃金の安いところに実装(当然詳細設計もだろう)を出しているわけだ。
これを聞いて、ますますドコも一緒だと感じた。

ただ、シリコンバレーはたいしたことないとか思ったわけでもない。
GoogleもAppleもシリコンバレーにあるし、
ケータイのOS作りたかったら、シリコンバレーに行くしかない。

日本のソフトウェアの業界でもぜひシリコンバレーのようであって欲しい点としては、
人同士のつながりが、次の職場を決めていく感じとか
自分が何をしているのかカンファレンスとかに出て発表したりしていないと
職にありつけなさそうな所だ。
自分の能力を人に伝える努力をしないと職にあぶれるのはかなり厳しいけれど、
とても良いところだと思う。
こういった仕組みがあれば、
パワポ職人みたいなエンジニアもどきの数は、多少は減るだろうと思う。

ただ、東京でもモバイル端末のアプリケーションに関しては、
上のような仕組みがそのうち生まれ始めるのではないかと思っていたりする。

2011年2月27日日曜日

AndroidのLooper覚書


デ部に参加したときにLooperの話を聞いた。
なんとなくは解ったけど実際のところどうなのか微妙な理解なので
ソースをみてみた。
frameworks/base/core/java/android/os/Handler.java
の109行目あたりにコンストラクタがある(この辺のソースは、そうそう変わらないだろうから行数かいてみた)が、
prepareしていないとエラーが起こるような仕組みになっている。
なので、
new Thread(new Runnable() {
   @Override
   public void run() {
    Handler handler = new Handler();
    
   }
   
  }).start();
こんなことすると落ちる。
UI threadの場合はActivityが作成される際に、Looper.java内のprepareMainLooper()がコールされて、準備OKになるけど、UIでないthreadは自分でprepareしてあげないといけない。
そのことはLooperのjavadocコメントに書いてあったりする。
class LooperThread extends Thread {
 public Handler mHandler;
 public void run() {
  Looper.prepare();
  
  mHandler = new Handler() {
   public void handleMessage(Message msg) {
    // process incoming messages here
   }
  };
  
  Looper.loop();
 }
}
こんな感じで書いてみればーって感じでコメントがある。

でも、実際どーやって使うの?
って感じなので動くサンプルかいてみた。
package com.matsuhiro.android.loopersample01;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class LooperSample01 extends Activity {
 private static final String TAG = "LooperSample01";
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  
  Button btn_ui = (Button) findViewById(R.id.button1);
  Button btn_not_ui1 = (Button) findViewById(R.id.button2);
  Button btn_not_ui2 = (Button) findViewById(R.id.button3);

  Thread th = Thread.currentThread();
  long id = th.getId();
  Log.v(TAG, "onCreate ui id="+id);
  
  final LooperThread lt = new LooperThread();
  lt.start();
  
  btn_ui.setOnClickListener(new View.OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Thread th = Thread.currentThread();
    long id = th.getId();
    Log.v(TAG, "ui id="+id);
   }
  });
  btn_not_ui1.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    lt.mHandler.post(new Runnable() {
     @Override
     public void run() {
      Thread th = Thread.currentThread();
      long id = th.getId();
      Log.v(TAG, "not ui id="+id);
     }
     
    });
    
   }
  });
  btn_not_ui2.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    lt.mHandler.post(null);
    
   }
  });
 }
 
 class LooperThread extends Thread {
  public Handler mHandler;
  public void run() {
   Log.v(TAG, "Looper.prepare()");
   Looper.prepare();
   mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
     // process incoming messages here
     Thread th = Thread.currentThread();
     long id = th.getId();
     Log.v(TAG, "LooperThread id="+id);
    }
   };
   Looper.loop();
  }
 }
}
LooperThreadクラスの中でHandlerのhandleMessageをoverrideしているけど
こいつはMessageの中にcallback(Runnableのこと)が無かったら実行される。(インスタンス生成時にデフォルトのcallbackを設定しなければの話ではあるが。)
なので、
起動してbtn_ui、btn_not_ui1、btn_not_ui2の順番にクリックすると
onCreate ui id=1 ← onCreateが呼ばれた
Looper.prepare() ← 勝手Looperの準備しました
ui id=1 ← btn_uiクリックしました
not ui id=8 ← btn_not_ui1クリックしました
LooperThread id=8 ← btn_not_ui2クリックしました
のようなLog.vが出力される。

正直なところ、こんなの知っていたからって何時使うんだろって感じではある。
ただ、AsyncTaskとかを利用するような処理をいっぱいbackgroundで走らせると
基本設計時に考えたThread構成が良くわかんない感じになりそうではある。
そんなときには、自分でworker threadを作ってそのworkerに決まった処理をpostする。みないな仕組みを自分で作るにはいいかもね。

RSSのパースとか


AndroidにおけるRSSのパースに関しては、実はIBMのサイトで詳しく解説されている。
リンクで上げたページでは、パースの方法をいろいろ説明するためにFactoryを作っているけど
自分はandroid.sax.*を利用して実装した。
パースする前にHTTP GETするわけだけど、そのときに
httpget.setHeader("User-Agent", "Mozilla/5.0");
をセットし忘れないようにしないと、レスポンスがUTF-8で来てくれない。もっとも今はこれで平気だけど、何時ダメになるかわからないんですがね。。。

以前のエントリのようにAsyncTaskを利用して、HTTP GETして、
レスポンスを
 protected List<message> doInBackground(String... params) {
  String query;
  query = "https://www.google.com/history/find?output=rss"+"&q="+params[0];
  HttpGet httpget = new HttpGet(query);
  httpget.setHeader("User-Agent", "Mozilla/5.0");
  HttpResponse response;
  List<message> messages = null;
  
  try {
   response = _HttpClient.execute(httpget);
   
   int status_code = response.getStatusLine().getStatusCode();
   if (status_code < 400){
    InputStream istream = response.getEntity().getContent();
    FeedParser parser = new AndroidSaxFeedParser(istream);
    messages = parser.parse();
    istream.close();
   }
  } catch (ClientProtocolException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } finally {
   // TODO finalize
  }
  return messages;
 }
のようにして取得する。(エラー処理とかははずしてしまったが。。。) messagesには各RSSのアイテムが入っているので、 それぞれ値を取得してリストに表示させてやればいい。

今回は欲しい情報を得るのに、認証が必要でその処理がCookieを利用していた。 あんまりいい方法じゃないかもしれないが、一度認証済みのHttpClientを他のActivityでも使いまわしたいな。。。と思ったので、"android データ Activity"でググッたら出てくる出てくる。 簡単に言うとApplicationのインスタンスにデータを保持させて全体からそれを覗く。ということだ。 準備は
public class SingleConnectApp extends Application {
 private DefaultHttpClient _HttpClient = null;
 
 public void setHttpClient(DefaultHttpClient client) {
  _HttpClient = client;
 }
 
 public DefaultHttpClient getHttpClient() {
  return _HttpClient;
 }
}
こんなクラスを作ってmanifestのapplicationタグを <application android:icon="@drawable/gfoot_icon" android:label="@string/app_name" android:name="SingleConnectApp"> のようにして、onCreate内とかで
SingleConnectApp app = (SingleConnectApp)this.getApplication();
  DefaultHttpClient client = app.getHttpClient();
のようにすればActivity間でインスタンスを共有できる。
よくよく考えるとDefaultHttpClientを拡張してsingletonのクラス作れば同じこと出来んじゃね?てかソッチのが良いな。とか思ったけど、Applicationクラスにデータを持たしてみたかったのでそのまま進めた。

2011年2月19日土曜日

leanbackとYouTube Remote


Androidで面白いのを見つけたので紹介。
youtubeのleanbackをリモートコントロールするソフトだ。
そもそもleanback自体があまり知られていない。
自分はIT企業に勤めているけど、そこでも知っている人のほうが少ないようだ。
youtubeのURLの後ろにleanbackと打つだけで、TVっぽいUIのページ(http://www.youtube.com/leanback)に飛ぶ(たぶんFlash)。
基本的に十字キーだけでコントロールできるように設計されている。

youtubeにログインして、leanbackのページをPCで表示させてから
これを起動すると、
Androidからleanbackの表示コンテンツがコントロールできる。
スゲー。
レスも速いし。
firefoxでleanbackのページを全画面表示にするとほんとにテレビっぽい。

youtubeに上げてみた。