レンダリングの原理からのFlutterメモリリークの調査(超詳細)

バックグラウンド

ご存知のとおり、メモリのレベルは、アプリのパフォーマンスを判断するための重要な指標の1つです。開発者がメモリリークの問題を分析、公開、解決するのをより簡単に支援する方法は、ほとんどすべてのプラットフォームまたはフレームワークで開発者が緊急に必要とする「標準」機能です。しかし、フラッターコミュニティにとっては、便利なメモリリークツールが不足しています。

フラッターを使用する場合、ダーツ言語を使用するため、レンダリングのためにc ++のskiaに送信されるレンダリングツリーの形成を通じて、ダーツレイヤーからc ++レイヤーへの長いレンダリングリンクがあり、ユーザーはレンダリングリンク全体の完全な理解、現時点でのメモリ使用量を理解するため。この論文では、レンダリングツリーの数に基づいてメモリリークを見つけるためのソリューションを提案します。

フラッターメモリには何が含まれていますか

仮想メモリまたは物理メモリ?

メモリについて話すときは、通常、物理メモリについて話します。同じアプリケーションが異なるマシンまたはオペレーティングシステムで実行される場合、オペレーティングシステムやマシンのハードウェアの状態が異なるため、物理メモリのサイズが割り当てられます。大まかに言えば、アプリケーションで使用される仮想メモリ(仮想メモリ)はほぼ同じです。この記事の説明では、仮想メモリについて説明します。

コード内で操作されるすべてのオブジェクトは仮想メモリで測定できることを直感的に理解でき、オブジェクトが物理メモリに存在するかどうかは関係ありません。オブジェクトの適用を減らすことができる限り、オブジェクトは白猫に関係なく、可能な限り保持されない黒猫は、ターゲットを減らすことができる「良い猫」です。

フラッターメモリーについて話し合うとき、私たちは何について話しているのですか?

フラッターは、使用する言語から3つの部分に分けることができます。

  • フレームワークレイヤーはDartによって作成され、開発者はアプリケーションレイヤー開発の最上位レイヤーに触れます

  • 主にグラフィックスレンダリング用にC / C ++で記述されたエンジンレイヤー

  • Objective-C / swiftを使用するiOS、Javaを使用するAndroidなど、インプラントレイヤーの言語で記述されたエンベッダーレイヤー

プロセスの観点からフラッターアプリケーションのメモリについて話すとき、3つのすべてのメモリの合計を意味します。

簡単にするために、ユーザーが直接触れることができるコードに基づいて、DartVMとネイティブメモリに簡単に分割できます。DartVMはDart仮想マシンが占有するメモリを指し、ネイティブメモリにはエンジンとプラットフォーム関連のメモリが含まれています。コードが実行されます。

Flutterのユーザーが触れることができる最も直接的なオブジェクトはDart言語を使用して生成されたオブジェクトであると言われているので、ユーザーはEngineレイヤーでオブジェクトを作成および破棄できないようです。これは、Dart仮想マシンバインディングレイヤーの設計について言わなければなりません。

ダートバインディングレイヤーのしくみ

パフォーマンスまたはクロスプラットフォームまたはその他の理由で、スクリプト言語または仮想マシンベースの言語は、c / c ++オブジェクトまたは関数を操作するために、c / c ++または関数オブジェクトを特定の言語オブジェクトにバインドするためのインターフェイスを提供しますこの層APIはバインディング層と呼ばれます。例:アプリケーションに最も簡単に埋め込まれるLuaバインディング、JavascriptV8エンジンのバインディングなど。

Dart仮想マシンは、初期化されると、C ++によって宣言された特定のクラスまたは関数をDartの特定のクラスまたは関数にバインドし、それらをDartランタイムのグローバルトラバーサルに挿入します。Dartコードが特定の関数を実行すると、特定のC ++オブジェクトまたは関数を指します。

以下は、いくつかの一般的なバインディングC ++クラスと対応するDartクラスです。

flutter :: EngineLayer-> ui.EngineLayer

flutter :: FrameInfo-> ui.FrameInfo

flutter :: CanvasImage-> ui.Image

flutter :: SceneBuilder-> ui.SceneBuilder

flutter :: Scene-> ui.Scene

ではui.SceneBuilderダートC ++オブジェクトのインスタンスがバインドされ、そしてコントロール作業C ++デストラクタインスタンスを学ぶ方法の一例。

Dartレイヤーレンダリングプロセスは、レイヤーレンダリングツリーを構成し、レンダリングのためにc ++レイヤーに送信するプロセスです。ui.SceneBuilderこのレンダーツリーのコンテナです

  1. Dartコードがコンストラクター ui.SceneBuilder()を呼び出すときは、C ++メソッドを呼び出します SceneBuilder_constructor

  2. 呼ばれる flutter::SceneBuilder工法およびC ++インスタンスを生成sceneBuilder

  3. flutter::SceneBuilder メモリカウントオブジェクトから継承される ため RefCountedDartWrappable、オブジェクトの生成後にメモリカウントが1増加します

  4. DartのAPIを使用してC ++インスタンスsceneBuilderを生成し、WeakPersitentHandleそれを生成 してDartに挿入します。この後、Dartはこのbuilderオブジェクトを使用してflutter::SceneBuilderC ++のこのインスタンスを操作 できます 

  5. プログラムが長時間実行された後、Dart仮想マシンがDartオブジェクトビルダーが他のオブジェクトによって参照されていないと判断した場合(たとえば、単純なケースでは、builder = null、到達不能とも呼ばれます)、オブジェクトはガベージコレクション(ガベージコレクション)リサイクルして解放すると、メモリ数が1つ減ります

  6. メモリカウントが0の場合、C ++デストラクタがトリガーされ、最後にC ++インスタンスが指すメモリブロックがリサイクルされます。

ご覧のとおり、DartはDart仮想マシンのGC(ガベージコレクション)を使用して、C / C ++インスタンスをWeakPersitentHandleにカプセル化し、Dartコンテキストに挿入することで、C / C ++インスタンスの作成とリリースを制御します。

端的に言えば、C / C ++インスタンスに対応するDartオブジェクトがGCによって正常にリサイクルできる限り、C / C ++が指すメモリスペースは正常に解放されます。

WeakPersistentHandleとは

DartオブジェクトはGCのデフラグメンテーションのためにVM内で移動されることが多いため、オブジェクトを使用するときにオブジェクトを直接指すのではなく、ハンドルを使用して間接的にオブジェクトを指し、c / c ++オブジェクトまたはインスタンスはOutsideof Dart仮想マシン、ライフサイクルはスコープによって制限されず、Dart仮想マシン全体に長い間存在していたため、永続的と呼ばれるため、WeakPersistentHandleは特にライフサイクルのハンドルとパーマネント。Dartで特にC / C ++の例をカプセル化するために使用されます。

flutterが公式に提供しているObservatoryツールでは、すべてのWeakPersistentHandleオブジェクトを表示できます

Peer列は、c / c ++オブジェクトをカプセル化するポインターです。

Dartオブジェクトの到達可能性

Dartオブジェクトのリリースは、ガベージコレクター(ガベージコレクション)によってリリースされます。これは、オブジェクトがまだ使用可能かどうか(可用性)を判断することによって実現されます。到達可能性とは、オブジェクトといくつかのルートノードを介したオブジェクトとの間の参照チェーンを介したオブジェクトへのアクセスを指します。オブジェクトが参照チェーンを介してアクセスできる場合は、オブジェクトが到達可能であることを意味し、そうでない場合は到達可能ではありません。

黄色にはアクセシビリティがあり、青にはアクセシビリティがありません

不可能なメモリリーク

これを見ると問題が見つかります。実際、DartオブジェクトにはC ++クラスのような統合されたデストラクタがないため、Dart側からC / C ++オブジェクトの消滅を認識することは困難です。オブジェクトが長くなると-循環参照などによる用語。オブジェクトは長期間参照され、GCはそれらを解放できず、最終的にメモリリークが発生します。

問題を少し拡大すると、フラッターがレンダリングエンジンであることがわかります。Dart言語を記述してウィジェットツリーを構築し、それを要素ツリー、RenderObjectツリー、描画などのプロセスを通じてレイヤーツリーに単純化し、このレイヤーツリーをに送信します。 C ++レイヤーを選択し、Skiaを使用してレンダリングします。

WigdetツリーまたはElementツリーのノードを長期間解放できない場合、その子ノードが関係して解放できなくなる可能性があり、リークされたメモリスペースが急速に拡大します。

たとえば、2つのインターフェイスAとBがあり、インターフェイスAはNavigator.pushを介してインターフェイスBを追加し、インターフェイスBはNavigator.popを介してインターフェイスAに戻ります。書き込みによりBのレンダリングツリーがメインレンダリングツリーから解かれてもBインターフェイスを解放できない場合は、元のBサブツリー全体を解放できなくなります。

レンダーツリーノードを検出することにより、メモリリークを検出します

上記の状況に基づいて、現在のフレームで使用されているレンダリングノードの数と現在のメモリ内のレンダリングノードの数を比較することで、以前のインターフェイスリリースのメモリリークを実際に判断できます。

Dartコードでは、ui.SceneBuilderEngineLayerレンダリングツリーを構築する方法追加することで、c ++ EngineLayerメモリの数、EngineLayerで使用されている現在のフレームの数でのみ検出されます。この数が、長期間使用されているEngineLayerメモリよりも大きい場合時間番号、それから私達はメモリリークがあると判断することができます

前回AページのpushBインターフェイスを使用したとき、BインターフェイスがAインターフェイスにポップバックするのは一例です。通常、メモリリークは発生しません。使用中のレイヤー数(青)とメモリ内のレイヤー数(オレンジ)は変動しますが、最終的には2つの曲線の適合性が向上します。

ただし、ページBメモリリークが発生すると、Aインターフェイスに戻った後、Bツリーをまったく解放できず、メモリ内のレイヤー数(オレンジ色)が最終的に青い曲線(のレイヤー数)に適合しなくなります。使用する)

つまり、レンダリングの場合、コードによってウィジェットツリーまたは要素ツリーがGCで長期間リサイクルできなくなると、重大なメモリリークが発生する可能性があります。

メモリリークの原因は何ですか?

現在、非同期で実行されるコード(Feature、async / await、methodChan)は、着信BuildContextを長時間保持しているため、要素は削除されますが、長期間存在しているため、最終的にリークが発生します。関連するウィジェットと状態の。

ページBのリークの例を引き続き見てください

正しい書き方と間違った書き方の違いは、Navigator.popを呼び出す前に、非同期メソッドFutureを使用してBuildContextを参照すると、Bインターフェイスでメモリリークが発生することだけが間違っていることです。

リークを見つける方法は?

フラッターメモリリーク検出ツールの現在の設計アイデアは、インターフェイスが入力される前後のオブジェクトを比較し、リリースされていないオブジェクトを見つけて、リリースされていない参照関係(保持パスまたはインバウンド参照)を確認してから、ソースコード、そして最後にエラーコードを見つけます。

Flutterに付属の天文台は、リークされた各オブジェクトの参照関係を1つずつ表示できますが、インターフェイスが少し複雑なため、最終的に生成されるレイヤーの数は非常に複雑であり、天文台のすべてのリークされたオブジェクトの1つになりたいです。問題のあるコードを見つけることは非常に複雑な作業です。

この目的のために、これらの複雑なポジショニングタスクを視覚化しました。

ここでは、各フレームをエンジンのすべてのEngineLayerに記録のために送信し、折れ線グラフの形式で記録しました。上記のメモリ内のレイヤー数が使用中のレイヤー数よりも異常に多い場合は、前のページでメモリリークが発生していると判断されました。

さらに、現在のページのレイヤーツリーの構造を取得して、どのRenderObjectツリーによって生成された特定のレイヤーツリーを見つけやすくし、どのElementノードによって生成されたRenderObjectノードの分析を続けることもできます。

または、WeakPersitentHandleのリファレンスチェーン補助分析を印刷することもできます

しかし、今日の問題点は依然として存在し、問題をより迅速に特定するために、Handleの参照チェーンを調べてソースコードを分析する必要があります。これもまた、次に解決すべき緊急の問題です。

総括する

  • レンダリングツリーの観点からフラッターメモリリークを調査する方法は、さまざまなタイプの他のすべてのDartオブジェクトに拡張できます。

  • コードを書くとき、開発者は常に非同期呼び出しに注意を払う必要があり、操作された要素が参照されて解放できないかどうかに常に注意を払う必要があります

Xianyuは、長期的かつ成長を続けるフラッターチームとして、フラッターツールチェーンにも引き続き取り組んでいます。もちろん、この重要なメモリ検出ツールの綿密な開発は不可欠です。引き続き注目してください。 !!

おすすめ

転載: blog.csdn.net/weixin_38912070/article/details/108764993