この記事は 『CRESCO Advent Calendar 2017』 19日目の記事です。

こんにちは。スマートソリューションセンターの吉田です。

クレスコでは、Microsoft買収による無償化が騒がれる前の2013年より、Xamarinに関する取り組みを開始しており、Xamarinを利用したモバイルアプリ開発に力を入れております。
本記事では、Xamarin製アプリケーションのメモリリーク解析を、Xamarin Profilerを使って実施する手順をご紹介します。

Xamarin Profilerとは

Xamarin社が提供するパフォーマンス分析ツールです。
Xamarinアプリケーションのメモリ使用量やオブジェクトの状態をリアルタイムで記録し、パフォーマンスボトルネック、メモリリーク問題を解析することができます。
Monoランタイムで実行されるプログラムに関する情報を収集することができ、Xamarin.Android、Xamarin.iOS、Xamarin.Macアプリケーションのプロファイリングをサポートします。
同様のツールとして、iOSアプリ向けにInstruments、Windows UWPアプリ向けにVisual Studio Performance Profilerというものがございますが、また別の機会に。

画面構成

ツールバー

プロファイラの上部にあり、プロファイリングを開始/停止する、ターゲットプロセスを選択する、アプリケーションの実行時間を表示する、プロファイラアプリケーションを構成する分割ビューを選択するオプションを提供します。

Instrument List

有効化したすべてのプロファイル種別が表示されます。

プロットチャート

解析内容(メモリ使用量や、循環サイクルなど)が時系列に沿って表示されます。スケールを変更することで、時間幅を変更できます。

インスペクタビュー

設定、統計、スタックトレース情報、およびルートへのパスが含まれます。

要件

ライセンス

Visual Studio Enterprise サブスクリプションが必要です。
ライセンスの詳細はこちらをご覧ください。
https://www.visualstudio.com/ja/vs/compare/

環境

  • Windows or Mac(Xamarin.iOS、Xamarin.Macの解析には、Macが必要です)
  • Xamarin Studio 5.6以上 or Visual Studio for Mac v7.0.1(以下VSと記載)
  • Xamarin Profiler Ver.1.5.4-19
※ Xamarin Profilerは、Visual Studioインストール時に同時インストールされている場合は必要ありません。
※ インストールされていない場合は下記URLからファイルを取得して利用してください。

対象アプリケーション

  • Xamarin.Android、Xamarin.iOS、Xamarin.Macアプリケーション
  • Simulator,実機いずれも可

解析シナリオ

Xamarin社公式GitHubのサンプルとして、Todoリストを管理するアプリ(Xamarin.Forms)があるので、そちらを使用します。
本Todoアプリは以下の2画面構成になっています。
一覧画面 / 詳細画面

一覧画面 から 詳細画面 に遷移する際、画面Stack上にPushして遷移します。

詳細画面 から 一覧画面 に戻るときは、画面Stack上から詳細画面がPopされ、どこからも参照されなくなるので、GC(ガベージコレクション)によって、詳細画面のインスタンスが破棄され、メモリが解放されます。

というはずなのですが、実際よくあるのが、参照されないはずの画面がどこかから強参照されていて、メモリが解放されない=「メモリリーク」ということです。本家のTodoアプリは、現時点ではメモリリークが発生しませんので、メモリリークするように一部手を加えてみましょう。

前準備

以下は、Visual Studio for Macの開発画面です。左側のソリューションパネルをご覧ください。

TodoPCLソリューションは、Todo.Android、Todo.iOS、Todo.UWP、Todoという4つのプロジェクトから構成されているのがわかります。

本アプリは画面共通化を行えるXamarin.Formsフレームワークを適用していますので、View(画面)、Model(ロジック)などは、全て共通部(Todo)に記載されています。

例えば、詳細画面のレイアウト情報は、TodoItemPage.xamlに記載されています。

Xamarin.Formsの画面は、Xamlと呼ばれるXMLの拡張言語にて記述します。

データはDataBindingという機構を用いて、Model部のデータを表示します。みていただくとわかるように非常にシンプルです。

Xamarinのご紹介はこのくらいにして、メモリリーク コードの埋め込みを行いましょう。

TodoプロジェクトModels配下に以下のPropertyChangedEventHandler を1つ持つクラスを作成します。

using System.ComponentModel;
namespace Todo.Models
{
public class TodoLeakModel
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

続いて、TodoプロジェクトApp.csに以下を追加します。

アプリが起動してから、終了するまで生存するインスタンス_leakmodelの誕生です。

public class App : Application
{
public static TodoLeakModel _leakmodel = new TodoLeakModel();
.....
}

最後に、TodoItemPage.xaml.csに以下を追加します。

詳細画面(TodoItemPage)のコンストラクタで、TodoLeakModelのイベント購読を行っています。
購読解除は行っていません。

public partial class TodoItemPage : ContentPage
{
private Models.TodoLeakModel _model;
public TodoItemPage()
{
InitializeComponent();
_model = App._leakmodel;
_model.PropertyChanged += _model_PropertyChanged;
}
....
  void _model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Oh my Leaks.");
}
}

本来、詳細画面(TodoDetailItemPage)は、一覧画面に戻るタイミングで、画面Stackからpopされ、実行ソースからは参照されない存在となります。なので、GC(ガベージコレクション)に回収されてほしいのですが、生存期間の長いTodoLeakModelのイベントを購読したことにより、

TodoLeakModelがTodoItemPageに対する強参照を保持するため、GCの回収対象にならない、という例になります。

前準備が長くなりましたが、それではXamarin Profilerでメモリリークの解析をやってみましょう。

Xamarin Profilerの起動

Xamarin Profilerの起動は、Visual Studioから行います。

ツールバー [実行] -> [プロファイルの開始]を選択します。

Xamarin Profiler起動後、下記画面となりますので「割り当て」を選択してください。

選択後、アプリが起動し、プロファイルが開始されます。アプリを操作することで、メモリ使用量や、インスタンスの情報などがリアルタイムで表示されます。

スナップショットの作成

Snapshotとはその時点で生存しているオブジェクトのダンプをとる機能です。左上にある写真マークのボタンを押すこと記録できます。

Snapshotの詳細を確認するには、対象のSnapshotをダブルクリックします。今回は「Snapshot 0」をダブルクリックします。
この画面ではSnapshot時のメモリを使用しているオブジェクト一覧のうち、”todo”を含むオブジェクトを表示しています。

なお、右側パネルの「新しいオブジェクトのみ」を選択することで、「生存オブジェクト総数」か、「前回Snapshotからの差分」かを切り替えることができます。必要に応じて切り替えながら調査してください。

  • Check On : 前回Snapshotからの差分
  • Check Off : 生存オブジェクト総数

メモリリーク解析の実践

上記で使用したSnapshot機能を使用してメモリリークを検出します。5回スナップショットをとります。

1回目:初期起動後の一覧画面

2回目〜5回目:一覧→詳細に遷移した後、一覧画面に戻った後

記録した結果が以下です。

Snapshot0とSnapshot1は生存期間の長いオブジェクトがあるため、Countは大きく増加していますが、その後はコンスタントに580ほど増えています。

試しに、Snapshot 4をみてみましょう。

TodoItemPageのオブジェクトカウントが4になっています。

4回、詳細画面に遷移しているので、その回数分オブジェクトがリークしていることがわかりました。

ちなみに、右側の新しいオブジェクトのみにチェックをつけると、前回スナップショットから増加した分のみ表示されます。

ここからも、TodoItemPageと、その要素であるTodoItemが解放されずにリークしていることがわかりますね。

解放されていないオブジェクトを見つけたらどのオブジェクトと参照関係にあるか知りたいですよね。

そこで、サイクルを使用します。Cyclesはオブジェクト間の循環参照状態を表示する機能を有しています。

もう一度Xamarin Profilerを立ち上げ、サイクルを指定して、同様の操作をおこなってください。

録画終了後、検索窓で”Todo”を入力します。

すると、TodoLeakModelが複数のTodoItemPageオブジェクトを参照していることがわかりましたね。
さらにグラフの項目にカーソルをホバーすると、以下のように、インスタンスが生成された場所が表示されます。

以上となります。

実際はもっと複雑な構成が多いため、解析には時間がかかることが多いですが、こういった工程を経て品質の高いアプリ・システムが出来上がっていくことを考えると、こういったツールを提供してくれているのは大きいですよね。

ということで、今回も「Xamarinはいいぞ」でしめくくりたいと思います。笑

明日20日は、benishogaさんの「1dayインターンシップでのJavaScriptプログラミングゲーム Sourcer の活用紹介」です。

引き続きアドベントカレンダーをお楽しみください。