Xamarin.Android のパフォーマンス

Xamarin.Android でビルドされたアプリケーションのパフォーマンスを高めるための方法は多数あります。 これらの手法をすべて使用することで、CPU で実行される作業量や、アプリケーションで消費されるメモリ量を大幅に減らすことができます。 この記事では、これらの方法について説明します。

パフォーマンスの概要

アプリケーションのパフォーマンス低下は、さまざまな挙動で現れます。 たとえば、アプリケーションが応答しなかったり、スクロールが遅くなったり、バッテリーの寿命が縮まったりすることがあります。 ただし、パフォーマンスを最適化するには、効率的なコードを実装するだけでは済みません。 アプリケーション パフォーマンスに関わるユーザー エクスペリエンスも考慮する必要があります。 たとえば、操作の実行によって、ユーザーが他の操作を実行できない状況にならないようにすることで、ユーザー エクスペリエンスを改善できます。

Xamarin.Android でビルドされたアプリケーションのパフォーマンスとユーザーの体感パフォーマンスを高めるための方法は多数あります。 具体的な内容を次に示します。

Note

この記事を読む前に、まず、「Cross-Platform Performance」 (クロスプラットフォーム パフォーマンス) をお読みください。Xamarin プラットフォームを使用してビルドされたアプリケーションのメモリ使用量とパフォーマンスを改善するための、プラットフォーム固有ではない手法について説明されています。

レイアウト階層を最適化する

アプリケーションに追加された各レイアウトには、初期化、レイアウト、および描画が必要です。 weight パラメーターを使用する LinearLayout インスタンスを入れ子にすると、レイアウト パスに影響する可能性があります。これは、子がそれぞれ 2 回測定されるためです。 LinearLayout の入れ子になったインスタンスを使用すると、ビュー階層が深くなり、その結果、ListView などの、複数回拡大されたレイアウトのパフォーマンスが低下する可能性があります。 したがって、このようなレイアウトを最適化することが重要です。そうすれば、パフォーマンスが向上します。

たとえば、アイコン、タイトル、および説明を含むリスト ビュー行の LinearLayout について考えてみます。 LinearLayout には ImageView と、2 つの TextView インスタンスを含む縦方向の LinearLayout が含まれます。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:padding="5dip">
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_marginRight="5dip"
        android:src="@drawable/icon" />
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:text="Mei tempor iuvaret ad." />
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:singleLine="true"
            android:ellipsize="marquee"
            android:text="Lorem ipsum dolor sit amet." />
    </LinearLayout>
</LinearLayout>

このレイアウトは 3 レベルの深さであり、各 ListView 行で拡大しても無駄になります。 ただし、以下のコード例のように、レイアウトをフラット化することで改善できます。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:padding="5dip">
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="5dip"
        android:src="@drawable/icon" />
    <TextView
        android:id="@+id/secondLine"
        android:layout_width="fill_parent"
        android:layout_height="25dip"
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:singleLine="true"
        android:ellipsize="marquee"
        android:text="Lorem ipsum dolor sit amet." />
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_above="@id/secondLine"
        android:layout_alignWithParentIfMissing="true"
        android:gravity="center_vertical"
        android:text="Mei tempor iuvaret ad." />
</RelativeLayout>

前の 3 レベルの階層は 2 レベルの階層に減り、1 つの RelativeLayout で 2 つの LinearLayout インスタンスが置き換えられています。 各 ListView 行のレイアウトを拡大すると、パフォーマンスが大幅に向上します。

リスト ビューを最適化する

ユーザーはListView インスタンスに対してスムーズなスクロールと高速な読み込み時間を期待します。 ただし、各リスト ビュー行に深い入れ子のビュー階層が含まれる場合、またはリスト ビュー行に複雑なレイアウトが含まれる場合、スクロールのパフォーマンスは低下する可能性があります。 ただし、ListView のパフォーマンス低下を回避するために使用できる手法があります。

  • 行ビューを再利用する。詳細については、「行ビューを再利用する」を参照してください。
  • 可能な場合は、レイアウトをフラット化する。
  • Web サービスから取得される行のコンテンツをキャッシュする。
  • イメージのスケーリングを避ける。

これらの手法をすべて使うことで、ListView インスタンスのスクロールをスムーズに保つことができます。

行ビューを再利用する

ListView で数百行を表示する場合、少数のみが 1 画面に表示されるなら、数百単位の View オブジェクトを作成するのはメモリの無駄です。 代わりに、画面の行に表示される View オブジェクトのみをメモリに読み込み、その再利用されるオブジェクトにコンテンツを読み込むことができます。 こうすることで、数百単位の追加オブジェクトのインスタンス化を防ぎ、時間とメモリを節約することができます。

そのため、次のコード例のように、行が画面から消えるときに、そのビューを再利用のためのキューに格納できます。

public override View GetView(int position, View convertView, ViewGroup parent)
{
   View view = convertView; // re-use an existing view, if one is supplied
   if (view == null) // otherwise create a new one
       view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);
   // set view properties to reflect data for the given row
   view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = items[position];
   // return the view, populated with data, for display
   return view;
}

ユーザーがスクロールすると、ListViewGetView オーバーライドを呼び出して、表示する新しいビューを要求します。使用可能な場合は、convertView パラメーターに未使用のビューを渡します。 この値が null の場合、コードで新しい View インスタンスが作成されます。それ以外の場合は、convertView プロパティをリセットして再利用できます。

詳細については、「Populating a ListView with Data」 (ListView へのデータの設定) の「Row View Re-Use」 (行ビューの再利用) を参照してください。

アクティビティのイベント ハンドラーを削除する

Android ランタイムでアクティビティが破棄されても、Mono ランタイムでは有効な場合があります。 したがって、Activity.OnPause の外部オブジェクトに対するイベント ハンドラーを削除して、破棄されたアクティビティへの参照がランタイムで保持されないようにします。

次のように、アクティビティで、クラス レベルでイベント ハンドラー (複数可) を宣言します。

EventHandler<UpdatingEventArgs> service1UpdateHandler;

次に、OnResume などの、アクティビティでハンドラーを実装します。

service1UpdateHandler = (object s, UpdatingEventArgs args) => {
    this.RunOnUiThread (() => {
        this.updateStatusText1.Text = args.Message;
    });
};
App.Current.Service1.Updated += service1UpdateHandler;

アクティビティの実行状態の終了時に、OnPause が呼び出されます。 OnPause 実装では、次のようにハンドラーを削除します。

App.Current.Service1.Updated -= service1UpdateHandler;

サービスの有効期間を制限する

サービスの開始時に、Android ではサービス プロセスが実行されたままとなります。 これにより、プロセスの負荷が高くなります。そのメモリをページングできないか、他の場所で使用できないためです。 必要でないときにサービスを実行したままにすると、メモリ制約により、アプリケーションのパフォーマンスが低下するリスクが増大します。 また、Android でキャッシュできるプロセスの数が減るため、アプリケーションの切り替え効率が悪くなる可能性があります。

サービスの有効期限は、IntentService を使用して制限することができます。これにより、開始元のインテントの処理後にサービス自体が終了します。

通知時にリソースを解放する

アプリケーションのライフサイクル中に、OnTrimMemory コールバックにより、デバイスのメモリが少なくなったときに通知が提供されます。 次のメモリ レベルの通知をリッスンするには、このコールバックを実装する必要があります。

  • TrimMemoryRunningModerate – アプリケーションはいくつかの不要なリソースを解放することができます
  • TrimMemoryRunningLow – アプリケーションは不要なリソースを解放する必要があります
  • TrimMemoryRunningCritical – アプリケーションは、可能な限り多くの重要でないプロセスを解放する必要があります

さらに、アプリケーション プロセスがキャッシュされると、次のメモリ レベルの通知が OnTrimMemory コールバックで受信される場合があります。

  • TrimMemoryBackground – ユーザーがアプリに戻った場合に、すばやく効率的に再ビルドできるリソースを解放します。
  • TrimMemoryModerate – リソースを解放することで、他のプロセスをキャッシュに保持でき、全体的なパフォーマンスが向上します。
  • TrimMemoryComplete – 多くのメモリがすぐに回復されない場合は、アプリケーション プロセスがすぐに終了します。

受信したレベルに基づいてリソースを解放することで、通知に応答する必要があります。

ユーザー インターフェイスが非表示の場合はリソースを解放する

ユーザーが別のアプリに移動したときに、アプリのユーザー インターフェイスで使用されているすべてのリソースを解放します。キャッシュされたプロセスに対する Android の容量が大幅に増加する場合があり、これにより、ユーザー エクスペリエンスの品質に影響する可能性があります。

ユーザーが UI を終了するときに通知を受信するには、Activity クラスで OnTrimMemory コールバックを実装し、UI が非表示になっていることを示す TrimMemoryUiHidden レベルをリッスンします。 この通知を受信するのは、アプリケーションの UI コンポーネントがすべて ユーザーに対して非表示になった場合のみです。 この通知の受信時に UI リソースを解放すると、ユーザーがアプリの別のアクティビティから戻った場合に、UI リソースを引き続き使用して、アクティビティをすばやく再開できます。

イメージ リソースを最適化する

アプリケーションが使用するリソースのうち最もコストが高いものとして画像があります。多くの場合、画像は高解像度でキャプチャされます。 したがって、画像を表示する場合は、デバイスの画面に必要な解像度で表示します。 画像の解像度が画面よりも高い場合は、スケール ダウンする必要があります。

詳細については、「Cross-Platform Performance」(クロスプラットフォーム パフォーマンス) ガイドの「Optimize Image Resources」(イメージ リソースの最適化) を参照してください。

未使用のイメージ リソースの破棄

メモリの使用量を節約するには、不要になったサイズの大きいイメージ リソースは破棄することをお勧めします。 ただし、正しくイメージを破棄することが重要です。 明示的な .Dispose() 呼び出しを使用せず、using ステートメントを使用すると、IDisposable オブジェクトを正しく使用できます。

たとえば、Bitmap クラスは IDisposable を実装します。 using ブロックに BitMap オブジェクトのインスタンスをラップすると、このブロックを終了するときに、それが正しく破棄されることが保証されます。

using (Bitmap smallPic = BitmapFactory.DecodeByteArray(smallImageByte, 0, smallImageByte.Length))
{
    // Use the smallPic bit map here
}

破棄可能なリソースの解放の詳細については、「IDisposable のリソースを解放する」を参照してください。

浮動小数点演算の使用を避ける

Android デバイスでは、浮動小数点演算が、整数演算の場合より約 2 倍遅くなります。 そのため、可能な場合は、浮動小数点演算を整数演算に置き換えてください。 ただし、最新のハードウェアでは floatdouble の演算の実行時間に違いはありません。

Note

整数演算の場合でも、CPU によってはハードウェアで除算できないこともあります。 したがって、多くの場合、整数除算と剰余演算はソフトウェアで実行されます。

ダイアログを閉じる

ProgressDialog クラス (または任意のダイアログやアラート) を使用する場合は、ダイアログの目的が完了したときに Hide メソッドを呼び出す代わりに、Dismiss メソッドを呼び出します。 それ以外の場合、ダイアログは引き続き有効であるため、アクティビティへの参照が保持され、そのアクティビティがリークされます。

まとめ

この記事では、Xamarin.Android でビルドされたアプリケーションのパフォーマンスを高めるための手法について説明しました。 これらの手法をすべて使用することで、CPU で実行される作業量や、アプリケーションで消費されるメモリ量を大幅に減らすことができます。