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を使いたくなる機会ってほぼないとは思うけど。

2012年1月2日月曜日

あけましておめでとうございます。





2011年はいろいろありましたが、初詣にも何年かぶりに行ってきたし、今年はなんだかやる気があるので、今までよりもがんばろうかと思います。

今年もよろしくお願いします。