ラベル android の投稿を表示しています。 すべての投稿を表示
ラベル android の投稿を表示しています。 すべての投稿を表示

2016年9月21日水曜日

Multi windowで他アプリを開く


すごい久しぶりに投稿します。
最近ではqiitaに書くのが普通みたいですがブログに書くことにします。

http://qiita.com/takahirom/items/6efc39a7bfe00144ff32
記事の中でも「マルチウインドウ中のActivityからstartActivityForResultで起動されたActivityはマルチウインドウで表示(画面分割された状態で表示)されます。」のように記述されていますが、orientationを固定するなどしておけばmulti windowモードになっても未対応な旨が表示されるだけでmuti windowモードでアプリが起動することはないです。
startActivityForResultの挙動がPreview版だけなのかと気になるところなので実際にアプリを作って試してみました。
作成したアプリ

コードの中身はといえば
https://github.com/matsuhiro/MultiWindowOpener/blob/348744991f94bffb072edbc4472213cd9264a6d4/app/src/main/kotlin/net/matsuhiro/multiwindowopener/MainActivity.kt#L55-L59
のようにstartActivityForResultを利用して起動しているだけです。
これだけで、どんなアプリもmulti windowモードで起動します。

ユーザー的にはブラウザでどこにポケモンがいるのか調べつつpokemonGoで遊ぶとか出来るわけですが、開発者的にはいい迷惑ですね。
アプリ的には画面サイズが変わっているだけの認識のはずですが、完璧に対応しようとしたら画面サイズのパターンがほぼ無限なので最初から工夫しなければ行けないわけですね。

画面サイズの変更時のイベントなど調べたらまた追記しようと思います

2013年5月27日月曜日

メモリー、通信量、CPU利用率の計測

アプリケーションが実行されている際に、メモリやCPUの利用率など知りたいと思うこともあると思う。端末によってはアプリのRAMの利用状況とか見れたりするが、すべての端末で見られる訳ではないのと見られる端末でも実行中に常に監視し続けることが出来るわけではない。
で、計測ツールを作ってみた作ってみたアプリの状態観測。

RAMの情報は、android.os.Debug.MemoryInfoでとることが出来る。
trafficに関しては、android.net.TrafficStats.getUidRxBytesから得ることが出来る。
CPUの利用率は、/proc/の下の情報から計算することが出来る。
それぞれ、自分のアプリの情報だけではなくてpidとuidが分かればどのようなアプリからでもどのようなアプリの情報もとることが出来る。

RAMとTrafficに関しては、pidとuidが分かればあとはJavaのI/Fから取得できるので特に何も考えなくても良いが、CPUに関してはJavaのI/Fがないので、Linuxでプロセスの情報がどこにどうやって保存されているから見て計算する。psでやっているのと同じことをすれば良いので、コードを呼んだりとかしつつ実装してみる。/proc下の情報については、http://man7.org/linux/man-pages/man5/proc.5.htmlとかにあったります。
使用率の計算については、http://aya213.blogspot.jp/2008/12/cpu.htmlにあったりもします。

pidやuidの取り方は、ActivityManagerからgetRunningAppProcesses()を利用するとRunningAppProcessInfoのリストがとれるので、適当に自分の欲しいアプリをパッケージなどから探してRunningAppProcessInfoからpidとuidを取得できる。

メモリ等の情報を見られたところで、どの関数をコールしているのか等の情報を得ることが出来ないから、分析等には使えないがパッと見である程度の情報が得られるので、詳細に分析すべきかどうかの目安にはなると思う。

2013年5月25日土曜日

performance analytics in android

最近、全くブログ書いてなかったのでたまには更新しようと思います。
一回書くと満足してまた書かなくなりそうなので、何を書くのかある程度宣言して自分にやる気を出させようかと思う。
前々から、Androidについては、まとめたいとは思っていたことがいくつかあるのでそれをあげてみると
  • パフォーマンスの計測と改善について
  • メモリー、通信量、CPU利用率の計測
  • ファイルダウンロードとそのキャッシュ
  • 通信などのI/OとAsyncTaskについて
  • アプリケーションを作成する際のThread構成とかとHandlerについて
  • google-guiceについて
といった感じ。最近だとDrawerLayoutとかかな。順番はばらつきそうだけどそれぞれについてなんか書く。

今回は、パフォーマンスの計測と改善について書こうかと思う。
Androidの開発に限らないと思いますが、最初はサクサク動いていたけど進めていくうちに重くなってきたりすることあると思います。最初は軽いからいいやと思って放置していたら、いろんな人の修正が積み重なった結果、気がついたらとんでもないことに。。。とかとか。
そうなる前に日々計測をしていたいところですが、ガンガン作り込んでいる時には、そうもいかないもんです。ある程度つくったら見直せばいい気がします。

パフォーマンスの計測にはandroid.os.Debug.startMethodTracing()を使って計測します。
こんな感じでコンテンツをgetする時にUserAgentをWebViewと同じものを指定したいときもあると思います。まあ、あんまないと思いますが。
そこで、AsyncTaskを作るところから、コンテンツを取得するところまでを計測してみる。
結果は、
$ adb pull /sdcard/profileSample.trace .
$ traceview profileSample.trace
を実行してtraceviewで結果を見る。
分析する場合には、まあ、上から順番に詳細を見ていくしかない訳です。基本的にはAndroidのフレームワーク部分は無視しても良いと思います。どうしても必要な処理があったりもするし、フレームワーク側で時間がかかる処理があったりもするが、それが問題になる場合には改善すべきはアプリケーション側からのそのコードの呼び出し回数やタイミングであるからだ。
パフォーマンスで気になるのは、目的にもよるけど大抵はUI threadでの実行についてなので、UI Threadでかつアプリケーション側の処理で一番時間がかかっているのを探す。今回は2.3.3のemulatorで実行してみた。
時間がかかっているのはgetUserAgent()であることが分かる。ていゆか、上のコードだとほぼそれしか関数がないので、当たり前なんだけどね。
で、その関数内で何が行われているのかをみるとほぼすべてWebViewのインスタンスを生成に使われていることが分かる。
この関数ではUserAgentさえとれれば良いわけでWebViewのコンテンツを表示させたい訳ではないので、改善しようと思ったらWebViewをインスタンスかしないでUserAgentを取得する方法があれば良い。API Level 17以上であればgetDefaultUserAgentてのがあるが、それ以下だとだめなのであきらめる。というのではなく、WebViewのソースを見たりとかしつつ、インスタンス化の回避策を探すとreflectionを使えばいけるのである。そこでgetUserAgent2()とかいう関数を作ってみる。
で、同じように計測した結果が、以下になる
これによって、だいぶUI Threadでの処理が節約できているのが分かると思う。
android.os.Debug.startMethodTracing()自体は、計測の度に数値が変わるので全体の傾向しか調べることしか出来ないが、Incl CPU timeが1/7くらいにはなっている。ちなみにInclは、関数内で実行されている他の関数も含めた全体の実行時間になる。要はEnter/Exitの間の実行時間になる。
こんな感じで、実行時間がかかっているところの一番ざっくりした関数を探して、その中にどんどん潜っていきつつ。処理の代替手段を探すのがよい。そもそも設計が間違っていたりする場合もあるが、たいていは代替手段を探すことになると思う。ここで上げたようにstaticな情報であれば大抵は、reflectionを利用することでインスタンス化をさけたりできると思います。

ここでの例では、WebViewを生成しないことによって、CookieやWebViewWorkerのthreadを生成していないので、コンテキストスイッチが走らない分早かったりもする。

2012年12月10日月曜日

Facebookアプリの作成


新しくアプリを作ってマーケットにアップしました。
https://play.google.com/store/apps/details?id=com.matsuhiro.android.share

起動するとカメラが立ち上がって、それをFacebookに投稿するだけです。撮った写真をそのままアップロードしてしまうので、使うときはお気をつけて。

そもそもこのアプリを作った目的がFacebookのSDKを使い勝手を見てみたかったからなので、その使用感を軽くまとめてみようかと。

SDKの良い点、悪い点などを箇条書きでまとめてみる

良い点
・サンプルを起動して機能を試すまでが速い。ものの10分くらいで試すとこまで行ける
・ログイン部分の実装が楽。ActivityをFacebookの用意したものにして、その中で任意の場所にFacebookのログインボタンを入れたげればいいので、簡単。
・認証の仕組みが簡単。Androidのsignatureの情報からアプリを特定していて、それを入力するだけなので簡単。
・アップロードが簡単。ファイルを指定してあげるだけでアップロードできるのでよい。Videoなども簡単にできそう

悪い点
・ドキュメントがしょぼい。ログイン済みかどうかを判断するためにSessionを使うわけだがドキュメントからはすぐにわからなくてコードを見た。
・permissionのモデルについての説明がない。あるにはあるが探しにくくて一覧になってないのでよくわからない。
・コメント付きの写真の投稿とかどうすればいいかの説明がない。GraphAPIたたけばいいのだろうけど、リンクくらいあってもよいと思う。
・いろいろ機能が入っているけど、ほとんど使わないのでライブラリをもっと小さくしてほしい。
・Q&A方式の機能説明とかほしい。Javadocだけだと使い方がよくわからない。


とりあえずは良いものだという印象だ。
Facebookのアプリを作るデベロッパーは、結局はログインできれば良いので、それが簡単に実装できるのは大きいと思う。
ただ、permissionに関しては説明がほとんどない。どんな種類があるのかなどがないし、認証した際のステートの変化などがないので、どうやってアプリをハンドルすればいいのかいまいちはっきりしないので、サンプルから予想して書いている感じになってしまう。

・簡単にログイン実装ができる
・permissionの説明とそれを使うときのユースケース含めた説明がある
・他機能が使いたかったら追加でライブラリをインポートできる
みたいになっていれば理想かと思う。


ちなみに今回作ったアプリのソースは
https://github.com/matsuhiro/Share4F
にあります。FacebookSDKは入れていないし、app_idとかは抜いてあります。
ちなみにこれ、半日ちょっとで作れました。FacebookSDKとdeveloperコンソールが優秀な証拠かなとか思う。

2012年8月27日月曜日

Life cycle of view inside Fragment


I could not clearly understand life cycle of fragments which is added to back stack .
So, I created sample program at github.


Fragmentを使おうと思ったのであるけど、Fragment自体をstackさせた時の挙動がどうにも理解しにくかった。
Activityもそうなのであるけど、stackした時のlife cycleについてのドキュメントが無いから、自分で調べないとなんとも言えないのだよなー。
特に、よく分からなかったのはFragmentを含んだActivityがメモリ不足等の理由でシステムによりdestroyされた際にFragment内で作成したViewはdestroyされるっぽいんだけど、Fragmentのインスタンス自体は削除されないし、View自体も実態は削除されていないことが非常に不思議だ。Activityに紐付いたまま。
なので、メモリ不足等でActivityが削除されて、またonCreateがコールされた際にFragmentをnewしてaddするとFragmentがどんどん増えてしまう。


1:  public class MainActivity extends FragmentActivity {  
2:    private static final String TAG = "FragmentTest";  
3:    
4:    @Override  
5:    public void onCreate(Bundle savedInstanceState) {  
6:      Log.v(TAG, "Activity Enter onCreate");  
7:      super.onCreate(savedInstanceState);  
8:      setContentView(R.layout.activity_main);  
9:    
10:      if (savedInstanceState == null) {  
11:        FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction();  
12:        Fragment newFragment = FirstFragment.newInstance("http://www.google.co.jp");  
13:        ft.add(R.id.fragment_container, newFragment, "first");  
14:        ft.commit();  
15:      }  
16:      Button btn = (Button) findViewById(R.id.button);  
17:      btn.setOnClickListener(new OnClickListener() {  
18:        @Override  
19:        public void onClick(View v) {  
20:          FragmentManager fm = MainActivity.this.getSupportFragmentManager();  
21:          FragmentTransaction ft = fm.beginTransaction();  
22:          Fragment newFragment = SecondFragment.newInstance("http://www.goo.ne.jp");  
23:          ft.replace(R.id.fragment_container, newFragment, "second");  
24:          ft.addToBackStack(null);  
25:          ft.commit();  
26:        }  
27:      });  
28:      Log.v(TAG, "Activity Exit onCreate");  
29:    }  

なので、上記の10行目みたいにonCreateが再生成でコールされたと判断できたら、Fragmentを作らないようにしなければいけない。
このようにしておけば、Fragmentが余計に生成されるのを避ける事が出来て納得の行くlife cycleになると思う。

もう一つ分かりにくかったのは、Fragmentをreplace等でstackさせた時にもonDestroyViewがコールされるのだ。それでbackキーで元のFragmentに戻った際に、新しくViewを作りなおさなければいけないところがなんとも釈然としなかった。stackしているだけなのだからonStopまでで良いと思うのだが。。。
しかも、ViewのInstance自体はRootのViewに紐付けられたままで削除されているわけではないので、元々あったViewのインスタンスをActivityは保持したままであるし。


1:  public class FirstFragment extends Fragment {  
2:    private static final String TAG = "FragmentTest";  
3:    private WebView mWebview = null;  
4:    
5:    @Override  
6:    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
7:      Log.d(TAG, "FirstFragment Enter onCreateView");  
8:      if (mWebview != null) return mWebview;  
9:      mWebview = (WebView) inflater.inflate(R.layout.first_fragment, container, false);  
10:      mWebview.setWebViewClient(new WebViewClient());  
11:      String url = "http://www.yahoo.co.jp";  
12:      Bundle args = getArguments();  
13:      if (mLastUrl != null) {  
14:        url = mLastUrl;  
15:      } else if (savedInstanceState != null && savedInstanceState.containsKey("url")) {  
16:        url = savedInstanceState.getString("url");  
17:      } else if (args != null && args.containsKey("url")) {  
18:        url = args.getString("url");  
19:      }  
20:      mWebview.loadUrl(url);  
21:      Log.d(TAG, "FirstFragment Exit onCreateView view = " + mWebview.toString());  
22:      return mWebview;  
23:    }  


などのように、8行目にある感じで、すでにFragment内で保持しているViewを返すとエラーで落ちるし。

FragmentをStackさせる際にreplaceを利用するとonDestroyViewがコールされるの前提でコードを書かなければいけないようだ。どこにもその辺の作法とか無いけれども、確かにサンプルでは必ずViewを新しく生成していますしね。

でも、そうするとWebViewを使った時にとても困る。WebViewを生成してonCreateViewで渡してユーザが操作した後に新しくFragmentを重ねるとViewが破棄されてしまうので、historyが消えてしまう。
historyの仕組みを自分で実装しなければいけなくって非常に面倒くさい。
Stackとかでhistoryをstackすれば良い気もするが、あまりhistoryを独自実装しないほうが良いと思う。stackからurlを取り出してloadUrlするとhistorybackなのにloadが走ってしまうから非効率だと思うし。

結論的にはwebviewを保持しているfragmentの上にはfragmentをstackさせるべきではないということだ。
ただ、WebViewを利用しないのであればFragment内でonSaveInstanceStateが使えて、非常に便利だし、backStackさせてもいい感じにViewが再生成されるので、Fragment自体はとても便利だと思う。

2012年2月27日月曜日

SMSの送受信


AndroidにおいてSMSの送信をしようと思ったらpreinstallされたアプリにIntentを投げるのが一番簡単ではあるが、アプリ内で送信してしまうのもありで、それは結構簡単でした。

こんなかんじ
public class SMSSenderDialog extends Dialog {

    public SMSSenderDialog(Context context) {
        super(context);
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sms_sender_dialog);
        this.setTitle("SMS sender");
        final EditText phoneNumber = (EditText) findViewById(R.id.phoneNumber);
        final EditText message = (EditText) findViewById(R.id.sms_message);
        
        
        Button btn = (Button)findViewById(R.id.sendSMS);
        btn.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                SmsManager smsManager = SmsManager.getDefault();
                String destinationAddress = phoneNumber.getText().toString();
                String text = message.getText().toString();
                
                smsManager.sendTextMessage(destinationAddress, null, text, null, null);
                dismiss();
            }
            
        });
    }

}


sendTextMessageを書けばいいのだかららくなもんである

受信に関してはAPIdemosに既に記述があって
./ApiDemos/src/com/example/android/apis/os/SmsMessageReceiver.java
./ApiDemos/src/com/example/android/apis/os/SmsReceivedDialog.java
をパクれば一発であった。
SMSについてはかなり好きなように出来そうで、メッセージの内容も好きに見られるので、そこからUIの処理を走らせて通知とかもふつうに出来そうだった。





2012年2月26日日曜日

AndroidでModalなDialog


AndroidでModalなDialogがほしいー。と思ったことがある人も少なくないと思う。
ModalDialogってMFCとかではあったけどAndroidにはない。AndroidだとAlertDialogとかを使ってYES/NOのDialogを表示したとしても、結果を取得するためにListenerを書かなければいけないし、必ず非同期になる。非同期で取得した結果から処理を分岐させたいときには、そのListenerの中で処理を記述しなければならない。こうすると関数のネストが深くなりがちになる。まあ、関数分ければいいだけの話だけど、いちいち他の関数に飛ぶのもなー。ということでModalなDialogが欲しくなる。

ModalDialog dialog = new ModalDialog();
int result = dialog.show();

のように書いたら結果が同期で帰ってくるようなやつ。
で、作ってみようかと思た。

結論だけ書くと無理だった。。。
java.util.concurrent.CountDownLatch
を使ってthread止めればいけんじゃね?とかおもったけど、
mStartSignal.await();
でUIのThread止めちゃうとイベントリスナーのコールバックが帰ってこないのだ。。。
まあ、ListenerもUIスレッドで動いているから当たり前ですよね。。。
やっぱAndroidって動きがシングルスレッドっぽいよね。

ただ、かなりインチキ臭いけど回避策もある。ModalDialogの呼び出しをUIスレッド以外で行えばいいのだ!そうすれば同期でコールできる。
でも呼び出し元はこうなる。

public class ModalDialogActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        Button btn = (Button) findViewById(R.id.start);
        btn.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                final Context c = v.getContext();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        final int result = ModalDialog.show(c);
                        ModalDialogActivity.this.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Toast toast;
                                switch (result) {
                                    case ModalDialog.YES:
                                        toast = Toast.makeText(c, "YES", Toast.LENGTH_SHORT);
                                        break;
                                    case ModalDialog.NO:
                                        toast = Toast.makeText(c, "NO", Toast.LENGTH_SHORT);
                                        break;
                                    case ModalDialog.CANCEL:
                                    default:
                                        toast = Toast.makeText(c, "CANCEL", Toast.LENGTH_SHORT);
                                        break;
                                }
                                toast.show();
                            }
                            
                        });
                    }
                }).start();
            }
            
        });
    }


コールした結果をToastで出すだけなのに、すげー深くなったww
まあ、教訓としてはAndroidではCountDownLatchを極力使わないほうがいいし、もし使うならLooperが絡んでいるThreadでは呼んじゃだめってことくらいか。
まあ、Androidの場合はCountDownLatchを使いたくなる機会ってほぼないとは思うけど。

2011年12月11日日曜日

WebViewからJavaScriptをコールしたり



AndroidとかiPhoneのアプリ開発はWebViewを使ったWebアプリが増えていく感じがあるが、
WebViewだけだと読み込みが遅いとかレスポンスが悪いとかまだまだNativeで作った方がユーザとしてはストレスが少ないものが作れるような気がする。
HTML5でアプリがWebアプリになったとしてもなかなかレスポンスの部分は解決が難しいのではないかな?というのが個人的な印象。
そんなときにはNativeとWebViewの組み合わせでアプリを作ろうとする場合があると思う。
両者を橋渡しすることになるのはJavaScriptになりそうだけど、それぞれのプラットフォームでコールの仕方とか違うみたいなので少し調べてみた。

AndroidとiPhoneを比べると
NativeからJSを呼び出すのはiPhoneのが整っていて
JSからNativeを呼び出すのはAndroidの方が整っている印象。


Androidの場合は、
・NativeからJSの呼び出し
mWeb.loadUrl("javascript:コールしたいJSの関数");
のようにWebViewのインスタンスからmethodをコールして
JSを呼び出すような感じになっている。
ただ、この関数戻り値がvoidのなので結果が分からない。。。
結果を知ろうと思ったら、関数コールの引数にcallbackを指定するような文字列を
入れるなどして結果を取得しなければならない。サンプルではconsole.logをフックして結果を表示しているけど、ほめられた方法ではない。


・JSからNativeの呼び出し
mWeb.addJavascriptInterface(new NativeClass(), "Android");
のようにするとNativeClassが"Android"がJS内における名前空間にように扱えるので
Android.callNative("arg");
のようにJS側からコールスことが出来る。callNativeはJavaのNativeClassのmethodになっている。
この関数は戻り値をとることが出来るので同期でコールすることが出来そうだ。


iPhoneの場合は、
・NativeからJSの呼び出し
[_webView stringByEvaluatingJavaScriptFromString:@"IPHONE.CallJSfromNative('hoge')"];
のように、WebViewのメソッドをコールスことでJSを呼び出すことが出来る。
ここでは、IPHONE.CallJSFromNativeがJS側で宣言されている関数になる。
stringByEvaluatingJavaScriptFromStringは戻り値があるので同期でコールすることが出来そうだ。


・JSからNativeの呼び出し
iPhoneの場合はJSからNativeをコールする方法はきちんと用意されていないようで、少し調べた限りでは、いろんな人がそれぞれ工夫しながら実現しているような印象だ。
具体的には独自のスキームを利用してコンテンツをJSからロードしたときにshouldStartLoadWithRequestがコールされるので、独自のスキームをフックして、処理を行うような感じにするのが一般的のようだ。
- (BOOL) webView:(UIWebView *)webView
                shouldStartLoadWithRequest:(NSURLRequest *)request
                navigationType:(UIWebViewNavigationType)navigationType {
    
    NSString *requestString = [[request URL] absoluteString];
    if (![requestString hasPrefix:@"callobjc:"]) return YES;
    
    NSArray *components = [requestString componentsSeparatedByString:@":"];
    NSString *function = [components objectAtIndex:1];
    [self performSelector:NSSelectorFromString(function)];   
    return NO;
}
上のような感じで、関数名を取り出した後にself performSelector:NSSelectorFromString()で内部に宣言した関数をコールしている。
サンプルをここにおいておいた。
WebがわはDjandoで作っているが、アプリではなく静的なファイルを戻すだけなので、
必要なのは、それぞれのディレクトリ内にあるindex.htmlとsite_templeteの中にあるJSのファイルだけだ。AndroidについてはeclipseにPydevさえ入れてあればwebサーバとかたてなくても試せるはず。


少し調べてみた感じだと、すべてのフレームワークに共通のIFを用意するのはなかなか難しそうで、用意するとしたら全部非同期になりそうだ。なぜなら戻り値がなかったりするから。
https://github.com/callback/callback-ios.git
にPhoneGapのソースがあって、これは結構面白かった。
JS側からNativeをコールするときにはPhoneGap.exec()を利用する。
内部では、gap:のスキームをloadするとWebViewのコールバックが呼ばれて、その中でJS側でキューイングされている文字列を取得して、Native側の関数をキューイングされている分だけすべて関数をコールする感じになっている。
独自にJSのNativeの連携部分を作るよりもこういったコードを流用する方がよいかもしれない。
./PhoneGapLib/javascripts/core/phonegap.js.base
は拡張子が微妙だけどJS部分だろうと思う。
./PhoneGapLib/Classes/PhoneGapDelegate.m
の中にJSからのコールバック部分があったりした。


実際にWebViewとNativeの組み合わせのアプリ作ってみて感じたけど、やっぱりWebだと連打はつらいなー。というのが現状のようだ。
それと思いのほかWebViewとNativeの組み合わせでやり取りしても、画面表示のレスポンスは結構よかった。


2011年10月4日火曜日

ichannel風のどの画面でもテロップ



いつでもどの画面でもテロップが表示され続けるアプリを作った。
Yahoo!の国内ニュースを取得してテロップで流し続けて
ドラッグで移動できるので邪魔にならない場所に移動できるが、消す方法が無いので我ながらかなりうざい。。。

コード整理したらマーケットに上げよーっと。

2011年9月30日金曜日

freeのPDF Viewer


Androidで使えるfreeでソースが手に入るPDF Viewerが欲しかったので探したがあまりいいのがなかった。
でもapvはい感じだった。
ただ、日本語の表示が出来なかったのでIPAフォントを入れて日本語表示できるようにした。
ソースは以下においてある。
https://github.com/matsuhiro/APV-Custom

コードを取得したら、
$ cd pdfview/scripts
$ ./build-native.sh
を実行するとnativeコードのライブラリができるので、
eclipseでプロジェクトをインポートしてあげれば動くはず。

拡大縮小の時に画面がチラつくが、表示の速さは実用レベルだw

これを自分用にカスタマイズしていこーっと。

写真は京都の鴨川です。3月のライオンにも出てきたところです。いいトコだった。マタ行きたい。

2011年9月12日月曜日

WindowsPhoneとAndroidのゲーム




WindowsPhoneゲームプログラミングの本を読んでみた。
発売前の本だけど、MSの人がブログ上でレビューアを募集していたので
メールしてみたらやらせてくれた。
9月末に発売する予定のようですが、内容はC#とか知らない自分でも十分に読める内容だったし、
量も程良いのでゲームプログラム以外でもWindowsPhoneアプリの入門には丁度よいかも。
WindowsPhoneの本自体は、ebookで公開されている。けどねぇ。。。
900ページ超の大作、しかも英語なので、読むとしてもこちらは検索してリファレンスとして使うくらいでしょうかね(^^ゞ


WindowsPhone自体はXNAとSilverlightの組み合わせで書くわけだけど、
Androidのアプリとか他のモバイルアプリを作ったことがある人はSilverlightで作るアプリは
とっつきやすいと思う。
XNAについては「ゲームってこう作るもんなのね。」という感想。
Silverlightは、フツーにXAML(ザムルとよむらしい。まあXMLですよね)でUIを書いて
UI部品のコールバックを書いてイベントドリブンで処理が進んでいく感じです。
Androidのアプリ開発と感覚が近いです。
でも、XNAの方は、初期化→コンテンツロード→状態更新→描画→状態更新→描画・・・
みたいに、状態更新と描画が繰り返し呼ばれる。しかも1/30秒ごとに。
じゃあイベントの処理(タッチとかジェスチャーとか)をどう処理するのか?というと
状態更新の中で、XNAのフレームワークの中にあるTouchPanelというクラスから
ReadGesture()のようなメソッドをコールしてイベントがあったら処理をする。
といった感じの処理でイベントキューから自分でイベントを取得して処理する感じだ。
フツーはアプリのフレームワーク側でやっているようなイベントキューからの
イベント取得処理をアプリ側でやる感じだと思う。


Androidでゲーム書くときは?
と思ったけどOpenGLでゲーム作ると少しだけど似た感じになるかな?
GLSurfaceView.RendererをimplするとonDrawFrameが繰り返し呼ばれて
その中で描画処理を行う。
イベント処理はというとGLSurfaceViewをオーバーライドして、onTouchEventの中でイベント処理をする。
なので、Androidは、描画は勝手に呼び出されるコールバックで行うけど
イベント処理はイベントドリブンで。
といった感じになっている。
onDrawFrame()の呼び出しはどこで行われているのかというと
frameworks/base/opengl/java/android/opengl/GLSurfaceView.java
の中。
GLをAndroidで使おうとするとGL専用のスレッドが勝手に作られるけど、
その中のguardedRun()でonDrawFrameがコールされている。
で、ソース見てみたらwhile(true)で延々コールしていた。
AndroidでJavaからGLを使うと勝手に繰り返しの描画が行われるみたいで
気おつけるべきだなーっと感じた。まあ、そうしないためのインテーフェースは用意されていたけど。


WindowsPhoneもAndroidもゲームを作るための仕組みはあるけど、
イベント処理はAndroidの方が楽そう。
でも描画や状態の更新が1/30secごとに呼び出されるというのは
ゲームを作る上では結構大事かも。(処理が重かったりすると描画がすっ飛ばされてコマ落ちみたいになることもあるみたいだけど。)
Androidはwhile(true)で処理しているから描画タイミングのすべてを把握できないのかも。
まあ、ソースをパッと見ただけなので、どこかのmethod内にタイミングを図るための仕組みが入っているかもです。

ただ、ゲームでないアプリを作るのであればWindowsPhoneのがよいかも。
ExpressionBlendがすごいので、いい感じのUIを作りやすいのはWindowsPhoneだと思う。

なんかモヤモヤした文章になってしまった。
やっぱりコード書いて確認しなきゃはっきりわからんね。
DevQuizも時間切れだし、こんど時間つくろ。

2011年6月29日水曜日

StatusBarのなかみ



以前、StatusBarに情報表示させるアプリを作った際に
ステータスバーでも自作のViewが表示出来ればいのにーと思ったのでStatusBarについて調べた。自分用のメモなので分かりにくくても仕方がない。

android.app.NotificationManager.notify(String tag, int id, Notification notification)
がアプリからコールされて、そのなかでenqueueNotificationWithTag()がコールされる。
サービスとプロセス間通信していいて、インターフェースのStubは
INotificationManager.Stubで、その実態の中で
com.android.server.StatusBarManagerService.addNotification()
がコールされている。
これも中でIPCのI/Fの
com.android.internal.statusbar.IStatusBar.addNotification()
をコールしている。
これの実態は
./frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
にIStatusBar.Stubの拡張として定義されているのだが、これがeclipseのパッケージに含まれてないんだよなー。なぜ??
まあいいや。
んで、それを
./frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
内で利用しているので、
StatusBarService.java内で作られたViewに対して、プロセス間通信を利用して
android.app.NotificationManagerから情報を渡していっている。
情報はStatusBarIcon implements Parcelableをプロセス間でわたして行っている。といったかんじ。

StatusBarIconView.set()からコールされているgetIcon()の中をみると
r = context.getPackageManager().getResourcesForApplication(icon.iconPackage);
みたいな感じにで、他のパッケージのリソースを取得している。
こうやれば他のパッケージが持っているリソースを利用出来るっぽい。
getIcon()の中でリソースのIDをとってDrawableを作成している。
なので、アイコンは自在に変更できそうもない。という結論。

プロセス間通信しまくりで、最終的にはSystemのUIに行ってしまったので、
やっぱり自作Viewの表示は無理そうだ。
でも、ソースを見ていて
./frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
は面白かった。
Handlerを拡張して自作のHandlerを作成して、
そのハンドラーに対して
mHandler.obtainMessage(MSG_UPDATE_NOTIFICATION, 0, 0, ne).sendToTarget();
みたいにMessageをsendしている。
handleMessageってどんな時に使うのが正解なんだろう?
と思っていたけど、このようにプロセス間通信で情報を取得して
それをUIの変更に反映させたい時には、プロセス間通信用のI/Fの中で
MessageをsendすることでUIスレッドに処理を変えてやることで、いい感じの実装になると感じた。

Handlerとかの仕組みって、どうやって思いついたんだろ?
本当はそれが一番知りたい。。。

2011年6月28日火曜日

androidのbuildが32bitでも出来るようになってた?


androidのソースの中身が更新されていて64bitじゃなきゃビルド無理ーな部分が消えていた。
実際、何も変更加えなくてもbuildできた。
でも、HPには8GBのRAMが必要って書いてあったけど、virtual machineじゃないからOK??
良く解らん。


マシンのスペックを晒すと以下のような感じ。

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=10.04
DISTRIB_CODENAME=lucid
DISTRIB_DESCRIPTION="Ubuntu 10.04.2 LTS"


$ cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 23
model name : Intel(R) Core(TM)2 Quad CPU Q9550 @ 2.83GHz
stepping : 10
cpu MHz : 2003.000
cache size : 6144 KB
physical id : 0
siblings : 4
core id : 0
cpu cores : 4
apicid : 0
initial apicid : 0
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs bts aperfmperf pni dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm tpr_shadow vnmi flexpriority
bogomips : 5652.55
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
power management:

processor : 1
vendor_id : GenuineIntel
cpu family : 6
model : 23
model name : Intel(R) Core(TM)2 Quad CPU Q9550 @ 2.83GHz
stepping : 10
cpu MHz : 2003.000
cache size : 6144 KB
physical id : 0
siblings : 4
core id : 1
cpu cores : 4
apicid : 1
initial apicid : 1
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs bts aperfmperf pni dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm tpr_shadow vnmi flexpriority
bogomips : 5652.46
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
power management:

processor : 2
vendor_id : GenuineIntel
cpu family : 6
model : 23
model name : Intel(R) Core(TM)2 Quad CPU Q9550 @ 2.83GHz
stepping : 10
cpu MHz : 2003.000
cache size : 6144 KB
physical id : 0
siblings : 4
core id : 2
cpu cores : 4
apicid : 2
initial apicid : 2
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs bts aperfmperf pni dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm tpr_shadow vnmi flexpriority
bogomips : 5722.90
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
power management:

processor : 3
vendor_id : GenuineIntel
cpu family : 6
model : 23
model name : Intel(R) Core(TM)2 Quad CPU Q9550 @ 2.83GHz
stepping : 10
cpu MHz : 2003.000
cache size : 6144 KB
physical id : 0
siblings : 4
core id : 3
cpu cores : 4
apicid : 3
initial apicid : 3
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs bts aperfmperf pni dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm tpr_shadow vnmi flexpriority
bogomips : 5652.50
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
power management:


$ cat /proc/meminfo
MemTotal: 3355252 kB
MemFree: 842344 kB
Buffers: 196160 kB
Cached: 1619120 kB
SwapCached: 3308 kB
Active: 1101240 kB
Inactive: 1249040 kB
Active(anon): 221664 kB
Inactive(anon): 320800 kB
Active(file): 879576 kB
Inactive(file): 928240 kB
Unevictable: 0 kB
Mlocked: 0 kB
HighTotal: 2498184 kB
HighFree: 624792 kB
LowTotal: 857068 kB
LowFree: 217552 kB
SwapTotal: 9823708 kB
SwapFree: 9809132 kB
Dirty: 112 kB
Writeback: 0 kB
AnonPages: 532084 kB
Mapped: 44960 kB
Shmem: 7456 kB
Slab: 98232 kB
SReclaimable: 85304 kB
SUnreclaim: 12928 kB
KernelStack: 2616 kB
PageTables: 6432 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 11501332 kB
Committed_AS: 1298504 kB
VmallocTotal: 122880 kB
VmallocUsed: 70356 kB
VmallocChunk: 34300 kB
HardwareCorrupted: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 4096 kB
DirectMap4k: 36856 kB
DirectMap4M: 872448 kB


全然関係ないけど、bloggerにも「+1」ボタンが増えていますね。

2011年6月15日水曜日

TYPE_SYSTEM_ALERTでいつでも一番上


なんか震災から落ち着かない日々が続いていたので、
全然かけてなかったけど書くかな。

Dock4DroidとかWave Launcherなど、Androidにおいて、
すべての画面で例外なく一番上にUI部品を配置する方法についてメモを取っておく。

基本的には
http://harehare1110.blogspot.com/2011/04/android-view.html
を参照すればそのようなことができる。
でもTYPE_SYSTEM_OVERLAYを使うとキーイベントとかが全部捨てられてしまうので、
TYPE_SYSTEM_ALERTを使う。
http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html
にWindowManager.LayoutParamsの説明がある。

public static final int FLAG_LAYOUT_NO_LIMITS
Since: API Level 1
Window flag: allow window to extend outside of the screen.

とか試してないけど、面白そうなフラグもあった。

で、自分でも試したいので、サンプルを作った。以下に置いてある。
https://github.com/matsuhiro/TestForTYPE_SYSTEM_OVERLAY
気をつけるのは、
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
をManifestに書くくらいで、作ったソースのフラグとかは以下のよううな感じ。
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
    WindowManager.LayoutParams.FLAG_FULLSCREEN |
    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
    PixelFormat.TRANSLUCENT);
  WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
FLAG_NOT_FOCUSABLEを入れてあげないと裏側の画面がうまく動かない。。。
でもAndroidってフォーカスが当たっているところにイベント配信する仕組みだったような??
InputMethodもViewキッカケだと起動しないみたいだし。
なんでもかんでも好きなようには出来ないかもですね(^^ゞ


調べてみて思ったのは、Dock4DroidとかWave Launcherなどのランチャーなら
こういった仕組みは便利だけど、それ以外だとちょっと使い道がなさそうということ。
今ランチャー以外で考えつくのは、C2DMとかのキッカケで全画面に
このようなViewを貼りつけて強制的に端末を操作不能にするとか?
うーむ。なんか危ないなこの仕組。
面白いけど、端末ベンダーの証明書で署名されていないアプリは
使えないようにしたほうがいいじゃねぇかな。

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年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に上げてみた。

2011年2月9日水曜日

AndroidでGoogle Web履歴をHTTP GET


Gfootというアプリをマーケットにリリースした。
アプリをリリースしたけど、何をやったのか?調べたのか?
書き残しておかないと忘れてしまいそうなので、
そろそろまとめてみる。
・ブラウザを利用しないでCookieを解決する
・RSSのパース
がメインかな?

GoogleのWeb履歴を見る際のCookieの扱いについては、ここを参照しました。
このサイトによると、履歴を見るためにログインするわけだが、そのためのCookieをログイン画面の
HTMLをGETで取得することで、解決していることがわかる。
また、ログインする際にformの情報をPOSTするわけだが、その情報も同時に取得している。
ただ、これだけだとAndroid上でどうやってログイン処理すればよいのかが
具体的にはわからないので、ここを参考にしてみた。
上記二つのサイトを参考に組み合わせてAndroidからGoogle Web履歴にアクセスしている。
以下は、説明に要らない部分など削っているので、変なところもあるかと思うが、
基本的には下記で得られた_HttpClient(DefaultHttpClientクラス)を利用すれば、履歴の取得が可能だ。
ちなみにHTMLをパースしてinput部分を取得するために
jericho-html-3.1.jarを利用しています。(LGPLなので使うだけならソースの公開義務はないはず。)
それと当然ですが、ネットワーク系の処理をするときにUIスレッドで処理すると
操作性が悪くなるので、AsyncTaskを利用してます。

@Override
  protected DefaultHttpClient doInBackground(String... params) {
   String email = params[0];
   String passwd = params[1];
   
   _HttpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY);
   _HttpClient.getParams().setParameter("http.connection.timeout", 5000);
   _HttpClient.getParams().setParameter("http.socket.timeout", 3000);
   
   HttpGet httpget = new HttpGet(https://www.google.com/accounts/ServiceLogin?hl=ja&nui=1&service=hist);
   HttpResponse response;
   String result = null;
   try {
    response = _HttpClient.execute(httpget);
    int status_code = response.getStatusLine().getStatusCode();
    Log.v("Gfoot","status_code="+status_code);
    if (status_code < 400){
     InputStream istream = response.getEntity().getContent();
     InputStreamReader reader = new InputStreamReader(istream);
     BufferedReader objBuf = new BufferedReader(reader);
     StringBuilder builder = new StringBuilder();
     String sLine;
     while((sLine = objBuf.readLine()) != null){
      builder.append(sLine);
     }
     result = builder.toString();
     istream.close();
    } else {
     _HttpClient = null;
    }
   } catch (ClientProtocolException e) {
    _HttpClient = null;
    e.printStackTrace();
   } catch (IOException e) {
    _HttpClient = null;
    e.printStackTrace();
   }
   if (result == null) {
    return null;
   }
   
   
   String name;
   String value;
   Source source = new Source(result);
   List linkElements = source.getAllElements(HTMLElementName.INPUT);
   int count = linkElements.size();
   List nameValuePair = new ArrayList(count);
   for (Element linkElement : linkElements) { 
    name = linkElement.getAttributeValue("name");
    value = linkElement.getAttributeValue("value");
    if ("Email".equals(name)) {
     value = email;
    }
    if ("Passwd".equals(name)) {
     value = passwd;
    }
    nameValuePair.add(new BasicNameValuePair(name, value));
   }
   // この段階でPOSTするために必要な情報は、nameValuePairに含まれている。
   
   HttpPost httppost = new HttpPost(https://www.google.com/accounts/ServiceLoginAuth?service=hist);
   try {
    httppost.setEntity(new UrlEncodedFormEntity(nameValuePair));
    response = _HttpClient.execute(httppost);
   } catch (UnsupportedEncodingException e) {
    _HttpClient = null;
    e.printStackTrace();
   } catch (ClientProtocolException e) {
    _HttpClient = null;
    e.printStackTrace();
   } catch (IOException e) {
    _HttpClient = null;
    e.printStackTrace();
   }
   
   return _HttpClient;
  }

ここでログインの際に
https://www.google.com/accounts/ServiceLogin?service=hist
というのを利用して、ログインのためのCookieを取得しているが、
これはどうやって知るのか?履歴以外はどうなのか?
気になるところだと思う。
このサイトがかなりいろいろな情報をもたらしてくれる。
service=histは履歴をさしているわけだ。

次回は、履歴やBookmarkをRSS形式で取得する部分と
パースについて。
また、おまけでアプリケーション全体において永続的なデータを作る方法。