Ctrip における Flutter マップのベスト プラクティス

Ctrip における Flutter マップのベスト プラクティス

著者について

Ctrip のシニア モバイル開発エンジニアである Leo は、クロスエンド テクノロジーに重点を置いており、効率的で高性能な開発に取り組んでいます。

Ctrip のシニア モバイル開発エンジニアである Jarmon 氏は、Flutter と iOS 開発に重点を置いています。

この記事では、flutter-boost のハイブリッド プロジェクトに基づいて、シングル エンジン モードで Flutter マップ プラグインにアクセスするときに発生する問題と解決策に焦点を当てます。

1. 背景

さまざまなマルチターミナル技術の活発な開発により、メインプロジェクトは純粋な Native プロジェクトから Native+RN を経て、現在の Native+RN+Flutter へと進化しました。私たちのビジネスはすべて Flutter テクノロジー スタックに基づいているため、表示マップをネストする必要があります。現在、マップのネスト表示を実現するための主なソリューションは 2 つあります。

公式の Flutter マップ プラグインにアクセスする際に直面する主な問題は次のとおりです。

  • 公式プラグインはまだ十分に成熟しておらず、既存のネイティブ API の一部は Flutter ではサポートされていません。
  • 現在、Flutter マップ プラグインに接続されているアプリケーションは非常に少なく、新しい領域を探索する必要があります。
  • 公式の適応は純粋な Flutter プロジェクト向けであるため、ハイブリッド プロジェクトでは多くの未知の困難な問題が発生する可能性があります。

ネイティブマップをFlutterページに直接表示します

  • ネイティブ マップは成熟しており、大きな落とし穴に遭遇することはありません。
  • 主な問題は、ビジネスが Flutter 上で行われ、Flutter が大量のマップ コンポーネントと対話し、データを要求し、リンクする必要があることです。操作データを渡すには多数のブリッジメソッドが必要です。
  • ネイティブ マップをネストするには、カスタム コンテナーが必要です。Android と IOS はそれぞれブリッジ、コンテナー、マップ ロジックを実装する必要があり、メンテナンス コストが増加します。

メンテナンスコストを考慮し、長所と短所を比較検討した結果、Flutter マップ プラグインに接続することにしました。一部の API をより適切にカスタマイズし、公式が時間内に更新しなかった一部の問題をより迅速に修正するため。 Flutter マップ プラグインにアクセスするには、ソース コードを使用します。この記事では、flutter-boost のハイブリッド プロジェクトに基づいて、シングル エンジン モードで Flutter マップ プラグインにアクセスするときに発生する問題と解決策に焦点を当てます。

2. ソースコードを統合する方法

ハイブリッド プロジェクトへのプラグインの統合は、主に Flutter とネイティブに分かれます。Flutter プラグインを統合する場合、プラグインのソース コードは公式デモから直接ダウンロードできます。この記事では、Flutter マップ プラグイン バージョン 3.3.1 を例として使用します。

2.1 フラッター統合

公式デモを入手したら、ディレクトリ内で flutter pub get を実行し、flutter SDK の下にある pub-cache 依存関係キャッシュ ファイル ディレクトリを見つけて、ビジネス ニーズに応じて各プラグイン src ファイルの下にあるコードを flutter プロジェクトにインポートします。

2.2 IOS統合

flutter pub get を実行した後、必要に応じて各プラグインの iOS/Classes/ ディレクトリ内のコードをプロジェクトにインポートします。

2.3 Android統合

Android ネイティブ側の統合は、IOS 側の統合と同様です。ネイティブ プロジェクトに新しいマップ モジュールを作成します。マップ デモ内のマップ プラグイン ソース コードの Android 部分をプロジェクトに配置するだけです。

3. マッププラグインの実装原理: platformView

マップ プラグインは、機能に応じて Map、Search、Util などのモジュールに分かれています。基本的な実装は似ており、MethodChannel を使用してネイティブと通信します。ここでは、Map を例にその実装を分析します。プラグインは PlatformView を使用してネイティブ マップを Flutter ページに埋め込みます。Flutter レイヤーは UIKitView と AndroidView です。マップを生成した後、ネイティブは対応する MethodChannel を含む viewId に従って BMFMapViewController を初期化します。 BMFMapViewController はマップ操作を集約し、さまざまなモジュールにディスパッチしてマップのネイティブ メソッドを呼び出します。

3.1 PlatformViewとは

PlatformView は、ネイティブ コンポーネントを Flutter ページに埋め込むことができるテクノロジーです。これにより、Flutter UI フレームワークで実装するのが難しい、成熟したネイティブ コンポーネント、マップ、WebView などのコンポーネントを Flutter ページに表示できるようになります。

Flutter では、PlatformView を実装するための 2 つの方法 (仮想ディスプレイとハイブリッド コンポジション) が提供されています。仮想ディスプレイ モードでは、ネイティブ ビューがメモリに読み込まれ、Flutter ウィジェットとともにレンダリングされます。ハイブリッド コンポジション モードでは、ネイティブ ビューが Flutter ビュー レイヤーに直接追加されます。 iOS ではハイブリッド コンポジション モードが使用され、Android では仮想ディスプレイ モードとハイブリッド コンポジション モードの両方が使用されます。

3.2 PlatformViewの実装原則

1) Flutterレンダリングプロセス

Hybrid Composition の実装を紹介する前に、次の図で Flutter のレンダリング プロセスを見てみましょう。

Dart レイヤーは、VSync 信号を受信した後、UI スレッドで Widget Tree、Element Tree、RenderObject Tree の更新と生成を完了し、描画情報を含んだレイヤー ツリーを生成して、レンダリングのためにエンジンに渡します。最後に、Compositor と Skia を介して、GPU スレッドで Flutter ビューがレンダリングされます。

2) ハイブリッド構成モード分析

iOS を例に、Hybird Composition モードの実行プロセスを段階的に分析します。まず、Dart レイヤーはネイティブ ビューを表示するための UIKitView コンポーネントを提供します。didChangeDependencies メソッドでは、ネイティブ ビューがチャネルを通じて 1 回初期化され、ネイティブ ビューを一意に識別する viewId が生成され、ネイティブ ビューが root_views_ にキャッシュされます。実際にレイヤーを組み立てる際には、dart レイヤーがネイティブ ビューの座標とサイズをエンジンに伝えて表示し、PlatformViewLayer を生成します。つまり、ネイティブ ビューの位置とサイズの情報は dart レイヤーによって制御されます。

 void FlutterPlatformViewsController::OnCreate(FlutterMethodCall* call, FlutterResult& result) { NSDictionary<NSString*, id>* args = [call arguments]; long viewId = [args[@"id"] longValue]; NSObject<FlutterPlatformView>* embedded_view = [factory createWithFrame:CGRectZero viewIdentifier:viewId arguments:params]; // 初始化UIView* platform_view = [embedded_view view]; FlutterTouchInterceptingView* touch_interceptor = [[[FlutterTouchInterceptingView alloc] initWithEmbeddedView:platform_view platformViewsController:GetWeakPtr() gestureRecognizersBlockingPolicy:gesture_recognizers_blocking_policies[viewType]] autorelease]; ChildClippingView* clipping_view = [[[ChildClippingView alloc] initWithFrame:CGRectZero] autorelease]; [clipping_view addSubview:touch_interceptor]; root_views_[viewId] = fml::scoped_nsobject<UIView>([clipping_view retain]); // 缓存}

現在のフレームのレイヤー ツリーを生成した後、ラスタライザー プロセスに入ります。まず、BeginFrame を呼び出してフレームをレンダリングし、PlatformViewLayer::Preroll をトリガーします。PlatformViewLayer は、現在のフレームに PlatformView があることをマークし、次に FlutterPlatformViewsController::PrerollCompositeEmbeddedView を呼び出して、Platform View の座標、サイズ、その他の情報を含む view_params_ を更新します。最後に、SubmitFrame メソッドで、ネイティブ ビューが取り出され、Flutter ビューに追加されてレンダリングが完了します。

 void PlatformViewLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { set_paint_bounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), size_.width(), size_.height())); context->has_platform_view = true; set_subtree_has_platform_view(true); // 标记当前帧存在Platform View std::unique_ptr<EmbeddedViewParams> params = std::make_unique<EmbeddedViewParams>(matrix, size_, context->mutators_stack); context->view_embedder->PrerollCompositeEmbeddedView(view_id_, std::move(params)); }

3.3 PlatformView はどのようにしてフレーム同期を実現するのでしょうか?

ネイティブ開発では、UI 操作を他のスレッドで実行できないため、フレームの非同期の問題が発生することが分かっています。 Flutter エンジンには、プラットフォーム、UI、ラスター、IO の 4 つのスレッドがあります。ネイティブ ビューはプラットフォーム スレッド (メイン スレッド) でレンダリングされますが、Flutter レンダリングは通常、ラスター スレッドで実行されます。Flutter はどのようにしてフレーム同期を確保するのでしょうか?

Flutter はスレッドをマージすることでフレーム同期を解決します。上図のRasterプロセスのPostPrerollActionメソッドでは、PlatformViewが存在するかどうかを判定します。以降の描画処理では、RasterスレッドとPlatformスレッドがマージされ、RasterキューのタスクがPlatformキューに配置されます。このようにして、すべてのレンダリング タスクがプラットフォーム スレッドで実行され、画面の同期が保証されます。

 PostPrerollResult FlutterPlatformViewsController::PostPrerollAction( fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) { if (!HasPlatformViewThisOrNextFrame()) { // 没有Platform View不用处理return PostPrerollResult::kSuccess; } if (!raster_thread_merger->IsMerged()) { // 线程还没有并不用处理CancelFrame(); // 取消绘制当前帧return PostPrerollResult::kSkipAndRetryFrame; // 合并后完成当前帧} BeginCATransaction(); raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration); return PostPrerollResult::kSuccess; } // 合并队列bool MessageLoopTaskQueues::Merge(TaskQueueId owner, TaskQueueId subsumed) { if (owner == subsumed) { return true; } std::lock_guard guard(queue_mutex_); auto& owner_entry = queue_entries_.at(owner); auto& subsumed_entry = queue_entries_.at(subsumed); auto& subsumed_set = owner_entry->owner_of; if (subsumed_set.find(subsumed) != subsumed_set.end()) { return true; } owner_entry->owner_of.insert(subsumed); subsumed_entry->subsumed_by = owner; if (HasPendingTasksUnlocked(owner)) { WakeUpUnlocked(owner, GetNextWakeTimeUnlocked(owner)); } return true; }

IV. 問題と解決策

4.1 iOS でマップ コンポーネントを切り替えると画面が白くなる問題

flutter_boost ハイブリッド開発を使用する場合、ページ A で platformview が使用され、新しいコンテナーが開かれて flutter ページ B にジャンプすると、platformView に短い白い画面が表示されますが、ページ A からネイティブ ページにジャンプするときには表示されません。外観から判断すると、最初の推測では単一のエンジンが原因だったと思われます。 Flutter ページ A が他のページにジャンプすると、SceneBuilder::pushTransform がトリガーされ、ページ A が再レンダリングされます。

 void SceneBuilder::pushTransform(Dart_Handle layer_handle, tonic::Float64List& matrix4, fml::RefPtr<EngineLayer> oldLayer) { SkMatrix sk_matrix = ToSkMatrix(matrix4); auto layer = std::make_shared<flutter::TransformLayer>(sk_matrix); PushLayer(layer); // matrix4 has to be released before we can return another Dart object matrix4.Release(); EngineLayer::MakeRetained(layer_handle, layer); if (oldLayer && oldLayer->Layer()) { layer->AssignOldLayer(oldLayer->Layer().get()); } }

Flutter ページ A が新しいコンテナを作成し、それを Flutter ページ B にプッシュすると、最初に viewDidLayoutSubviews がトリガーされます。このメソッドは、エンジンに対応する viewController flutterView を変更します。viewDidLayoutSubviews の後に SceneBuilder::pushTransform もトリガーされ、platformView がネイティブでレンダリングされます。ページ A が再レンダリングされると、対応する platformView が見つからず、白い画面の問題が発生します。非 Flutter ページにプッシュする場合、surfaceUpdated はトリガーされないので、この問題は発生しません。

 - (void)viewDidLayoutSubviews { ... if (firstViewBoundsUpdate && applicationIsActive && _engine) { [self surfaceUpdated:YES]; } ... } - (void)surfaceUpdated:(BOOL)appeared { if (appeared) { [self installFirstFrameCallback]; [_engine.get() platformViewsController]->SetFlutterView(_flutterView.get()); [_engine.get() platformViewsController]->SetFlutterViewController(self); [_engine.get() iosPlatformView]->NotifyCreated(); } }

最初の解決策は、viewWillAppear で sufaceUpdated を呼び出すことでしたが、リリース環境では停止してしまいます。もう 1 つの解決策は、[super bridge_viewWillAppear:animated]; を [super viewWillAppear:animated]; に変更することです。[super viewWillAppear:animated]; は親クラス メソッドを呼び出し、親クラス メソッドは sufaceUpdated を呼び出すため、白い画面の問題が解決されます。

4.2 Android マップがフリーズして操作できない

1) 問題の説明

ページ A には地図が埋め込まれており、そこからページ B にジャンプします。その後ページAに戻りますが、マップはスライドできません。

前述のように、Flutter マップ プラグインは実際には操作をネイティブ マップ ビューに渡し、MathodChannel を介して処理します。ネイティブ コードをデバッグしたところ、PlatformViewsController クラスの onTouch() メソッドで、コンテキストが null オブジェクト参照で仮想メソッド 'android.content.res.Resources android.content.Context.getResources()' を呼び出そうとしたことを報告していることがわかりました。

 public void onTouch(@NonNull PlatformViewsChannel.PlatformViewTouch touch) { final float density = context.getResources().getDisplayMetrics().density; }

2) 問題を分析する

このエラーは、コンテキスト オブジェクトがリサイクルされることによって発生します。これで、コンテキスト オブジェクトがリサイクルされる理由を分析することによってのみ、問題を見つけることができます。ソース コードを読むと、コンテキスト オブジェクトは detach() メソッドでのみリサイクルされていることがわかります。

 public void detach() { context = null; }

ログ出力と組み合わせると、確かにページ A に戻るときに attachment() メソッドが実行されますが、detach() メソッドはすぐに実行されることがわかります。ここで、ページ A の PlatformViewsController で datach() が実行される理由を調べる必要があります。

从B页面返回A页面2022-08-22 15:13:08.126 21878-21878/ctrip.flutter.demo D/PlatformViewsController: B===>detach() 2022-08-22 15:13:08.135 21878-21878/ctrip.flutter.demo D/PlatformViewsController: A====>attach() 2022-08-22 15:13:08.249 21878-21878/ctrip.flutter.demo D/PlatformViewsController: A=====>detach()

呼び出しチェーンを表示します。

ソース コードをクラスごとに読んでいくと、FlutterActivityAndFragmentDelegate の OnDetach() メソッドで、エンジンのライフ サイクルと Activity のライフ サイクルがバインドされていることがわかりました。ページが終了すると、エンジンは破壊されます。

 void onDetach() { if (host.shouldAttachEngineToActivity()) { if (host.getActivity().isChangingConfigurations()) { flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges(); } else { flutterEngine.getActivityControlSurface().detachFromActivity(); } }

3) 問題解決

shouldAttachEngineToActivity を false に設定すると、Flutter エンジンはアプリケーションのライフサイクル全体にわたって存続し、アクティビティから独立します。アクティビティが破棄されても、Flutter エンジンは破棄されません。問題は解決しました。この問題は、新しいコンテナを開いて新しい B ページを作成するために発生します。ページ B の FlutterFragment の onDetach() メソッドは、ページ A の onAttach() の後に実行されます。この問題は、純粋な Flutter プロジェクトを使用するか、新しいコンテナーを開かずに Push を使用して新しいページを開くことで回避できます。

 public boolean shouldAttachEngineToActivity() { return false; }

4.3 Android マップ メモリ オーバーフローの問題

1) 問題の説明

Android Flutter マップ ページを複数回開くと、だんだん固まっていき、最終的にはマップ全体が黒くなります。これは明らかにメモリ オーバーフローを示しています。 Android Studio IDE に付属するメモリツール Android Profiler を見ると、ページを開くたびにメモリ使用量が増加し、最終ページのメモリが解放されていないことがわかります。

2) 問題を分析する

Flutter Boost とマップ プラグインには大量のサードパーティ コードが含まれています。どうすれば問題を特定できるでしょうか?プラグインまたはフレームワークが原因ですか? LeakCanary の助けを借りれば、メモリ リークを簡単に見つけることができます。

統合も非常に簡単です。Android build.gradle に leakcanary を導入します。

 debugImplementation'com.squareup.leakcanary:leakcanary-android:2.6'

次に、アプリを実行し、LeakCanary からプロンプトが表示されるまで問題の再現プロセスを繰り返します。リークメモリオーバーフローのスタック情報を表示します。これは、SingleViewPresentation が常にコンテナ TripFlutterActivity のコンテキスト オブジェクトを保持するためです。 MapView のライフサイクルに何か問題があるのではないかと思います。破棄は実行されませんか?デバッグ後、PlatformViewsHandler ハンドラー オブジェクトは空になり、後続のプロセスは実行されません。

3) 問題解決

ソース コードを見ると、PaltformViewsController detach() メソッドのみがハンドラーを null に設定しています。

 public void detach() { if (platformViewsChannel != null) { platformViewsChannel.setPlatformViewsHandler(null); } }

デバッグ後、FlutterActivity コンテナが終了し、onDestroy() メソッドが呼び出されると、PaltformViewsController detach() が実行されています。 MapView が破棄される前にコンテナの onDestroy() が呼び出され、ハンドラー オブジェクトが空になります。

この問題を解決するアイデアは非常にシンプルです。onDestroy() でハンドラー オブジェクトを保持し、必要に応じてクリアします。 viewIdSet を使用して、View データのコピーを維持します。 creat メソッドで、disposeArgs.get("id") を呼び出し、dispose メソッドが実行された場合は、viewIdSet.remove(viewId) を削除します。 setPlatformViewsHandler が空の場合、まず、dispose を実行するビュー ハンドラーがリサイクルされていないかどうかを確認します。次のように:

 public void setPlatformViewsHandler(@Nullable PlatformViewsHandler handler) { if(handler == null && viewIdSet != null && viewIdSet.size() > 0) { needReset = true; return; } this.handler = handler; }

現在、dispose が実行され、needReset が true の場合、ハンドラーは null に設定されます。公式デモではなぜ問題がないのでしょうか?主な理由は、デフォルトでは単一のエンジンである FlutterBoost に接続したのに対し、公式デモは複数のエンジンを備えた純粋な Flutter プロジェクトであるためです。ページ終了時にはエンジンを破壊して問題をカバーしているので、メモリ回復が非常にスムーズです。

5. カスタムテキストビットマップマーカー

マーカーのカスタマイズは、マップ サービスでよく求められる要件です。マップは PlatformView を通じて実装されるため、最も簡単な方法は、マーカーに対応するスタイル ID と表示に必要なデータを Channel を通じて渡し、両端にマーカーを描画することです。このアプローチでは人件費がかさみ、スタイルに一貫性がなくなる可能性があり、Flutter フレームワークの利点が失われます。

マッププラグインは、v3.0 では画像データ情報を渡すための iconData パラメータを提供しています (v3.0 より前は、自分で実装する必要があります)。Flutter 側では、テキストと画像を描画して絵を生成し、生成された画像データをネイティブ側に渡します。この実装では、両端のコードを変更する必要はありません。描画する際、ビューのサイズは論理ピクセルではなく物理ピクセルであることに注意してください。

 Future<Uint8List?> customMark(String name, BuildContext context) async { final scale = MediaQuery.of(context).devicePixelRatio; final recorder = PictureRecorder(); final canvas = Canvas(recorder); final paint = Paint(); final textPainter = TextPainter(textDirection: TextDirection.ltr); ... final path = Path(); canvas.drawPath(path, paint); // 绘制图片final imageInfo = await UIImageLoader.imageInfoByAsset(HotelListImage.mapPoiMark); paintImage(canvas: canvas,rect: rect,image: imageInfo.image); // 生成绘制图片final image = await recorder.endRecording().toImage( width.toInt(), (textBgHeight + arrowHeight + iconHeight + 2).toInt()); final data = await image.toByteData(format: ImageByteFormat.png); return data?.buffer.asUint8List(); }

flutter 2 から flutter 3 にアップグレードする際に、小さなインシデントが発生しました。iOS デバッグ環境で toImage を呼び出すプロセスが終了してしまうのです。アップグレード後、Flutter は弱参照ポインター呼び出しのスレッド チェックを実装しました。作成と使用が同じスレッドで行われない場合、デバッグ環境でプロセスが終了します。 toImage() メソッドでは、fml::WeakPtr<SnapshotDelegate> snapshot_delegate 弱参照ポインターが使用されます。snapshot_delegate はラスター スレッドで作成されるため、通常の呼び出しもラスター スレッドで行う必要があります。PlatformView が Flutter ページに埋め込まれると、レンダリングの一貫性を確保するためにラスター スレッドがメイン スレッドとマージされ、メイン スレッドで snapshot_delegate が呼び出され、スレッド チェック終了プロセスがトリガーされますが、リリース環境には影響しません。

 class WeakPtr { T* operator->() const { CheckThreadSafety(); return get(); } } if (0 == pthread_getname_np(current_thread, actual_thread, buffer_length) && 0 == pthread_getname_np(self_, expected_thread, buffer_length)) { FML_DLOG(ERROR) << "IsCreationThreadCurrent expected thread: '" << expected_thread << "' actual thread:'" // Object被创建的线程<< actual_thread << "'"; // 实际执行线程}

6. マーカーをカスタマイズして可視範囲内に表示する

マップにマーカーを追加した後、追加されたすべてのマーカーを可視範囲内に表示することも一般的な要件です。プラグインは iOS をサポートする showmarkers メソッドを提供しますが、明らかにニーズを満たすことはできません。表示されるマップの地理的範囲を指定するために、setVisibleMapRectWithPadding の使用を検討します。このメソッドでは、visibleMapBounds パラメータを渡して、地理的範囲の北東と南西の座標を設定する必要があります。右上隅と左下隅の緯度と経度は、目に見える地理的範囲の最大と最小であるため、北東と南西の座標を取得できます。

 BMFCoordinateBounds? getMarkersVisibleMapBounds(List<BMFMarker> markers) { if (markers.isEmpty) return null; final firstPosition = markers.first.position; double maxLatitude = firstPosition.latitude; double minLatitude = firstPosition.latitude; double maxLongitude = firstPosition.longitude; double minLongitude = firstPosition.longitude; for (final marker in markers) { final lat = marker.position.latitude; final lon = marker.position.longitude; maxLatitude = max(maxLatitude, lat); minLatitude = min(minLatitude, lat); maxLongitude = max(maxLongitude, lon); minLongitude = min(minLongitude, lon); } return BMFCoordinateBounds( northeast: BMFCoordinate(maxLatitude, maxLongitude), southwest: BMFCoordinate(minLatitude, minLongitude)); }

ビジネスが進化するにつれて、大きなマップをリストに統合する必要があります。大マップと小マップの切り替えアニメーションをよりスムーズにするために、小マップをロードすると、実際にはマップサイズが大マップと同じサイズにレンダリングされ、下半分がリストでブロックされます。つまり、ミニマップは表示範囲のオフセットを設定する必要がありますが、挿入パラメータは iOS と Android で異なる方法で計算されます。iOS はポイントに基づいて計算され、Android はピクセルに基づいて計算されるため、プラットフォームを区別するために変換を実行する必要があります。

 Future<bool> setAllMarkersVisibleWithPadding( List<BMFMarker> markers, BuildContext context, { EdgeInsets insets = const EdgeInsets.all(20.0), }) async { final bounds = getMarkersVisibleMapBounds(markers); if (bounds == null) return false; if (Util.isAndroid()) { final scale = MediaQuery.of(context).devicePixelRatio; insets = EdgeInsets.only( top: insets.top * scale, bottom: insets.bottom * scale, left: insets.left * scale, right: insets.right * scale); } return await setVisibleMapRectWithPadding( visibleMapBounds: bounds, insets: insets, animated: true); }

VII. 結論

Flutter マップ プラグインは、ネイティブ マップ Android および iOS SDK に基づく二次カプセル化です。Flutter の MethodChannel インタラクションを使用して、マップの表示、インタラクション、オーバーレイ描画、イベント応答などの機能を実装します。ハイブリッド プロジェクトを Flutter マップに接続したときに発生しやすい問題は、基本的に PlatformView に集中しています。これは通常、コンテナとビュー間のイベントとライフサイクルの同期に関する問題です。

この記事では主に、FlutterBoost のハイブリッド プロジェクトと、Flutter マップ プラグインに接続する際に発生するさまざまな問題と解決策を紹介します。この記事では、PlatformView の動作原理について説明します。これにより、Flutter マップ プラグインをより深く理解できるようになります。また、Android Studio が提供するツールを使用してメモリ例外を直感的に表示する方法も紹介します。メモリ オーバーフローの検出には、leakcanary のクラスとメソッドもお勧めします。これが Flutter マップ プラグインへのアクセスに役立つことを願っています。

<<:  67トピック、11528の質問、新しい中国の大規模モデルマルチタスクベンチマークCMMLUがリリースされました

>>:  OpenAI、超知能AIの制御に関する中核的な技術的課題に取り組むため新チームを結成

ブログ    

推薦する

AIで意思決定を自動化するのは超簡単ですか?

人工知能とは何を意味するのでしょうか?人工知能はコンピュータサイエンスの範囲を指し、AI とは、設計...

...

...

...

...

F1カーがハッキングされた、人工知能技術が救世主となるのか?

それは1998年、オーストラリアF1グランプリの時のことでした。 36周目にフィンランド人ドライバー...

2021年最新Contrastive Learning(対照学習)主要会議での必読古典論文解釈

みなさんこんにちは。私はDiaobaiです。最近、対照学習が流行っているので、ICLR2020では、...

Baidu Brain EasyDL Professional Editionは、Baiduの超大規模事前学習済みモデルをリリースしました

ディープラーニングの分野では、「転移学習」という用語がますます注目を集めています。パフォーマンスが優...

...

人工知能の知られざる12の秘密

[[375984]] [51CTO.com クイック翻訳] 人工知能技術がさまざまな業界でますます使...

機械学習モデルを使用して数十億のデータポイントの性別を予測する方法

[[327734]]ユーザーポートレートに基づいた広告は、広告効果を最適化し、精密なマーケティングを...

プロフェッショナルスキルを向上させる: 10のNLPテクニックを理解して習得する

1. 感情分析感情分析とは、ツイート、製品レビュー、顧客からのフィードバックなどのテキストの背後にあ...

AI人材の確保をめぐる秘密の戦い:中国が勝利する可能性は?

[[251811]]画像ソース @Visual China人工知能の概念は、提唱されてから60年以...

...

意思決定インテリジェンス: 人工知能における新たな方向性

[[353168]]記者趙光麗最近、中国科学院自動化研究所(以下、自動化研究所)は、「妙算智慧」戦術...