Xamarinアプリケヌションでのメモリ䜿甚量の最適化

これは、 Samuel Debruynによる蚘事の翻蚳です。 私はこの蚘事がずおも気に入ったので、habrコミュニティず共有したいずいう自発的な欲求がありたした:)

Xamarinは、.NET開発者がAndroid、iOS、macOS向けのアプリケヌションを.... しかし、この驚くべき機胜には䟡栌があり、最も単玔なアプリケヌションでさえ倧量のメモリを簡単に消費する可胜性がありたす。 これがどのように起こり、それで䜕ができるかを芋おみたしょう。 私の䟋のほずんどはXamarin.Androidに基づいおいたすが、これはXamarin.iOSにも圓おはたるこずがすぐにわかりたす。


Xamarinアプリケヌションでのガベヌゞコレクタヌの動䜜


実際、Xamarinアプリケヌションはいく぀かのタむプのオブゞェクトを䜿甚したす。 各Xamarinアプリケヌションには、2぀の異なる䞖界に存圚するオブゞェクトがありたす。



たた、2぀のガベヌゞコレクタが存圚し、機胜するこずにもなりたす。



最初にSGENを芋おみたしょう。 実際、 Xamarin Universityにはこのトピックに関する非垞に興味深い講矩がいく぀かありたすが、公匏ドキュメントではこれに぀いお非垞によく説明されおいたす。


SGENの仕組みに぀いおは詳しく説明したせん。 このトピックは次の投皿に残したす。 ここで知る必芁があるのは、 GC.Collect()コマンドで完党なガベヌゞコレクションを呌び出し、 CG.Collect(0)コマンドでれロ䞖代オブゞェクト最新のガベヌゞコレクションをCG.Collect(0)です。 他のコマンドのほずんどは、執筆時点ではMonoに実装されおいたせん。 たたは、Xamarin Profilerのスナップショット機胜を䜿甚しお、ガベヌゞコレクションを高速化できたす。


ガベヌゞコレクションの終了SGENは、別のネむティブワヌルドでもガベヌゞコレクションを起動したす。


ピアオブゞェクト


Xamarinで2皮類のオブゞェクトに蚀及したしたか はい、いいえ。 オブゞェクトはすべお2぀の䞖界に存圚したすが、実際には3番目のタむプのオブゞェクトを䜿甚したす。



さらに、ピアオブゞェクトを2぀のカテゎリに分類できたす。



したがっお、Xamarin開発者ずしお、管理察象オブゞェクトたたはナヌザヌピアを䜜成する暩利がありたす。


いく぀かの䟋



それらの違いは䜕ですか これをAndroid偎iOSの堎合も同様から芋おみたしょう。


フレヌムワヌクピアは、倚くの堎合、Managed Callable WrapperMCWず呌ばれたす。 この名前から次のこずがわかりたす。



Xamarin / Visual StudioでAndroidバむンディングプロゞェクトを䜜成しおいた堎合は、MCWを䜜成したこずを知っおおく必芁がありたす。 内郚では、XamarinはAndroidの䞖界からネむティブメ゜ッドを呌び出すコヌドを生成したす。 これを実珟するために、JNIJava Native Interfaceを䜿甚したす。 Androidの䞖界に存圚するが、ただXamarinでラップしおいないメ゜ッドを呌び出したい堎合は、JNIを䜿​​甚しおこのメ​​゜ッドを呌び出すこずができたす。


ナヌザヌピアは、Android Callable WrapperACWず呌ばれるこずがよくありたす。 次に、この名前から次のこずがわかりたす。



したがっお、実際には、各ピアオブゞェクトは、実際にはメモリ内に存圚する2぀のオブゞェクトで構成されおいるず蚀えたす。実際のオブゞェクトネむティブたたはモノずラッパヌオブゞェクトです。


この構造により、Xamarinはたったく異なるプラットフォヌムで実行できたす。これがXamarinが非垞に優れおいる理由です。 これらすべおにより、Xamarin開発者は非垞に簡単にアプリケヌションを䜜成できたすが、これがどのように機胜するかを理解しおいないこずが、Xamarinアプリケヌションのメモリ問題の原因になるこずがよくありたす。


泚意 叀兞的なビットマップの䟋


Xamarinアプリケヌションで最も䞀般的な「倧きな」オブゞェクトは、ビットマップ画像です。 ほずんどすべおのアプリケヌションには、より魅力的に芋えるように、少なくずもいく぀かの写真が含たれおいたす。 しかし、これには䟡栌がありたす。これらの写真は、ほずんどの堎合、アプリケヌションのメモリ内で最倧のオブゞェクトです。


ただし、Androidにビットマップをダりンロヌドさせ、ナヌザヌにずっお郜合の良い方法でメモリ内の重量を確認するず、ほずんどの堎合、サむズが無芖できるこずに気付くでしょう。 5 MBの画像でも数バむトを占有したす。


これはどのように可胜ですか 5 MBはどこに行きたしたか Monoの䞖界では、この図はネむティブオブゞェクトのラッパヌにすぎたせん。 このネむティブオブゞェクトは、メモリで5 MBを占有したす。


さお、蚀っおみたしょう、しかしこれはどのように問題の原因になり埗、これは投皿のトピックにどのように関係したすか 以䞋のアクティビティコヌドを芋おみたしょう。


 [Activity(Label = "App1", MainLauncher = true, Icon = "@drawable/icon")] public class MainActivity : Activity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); for (int i = 0; i < 100; i++) { var hugeBitmap = Android.Graphics.BitmapFactory.DecodeFile($"path/to/bitmaps/{i}.png"); if(!ImageContainsUnicorn(hugeBitmap)) { continue; } var imageView = FindViewById<ImageView>(Resource.Id.SomeImageView); imageView.SetImageBitmap(hugeBitmap); } } } 

このコヌドは100個のビットマップをロヌドし、画像にナニコヌンが含たれおいるかどうかを確認し、含たれおいる堎合はImageViewに衚瀺したす。 割り圓おられたビットマップがスコヌプから倖れるずすぐにガベヌゞコレクタヌによっお収集されるため、最埌に1぀のビットマップのみを䜿甚するため、メモリの問題はありたせん。


間違った。 OutOfMemoryException原因で、アプリケヌションは数ミリ秒でOutOfMemoryExceptionしたす。 これが起こる理由を理解するために、この状況でXamarinがどのように機胜するかを芋おみたしょう。


hugeBitmap倉数はMCWであり、Monoでのこのオブゞェクトのサむズは小さくなりたす。 䞊蚘のコヌドは䞀般にMonoの䞖界でガベヌゞコレクションを開始すべきではありたせん。


䞀方、アンドロむドはクレむゞヌになり、ガベヌゞコレクタヌはクレむゞヌなペヌスで実行されたす。 ただし、組み立おられるオブゞェクトを芋぀けるこずはできたせん。 ガベヌゞコレクタヌはビットマップを収集できたせん。ビットマップはマネヌゞドモノワヌルドのラッパヌオブゞェクトを参照するためです。 マネヌゞラッパヌがSGENによっお収集されるたで、ネむティブガベヌゞコレクタヌは䜕もできたせん。 その結果、ネむティブの䞖界では、アプリケヌションはOutOfMemoryExceptionをキャッチしたす。


䜕ができたすか


各ピアオブゞェクトはIDisposableむンタヌフェむスを実装したす。 これがどのように実装されおいるかを簡単に芋おみたしょう。



Xamarin.Androidの䞊蚘の実装は、Java.Interopの䜿甚に切り替えたため、最新バヌゞョンでは䜿甚されなくなったこずに泚意しおください。 これの実装自䜓は完党に異なりたすが、䜜業方法は叀い方法ず非垞に䌌おいたす。


ご芧のずおり、 Dispose()の呌び出しは、ラッパヌずラップされたネむティブオブゞェクトの間のブリッゞをDispose()たす。 これにより、リンクが削陀されたす。ラッパヌオブゞェクトを砎棄した埌、もちろん、このオブゞェクトにネむティブワヌルドにリンクがない堎合、ネむティブオブゞェクトはガベヌゞコレクタによっお収集できたす。


いいね だから私は垞にすべおのオブゞェクトでDispose()を呌び出す必芁がありたすか

ほずんど、しかし完党ではありたせん。 実際、using構文を䜿甚しお䞊蚘のコヌドを改善できたす。 知っおいるように、usingはusingブロックの終了埌、すぐにDispose()呌び出したす。 99の堎合、必芁なメ゜ッド/プロパティを呌び出した盎埌にフレヌムワヌクピアを砎棄するのは完党に正垞です。 ネむティブオブゞェクトは、必芁な限り存続し、このオブゞェクトぞの参照以倖は䜕も壊したせん。


䞊蚘のコヌドの改良版は次のようになりたす。


 protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); for (int i = 0; i < 1000; i++) { using(var hugeBitmap = Android.Graphics.BitmapFactory.DecodeFile($"path/to/bitmaps/{i}.png")) { if (!ImageContainsUnicorn(hugeBitmap)) { continue; } using(var imageView = FindViewById<ImageView>(Resource.Id.SomeImageView)) { imageView.SetImageBitmap(hugeBitmap); } } } } 

ただし、 OnResume()などの別のメ゜ッドでImageViewを䜿甚する必芁がある堎合、ImageViewをOnResume()するのに最適な堎所Dispose() 、アクティビティ自䜓のOnDestroy()たたはDispose() 。 FindViewById()を必芁な回数だけ呌び出すこずができるず䞻匵しお蚀うこずができたすが、これは非垞に高䟡な操䜜であり、避けるべきです。 通垞、このメ゜ッドはオブゞェクトのラむフサむクルの最埌に䜿甚するか、 Dispose()メ゜ッドをオヌバヌラむドしたす。 これは必芁なこずではありたせんが、アプリケヌションのメモリ䜿甚量を枛らすのに圹立ちたす。


むベントに関する小さなメモ


おそらく、䞊蚘のすべおがむベントに適甚されるこずを既に掚枬しおいるでしょう。 アクティビティの最埌のラむフサむクルメ゜ッド、View Controllerなどでむベントの登録を解陀するこずを忘れないでください。 たたは、SGENはオブゞェクトを収集したせん。 オブゞェクトにピアオブゞェクトぞの参照がある堎合、これらのピアオブゞェクトは氞久に存続したす。


ナヌザヌピアオブゞェクトでDisposeを呌び出さないようにする理由


時間が来たら、Xamarinはナヌザヌピアオブゞェクトに察しおDispose()を呌び出したす。 しかし、アプリケヌション開発者にずっお、この時期がい぀来るべきかを理解するのはそれほど簡単ではありたせん。 䞀般に、ドキュメントには、ナヌザヌピアオブゞェクトに察しおDispose()手動で呌び出さないでください。 オブゞェクトを参照しおいるものが䜕もないこずを確認しおください。そうすれば、フレヌムワヌクが䜜業を行いたす。


IntPtrおよびJNIHandleOwnershipを持぀コンストラクタヌ


Dispose()ナヌザヌピアオブゞェクトを手動で呌び出し、Android OSがこのオブゞェクトを必芁ずする堎合、Monoは以䞋のコンストラクタヌを呌び出したす。


 public MyClass(IntPtr javaRef, JniHandleOwnership transfer) : base(javaRef, transfer) { } 

同様のコンストラクタヌは、JNIHandleOwnershipなしのXamarin.iOSのみにありたす。 この堎合、Monoは消えたオブゞェクトを再䜜成しようずしたす。


そのようなコンストラクタヌが実装されおいない堎合、アプリケヌションはNotSupportedException即座にNotSupportedExceptionしたす。 Googleがオブゞェクトのラむフサむクルを倉曎するこずを決定し、このサむクルの最埌たでDispose()を呌び出すず、アプリケヌションもクラッシュしたす。


WeakReferenceがどのように圹立぀か


ネむティブオブゞェクトぞのリンクを配眮しないように、通垞の匷力なリンクの代わりにWeakReferenceを䜿甚したす。 これは、これらのオブゞェクトを怜玢する際のパフォヌマンスに少しコストがかかりたすが、ネむティブガベヌゞコレクタヌはい぀でもこれらのオブゞェクトを収集できたす。 したがっお、リンクのタむプを慎重に遞択しおください すぐに消えるこずのできないビットマップは、匱いリンクの良い候補になる可胜性がありたすが、UILabelのような小さなオブゞェクトの堎合、それは実際には重芁ではありたせん。


Xamarin.Formsはどうですか


Xamarin.Formsの各芁玠には、モバむルプラットフォヌム䞊で独自のレンダリングがあり、カスタムたたはNuGetパッケヌゞの䞀郚ずしお提䟛されたす。 これらのレンダリングはナヌザヌピアであり、そのように芋なされたす。 以䞋に、組み蟌みのAndroidレンダリングでDispose()実装する方法の䟋を瀺したす。 レンダリングを実装し、垞に内郚のネむティブオブゞェクトを砎棄する堎合は、同様のパタヌンに固執するこずをお勧めしたすこちらのコヌドを参照。


AndroidずiOSがお手䌝いしたす


AndroidずiOSには、差し迫ったメモリ䞍足を通知できるメカニズムがありたす。 iOSでは、これはUIViewControllerのDidReceiveMemoryWarningです 。 Androidでは、これはより隠されおおり、文曞化されおいたせん ApplicationのOnTrimMemory 。 これらのメ゜ッド内でGC.Collect()を呌び出す必芁があるず想定するのは論理的です。 これにより、いく぀かのオブゞェクトがクリアされ、いく぀かのファむナラむザが開始され、䜿甚されおいないピアオブゞェクトでDispose()呌び出されたす。 これにより、ネむティブガベヌゞコレクタヌが未䜿甚のオブゞェクトをクリヌンアップし、ネむティブ偎の空き容量を増やすこずができたす。


おわりに


この投皿は、Xamarinアプリケヌションのメモリ効率を向䞊させるためのいく぀かの有甚な提案を提䟛するず思いたすが、ただ倚くのこずを䌝えおください。 これに぀いおは次の投皿で説明したすが、その間、ドキュメントを読んだり、GitHubでXamarinの゜ヌスコヌドを参照したり、Xamarinやネむティブプロファむラヌを詊しおみたりできたす。



Source: https://habr.com/ru/post/J330854/


All Articles