2019年12月3日火曜日

1人 Flutter Advent Calendar 2019:#3 display resolution


前回のソースをQVGAのデバイス実行してみると以下のようになります。

色々とはみ出していてデザインが破綻しています。
ボタンが要素の右の端にあったらもう押すことも出来ないです。

Androidだけではなく、iOSでも画面の解像度にに種類があります。
FlutterではMediaQueryから取得できるsizeはLogical Pixelとされており、Deviceに関係なくだいたい同になるようにいい感じにやってくれます。Androidのdpみたいなもんですね。

しかし、画面自体が小さすぎれば当然、上の画像のように切れてしまいます。

Androidは画像やsizeの定義ファイルを解像度ごとにdirectoryで分けることで、こういった状況に対処します。
https://developer.android.com/training/multiscreen/screendensities?hl=ja

Flutterでも画像に関しては似たような回避策がありそうです。https://flutter.dev/docs/get-started/flutter-for/android-devs#where-do-i-store-my-resolution-dependent-image-files

しかし、widthやheightなどの値については回避策が無さそうでした。
また、デザインツールで出力されるものはwidhtなどが直値で指定されるので、これですべての解像度に対応するのは難しいです。

とはいえ、デザインツールで作成したものからは直値しかわかりません。
アプローチの方法は、Percentで指定する、全体にScaleをする、FlexBoxを利用してサイズに対して柔軟に対応するかだと思います。

Percent指定ですとflutter_screenutil というのがあります。
最初に画面のサイズを決めて、以降はutilityで提供されている関数でwidth/heightを指定します。
基本的には最初に設定したサイズでscaleしているだけになります。
ただ、これだと全体でScreenUtil.getInstance()を利用してサイズを指定しないとダメなので、ちょっと手間ですし、画面の縦横比に従って設定値を調整しないといけません。
https://github.com/matsuhiro/display_resolution_tests/blob/master/lib/main.dart#L189-L275

全体にScaleする方法につい
https://flutter.dev/docs/development/ui/widgets/layout
の中から利用できるものを選んで解決しました。

私の見た中ではFittedBoxが解決策に最適でした。
使った場所はこんなかんじです。
https://github.com/matsuhiro/display_resolution_tests/blob/master/lib/main.dart#L85-L87

全体のレイアウトは崩さずに全体的にScaleされます。
現実には、見栄えのために画面幅に応じたPaddingを入れるのが良いと思います。

これは、最大でも画面幅の90%になるようにPaddingを入れてあります。

これを解像度が大きい端末で実行すると
この様になって、全体にきれいに表示できると思いますし、解決策としても簡単に出来るのではないかと思います。

ここで扱ったサンプルは
https://github.com/matsuhiro/display_resolution_tests
にあります。


FlexBoxに関しては、また違う機会に扱おうと思います。

2019年12月2日月曜日

1人 Flutter Advent Calendar 2019:#2 FigmaとZeplinでUI構築


Flutterでアプリを作成するにあたって、デザインツールでデザインしたものをきれいに取り込みたいという要望とか同期はアプリを作っていれば当然あると思います。
それを実現する方法としてFigma→Zeplin→Flutterの方法を一つの案として照会しようと思います。

連携のためにFigmaとZeplinはそれぞれDesktop版を入れてくれさい。
また、FigmaとZeplinでそれぞれProjectを一つ作っておいてください。

Figmaの細かい説明はしませんがFrameを作成してGoogle Pixel 2 XLを選択してみてください。これでだいたいいい感じに表示されると思います


Group化して、Component化などします。

(画像は適当に拾ったものです。。。)

Component化したGroupを選択した状態で、メニューからExportを選択すると

このようなメニューが出てくるので、Zeplinを選びます。
すると、1回、Browserを経由したあとで、以下のような画面になります。


Figma側では、以下を選んでおきます。


そうすると、Componentに以下のように取り込めます。


ココまでで、Designを取り込む準備が出来ました。

次にZeplinにFlutterのExtensionがあるので入れてみてください。
https://extensions.zeplin.io/johnatagoranet/flutter_extension
これです。

Zeplinを再起動すると以下のようにWidgetのコードが見られます。

これをコピペーすると


画像をassetsディレクトリーに入れて実行すると

となります。うまくいかないときは、Figmaの部品の並び順を見てみてください。z-indexで考えた時に下にあるものほど、下の順番にしないときちんと描画されないです。
Figmaの並び順でFlutterのコードも生成されるので、その順番で重なるためです。

なかなかの再現度ですが、微妙にずれています。
ずれている原因はborderのwidthの計算がextensionとFlutterの実装が合っていないようです。
borderのwidthを0にするとぴったり一致します。Blurなどのeffectは再現できていないようです。

同じようなことは、Sketch → Zeplinでも可能です。

このようにデザイナーが作成したものをきれいに取り込むことが出来るわけですが、小さい解像度のデバイスなどでは以下のようになってしまいます。

これについては、別途記述したいと思います。

ここで用いたcodeは
https://github.com/matsuhiro/figma_zeplin_test
こちらにあります。
Figmaはfigファイルというのを作成できだので、後でgitにでもpushしておきます。
Zeplinのプロジェクトは僕が無料版使っているから共有できないっぽいですね。


2019年12月1日日曜日

1人 Flutter Advent Calendar 2019:#1 snackbar


Advent Calendarに参加してみようかと思いましたが、なんかもう席が埋まっていたので1人でやることにしました。
25日間続けばいいけど。。。

FlutterのSnackbarはAndroidのSnackbarみたいなもんです。
両方ともMaterial Designといっているわけだし
https://material.io/components/snackbars/
の実装って感じです。

Android的なSnackBarだと表示できるものはCharSequenceだけなので、文字しか表示できません。
Spannableとかを利用すればiconを表示したりは出来ると思いますが、そこまでする必要ないだろうなとは思います。
Flutterの場合は、任意のWidgetがcontentとして指定することが出来ます。そのため、大きい画像を入れたり、好きなことが出来ます。

          final snackBar = SnackBar(
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.only(
                topLeft: Radius.circular(20),
                topRight: Radius.circular(20),
              ),
            ),
            behavior: SnackBarBehavior.fixed,
            content: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                Image.asset('assets/image_1.jpeg'),
                Text('Yay! A SnackBar!'),
              ],
            ),
            action: SnackBarAction(
              label: 'Undo',
              onPressed: () {
                // Some code to undo the change.
              },
            ),
          );


https://github.com/matsuhiro/snack_bar_test/blob/master/lib/main.dart#L60-L67


しかしながら、あまり自由なことをしないほうが無難でしょう。SnackBarのソースコードのコメントにも書かれていますし。
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/snack_bar.dart#L187-L188

注意することがあるとするとScaffoldのtreeの下にしか存在できないということでしょうか。
Scaffold.of(context).showSnackBar(snackBar)を利用するので、Scaffoldの配下のWidgetから呼び出すことが前提です。

ですが、floatingActionButtonのonPressed内部でshowSnackBarを呼び出そうするのは、誰しもが踏むみたいですね。
解説の記事が至るところにあります。
そういった解説記事を読むのも良いですが、たぶん一番良いのはFlutterが出しているErrorの内容をちゃんとよく読むのが良いです。
エラーの内容に回避策が書かれていますので。

回避策の一例を、いちおう上げておきます。
https://github.com/matsuhiro/snack_bar_test/blob/master/lib/main.dart#L131-L138

2017年5月22日月曜日

Dagger2を試す


また、だいぶ間が空いてしまった。

個人でツールみたいなアプリ作っていても大規模化することとかは無くて、Dagger2など使う必要など全く無かったりするんですが、そのまま使う必要ないなーと思っているだけなのもつまらないのでちょっと調べてみました。

Dagger2の導入の方法などは公式(https://github.com/google/dagger)を見るのが良いと思います。僕が試したときには最新は2.10でした。

大体の記事がmoduleを作ってcomponentを作って、そこからinjectするのですが、gsonとかアプリ全体で使うやつとrepositoryとか一部のactivityでしか使わなそうやつを分けたいなとか思ったりもするわけでして、moduleを2つに分けたので、componentもそれぞれ作って、2つのcomponentを特定のactivityで利用してinjectしたいなとか思ったので作ってみました。

1:  ├── AppComponent.java  
2:  ├── MainActivity.java  
3:  ├── RepositoryComponent.java  
4:  └── module  
5:    ├── AppModule.java  
6:    └── RepositoryModule.java  

ただ、これコンパイル通りません。
Componentでinject(MainActivity activity)などのmethodがあるとAnnotationProcessorがソースコードをapp/build/generated/source/apt/debug/com/matsuhiro/dagger2sample/MainActivity_MembersInjector.java
に生成します。
inject(MainActivity activity)を持っているComponentが複数あるとそれぞれのComponentがMainActivity_MembersInjector.javaを生成しようとするなどするけど出来ないようです。
複数のComponentをどうやって1つのactivityにinjectしようと調べたりもしたんですが、ActivityComponentみたいに専用のComponent1つ作ればいいかと思いました。
Moduleを複数作って、Componentは、Injectしたいclass毎に作ればよいかと。

でこうしました。

1:  ├── ActivityComponent.java  
2:  ├── MainActivity.java  
3:  └── module  
4:    ├── AppModule.java  
5:    └── RepositoryModule.java  

使い方は、これで良いとしてDagger2をAndroidで試している記事を見るとActivityに何かしらのclassをInjectしていものが多い気がしました。
Dagger2というかDIの場合に、どんなことが嬉しいかっていうと、やはりTestコードを書く時にテストしたいModule以外のModuleを置き換えやすいというのがあると思うのですが、ActivityのメンバーにInjectしたところでTestの時に置き換えやすくない。。。
https://github.com/google/dagger/issues/110#issuecomment-98260225
http://qiita.com/arenahito/items/d9bbca61c8a67cfad226
こんな感じのworkaroundが見つかったりもしますが、ApplicationクラスにComponent持たせて外から差し替えるって、なんだかテストのために無理やりやってる感がすごいなと。まあworkaroundなんでそうなんでしょうけど。

AndroidでDIを使おうと思ったら、ActivityなどAndroid特有の場所以外にロジックを押し込んで、そこでやるのが良さそうです。
そういうので、一番参考になるのってhttps://github.com/android10/Android-CleanArchitecture なわけですが、やっていることがワリといろんなところに飛ぶのと、Testの方がだいぶ素朴なので、自分で最小と思われるものを作ってみました。

ソースコード
1:  public class UseCaseUnitTest {  
2:    final class MockedSettingRepositoryImpl implements SettingRepository {  
3:      @Override  
4:      public String getValue(String key) {  
5:        return "mockrepo";  
6:      }  
7:      @Override  
8:      public void setValue(String key, String value) {}  
9:    }  
10:    @Test  
11:    public void MockedRepository() throws Exception {  
12:      UseCase useCase = new UseCase(new SomeBusinessService(), new MockedSettingRepositoryImpl());  
13:      String message = useCase.doSomething("hoge");  
14:      assertEquals(message, "mockrepo,biz");  
15:    }  
16:    @Test  
17:    public void MockedService() throws Exception {  
18:      SomeBusinessService mockedSomeBusinessService = Mockito.mock(SomeBusinessService.class);  
19:      Mockito.when(mockedSomeBusinessService.doSomething(Mockito.any(String.class))).thenReturn("mockbiz");  
20:      UseCase useCase = new UseCase(mockedSomeBusinessService, RepositoryModule_ProvideSettingRepositoryFactory.create(new RepositoryModule()).get());  
21:      String message = useCase.doSomething("hoge");  
22:      assertEquals(message, "mockbiz");  
23:    }  
24:    @Test  
25:    public void MockedInput() throws Exception {  
26:      SomeBusinessService mockedSomeBusinessService = Mockito.mock(SomeBusinessService.class);  
27:      Mockito.when(mockedSomeBusinessService.doSomething(Mockito.any(String.class))).thenReturn("mockbiz");  
28:      UseCase useCase = new UseCase(mockedSomeBusinessService, new MockedSettingRepositoryImpl());  
29:      String message = useCase.doSomething("hoge");  
30:      assertEquals(message, "mockbiz");  
31:    }  
32:    @Test  
33:    public void MockedInput2() throws Exception {  
34:      SomeBusinessService mockedSomeBusinessService = Mockito.mock(SomeBusinessService.class);  
35:      Mockito.when(mockedSomeBusinessService.doSomething(Mockito.any(String.class))).thenReturn("mockbiz");  
36:      SettingRepository mockedSettingRepository = Mockito.mock(SettingRepository.class);  
37:      Mockito.when(mockedSettingRepository.getValue(Mockito.any(String.class))).thenReturn("mockrepo");  
38:      UseCase useCase = new UseCase(mockedSomeBusinessService, mockedSettingRepository);  
39:      String message = useCase.doSomething("hoge");  
40:      assertEquals(message, "mockbiz");  
41:    }  
42:  }  

テストする時にUseCaseの引数にMockなどを突っ込めばOKなわけです。
MockedRepository()でやっているように引数にinterfaceを持たせる感じにすれば、テスト内で自由に挙動を変更できます。
ただ、めんどくさいですね。Mockito使うのが良いと思います。

MockedService()ではMockito使ってSomeBusinessServiceを差し替えてます。
@Mock使えばよいだけの気もしてます。
ここで、SettingRepositoryのインスタンス化にDagger2が自動生成したコードを使っています。実際にSettingRepositoryがアプリのコードでインスタンス化されるときにはこんな風にされています。
ただ、テストしたいのはUseCaseクラスなので全部Mockにすべきでしょう。
なのでMockedInputとMockedInput2では、違うやり方ですが引数を外部から変更しています。

Dagger2の使い方とか調べてましたが、Activityの中でinject(MainActivity activity)を呼び出したタイミングでactivityのメンバーに対してインスタンスがinjectされるのが自動生成されたコードからわかります。

1:  // Generated by dagger.internal.codegen.ComponentProcessor (https://google.github.io/dagger).  
2:  package com.matsuhiro.dagger2sample;  
3:  import com.matsuhiro.dagger2sample.domain.UseCase;  
4:  import dagger.MembersInjector;  
5:  import javax.inject.Provider;  
6:  public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {  
7:   private final Provider<UseCase> useCaseProvider;  
8:   public MainActivity_MembersInjector(Provider<UseCase> useCaseProvider) {  
9:    assert useCaseProvider != null;  
10:    this.useCaseProvider = useCaseProvider;  
11:   }  
12:   public static MembersInjector<MainActivity> create(Provider<UseCase> useCaseProvider) {  
13:    return new MainActivity_MembersInjector(useCaseProvider);  
14:   }  
15:   @Override  
16:   public void injectMembers(MainActivity instance) {  
17:    if (instance == null) {  
18:     throw new NullPointerException("Cannot inject members into a null reference");  
19:    }  
20:    instance.useCase = useCaseProvider.get();  
21:   }  
22:   public static void injectUseCase(MainActivity instance, Provider<UseCase> useCaseProvider) {  
23:    instance.useCase = useCaseProvider.get();  
24:   }  
25:  }  

具体的には20行目ですね。

onCreate()の一番最初とかで呼び出してあげなとバグになりそうだなとか、何回呼び出しても大丈夫なんだなーとかわかったのは面白かったです。

Dagger2のアプリのコード部分だけみていると魔法のようも思えますが、自動生成されたコードをみると割合と単純な事をやっているもんですね。

bloggerでソースコード書くのめんどくさくなってきたのでqiitaに移ろうかなとか思います。

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を生成していないので、コンテキストスイッチが走らない分早かったりもする。