フラッターアーキテクチャ
Flutterは簡単に言うと、上からフレームワーク層、エンジン層、エンベディング層の3層に分かれますので、以下に分けて紹介していきます。
1.フレームレイヤー
Flutter Framework、フレームワーク層。これは純粋な Dart 実装 SDK であり、一連の基本ライブラリを下から順に実装します。簡単に紹介します。
-
下の 2 つのレイヤー (ファンデーションとアニメーション、ペイント、ジェスチャー) は、一部の Google ビデオの dart UI レイヤーにマージされます。これは、Flutter の dart:ui パッケージに対応します。これは、Flutter エンジンによって公開される基礎となる UI ライブラリであり、アニメーション、ジェスチャーを提供します。そして描画能力。
-
レンダリング層、つまりレンダリング層は、Dart UI 層に依存する抽象レイアウト層です。レンダリング層は、レンダリング可能なオブジェクトで構成されるレンダリング ツリーを構築します。これらのオブジェクトが動的に更新されると、レンダリング ツリーは何が変わったのか、レンダリングを更新します。レンダリング層はFlutterフレームワーク層の中核ともいえる部分で、各レンダリングオブジェクトの位置やサイズを決定するほか、座標変換や描画(基盤となるdart:uiの呼び出し)を行います。
-
Widgets レイヤーは、Flutter によって提供される基本コンポーネント ライブラリのセットです。Flutter は、基本コンポーネント ライブラリに加えて、マテリアルと iOS のデザイン仕様をそれぞれ実装する 2 つのビジュアル スタイル コンポーネント ライブラリ、マテリアルとクパチーノも提供します。
Flutter フレームワークは、開発者が使用する可能性のある一部の高レベル機能が異なるパッケージに分割されており、カメラやwebviewなどのプラットフォーム プラグインやアニメーションなどのプラットフォームに依存しない機能を含む、Dart と Flutter のコア ライブラリを使用して実装されているため、比較的小規模です。。
2. エンジン層
Engine、エンジン層。Flutter の中核であることは間違いなく、この層は Skia エンジン、Dart ランタイム (Dart ランタイム)、テキスト レイアウト エンジンなどを含め、主に C++ で実装されています。コードが dart:ui ライブラリを呼び出すと、その呼び出しは最終的にエンジン層に行き、その後実際の描画と表示が実現されます。
3. 埋め込み層
Embedder、埋め込み層。Flutter の最終的なレンダリングと対話は、そのプラットフォームのオペレーティング システム API に依存し、埋め込み層は主に特定のプラットフォームに Flutter エンジンを「インストール」します。埋め込み層は、Android の場合は Java と C++、iOS と macOS の場合は Objective-C と Objective-C++、Windows と Linux の場合は C++ など、現在のプラットフォームの言語で記述されます。Flutter コードは、埋め込み層を介してモジュール方式で既存のアプリケーションに統合することも、アプリケーションの本体として使用することもできます。Flutter 自体には各共通プラットフォームの埋め込み層が含まれていますが、将来的に Flutter が新しいプラットフォームをサポートしたい場合は、新しいプラットフォーム用の埋め込み層を記述する必要があります。
一般に、開発者はエンジンとエンベッダーの存在を意識する必要はありません(プラットフォーム システム サービスを呼び出す必要がない場合)。フレームワークは開発者が直接操作する必要があるため、フレームワーク全体の最上位にもあります。階層化アーキテクチャモデル。
ウィジェットインターフェース
Flutterにおける機能は。要素描画されるデバイス画面上に」ことです。つまり、UI要素の構成情報を記述するwidget
「クラス宣言を見てみましょう。Widget
Widget
Text
Widget
// 不可变的
abstract class Widget extends DiagnosticableTree {
const Widget({
this.key });
final Key? key;
@factory
Element createElement();
String toStringShort() {
final String type = objectRuntimeType(this, 'Widget');
return key == null ? type : '$type-$key';
}
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
}
bool operator ==(Object other) => super == other;
int get hashCode => super.hashCode;
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
...
}
@immutable
これは、ウィジェットが immutable であることを意味します。これによりWidget
、 で定義されたプロパティ (つまり、構成情報) が不変 (final
) に制限されます。なぜWidget
で定義されたプロパティは変更できないのでしょうか? これは、Flutter では、属性が変更されるとツリーが再構築されるためWidget
です。つまり、古いインスタンスWidget
を置き換えるために新しいインスタンスが再作成されるWidget
ため、属性の変更Widget
を許可することは無意味です。Widget
交換されます。このため、Widget
で定義されたプロパティは でなければなりませんfinal
。Widget
このクラスは、 「診断DiagnosticableTree
ツリー」から継承しており、その主な役割はデバッグ情報を提供することです。DiagnosticableTree
Key
: この属性はの属性key
と似ています。主な機能は、古い属性を次回再利用するかどうかを決定することです。決定条件はメソッド内にあります。React/Vue
key
build
widget
canUpdate()
createElement()
: 上で述べたように、「1 つはwidget
複数に対応できますElement
」; Flutter フレームワークが UI ツリーを構築するとき、最初にこのメソッドを呼び出して、ノードに対応するオブジェクトを生成しますElement
。このメソッドは Flutter フレームワークによって暗黙的に呼び出され、開発中には基本的に呼び出されません。debugFillProperties(...)
親クラスをオーバーライドする方法は、主に診断ツリーのいくつかの特性を設定することです。canUpdate(...)
これは静的メソッドであり、主にウィジェット ツリーが再構築されるときに古いウィジェットを再利用するために使用されます。実際には、新しいウィジェット オブジェクトを使用して、古い UI ツリー上の対応する Element オブジェクトの構成を更新するかどうかです。 ;と のnewWidget
合計が等しい限り、そのソース コードを確認できますoldWidget
。新しいウィジェットはオブジェクトの構成を更新するために使用され、それ以外の場合は新しいウィジェットが作成されます。runtimeType
key
Element
Element
Flutter では、すべてがすべてであると言えますWidget
。従来の命令型 UI 開発では、センタリング機能であっても、通常はプロパティとして設定されますが、Flutter では名前付きコンポーネントに抽象化されますCenter
。さらに、Flutter はラディカルな組み合わせ開発、つまり可能な限り一連の基本構造を通じてWidget
目標を構築することを推奨していますWidget
。
上記 2 つの特徴に基づいて、Flutter のコードはさまざまな でいっぱいになりWidget
、UI 更新のすべてのフレームは部分的なWidget
再構築を意味します。この設計は肥大化しすぎて非効率ではないかと心配するかもしれませんが、実際には、これは逆に、Flutter が高性能のレンダリングを実行する能力の基礎となります。Flutter がこのように設計されている理由も、次の 2 つの事実に基づく意図的なものです。
-
ウィジェット ツリー上の
Widget
ノードが多いほど、 Diffアルゴリズムを通じて再構築する必要がある部分がより正確かつ小さくなります。UI レンダリングの主なパフォーマンスのボトルネックはWidget
ノードの再構築です。 -
Dart 言語のオブジェクト モデルとGC モデルは、スモール オブジェクトの高速読み取り割り当てと回復を最適化します。これがこのスモール オブジェクトです。
Widget
フラッターの三本の木
WidgetはUI要素の構成情報を記述するだけなので、実際のレイアウトや描画は誰が行うのでしょうか?
Flutterフレームワークの処理の流れは以下のとおりです。
- Treeに基づいてツリー
Widget
を生成しElement
、Element
ツリー内のノードはElement
クラスを継承します。 Element
ツリー(レンダーツリー)はtreeに従って生成されRender
、レンダーツリー内のノードはRenderObject
クラスを継承します。- レンダリング ツリーに基づいてツリーを生成し
Layer
、画面に表示します。Layer
ツリー内のノードはすべてLayer
クラスを継承します。
Render
実際のレイアウトとレンダリング ロジックはツリー内にあり、これはと の接着剤Element
であり、中間エージェントとして理解できます。Widget
RenderObject
3 つのツリーのそれぞれの機能は次のとおりです。
Widget
Element
:設定を担当し、 UIの設定情報を記述するために公開APIを持っており、開発者が直接認識して利用できる部分でもあります。Element
: Flutter Virtual DOM のマネージャー、widget
それが管理するライフサイクルは、メモリ内に実際に存在する UI データを表し、widget
ツリー内の特定の場所のインスタンス化、widget
それが保持する参照、および親子関係の管理を担当します。ツリー内では、実際にWidget Tree
はドライバーによって生成されRenderObject Tree
ます。Element Tree
RenderObject
: サイズ、レイアウト、描画の処理を担当し、自身を描画したり、子ノードを配置したりします。
ここで注意してください:
- 3本の木のうち、
Element
と はWidget
1対1に対応していますが、Element
と はRenderObject
1対1ではありません。たとえばStatelessWidget
、 と にStatefulWidget
は対応する がありませんRenderObject
。 - レンダリング ツリーは画面に表示される前にツリーを生成する
Layer
ため、実際には Flutter には 4 つのツリーがありますが、理解する必要があるのは上の 3 つのツリーだけです。
ステートレスウィジェット
StatelessWidget
比較的単純で、クラスを継承しwidget
、createElement()
メソッドをオーバーライドします。
createElement() => StatelessElement(this);
StatelessElement
StatelessElement
クラスから間接的に継承されElement
、StatelessWidget
(その構成データとして) に対応します。
StatelessWidget
状態を維持する必要のないシナリオで使用され、通常はbuild
メソッド内で他の要素をネストすることによってUI を構築しwidget
、構築プロセス中にネストされた要素を再帰的に構築しますwidget
。
簡単な例を次に示します。
class Echo extends StatelessWidget {
const Echo({
Key? key,
required this.text,
this.backgroundColor = Colors.grey, //默认为灰色
}):super(key:key);
final String text;
final Color backgroundColor;
Widget build(BuildContext context) {
return Center(
child: Container(
color: backgroundColor,
child: Text(text),
),
);
}
}
これを次のように使用できます。
Widget build(BuildContext context) {
return Echo(text: "hello world");
}
慣例により、
widget
コンストラクターパラメーターは名前付きパラメーターを使用する必要があり、名前付きパラメーターで渡す必要があるパラメーターにはrequired
キーワードを追加する必要があります。これは、静的コード アナライザーがチェックするのに有益です。 を継承する場合widget
、最初のパラメーターは通常、 である必要がありますKey
。また、widget
受信者が必要な場合はwidget
、通常child
、 またはchildren
パラメータをパラメータ リストの最後に配置する必要があります。また、慣例により、偶発的な変更を防ぐために、widget
プロパティは可能な限り宣言する必要があります。final
コンテクスト
build
このメソッドには、ツリー内の現在のコンテキストを表すクラスのインスタンスcontext
であるパラメータがあり、それぞれがオブジェクトに対応します。BuildContext
widget
widget
widget
context
実際、これはツリー内のcontext
現在位置で「関連する操作」を実行するためのハンドル( ) です。たとえば、ツリーを現在から上にたどり、タイプによって親を見つけるためのメソッドを提供します。widget
widget
handle
widget
widget
widget
widget
サブツリー内の親を取得するwidget
例を次に示します。
class ContextRoute extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Context测试"),
),
body: Container(
child: Builder(builder: (context) {
// 在 widget 树中向上查找最近的父级`Scaffold` widget
Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
// 直接返回 AppBar的title, 此处实际上是Text("Context测试")
return (scaffold.appBar as AppBar).title;
}),
),
);
}
}
ステートフルウィジェット
1 つ持っているならStatelessWidget
、なぜ別のものを持つ必要があるのでしょうかStatefulWidget
? Flutter ではWidget
すべて不変ですが、実際には対応する状態に応じて更新する必要があるため、と の2 つのオブジェクトで構成されるオブジェクトWidget
が生成されます。StatefulWdiget
StatefulWidget
Widget
State
StatefulWidget
また、widget
クラスを継承してcreateElement()
メソッドを書き換えますが、異なる点は、返されるElement
オブジェクトが同じではなく、1 つが返されることStatefulElement
と、StatefulWidget
新しいインターフェイスがクラスに追加されることですcreateState()
。
abstract class StatefulWidget extends Widget {
const StatefulWidget({
Key key }) : super(key: key);
StatefulElement createElement() => StatefulElement(this);
State createState();
}
-
StatefulElement
クラスから間接的に継承されElement
、StatefulWidget
(その構成データとして) に対応します。state() オブジェクトを作成するためにStatefulElement
複数回呼び出すことができます。開発者はこの方法を気にする必要はありません。createState()
State
-
createState()
状態を作成およびStatefulWidget
関連付けるには、開発者はこのメソッドをサブクラスに実装する必要があります。StatefulWidget
の存続期間中に複数回呼び出される可能性があります。たとえば、ツリーが複数の位置に同時にStatefulWidget
挿入された場合、Flutter フレームワークはこのメソッドを呼び出して、位置ごとに独立したインスタンスを生成します (実際には、基本的に 1 つのインスタンスに対応するものになります)。widget
State
StatefulElement
State
では
StatefulWidget
、State
オブジェクトはStatefulElement
互いに 1 対 1 に対応するため、Flutter の SDK ドキュメントでは、「State オブジェクトをツリーから削除する」または「State オブジェクトをツリーに挿入する」といった記述がよく見られます。ウィジェット ツリーによって生成された要素ツリーを指します。「ツリー」は Flutter の SDK ドキュメントでよく言及されており、コンテキストに基づいてそれがどのツリーを指しているのかを判断できます。実は、ユーザーにとってはそれがどのようなツリーであるかは気にする必要はなく、最終的にはUIの構造や描画情報を記述することが目的なので、それを「ノード」として理解すればよいのです。 「ユーザー インターフェイスを構成するツリー」については、これらの概念にあまり巻き込まれないでください。
州
クラスはクラスStatefulWidget
に対応しState
、State
それに対応してStatefulWidget
維持される状態を示します。State
その中に保存される状態情報は次のとおりです。
State
widget
ビルド時に同期的に読み取ることができます。State
widget
ライフ サイクル中に変更することがsetState()
でき、メソッドを手動で呼び出してState
変更することができ、これにより Flutter フレームワークに状態変更が通知されます。メッセージを受信した後、Flutter フレームワークはそのメソッドを再度呼び出してツリーを再構築しますbuild
。widget
UIを更新するという目的を達成するため。
State
には 2 つの共通プロパティがあります。
-
State.widget
これにより、この インスタンス にState
関連付けられたインスタンスを取得できますwidget
。このインスタンスは、Flutter フレームワークによって動的に設定されます。アプリケーションのライフサイクルでは、UI ツリー上のノードのインスタンスはwidget
再構築時に変更される可能性があるため、この関連付けは永続的なものではないことに注意してください。ただし、State
インスタンスは初めてツリーに挿入されたときにのみ作成されます。リビルド時に が変更されている場合widget
、Flutter フレームワークは新しいインスタンスState.widget
を指すように動的に設定されます。widget
-
State.context
StatefulWidget
に対応しておりBuildContext
、同じStatelessWidget
効果がありますBuildContext
。フレームワークが作成する必要がある場合、メソッドをStatefulWidget
呼び出して、オブジェクトをオブジェクトに関連付けます。この関連付けは永続的であり、オブジェクトによって変更されることはありませんが、それ自体はツリー内で移動できます。StatefulWidget.createState()
State
BuildContext
State
BuildContext
BuildContext
状態のライフサイクル
State
オブジェクトがメソッドによって作成された後、メソッドがcreateState()
呼び出され、独自のライフサイクルが開始されます。initState()
各コールバック関数の意味は次のとおりです。
-
initState()
widget
:初めてツリーに挿入されたときに呼び出されますwidget
。オブジェクトごとに、Flutter フレームワークはこのコールバックを1 回State
だけ呼び出します。したがって、通常、このコールバックで状態の初期化や状態の初期化などの 1 回限りの操作を実行します。サブツリーイベントのサブスクリプション通知など注:初期化が完了した後、ツリーも変更される可能性があるため、メソッド内で
initState
呼び出すことはできません(このメソッドは、ツリー上で最も近い親を取得するBuildContext.dependOnInheritedWidgetOfExactType
ために使用されます)。そのため、正しい方法はメソッド内で呼び出すか、メソッド内で呼び出す必要があります。widget
widget
InheritedWidget
widget
InheritFrom widget
build()
didChangeDependencies()
-
didChangeDependencies()
:State
オブジェクトの依存関係が変更されると呼び出されます。たとえば、build()
あるオブジェクトが前のオブジェクトに含まれていてInheritedWidget
、後のオブジェクトbuild()
でInherited widget
変更された場合、この時点でInheritedWidget
サブルーチンwidget
のコールバックdidChangeDependencies()
が呼び出されます。典型的なシナリオは、システム言語Locale
またはアプリケーションのテーマが変更されると、Flutter フレームワークがwidget
このコールバックを呼び出すように通知することです。コンポーネントが最初に作成およびマウントされるとき (再作成を含む)、対応するコンポーネントdidChangeDependencies
も呼び出されることに注意してください。 -
build()
: 主にwidget
サブツリーを構築するために使用され、次のシナリオで呼び出されます。- 電話の後
initState()
。 - 電話の後
didUpdateWidget()
。 - 電話の後
setState()
。 - 電話の後
didChangeDependencies()
。 - オブジェクト
State
がツリー内のある場所から削除されdeactivate
、ツリー内の別の場所に再挿入された後に呼び出されます。
- 電話の後
-
reassemble()
: 開発およびデバッグ用に特別に提供されるコールバック。ホット リロード中に呼び出されます。このコールバックは、リリースモードでは決して呼び出されません。 -
didUpdateWidget()
: リビルド時に、Flutter フレームワークは、ツリー内の同じ位置にある古いノードと新しいノードを検出するためにwidget
呼び出し、更新が必要かどうかを判断します。返された場合は、このコールバックが呼び出されます。widget.canUpdate
widget
widget.canUpdate
true
前に述べたように、古い値
widget.canUpdate
と新しい値 が同時に等しい場合に戻ります。つまり、この場合に呼び出されるということです。(実際、ここでのフラッター フレームワークは新しいウィジェットを作成し、この状態をバインドし、この関数で古いウィジェットを渡します) 上位レベルのノードがウィジェットを再構築するとき、つまり上位レベルのコンポーネントの状態が変更されるとき、サブウィジェットの実行もトリガーされます。コントローラーの変更が関係する場合、この関数で古いコントローラーのリスナーを削除し、新しいコントローラーのリスナーを作成する必要があることに注意してください。widget
key
runtimeType
true
didUpdateWidget()
didUpdateWidget
-
deactivate()
:State
このコールバックは、オブジェクトがツリーから削除されるときに呼び出されます。一部のシナリオでは、オブジェクトを含むサブツリーがツリーのある位置から別の位置に移動するときなど、State
Flutterフレームワークはオブジェクトをツリーに再挿入します(これはによって実現できます)。メソッドが削除され、ツリーに再挿入されなかった場合、次にこのメソッドが呼び出されます。State
GlobalKey
dispose()
-
dispose()
:State
オブジェクトがツリーから完全に削除されるときに呼び出されます。通常、このコールバックでリソースが解放され、サブスクリプションが解除され、アニメーションがキャンセルされます。
次の反例で説明します。
class CounterWidget extends StatefulWidget {
const CounterWidget({
Key? key, this.initValue = 0});
final int initValue;
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
void initState() {
super.initState();
//初始化状态
_counter = widget.initValue;
print("initState");
}
Widget build(BuildContext context) {
print("build");
return Scaffold(
body: Center(
child: TextButton(
child: Text('$_counter'),
//点击后计数器自增
onPressed: () => setState(
() => ++_counter,
),
),
),
);
}
void didUpdateWidget(CounterWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("didUpdateWidget ");
}
void deactivate() {
super.deactivate();
print("deactivate");
}
void dispose() {
super.dispose();
print("dispose");
}
void reassemble() {
super.reassemble();
print("reassemble");
}
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
}
注:アノテーションを含む親クラスのメソッドの
StatefulWidget
メソッドを継承して書き換える場合、親クラスのメソッドをサブクラスのメソッドで呼び出す必要があります。@mustCallSuper
次に、1 つだけを表示する新しいルート ページを作成しますCounterWidget
。
class StateLifecycleTest extends StatelessWidget {
const StateLifecycleTest({
Key? key}) : super(key: key);
Widget build(BuildContext context) {
return CounterWidget();
}
}
アプリケーションを実行し、ルーティング ページを開きます。新しいルーティング ページが開くと、画面の中央に数字 0 が表示され、コンソール ログが出力されます。
I/flutter ( 5436): initState
I/flutter ( 5436): didChangeDependencies
I/flutter ( 5436): build
ご覧のとおり、ツリーStatefulWidget
に挿入するときにwidget
最初のinitState
メソッドが呼び出されます。
次に、⚡️ ボタンをクリックしてホットリロードすると、コンソール出力ログは次のようになります。
I/flutter ( 5436): reassemble
I/flutter ( 5436): didUpdateWidget
I/flutter ( 5436): build
build
メソッドの前に と のみreassemble
が呼び出されていることがわかりますdidUpdateWidget
。
次に、widget
Tree を削除しCounterWidget
、のメソッドStateLifecycleTest
をに変更しますbuild
。
Widget build(BuildContext context) {
//移除计数器
//return CounterWidget ();
//随便返回一个Text()
return Text("xxx");
}
次にホットリロードすると、ログは次のようになります。
I/flutter ( 5436): reassemble
I/flutter ( 5436): deactive
I/flutter ( 5436): dispose
ご覧のとおり、ツリーCounterWidget
から削除するときに、が順番に呼び出されます。widget
deactive
dispose
setState()
setState()
setState(() {_myState = newValue;});
このメソッドは、Flutter で状態を変更する唯一の正当な方法です。このメソッドは、このオブジェクトの内部状態が変更されたことをフレームワークに通知します。呼び出しメソッドは、開発者が状態値を変更する必要があるコールバック メソッドを提供します。そうすると、コールバックがすぐに同期的に呼び出されます。このコールバックでは時間のかかる計算を実行してはならず、ましてや計算を返してはいけないことに注意してくださいFuture
(コールバックasync
に " " のマークを付けることはできません)。このコールバックは通常、状態への実際の変更をラップするためにのみ使用されます。
を呼び出した後setState()
、現在widget
対応するelement
オブジェクトは としてマークされdirty
、build()
メソッドが呼び出されて UI が更新されます。setState()
参加後の State のライフサイクルは次のとおりです。
状態のライフサイクルは実際には 3 つの段階に分けられるようです。
- 初期化: コンストラクター
initState()
、、、didChangeDependencies()
- 状態変更: 構成変更による実行
didUpdateWidget()
、構成変更によるbuild()
実行setState()
build()
- コンポーネントの削除:
deactivate()
、dispose()
StatefulWidget が State と Widget を分離するのはなぜですか?
StatefulWidget
なぜbuild
メソッドをState
ではなくクラスに置くのですかStatefulWidget
?
- これは主に、開発の柔軟性とパフォーマンスを考慮するためです。
build()
このメソッドを組み込む場合StatefulWidget
、柔軟性の点で 2 つの問題があります。
1. 不便な状態へのアクセス。
想像してみてください。StatefulWidget
状態がたくさんあり、build
状態が変わるたびにメソッドが呼び出されます。状態は に格納されているため、メソッドが にある場合State
、メソッドとメソッドはそれぞれ 2 つのクラスにあり、その間に状態を読み取ります。工事が大変になりますので、大変不便になります!想像してみてください。メソッドが実際にに配置されている場合、ユーザー インターフェイスを構築するプロセスには依存関係が必要なため、メソッドはおそらく次のようなパラメーターを追加する必要があります。build
StatefulWidget
build
状态
build
StatefulWidget
State
build
State
Widget build(BuildContext context, State state){
//state.counter
...
}
この場合、State
クラスのすべての状態はパブリック状態としてのみ宣言できるため、State
クラスの外部から状態にアクセスできます。ただし、状態をパブリックに設定すると、状態はプライベートではなくなり、状態の変更が制御できなくなります。ただし、build()
メソッドをState
に置くと、ビルド プロセスが状態に直接アクセスできるだけでなく、プライベート状態を公開する必要もなくなるので、非常に便利です。
2. StatefulWidgetの継承が不便。
たとえば、Flutter には、クラスAnimatedWidget
を継承するアニメーション ウィジェットの基本クラスがありますStatefulWidget
。AnimatedWidget
では抽象メソッドが導入されておりbuild(BuildContext context)
、から継承されたAnimatedWidget
アニメーションはwidget
このbuild
メソッドを実装する必要があります。ここで、前述したように、StatefulWidget
class にすでにメソッドが存在する場合、メソッドはこの時点でオブジェクトを受け取る必要があるとします。つまり、サブクラスには独自のオブジェクト ( として示されます) をそのサブクラスに提供する必要があります。 in そのメソッド内で、親クラスを呼び出すメソッドは次のようになります。build
build
State
AnimatedWidget
State
_animatedWidgetState
build
build
class MyAnimationWidget extends AnimatedWidget {
Widget build(BuildContext context, State state){
// 由于子类要用到AnimatedWidget的状态对象_animatedWidgetState,
// 所以AnimatedWidget必须通过某种方式将其状态对象_animatedWidgetState 暴露给其子类
super.build(context, _animatedWidgetState)
}
}
AnimatedWidget
状態オブジェクトはAnimatedWidget
内部実装の詳細であり、外部に公開すべきではないため、これは明らかに不合理です。
親クラスの状態をサブクラスに公開したい場合は、転送メカニズムが必要ですが、親クラスと子クラス間の状態の転送は何の関係もないため、この一連の転送メカニズムを実行することは無意味です。サブクラス自体のロジック。
StatefulWidget
要約すると、 の場合、build
メソッドを組み込むことでState
開発に大きな柔軟性をもたらすことがわかります。
一方、これは主にパフォーマンスを考慮したもので、State は状態を管理するもの (Controller として理解できます)、Widget は UI (つまり View) であり、状態の変化に応じて Widget (つまり View) を生成することでメモリを節約することができます。毎回状態オブジェクト State を作成します。
ウィジェットツリー内のStateオブジェクトを取得します。
StatefulWidget の特定のロジックは State にあるため、多くの場合、いくつかのメソッドを呼び出すために、StatefulWidget に対応する State オブジェクトを取得する必要があります。たとえば、Scaffold コンポーネントに対応する状態クラス ScaffoldState は、SnackBar (下部のプロンプト バー) を開くことを定義します。ルーティング ページの) メソッド。子ウィジェット ツリー内の親 StatefulWidget の State オブジェクトを取得するには 2 つの方法があります。
1. コンテキストを通じて状態を取得する
context
オブジェクトには、現在のノードからツリーをたどって、指定されたタイプの対応するオブジェクトを見つけるfindAncestorStateOfType()
メソッドがあります。以下はopen を実装する例です。widget
StatefulWidget
State
SnackBar
class GetStateObjectRoute extends StatefulWidget {
const GetStateObjectRoute({
Key? key}) : super(key: key);
State<GetStateObjectRoute> createState() => _GetStateObjectRouteState();
}
class _GetStateObjectRouteState extends State<GetStateObjectRoute> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("子树中获取State对象"),
),
body: Center(
child: Column(
children: [
Builder(builder: (context) {
return ElevatedButton(
onPressed: () {
// 查找父级最近的Scaffold对应的ScaffoldState对象
ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>()!;
// 打开抽屉菜单
_state.openDrawer();
},
child: Text('打开抽屉菜单1'),
);
}),
],
),
),
drawer: Drawer(),
);
}
}
一般的に言えば、StatefulWidget の状態がプライベートである (外部に公開すべきではない) 場合、コード内でその State オブジェクトを直接取得すべきではありません; StatefulWidget の状態が公開されることが予想される場合 (通常はいくつかのコンポーネント操作があります)メソッド) を使用すると、その State オブジェクトを直接取得できます。ただし、context.findAncestorStateOfType を通じて StatefulWidget の状態を取得する方法は普遍的であり、StatefulWidget の状態が文法レベルでプライベートであるかどうかを指定することはできないため、Flutter 開発ではデフォルトの合意があります。公開された場合は、State オブジェクトを取得するために StatefulWidget に of static メソッドを提供する必要があり、開発者はこのメソッドを通じて直接取得できます。State を公開したくない場合は、 of メソッドを提供しないでください。この規則は、Flutter SDK のあらゆる場所で見られます。したがって、上記の例の Scaffold は、実際に直接呼び出すことができる of メソッドも提供します。
Builder(builder: (context) {
return ElevatedButton(
onPressed: () {
// 直接通过of静态方法来获取ScaffoldState
ScaffoldState _state=Scaffold.of(context);
// 打开抽屉菜单
_state.openDrawer();
},
child: Text('打开抽屉菜单2'),
);
}),
別の例として、スナックバーを表示したい場合は、次のコードを通じてそれを呼び出すことができます。
Builder(builder: (context) {
return ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("我是SnackBar")),
);
},
child: Text('显示SnackBar'),
);
}),
2. GlobalKey を通じて状態を取得する
Flutter には、State オブジェクトを取得する一般的な方法もありますGlobalKey
。手順は 2 つのステップに分かれています。
- ターゲットに
StatefulWidget
追加しますGlobalKey
。
//定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
...
Scaffold(
key: _globalKey , //设置key
...
)
- オブジェクトを
GlobalKey
取得するにはState
_globalKey.currentState.openDrawer()
GlobalKey
FlutterがApp全体で参照するために提供する仕組みですelement
。a がwidget
設定されている場合は、オブジェクトを取得することで対応するオブジェクトを取得GlobalKey
でき、現在がである場合は、オブジェクトを取得することにより、対応するオブジェクトを取得できます。globalKey.currentWidget
widget
globalKey.currentElement
widget
element
widget
StatefulWidget
globalKey.currentState
widget
state
注: 使用すると
GlobalKey
コストがかかるため、他のオプションが利用可能な場合は使用しないでください。さらに、同じものはツリーGlobalKey
全体で一意でwidget
ある必要があり、繰り返すことはできません。
ナビゲーション ルーティングの観点から見た状態のライフ サイクル
StatefulElement の観点から見た StatefulWidget ライフサイクルの分析
各ライフサイクル コールバックのトリガーがBuildOwner
間接的StatefulElement
に駆動されることがわかります。State
以下では、トリガー パスと各コールバックの意味をソース コードの観点から分析します。
// 代码清单8-1 flutter/packages/flutter/lib/src/widgets/framework.dart
StatefulElement(StatefulWidget widget)
: state = widget.createState(), // 触发State创建
super(widget) {
state._element = this; // State持有Element和Widget的引用
state._widget = widget;
assert(state._debugLifecycleState == _StateLifecycle.created);
}
StatefulElement
作成は独自のコンストラクターで完了しState
、2 つのキー フィールドの割り当てが完了します。その後、このElement
ノードの処理が実行されますBuild
が、具体的なロジックはコードリスト8-2に示されています。
// 代码清单8-2 flutter/packages/flutter/lib/src/widgets/framework.dart
void _firstBuild() {
try {
// 触发initState回调
final dynamic debugCheckForReturnedFuture = state.initState() as dynamic;
} finally {
...... }
state.didChangeDependencies(); // 触发didChangeDependencies回调
super._firstBuild();
}
上記のロジックはライフ サイクルでinitState
sumメソッドをトリガーするため、通常は内部メンバー変数の初期化が開始される時点として使用されます。didChangeDependencies
initState
State
didChangeDependencies
最初のビルド中に無条件にトリガーされますが、リスト 8-3 に示すように、後続のビルド プロセスでもトリガーされます。
// 代码清单8-3 flutter/packages/flutter/lib/src/widgets/framework.dart
// StatefulElement
void performRebuild() {
if (_didChangeDependencies) {
// 通常在代码清单8-13中设置为true,详见8.2节
state.didChangeDependencies(); // 当该字段为true时再次触发didChangeDependencies
_didChangeDependencies = false;
}
super.performRebuild();
}
didChangeDependencies
これは依存ノードが変更されたことを示しElement
、didChangeDependencies
この時点でメソッドが再度呼び出されるため、このコールバックは依存する更新に応答するのに適しています。performnRebuild
最終的にトリガーされるメソッドをリスト 8-4 に示しますStatefulElement
。build
// 代码清单8-4 flutter/packages/flutter/lib/src/widgets/framework.dart
Widget build() => state.build(this);
上記のロジックは使用の直感に沿っていますStatefulWidget
。つまり、build
メソッドはState
コールバックに配置され、コールバックは主に UI の更新に使用されます。Element
現在のノードが としてマークされている場合dirty
、build
メソッドは確実に呼び出されるため、UI の流暢さに影響を与えないように、時間のかかる操作を実行することはお勧めできません。
最初以外のビルド (通常トリガーされるメソッド)の場合Element
、ロジックはリスト 8-5 に示されています。update
StatefulElement
// 代码清单8-5 flutter/packages/flutter/lib/src/widgets/framework.dart
void update(StatefulWidget newWidget) {
// StatefulElement,见代码清单5-47
super.update(newWidget);
assert(widget == newWidget);
final StatefulWidget oldWidget = state._widget!;
_dirty = true;
state._widget = widget as StatefulWidget;
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final dynamic debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget)
as dynamic;
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
rebuild(); // 触发代码清单8-4中的逻辑
}
上記のロジックは、rebuild
トリガーされたメソッドを渡す前にState
そのメソッドbuild
をトリガーします。didUpdateWidget
への参照と依存関係の一部を削除しoldWidget
、プロパティに依存するリソースを更新するのにWidget
適切な時期です。
Element
ノードはステージでメソッドをdetach
呼び出します。その特定のロジックはコード リスト 8-6 に示されています。_deactivateRecursively
// 代码清单8-6 flutter/packages/flutter/lib/src/widgets/framework.dart
static void _deactivateRecursively(Element element) {
// 见代码清单5-50
element.deactivate();
assert(element._lifecycleState == _ElementLifecycle.inactive);
element.visitChildren(_deactivateRecursively);
}
void deactivate() {
// StatefulElement
state.deactivate(); // 触发deactivate回调
super.deactivate(); // 见代码清单8-7
}
上記のロジックによりState
メソッドがトリガーされ、現在のノードがステージdeactivate
に入ります。inactive
コールバック トリガーは、現在のElement
ノードが削除されているElement Tree
ことを示しますが、まだ再結合することができます。この時間は、現在の状態に強く関連するいくつかのリソースを解放するのに適しています。状態に関係のないリソースの場合は、ノードがまだElement
入る可能性があることを考慮するElement Tree
と、その必要はありません。この時点で解放するのが適切です (Android のコールバックと比較できますActivity
) onPause
。
上記のロジックのsuper
メソッドdeactivate
を呼び出す必要があります。そのロジックをリスト 8-7 に示します。
// 代码清单8-7 flutter/packages/flutter/lib/src/widgets/framework.dart
void deactivate() {
// Element
if (_dependencies != null && _dependencies!.isNotEmpty) {
// 依赖清理
for (final InheritedElement dependency in _dependencies!)
dependency._dependents.remove(this);
}
_inheritedWidgets = null;
_lifecycleState = _ElementLifecycle.inactive; // 更新状态
}
上記のロジックは主に、対応する依存関係を削除するために使用されます。
ビルド プロセスが終了すると、Element
メソッドunmount
が呼び出されます。そのロジックはリスト 8-8 に示されています。
// 代码清单8-8 flutter/packages/flutter/lib/src/widgets/framework.dart
// StatefulElement,见代码清单5-52
void unmount() {
super.unmount();
state.dispose(); // 触发dispose回调
state._element = null;
}
void unmount() {
// Element
final Key? key = _widget.key;
if (key is GlobalKey) {
key._unregister(this); // 取消注册
}
_lifecycleState = _ElementLifecycle.defunct;
}
上記のロジックでは、StatefulElement
main メソッドがState
呼び出されますdispose
。Element
一般的なロジックは、GlobalKey
関連する登録を破棄する役割を果たします。
Flutter の観点から見たアプリのライフサイクル
アプリのライフサイクルを知りたい場合は、WidgetsBindingObserver
を通じて取得する必要があることに注意してくださいdidChangeAppLifecycleState
。このインターフェイスを通じて取得できるライフサイクル状態はAppLifecycleState
クラス内にあります。一般的な状態には次のようなものがあります。
- 再開: Android と同じように、インターフェイスが表示され、ユーザー入力に応答できます。
onResume
- inactive : インターフェイスがバックグラウンドに戻るか、ダイアログ ボックスがポップアップするとき、インターフェイスは非アクティブ状態になります。つまり、フォーカスを失い、ユーザー入力を受け取りませんが、コールバックは引き続き実行できます。Android と同じです
drawFrame
。onPause
- 一時停止: アプリケーションは一時停止されており、現在ユーザーには見えません (バックグラウンドに後退する、フォーカスを失う、ユーザー入力に応答できない、
drawFrame
コールバックを受信しない (エンジンはコールバックしないPlatformDispatcher
) ; AndroidonBeginFrame
と同じ)onDrawFrame
onStop
- detach : アプリはまだフラッター エンジンでホストされていますが、ホスト ビューから切り離されており、コールバックは実行されなくなります。
drawFrame
アプリがこの状態にある場合、エンジンはビューなしで実行されます。これは、エンジンが最初に初期化されたとき、またはNavigator
ポップによりビューが破棄された後にビューをアタッチする処理中である可能性があります。
使用例:
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this);
}
void didChangeAppLifecycleState(AppLifecycleState state) {
print(state);
}
}
Flutter と他のクロスプラットフォーム ソリューションとの比較
現在、業界にはすでに多くのクロスプラットフォーム フレームワーク (特に Android および iOS プラットフォームを指します) が存在しており、その原則に従って、主に次の 3 つのカテゴリに分類されます。
- H5 + ネイティブ (Cordova、Ionic、WeChat アプレット)
- JavaScript 開発 + ネイティブ レンダリング (React Native、Weex)
- 自己描画 UI + ネイティブ (Qt for mobile、Flutter)
上表の開発言語は主にアプリケーション層の開発言語を指し、開発効率はコーディング時間、デバッグ時間、デバッグや互換性問題への対応時間を含む開発サイクル全体の効率を指します。動的とは主に、動的コード配信をサポートするかどうか、およびホット アップデートをサポートするかどうかを指します。Flutter のリリース パッケージはデフォルトで Dart AOT モードを使用してコンパイルされるため、ダイナミゼーションをサポートしていないことに注意してください。ただし、Dart には JIT またはスナップショット操作モードもあり、これらのモードはダイナミゼーションをサポートしています。
解決策 1: H5 + ネイティブ
このタイプのフレームワークの主な原理は、動的に変更する必要があるコンテンツを HTML5 (略して H5) を通じてアプリに実装し、ネイティブ Web ページ読み込みコントロール WebView (Android) または WKWebView (iOS) を通じて読み込みます。このソリューションでは、バージョンを公開せずに H5 部分をいつでも変更でき、動的な要件を満たすことができます。同時に、H5 コードは一度開発するだけで済むため、Android と iOS の両方で実行できます。コスト、つまりH5パーツの機能が多ければ多いほど、開発コストは安くなります。これをH5 + ネイティブ開発モードハイブリッド開発と呼びます。ハイブリッド モードで開発されたアプリは、ハイブリッド アプリケーションまたはHTMLybrid アプリと呼ばれます。アプリケーションのほとんどの機能が H5 によって実装されている場合、それをWeb アプリ と呼びます。
現在のハイブリッド開発フレームワークの代表的なものは、Cordova、Ionic です。ほとんどのアプリには H5 によって開発された機能がいくつか含まれていますが、少なくとも今のところ、HTMLybrid アプリは依然として最も多用途で成熟したクロスエンド ソリューションです。
ここで小規模プログラムについて言及する必要があるが、現在、国内各社の小規模プログラムアプリケーション層の開発技術スタックはWeb技術スタックであり、その基盤となるレンダリング手法は基本的にWebViewとネイティブの組み合わせとなっている。
ハイブリッド開発技術のポイント
1) 例: JavaScript はネイティブ API を呼び出して電話モデルを取得します
次に、Android を例として、 を呼び出すための電話モデルを取得するためのネイティブ API を実装しますJavaScript
。この例では、JavaScript
ネイティブ API を呼び出す処理を示します。通信にはGithub上のオープンソースライブラリのdsBridgeを選択します。JsBridge
- まず、ネイティブで電話モデルを取得するための API を実装します。
class JSAPI {
public Object getPhoneModel(Object msg) {
return Build.MODEL;
}
}
- ネイティブ API
WebView
を登録するにJsBridge
は、
import wendu.dsbridge.DWebView
...
//DWebView继承自WebView,由dsBridge提供
DWebView dwebView = (DWebView) findViewById(R.id.dwebview);
//注册原生API到JsBridge
dwebView.addJavascriptObject(new JsAPI(), null);
JavaScript
ネイティブ APIを呼び出す
var dsBridge = require("dsbridge")
//直接调用原生API `getPhoneModel`
var model = dsBridge.call("getPhoneModel");
//打印机型
console.log(model);
上記の例はJavaScript
、ネイティブ API を呼び出すプロセスを示しています。同様に、一般的に言えば、優れたものはJsBridge
ネイティブ呼び出しもサポートしておりJavaScript
、dsBridge
それらもサポートされています。興味がある場合は、Github dsBridge プロジェクトのホームページにアクセスして表示できます。
さて、振り返ってみますと、ハイブリッド アプリケーションとは、システムの機能にアクセスできるようJavaScript
に、最初のステップで呼び出すための一連の API を事前に実装しているだけです。JavaScript
自分自身でフレームワークを作成します。
まとめ
ハイブリッド アプリケーションの利点は、H5 で動的コンテンツを開発できること、H5 が Web 技術スタックであること、Web 技術スタックにはオープンなエコロジーと豊富なコミュニティ リソースがあり、全体的な開発効率が高いことです。欠点は、パフォーマンス エクスペリエンスが良くなく、複雑なユーザー インターフェイスやアニメーションの場合、WebView が圧倒される場合があることです。
ソリューション 2: JavaScript 開発 + ネイティブ レンダリング
React リアクティブ プログラミング
React はレスポンシブ Web フレームワークです。まず、DOM ツリーとレスポンシブ プログラミングという 2 つの重要な概念を理解しましょう。
DOM ツリー
ドキュメント オブジェクト モデル (略して DOM) は、ドキュメントのコンテンツと構造にアクセスして変更する、プラットフォームや言語に依存しない方法である Extensible Markup Language を処理するために W3C 組織によって推奨されている標準プログラミング インターフェイスです。言い換えれば、これは HTML または XML ドキュメントを表現および操作するための標準インターフェイスです。DOM とは、簡単に言うとユーザー インターフェイスのコントロール ツリーに相当するドキュメント ツリーのことで、フロントエンド開発では HTML に相当するレンダリング ツリーを指すことが多いですが、広義にはコントロール ツリーも DOM と呼びます。 Android の XML レイアウト ファイルに相当する用語 DOM 操作 レンダリング ツリー (またはコントロール ツリー) を直接操作することを指します。後者は Web 開発で使用され、後者はネイティブ開発でよく使用されます。
リアクティブプログラミング
React では、状態が変化すると UI が自動的に変化するという重要なアイデアが提案されています。React フレームワーク自体は、ユーザー状態変更イベントに応じてユーザー インターフェイスを再構築する作業を実行します。これは典型的なレスポンシブプログラミング パラダイムです。React のレスポンシブ原則を以下にまとめてみましょう:
- 開発者は状態の転送 (データ) に注意するだけでよく、状態が変化すると、React フレームワークは新しい状態に応じて UI を自動的に再構築します。
- ユーザー状態変更通知を受信した後、React フレームワークは、現在のレンダリング ツリーと最新の状態変更に従って Diff アルゴリズムを通じてツリーの変更部分を計算し、変更部分のみを更新 (DOM 操作) することで、ツリー全体 パフォーマンスを向上させるためのツリーのリファクタリング。
2 番目のステップでは、状態が変化した後、React フレームワークは DOM ツリーの変更された部分をすぐに計算してレンダリングするのではなく、逆に、React は DOM ツリーに基づいて抽象レイヤーを構築します。つまり、仮想 DOMツリーです。データと状態に加えられた変更は、変更があるたびに DOM を操作するのではなく、自動的かつ効率的に仮想 DOM に同期され、最終的にはバッチで実際の DOM に同期されます。
DOM ツリーが変更されるたびに、DOM ツリーを直接操作できないのはなぜでしょうか? これは、ブラウザーでのすべての DOM 操作により、ブラウザーの再描画またはリフロー (レイアウトの再タイプ設定、DOM ノードのサイズと位置の決定) が発生する可能性があるためです。
- DOM の外観とスタイルのみが変更される場合 (色の変更など)、ブラウザーはインターフェイスを再描画します。
- サイズ、レイアウト、ノードの非表示など、DOM ツリーの構造が変更された場合は、ブラウザをリフローする必要があります。
ブラウザの再描画とリフローは比較的高価な操作です。各変更が DOM に対して直接操作されると、パフォーマンスの問題が発生します。ただし、バッチ操作では DOM の更新のみがトリガーされるため、パフォーマンスが向上します。
リアクトネイティブ
React Native (略して RN) は、2015 年 4 月に Facebook によってオープンソース化されたクロスプラットフォーム モバイル アプリケーション開発フレームワークです。ネイティブ モバイル アプリケーション プラットフォーム上の Facebook の初期のオープンソース Web フレームワーク React の派生です。現在、iOS と Android の両方をサポートしています。プラットフォーム。RN は、JSX 言語 (主に JavaScript で HTML タグを記述するための拡張 JavaScript) と CSS を使用してモバイル アプリケーションを開発します。したがって、Web フロントエンド開発に精通している技術者は、ほとんど学習せずにモバイル アプリケーション開発の分野に参入できます。
RN と React の原理は似ており、Flutter もアプリケーション層で React からインスピレーションを得ているため、多くのアイデアも似ています。
上で述べたように、React Native はネイティブ モバイル アプリケーション プラットフォームの React から派生したものですが、この 2 つの主な違いは何ですか? 実際、主な違いは、仮想 DOM によってマップされるオブジェクトです。React の仮想 DOM は最終的にブラウザ DOM ツリーにマッピングされますが、RN の仮想 DOM は JavaScriptCore を介してネイティブ コントロールにマッピングされます。
JavaScriptCore は JavaScript インタープリターであり、React Native には 2 つの主要な機能があります。
-
JavaScript の実行環境を提供します。
-
JavaScriptとネイティブアプリケーション間の通信ブリッジであり、その機能はJsBridgeと同じであり、実際、iOSではJsBridgeの実装の多くがJavaScriptCoreをベースとしています。
仮想 DOM を RN のネイティブ コントロールにマッピングするプロセスは、主に 2 つのステップに分かれています。 -
レイアウト メッセージ パッシング。仮想 DOM レイアウト情報をネイティブに渡します。
-
レイアウト情報に従って、対応するネイティブ コントロールを介したネイティブ レンダリング。
これまでのところ、React Native はクロスプラットフォームを実現しています。React Native は、ハイブリッド アプリケーションと比較して、ネイティブ コントロールをレンダリングするため、ハイブリッド アプリケーションでは H5 よりもパフォーマンスが向上します。同時に、React Native は、ネイティブ コンポーネントに対応する多くの Web コンポーネントを提供します。ほとんどの場合、開発者は、 Web技術スタック、アプリ開発可能。このようにして、1 つのコードを維持でき、クロスプラットフォームに対応できることがわかります。
ウィークス
Weex は、Alibaba が 2016 年にリリースしたクロスプラットフォームのモバイル開発フレームワークです。その考え方と原則は React Native に似ています。最下層はネイティブにレンダリングされます。違いは、アプリケーション層の開発構文 (DSL、ドメイン固有言語) です。Weexは Vue 文法と Rax 構文をサポートしていますが、Rax の DSL (Domain Specific Language) 構文は React JSX 構文に基づいて作成されていますが、RN の DSL は React に基づいており、Vue をサポートしていません。
まとめ
JavaScript 開発 + ネイティブ レンダリングの主な利点は次のとおりです。
- Web 開発テクノロジ スタックを使用すると、コミュニティは巨大ですぐに習得でき、開発コストは比較的低くなります。
- ネイティブレンダリングなので、H5と比べてパフォーマンスが大幅に向上しています。
- これは動的であり、ホット アップデートをサポートします。
不十分:
- レンダリング時にJavaScriptとネイティブ間の通信が必要となるため、ドラッグなどの一部のシナリオでは頻繁に通信を行うとラグが発生する場合があります。
- JavaScript はスクリプト言語であり、実行中に解釈して実行する必要があります (この実行方法は通常 JIT またはジャスト イン タイムと呼ばれ、実行中にマシン コードをリアルタイムで生成することを指します)、実行効率、およびコンパイル言語 (実行コンパイル言語の方式は AOT (Ahead Of Time) であり、コードが実行される前にソース コードが前処理されていることを意味します。この前処理は通常、ソース コードをマシン コードまたはある種の中間コードにコンパイルします)。ギャップ。
- レンダリングはネイティブ コントロールに依存しているため、さまざまなプラットフォーム上のコントロールを個別に維持する必要があり、システムが更新されると、コミュニティ コントロールが遅れる可能性があります。さらに、そのコントロール システムもネイティブ UI システムによって制限されます。 Android、ジェスチャの競合 曖昧さ回避ルールが修正されたため、異なる人が作成したネストされたコントロールを使用する場合、ジェスチャの競合の問題が非常に困難になります。これにより、カスタムのネイティブ レンダリング コンポーネントが必要な場合、開発コストとメンテナンス コストが高額になります。
解決策 3: 自己描画 UI + ネイティブ
このテクノロジーのアイデアは、システムのネイティブ コントロールに依存せずに、異なるプラットフォーム上で統一されたインターフェイスを持つレンダリング エンジンを実装することで UI を描画し、異なるプラットフォームの UI の一貫性を実現することです。
自己描画エンジンは UI のクロスプラットフォームの問題を解決することに注意してください。他のシステム機能呼び出しが関与する場合でも、ネイティブ開発が関与します。このプラットフォーム技術の利点は次のとおりです。
-
高いパフォーマンス。自己描画エンジンがシステム API を直接呼び出して UI を描画するため、ネイティブ コントロールに近いパフォーマンスを実現します。
-
柔軟で保守が容易なコンポーネント ライブラリ、UI の外観の忠実性と一貫性。UI レンダリングはネイティブ コントロールに依存しないため、さまざまなプラットフォームのコントロールに応じて個別のコンポーネント ライブラリを保守する必要がなく、コードの保守が容易です。 。コンポーネント ライブラリは同じコード セットと同じレンダリング エンジンであるため、コンポーネントの表示外観は、さまざまなプラットフォーム上で高い忠実度および高い一貫性を実現できます。さらに、ネイティブ コントロールに依存しないため、コンポーネントの表示は、ネイティブ レイアウト システム: このレイアウト システムは非常に柔軟です。
不十分:
- ダイナミクスが不十分です。UI 描画パフォーマンスを確保するために、自己描画 UI システムは通常、AOT モードを使用してリリース パッケージをコンパイルするため、アプリケーションのリリース後、JavaScript (JIT) を使用するハイブリッド フレームワークや RN フレームワークのようなコードを動的に配信できません。開発言語。
- アプリケーションの開発効率が低い: Qt は開発言語として C++ を使用しており、プログラミングの効率がアプリ開発の効率に直接影響しますが、C++ は静的言語であるため、UI 開発の点で JavaScript などの動的言語ほど柔軟性がありません。 、C++ を開発するか、メモリ割り当てを手動で管理する必要があります。JavaScript と Java にはガベージ コレクション (GC) メカニズムがありません。
Flutter がこのタイプのクロスプラットフォーム テクノロジに属することを推測したかもしれませんが、Flutter は一連の自己描画エンジンを実装し、独自の UI レイアウト システムを備えていると同時に、開発効率において大きな進歩を遂げました。ただし、自己描画エンジンというアイデアは新しい概念ではなく、Flutter がこれを試みた最初の概念ではなく、その前には典型的な代表、つまり有名な Qt がありました。
Qtモバイル
Qt は、1991 年に Qt Company によって開発されたクロスプラットフォーム C++ グラフィカル ユーザー インターフェイス アプリケーション開発フレームワークです。2008 年に Qt Company のテクノロジーが Nokia に買収され、Qt は Nokia 傘下のプログラミング言語ツールになりました。2012 年に Qt は Digia に買収されました。2014 年 4 月に、クロスプラットフォームの統合開発環境 Qt Creator 3.1.0 が正式にリリースされ、iOS を完全にサポートし、WinRT、Beautifier およびその他のプラグインを追加し、Python インターフェイスを使用しない GDB デバッグ サポートを廃止し、Clang ベースの C/C を統合しました。 C++ コード モジュールは、Android サポートに調整を加え、これまでのところ iOS、Android、WP を完全にサポートしており、アプリケーション開発者にグラフィカル ユーザー インターフェイスの構築に必要なすべての機能を提供します。
しかし、Qt は PC 側で大きな成功を収め、コミュニティからの期待も高いものの、モバイル側ではあまりうまく機能せず、近年、Qt はクロスであるにもかかわらず、Qt の声はほとんど聞かれなくなりました。 -platform モバイル開発用の自己描画プラットフォーム エンジンの先駆者だったが、殉教者となった。理由としては次のことが考えられます。
- Qt モバイル開発コミュニティは小さすぎ、学習教材も不十分で、エコロジーも良くありません。
- 公式のプロモーションは良くなく、サポートも十分ではありません。
- モバイル端末は後発であり、市場は他の動的フレームワーク (ハイブリッドおよび RN) によって占有されてきました。
- モバイル開発では、C++ 開発には Web 開発スタックと比較して固有の欠点があり、その直接的な結果として Qt 開発の効率が低すぎることが挙げられます。
フラッター
Flutter は、クロスプラットフォームの高性能モバイル アプリケーションを作成するために Google がリリースしたフレームワークです。Flutter は、Qt mobile と同様にネイティブ コントロールを使用しませんが、どちらも自己描画エンジンを実装し、独自のレイアウトと描画システムを使用します。それでは、Qt mobile が直面している問題は Flutter と同じなのでしょうか。Flutter は Qt mobile の足跡を踏み、新たな殉教者となるのでしょうか? 2017年にFlutterが誕生して以来、長年の経験を経てFlutterエコシステムは急速に成長し、国内外でFlutterをベースにした成功事例が数多くあり、国内のインターネット企業は基本的にFlutter専門チームを抱えています。つまり、Flutter は急速に発展し、業界で広く注目と認知を受け、開発者から温かく歓迎され、モバイルのクロスエンド開発で最も人気のあるフレームワークの 1 つになりました。
ここで、Qt mobile と比較してみましょう。
- エコロジー: Flutter エコシステムは急速に発展しており、コミュニティは非常に活発で、開発者の数とサードパーティ コンポーネントの数はすでに非常に多くなっています。
- 技術サポート: 現在、Google は Flutter を精力的に推進しており、Flutter の作者の多くは Chromium チームの出身で、Github で非常に活発に活動しています。別の観点から見ると、Flutter の誕生から現在に至るまで、頻繁にバージョンがリリースされるということは、Google が Flutter に多くのリソースを投資していることを示すものでもあり、公式の技術サポートについて心配する必要はありません。
- 開発効率: 1 セットのコード、マルチターミナル操作、開発プロセス中、Flutter のホット リロードにより、開発者は迅速なテスト、UI の構築、機能の追加、エラーの修正を迅速に行うことができます。ミリ秒レベルのホット リロードは、状態を失うことなく、iOS および Android シミュレータまたは実際のデバイスで実現できます。これは本当に素晴らしいことです。信じてください。もしあなたがネイティブ開発者であれば、Flutter 開発フローを経験した後は、おそらく再びネイティブの開発に戻りたくないと思うでしょう。結局のところ、Flutter のコンパイル速度について文句を言わない人はほとんどいません。ネイティブ開発。
Flutter での GC
Flutter は開発言語およびランタイム メカニズムとして Dart を使用します。Dart はデバッグ モード (デバッグ) であってもリリース モード (リリース) であってもランタイム メカニズムを常に保持していますが、2 つの構築方法には大きな違いがあります。
- デバッグモードでは、Dart はすべてのパイプライン (使用する必要があるすべてのアクセサリ) をデバイスにロードします: Dart ランタイム、JIT (ジャストインタイム) コンパイラー/インタープリター (Android の場合は JIT、iOS の場合はインタープリター)、デバッグとパフォーマンス分析サービス。
- リリースモードでは、 AOTコンパイルが使用され、JIT は削除されますが、Dart ランタイムは引き続き保持されます。これは、Dart ランタイムが Flutter アプリの主な貢献者であるためです。
Dart のランタイムには、ガベージ コレクターという非常に重要なコンポーネントが含まれています。その主な役割は、オブジェクトがインスタンス化されるか到達不能になったときにメモリを割り当てたり解放したりすることです。
Flutter の実行中は、多数のオブジェクトが存在します。これらは (実際にはまだ)レンダリングの前に作成されますStatelessWidget
。StatefulWidget
状態が変化すると、再び破壊されます。実際、それらの寿命は非常に短いです。複雑な UI インターフェイスを構築する場合、これらのインターフェイスは何千も存在することになりますWidget
。
では、Flutter 開発者として、ガベージ コレクターがこれらを適切に管理できるかどうかを心配する必要があるでしょうか? (多くのパフォーマンス上の問題を引き起こすでしょうか?) Flutter はこれらのWidget
(オブジェクト) オブジェクトを頻繁に作成および破棄するため、開発者はこの動作を制限する措置を講じるべきでしょうか?
- 新しい Flutter 開発者は、ウィジェットが時間の経過とともに変更されないとわかっている場合、ウィジェットが破棄されたり再構築されたりしないように
Widget
参照を作成して配置しますが、これは珍しいことではありません。State
そんなことする必要はない
- Dart の GC について心配する必要はまったくありません。その世代アーキテクチャはオブジェクトを頻繁に作成および破棄できるように特別に最適化されているからです。ほとんどの場合、Flutter のエンジンに必要なものをすべて作成および破棄させるだけで済みます
Widget
。
ダーツGC
Dart の GC は世代別 (世代別) であり、次の 2 つのステージで構成されます: Young Space Scavenger (スカベンジャーは若いバッグをリサイクル) と Parallel Marking and Concurrent Sweetping (スイープ コレクターは古い世代をリサイクル)
スケジュール設定
アプリケーションと UI のパフォーマンスに対する GC の影響を最小限に抑えるために、ガベージ コレクターは Flutter エンジンにフックを提供し、ユーザーの操作がなくアプリケーションがアイドル状態であることをエンジンが検出したときに警告を発します。これにより、ガベージ コレクター ウィンドウは、パフォーマンスに影響を与えることなく収集フェーズを実行できるようになります。
ガベージ コレクターは、これらのアイドル期間中にスライディング コンパクションを実行することもできます。これにより、メモリの断片化が軽減され、メモリのオーバーヘッドが最小限に抑えられます。
フェーズ 1: 若い宇宙スカベンジャー
この段階は主に、 などの存続期間の短いオブジェクトをクリーンアップすることですStatelessWidget
。ブロックされた場合、そのクリーニング速度は第 2 世代のマーク/スイープ方式よりもはるかに高速です。また、スケジュール設定と組み合わせて完了すると、プログラム実行時の一時停止現象を解消できます。
基本的に、オブジェクトはメモリ内の連続した領域に割り当てられ、オブジェクトが作成されると、割り当てられたメモリがいっぱいになるまで、次に使用可能な領域に割り当てられます。Dart はバンプ ポインター割り当てを使用して新しいスペースをすばやく割り当て、プロセスを非常に高速にします。(free_listを維持してmallocのように再配布すると効率が非常に悪くなります)
新しいオブジェクトが割り当てられる新しいスペースは、ハーフスペースと呼ばれる 2 つの半分で構成されます。一度に使用されるのはスペースの半分だけです。半分はアクティブで、もう半分は非アクティブです。新しいオブジェクトは領域のアクティブな半分に割り当てられ、アクティブな半分がいっぱいになると、アクティブなオブジェクトはアクティブな領域から非アクティブな領域にコピーされ、無効なオブジェクトはクリアされます。その後、非アクティブな半分がアクティブになり、プロセスが繰り返されます。(これは、JVM の世代リサイクル戦略における新世代の生存空間に非常に似ています)
どのオブジェクトが生きているか死んでいるかを判断するために、GC はルート オブジェクト (スタック変数など) から開始し、それらが何を参照しているかを調べます。次に、参照されたオブジェクト (生存) を非アクティブ状態に移動すると、生存しているすべてのオブジェクトが直接移動されます。死んだオブジェクトには参照がないため残されますが、将来のガベージ コレクション イベントでは生きているオブジェクトが上書きコピーされます。
これについて詳しくは、チェイニーのアルゴリズムをご覧ください。
フェーズ 2: 並行マーキングと同時スイープ
オブジェクトが特定の年齢に達すると (最初のフェーズで GC によって再利用されない)、オブジェクトは第 2 世代のコレクターであるマーク スイープによって管理される新しいメモリ空間に昇格されます。
このガベージ コレクション手法には 2 つのフェーズがあります。最初にオブジェクト グラフを走査し、次にまだ使用中のオブジェクトにマークを付けます。第 2 フェーズでは、メモリ全体がスキャンされ、マークされていないオブジェクトがあれば再利用されます。次に、すべてのフラグをクリアします。
この GC 手法はマーキング フェーズをブロックします。メモリの突然変異は発生せず、UI スレッドもブロックされます。ただし、一時的なオブジェクトはヤング スペース スカベンジャー フェーズですでに処理されているため、この状況は非常にまれです。ただし、この形式の GC を実行するには、Dart ランタイムを一時停止する必要がある場合があります。Flutter の計画されたコレクションの機能を考慮すると、その影響は最小限に抑えられるはずです。
アプリケーションが世代の仮定 (つまり、ほとんどのオブジェクトが早期に消滅するという仮定) に従っていない場合、この形式の GC がより頻繁に発生することに注意することが重要です。Widget
Flutter での動作を考えると、これが起こる可能性は低いですが、知っておくべきことがあります。
隔離する
Dart のメカニズムには、互いに独立したプライベート ヒープの概念がIsolate
あることは注目に値します。それぞれには実行する独自のスレッドがあり、それぞれの GC は他のスレッドのパフォーマンスに影響を与えません。を使用することは、UI のブロックを回避し、プロセス集中型のアクティビティをオフロードするための優れた方法です。(時間のかかる操作には Isolate を使用できます)Isolate
Isolate
Isolate
Isolate
ここで理解していただきたいのは、Dart は強力な世代別ガベージ コレクターを使用して、Flutter での GC のパフォーマンスへの影響を最小限に抑えることです。したがって、Dart のガベージ コレクターについて心配する必要はなく、自信を持ってビジネスに集中できます。
参考: