Xamarin.Profilerでメモリリーク解析(Android)

メモリリークの原因検出スクリプト作りました

Xamarin.Androidのアプリを作っててなんかいろいろメモリリークしてるっぽいですが、どこでどう参照が握られてるのかわからず困ってたのの解決に役立ちそうなの見つけたのでカキコ。

Xamarin.Profilerもメモリーアロケーションなどについて調べるのあるんですが、どこでどれだけ作られたか、何がどんだけ残ってるかなどはわかるのですが、それがメモリから消えてくれないのはなんでなんだぜ?がわかりません。
ほしいのはルートからの参照がどうつながってるから消えてくれないかなんですが、Xamarin.Profilerにはなんかまだ実装されてないっぽい…

なんか調べてたらGitHub - mono/heap-shotというものがあるらしく、それだとGUIツールもあって目的の参照元を探せるっぽい。
その元となるファイルもXamarin.Profilerから吐けるっぽい。ステキ!

どれどれと思って落としてコンパイルしてみたんですが、その中のHeapShotプロジェクトのReferenceTreeReport.csが明らかにコンパイル通らない。経緯よくわからないけれど明らかにHeapShot.ReaderをいじってHeapShotを変更しないままコミットしてる感…
で中を見ながらReferenceTreeReport.csをコンパイル通るようにつじつま合わせてコンパイル通って動くようになりました。エラー内容としてはHeapSnapShotに実装されてるメンバを呼びたいっぽいのでObjectFileMapReaderの中のHeapSnapShotの最後のものを使うようにしたですが、本論ではないので割愛。
動かすとなんかそれっぽいクラス名とか出てるのでなんかデータは読み込めてるっぽい。
GUIツールもあったけれど、なんかMac用っぽいしよくわからん。
ソース見てたらHeapShot.Readerをライブラリとして使ってゴニョゴニョすればなんか目的の情報取れそう。

のでゴニョゴニョいじりながら試すならF# Interactiveですよね?という感じでF#でゴニョゴニョしてみました。

その成果がこちら。

#I @"D:\GitHub\heap-shot\HeapShot.Reader\obj\Debug"
#r "heapshot.reader.dll"

open HeapShot.Reader
open System.IO

let path= @"D:\GitHub\heap-shot\HeapShot"
let fName=Path.Combine(path,"l.mlpd")
let omap=new ObjectMapReader(fName)
omap.Read()

let ls=omap.LastSnapshot
//dummy listener
let listener={new IProgressListener with
                member this.ReportProgress(msg,progress)=
                  printfn"[progress] %s %f" msg progress
                member this.Cancelled=false}
//node in PathTree->o and name
let nodeToO (pTree:PathTree) node=
  let o=pTree.GetNodeObject node
  let name=ls.GetObjectTypeName o
  o,name  
//dump each reference
let rec dump (pTree:PathTree) depth maxDepth node=
  let tabs="".PadLeft(depth*2)
  let o,name=nodeToO pTree node
  printfn "%s%s(%d)" tabs name o
  if depth<maxDepth then
    pTree.GetChildNodes node
    |>Seq.iter(fun cNode->dump pTree (depth+1) maxDepth cNode)
//dump all reference  
let dumpAll (pTree:PathTree) maxDepth=
  pTree.GetRootNodes()
  |>Seq.iter(fun rNode->
    let _,name=nodeToO pTree rNode
    printfn"---- %s ------" name
    dump pTree 0 maxDepth rNode)
//type,name,object count from type name
let tncs=
  ls.GetTypes()
  |>Seq.map(fun t->t,ls.GetTypeName t)
  |>Seq.filter(fun(_,n)->n.Contains("ChartVM"))
  |>Seq.map(fun(t,n)->
    let os=ls.GetObjectsByType  t
    let c=os|>Seq.length
    t,n,c)
tncs|>Seq.iter(fun(t,n,c)->printfn"[%d] %s  %d" t n c)
//take head type.
let t,_,_=tncs|>Seq.head
//to PathTree
let pTree=ls.GetRoots(listener,t)
//dump all with max depth
dumpAll pTree 100

結果がこちら

参照元とれたー(´・ω・`)
左上が消えてほしいインスタンスでそれが右下の者たちに握られてるから消えられないのよという感じになってます。
なんかいろんなところから握られてますが、これを全部断ち切ればきっと消えるはず…

で使い方です。

Xamarin.Profiler起動

まずVSで目的のプロジェクトをコンパイルなどしたのちメニューよりXamarin.Profilerを立ち上げます。

とりあえずAllocation選びます。

↓これはいらない模様。Allocation選んでそのまますすんでおけーです
するとすぐにProfiling始まるんですが、ひとまず止めてCyclesも追加します。
これ必要なのか確認してませんが、これしてたら目的の動作はしてるのでとりあえずやってます。もしかしたら必要ないかも…
左上の白い四角で止めて右の+を押してダイアログでCyclesを選択。


Xamarin.Profiler設定

SnapShotを適当なところで取りたいのですが、Androidでは(だけ?)カメラマークでSnapShotを取るとXamarin.Profilerが固まるという香ばしいバグがずっと放置されているのでオプションで自動でSnapShotを取る風に設定します。時間はお好きに。

リーク状況確認

したら気になってるインスタンスが生成された後本当はなくなっててほしい状態にアプリを操作します。
自分の場合はチャートを表示した後、閉じたのでChartVMというクラスがなくなってるはずなのになぜか残ってるので調べたい…という感じです。

検索でChartVMと打つとやはり残ってるようです。

プロファイルデータ保存

上の画面の縦の赤線はそこでSnapShotが取られたという意味です。操作してそれがSnapShotにとられた風になったらFile->Save Asを選択、mlpdファイルを保存します。これにプロファイルしたデータが入ってます。


スクリプトで解析・表示

スクリプトで保存した場所やファイル名(例:"l.mlpd")を指定、探したいクラス名を指定(例:"ChartVM")、深さ何処までたどるかをお好みで指定(例:100)してスクリプトを実行!したら上にあったツリー風な感じに表示されるはず…

もし実行されたい方は上のスクリプトのソースをxxx.fsxファイルに張り付けて保存、heap-shotをビルド(heapshot.readerだけでいいです)してその出力のdllをスクリプトから参照できるところに置き、上に書いたmlpdファイルの場所など合わせてVisualStudioなどから実行してください。実行の仕方はググるtwitterなどで連絡ください#手抜き。

まとめ

とりあえずの動作としては探したいクラス名に引っかかった最初のものの各インスタンスの被参照状態をツリー上にダンプとなってます。
時間ができたらGUIツール作りたいところですね…
あとスクリプトなのでもっといろいろゴニョゴニョできると思うので色々試してみてください。

あと延々書きましたが、たぶんMacな人はコンパイルさえ通せばGUIツール使えて何も考えずに使えそうな予感…使えたら教えてください(´・ω・`)
けどスクリプトでゴニョゴニョできればきっと自分の使いやすいようにごにょっとできるはず…

関係ないですが、heap_shotの中のPathTreeのデータの覚え方ってのはこの手のものでよくあるパターンなんです?効率的そうだけど読みにくいす…教えてエロい人。

それでは素敵なメモリーリークチェックライフを!

Xamarin.Formsで順序変更リストビュー風なコントロール作りました。

本来なら動くサンプルにしてGitHubに上げろって感じですが、ちと忙しいのでソースだけ…
イメージとしては並び替えをしたいものを配列としてそれを引数にVM作成、DateTemplateをもとにそれらを表示するリストビュー風なもので好きに並び替え。並び替えたらVMにセットされるのでそれを好きなタイミングでよしなにすると。

リストビュー風といってますが、実態はRelativeLayoutです。それをScrollViewの上に置いてDateTemplateをもとに作成したViewを順序に応じてTranslateY変えることでリスト上に表示しています。
つかんだらそのViewを位置を調整しつつ掴んでるもの表示用のRelativeLayoutに移し、ドラッグ位置に応じてスクロールしつつ離したところの位置に調整して元のRelativeLayoutに戻します。

イベントの処理はAndroid側はRendererでMotionEvent使って、iOS側はXamarin.FormsのPanGestureを使ってます。この辺統一されてないのはScrollView上でイベントハンドリングするのに試行錯誤したらこんな風になったという感じです…

VM側はF#に書かれてたり、いらないnamespaceの定義があったりなどそのままだと動かないと思いますがきっとソース見てもらえればわかるはず…そのうちほかのもの合わせて動くサンプルにする予定。

とりまなんかコントロール作り方例として…(´・ω・`)

Xamarin.Forms 順序変更リストビュー風コントロール · GitHub

Xamarin Android Playerで端末が起動に失敗する

2016-04-26 - omanuke-ekunamoの日記の記事でHyper-V周りの設定を放置してたのでXamarinAndroidPlayerが起動してもスピードが遅く、代わりにVisualStudioEmulatorForAndroidを使ってたのですが、最近Windows10が勝手に夜中に起動することが多く、Hyper-Vのせいじゃないかという話があったのでHyper-Vを切ることにしました。

Hyper-Vの無効化と有効化の仕方について - moshimore Knowledge
のように

bcdedit /set hypervisorlaunchtype off

としてHyper-V切りました。


そするとXamarinAndroidPlayerが動かせないといろいろ支障でそうだったので一応動くか確認することに。


…端末起動しない…
なんかVirtualBoxのダイアログがでて何かを設定してる風なんだけど4回ぐらい出て端末ウィンドウが消えてしまう。
XAPとVirtualBoxを再インストールなどしても状況変わらず。

VirtualBoxネットワークアダプタの設定が悪いのかなど試したけど改善せず。


そうこうしてるうちにHyper-Vはオフにしててもなんか資源握ってるというようなことを書いてる記事があったので物は試しにWindows10 - Windows の機能の有効化または無効化 - PC設定のカルマのようにHyper-Vをチェック外して無効化。Windows再起動してXAP試す。


…無事端末まで起動しました\(^o^)/


とはいってもほんとに機能を無効化したせいで起動するようになったのかは確証もててません。
もしもの時は物の試しとしてみてください…

Xamarin入ってないマシンでXamarin.Formsプロジェクトの新規作成->デバッグまでやってみた

twitterでXamarin for VSのインスコ直後のFormsプロジェクト作成後、そのままデバッグ実行できないと言われたのでちょうどXamarin入れてないマシンがあったので試してみました。

デバッグ実行するまでにやったことを以下羅列します。

  • Xamarinインストール
  • Xamarin.FormsのPCL新規プロジェクトを作成。UWPがらみのプロジェクトは関係ないのでひとまず消す。
  • Startにdeviceが出てこないのでdeployment errorになる。
  • ソリューション閉じて開きなおす
  • 使いそうなAndroidSDKをTools>Android>Android SDK Managerを開いてダウンロード
  • StartにDeviceが出るので(SDKのインストールとは関係ないと思われ)適当にXamarin_Android_API_23を選ぶ
  • F5を押すとエミュレータが立ち上がるが、起動中のためかdeployment error
  • 画面が出てきたのでF5押したがBuild startedのまま返ってこない。
  • エミュレータは端末のロックマークを押すと端末の画面が真っ黒になったり怪しい挙動。結局ロック外れない。
  • Xamarin.Android.Player入れたけれど、スタートメニューから立ち上げようとしてもVirtualBoxが見つからないとエラー
  • マシン再起動したらXamarin.Android.Player立ち上がる。
  • 適当な端末を選んで三角の再生ボタンを押す。端末のインストールが始まる。途中でVirtualBoxのネットワークコンフィギュレーションが立ち上がるのでハイハイといいなりになる。
  • 適当な端末を立ち上げる。Nexus4-Marshmallow(先ほどインストールしたもの)を立ち上げたら途中で、Hyper-V動いてると遅くなるぞーとかシングルコアガーとか言われるのでシングルコアで遅くなるの我慢して動くようにいいなりになる。
  • なんか起動が遅いのでその間にVisualStudio Emulator for Androidをインストールする。
  • インストールしようとしたらWindowsPhone emulatorをサポートしてないといわれてインストールできず。Hyper-Vがらみ?
  • How to enable Hyper-V for the emulator for Windows Phone 8 | Microsoft Docsの仰せの通りにする。自分の場合はBIOSで仮想化がDisabledになってたのでEnabledに。
  • 設定後立ち上げなおしたらVisualStudio Emulator for Android(以下VSEmu)をインストール。
  • 適当に5 KitKat API 19を選んで端末起動。途中ネットワーク権限のため特権で再スタートとか出るので仰せの通りに。
  • VSでそれっぽい端末を選んでデバッグ実行
  • Android上のアプリ一覧にデプロイされた風だけどデバッグすぐ止まる。
  • アプリ直接実行しても一瞬起動画面出てすぐ消える。
  • Could not connect to the debugger.とか出てる。
  • その後端末上でアプリ何回かタップしたら立ち上がった。Welcom to Xamarin Forms!
  • デバッグ実行は変わらずCould not connect to the debugger.とでて出来ず。
  • もう調査してる時間ないので断念。
  • Dotnet by Example: Fix for “could not connect to the debugger” while deploying Xamarin Forms apps to the Visual Studio Android Emulatorの仰せの通りにUse Fast DeploymentをオフにしてHyper-Vマネージャ>端末の右クリ設定>プロセッサ>互換性>プロセッサバージョンが異なる物理コンピューターへ移行するをオンにしたらデバッグ実行できた<--New!

結論:結局デバッグ実行できてねぇ。初心者無理ぽ(´・ω・`)結局デバッグ実行できた。けど初心者無理ぽ(´・ω・`)

Xamarin.Forms Androidのマテリアルデザイン化

https://blog.xamarin.com/material-design-for-your-xamarin-forms-android-apps/
こちらの記事に従って変更試みたのですが、

Error retrieving parent for item: No resource found that matches the given name 'Theme.AppCompat.Light.NoActionBar'.
などと他にもcolorPrimaryやcolorPrimaryDarkなどが見つからないと出てコンパイル通らず。

ググったら同じようにはまっている人いろいろいるらしく、colorPrimaryは頭にandroid:をつけてandroid:colorPrimaryとしなければいけないなどと聞いてつけるも'Theme.AppCompat.Light.NoActionBar'のエラーだけは取れず。

結局 田淵 義人@エクセルソフト on Twitter: "@omanuke SDKだけ。6.0,5.1,5.0,4.4,4.1,4.0のSDKが入っていれば大丈夫だと思いますが。後は %localappdata%\Xamarin のhttps://t.co/0LRhdzq19x の5個を全部消して再ビルドですかね。" と教えてもらってそのようにC:\Users\xxx\AppData\Local\Xamarinの下のAndroid.Supportフォルダを消してリビルトしたら無事コンパイル通り。なんかビルドのゴミが残ってる?とりあえずXamarin( ゚Д゚)イッテヨスィ

colorPrimaryなどはむしろandroid:つけてはいけないようなので外します。

追加するstyle.xmlなどはこちらも参考にするといいのかも。
GitHub - jamesmontemagno/Hanselman.Forms: The most awesome Hanselman app

Xamarin.FormsでカスタムPicker

Xamarin.FormsでPickerを使いたかったのですが、
・表示をカスタマイズしたい。
・ピックする直前に選択する候補を選びたい。

などをしたかったのでデフォルトのPickerをそのまま使うのではちと都合悪く。

いろいろググっていたらこちらの
How to Create a Custom Color Picker for Xamarin.Forms | ComponentOne
が見つかったのですが、そこの中でやってるようなタップイベントを拾って非表示にしてたPickerにFocusをあてるやり方だと何回か表示された後、AndroidでもiOSでもFocusあてなおしても表示されなくなる現象がが。

カスタムレンダラーでどうにかしようかとかも思ったのですが、大げさになるしめんどくさく。
どうしたものかと思ってたのですが、タップされるたびにPickerを生成して表示したらどうかと思ったら今のところ良い感じ表示されなくなることもなく動いてます。

コードは以下のような感じに。

        <Grid x:Name="_pickerGrid">
          <BoxView BackgroundColor="Red"/>
          <Label Text="Hoge" TextColor="White"
                 x:Name="_label"></Label>
        </Grid>
            _label.GestureRecognizers.Add(
                new TapGestureRecognizer()
                {
                    Command=new Command(() =>
                    {
                        var _picker=new Picker()
                        {
                            Opacity=0.1,
                        };
                        _picker.SelectedIndexChanged += _picker_OnSelectedIndexChanged;
                        foreach(var i in Enumerable.Range(1,5))
                            _picker.Items.Add(i.ToString());
                        _pickerGrid.Children.Add(_picker);
                        _picker.Focus();
                    })
                });

画面はこんな感じに。

ラベルをタップしたらこんな感じに。

非表示された後Gridから削除するなど必要ですが、ひとまず何とかなりそうということで後ほど…(´・ω・`)

追記:
下記のように変えました。
_curPickerと取っといて、あったら削除するように。OPacity=0.1だとまだ見えてたので表示してから0に。
こんな感じのもとに使いやすいようなコントロールカプセル化するところでよしなにすればよいかと。
あとは表示時にSelectedIndexを既選択のものにする処理が必要と思われ…

            _label.GestureRecognizers.Add(
                new TapGestureRecognizer()
                {
                    Command=new Command(() =>
                    {
                        if (_curPicker != null)
                            _pickerGrid.Children.Remove(_curPicker);
                        var picker = new Picker();
                        picker.SelectedIndexChanged += _picker_OnSelectedIndexChanged;
                        foreach(var i in Enumerable.Range(1,5))
                            picker.Items.Add(i.ToString());
                        _pickerGrid.Children.Add(picker);
                        picker.Unfocused += (sender, e) => picker.IsVisible=false;
                        picker.Focus();
                        picker.Opacity = 0;
                        _curPicker = picker;
                    })
                });

Xamarin.FormsのPickerの"Done"を変更する->とりあえず"完了"にはなりました。

Xamarin.FormsのPicker使うとデフォルトでもAndroid側のはまぁいいんですが、iOSのだと右上に"Done"と表示されてちょっとどうなのよ感。
PickerRendererのソースをILSpyで見てもちょこっと修正というのではない感。

どうしたものかとググってたら
Info: How to change language (e.g. Back, Cancel) in standard-controls in iOS? — Xamarin Community Forums
にたどり着きました。

"Note: mabye you have to change the strings to your needed language."
などと書かれてましたが、info.plist開いて最後の方に

    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>CFBundleLocalizations</key>
    <array>
        <string>en</string>
        <string>de</string>
        <string>es</string>
        <string>fr</string>
        <string>ja</string>
        <string>pt-BR</string>
        <string>ru</string>
        <string>zh-Hans</string>
        <string>zh-Hant</string>
    </array>
  </dict>

という感じに追加したらとりあえずPickerは変わった様子。

好きな文字にする方法はわかりませんが、ひとまず日本語になったのでよしとします…(´・ω・`)

追記:
シミュレーターで試したときは変わっておらず、実機にデプロイしたら変わりました。たぶんシミュレータの言語設定などがおかしいせいだと思いますが、未確認です。