モバイル向けアプリの体感速度を少しだけ改善したい

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

そんな訳で、『なんちゃってパフォーマンスチューニング』についてのメモです。

サンプルプロジェクト:ActivateSampleをダウンロード

スマホやタブレット向けのモバイルアプリを開発していくと、PC上のデバッグ用シミュレータ上では気にならなくても、実機にインストールしてみたらビックリするほどモッサリした動作にヘコむというのは、よくある話です。(山田調べ)

例えば、画面遷移におけるモッサリ感。
(※画面が横方向とかにシュッとスライドするアレ。)
ボタンタップ後、いくらかの間隔を置いてから遷移されたりすると、ユーザビリティとしては致命的だったりします。
ユーザーからのアクションに対してのレスポンスは即座に実行されないと、不安感すら与えかねません。
(※タップしたらすかさずシュッと行かなくては!)

「あれ?動かないな、バグってるのか?」

「…あ、やっと動いた…」

なんて事にでもなったら、ユーザーはガッカリです。(山田調べ)

その時のソースコードを基にしたサンプルがこれ。

HomeView.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009" 
		xmlns:s="library://ns.adobe.com/flex/spark"
		title="HomeView">
	<fx:Script>
		<![CDATA[
			/** 画面遷移ボタンクリック */
			private function transitionButtonClickhandler(evt:MouseEvent):void {
				this.navigator.pushView(ListView1);
			}
		]]>
	</fx:Script>
	<s:actionContent>
		<s:Button id="btnTransition" label="→" click="transitionButtonClickhandler(event)" />
	</s:actionContent>
	<s:Label text="ここは最初のビューです。" horizontalCenter="0" verticalCenter="0" />
</s:View>

ListView1.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009" 
		xmlns:s="library://ns.adobe.com/flex/spark"
		title="修正前"
		initialize="initializeHandler(event)"
		backgroundColor="0xe4f2f5">
	
	<fx:Script>
		<![CDATA[
			import mx.collections.ArrayCollection;
			import mx.events.FlexEvent;
			
			private var listData:ArrayCollection = new ArrayCollection([
				{label:"アイテム0", code:0},
				{label:"アイテム1", code:1},
				{label:"アイテム2", code:2},
				{label:"アイテム3", code:3},
				{label:"アイテム4", code:4},
					・・・省略・・・
				{label:"アイテム20", code:20},
			]);
			
			/** 画面要素生成完了イベント */
			private function initializeHandler(evt:FlexEvent):void {
				if (lst) {
					//データをリストに表示する
					lst.dataProvider = listData;
				}
			}
			
			/** ホームボタンクリック */
			private function backhomeButtonClickHandler(evt:MouseEvent):void {
				this.navigator.popToFirstView();
			}
			
		]]>
	</fx:Script>
	
	<s:navigationContent>
		<s:Button id="btnBackHome" label="家" click="backhomeButtonClickHandler(event)" />
	</s:navigationContent>
	
	<s:Label text="MXMLタグでListを定義し、Initializeイベントでデータをセットしています。"
			 top="10" right="10" left="10"/>
	
	<s:List id="lst" width="100%" top="60" bottom="0"/>
</s:View>

このサンプルでの処理の流れとしては、

  1. 画面遷移ボタンをタップしてイベントハンドラの呼び出し
  2. 遷移する画面のコンポーネントを生成・初期化する
  3. 出来あがった画面オブジェクトがプッシュされる(画面の一番手前に配置されるということ)

といった感じになります。

処理2で、『画面のコンポーネントを生成・初期化する』とありますが、もう少し細かく見ていくと

  1. ListViewというオブジェクトをインスタンス化する
    ※ここまでが生成フェーズ
  2. ListViewオブジェクト内の子要素を片っぱしからインスタンス化していく
    ※initializeイベントはだいたいこの辺り
  3. 一覧表示するデータをListコンポーネントにセットする
    ※27行目
  4. 子要素のサイズやレイアウトとかを設定していく
    ※ここまで来たらcreationCompleteイベントが発生します。


だいぶ大雑把ですが、簡単にいえばこんな感じの流れになっています。

こういった『Flexコンポーネントのライフサイクル』に関しては、こちらのページで非常に分かりやすく解説されております。

要するに、画面要素等を全てMXMLファイル内にベタ書きしてしまうと、それらが全て完全に作り終わるまで表示はされない、つまり画面遷移は開始されないということですな。

では、どうするか……

  1. あきらめて、このままユーザーに我慢してもらう
  2. コンポーネントの軽量化なりロジックを見直すなり、がっつりチューニングする
  3. 先に画面遷移だけ実行させて、そのあとで子要素の生成・初期化処理等を行う

可能な限り2も行うのは勿論ですが、3なら2よりも手っ取り早く実現できそうです。

先ほどのサンプルを修正してみました。

ListView1.mxml ※追記部分

/** 画面遷移ボタンクリック */
private function transitionButtonClickhandler(evt:MouseEvent):void {
	this.navigator.pushView(ListView2);
}
<s:actionContent>
	<s:Button id="btnTransition" label="→" click="transitionButtonClickhandler(event)" />
</s:actionContent>

ListView2.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009" 
		xmlns:s="library://ns.adobe.com/flex/spark"
		title="修正後"
		backgroundColor="0xfede93"
		initialize="initializeHandler(event)">
	<fx:Script>
		<![CDATA[
			import mx.collections.ArrayCollection;
			import mx.events.FlexEvent;
			import spark.components.List;
			import spark.events.ViewNavigatorEvent;
			
			private var listData:ArrayCollection = new ArrayCollection([
				{label:"アイテム0", code:0},
				{label:"アイテム1", code:1},
				{label:"アイテム2", code:2},
				{label:"アイテム3", code:3},
				{label:"アイテム4", code:4},
					・・・省略・・・
				{label:"アイテム20", code:20},
			]);
			
			
			/** 画面要素生成完了イベント */
			private function initializeHandler(evt:FlexEvent):void {
				this.addEventListener(ViewNavigatorEvent.VIEW_ACTIVATE, viewActivateHandeler);
			}
			
			/** Viewアクティブ化イベント */
			private function viewActivateHandeler(evt:ViewNavigatorEvent):void {
				//リストコンポーネントを作成
				var lst:List = new List();
				lst.percentWidth = 100;
				lst.top = 60;
				lst.bottom = 0;
				//データをリストに表示する
				lst.dataProvider = listData;
				//リストを表示する
				this.addElement(lst);
				//イベントハンドラを削除する
				this.removeEventListener(ViewNavigatorEvent.VIEW_ACTIVATE, viewActivateHandeler);
			}
			
			/**ホームボタンクリック*/
			private function backhomeButtonClickHandler(evt:MouseEvent):void {
				this.navigator.popToFirstView();
			}
			
		]]>
	</fx:Script>
	<s:navigationContent>
		<s:Button id="btnBackHome" label="家" click="backhomeButtonClickHandler(event)" />
	</s:navigationContent>
	<s:Label text="ViewActivateイベントでListを作成し、画面に表示しています。"
			 top="10" right="10" left="10"/>
</s:View>

  • ListコンポーネントはMXMLタグで直接記述しない
  • viewActivateイベントを定義する
  • そのイベントハンドラー内でListコンポーネントを作成し、Viewオブジェクトに追加する

viewActivateイベントは、コンポーネントの生成・初期化が全て完了し、プッシュされてから発生するものなので、Listコンポーネントは画面がシュッとスライドし終わった後で作られるということになります。
これならユーザーからのアクションが起きてから、子要素の生成・初期化にかかる時間がなくなるぶん、画面遷移が素早く開始されますし仮に遷移後の子要素生成・初期化処理に時間がかかってしまったとしても、その間プリローダを表示しておけば、「ただいま読み込み中です。もう少しお待ちください…」といった明確な意思表示をユーザーに伝えることが出来ます。

まとめ

今回の小技では単に処理手順を入れ替えただけで、実際の処理にかかったトータルの時間を短縮したわけではないため、本来の意味の「パフォーマンスチューニング」とは少し違います。
ですが、アクションに対して出来るところまでは即座に反応したり、いまどんな処理をしているのかを明示したりすることでユーザーの不安感、ストレスを軽減し、体感速度を改善することは可能です。

今回はミニサイズなサンプルでしたので、殆ど差は感じないかも知れませんが、
ちょっと凝った作りのアプリとかになってくると、違いが体感できるのではないかと思いますよ、と。