您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

Flutter 地图在携程的最佳实践

时间:2023-07-07 12:31:51  来源:携程技术  作者:


作者简介

Leo,携程高级移动开发工程师,关注跨端技术,致力于高效、高性能开发。

Jarmon,携程高级移动开发工程师,专注 Flutter、IOS 开发。

本文将重点突出基于 flutter-boost 的混合工程,单引擎模式下接入 Flutter 地图插件遇到的问题和解决方案。

一、背景

随着各种多端技术的蓬勃发展,项目主体从纯 Native 项目,到 Native+RN,到现在的 Native+RN+Flutter。基于我们的业务都在 Flutter 技术栈上面,这要求我们需要嵌套展示地图。目前,实现嵌套展示地图的主要方案有二个:

接入官方提供的 Flutter 地图插件,主要面临的问题有:

  • 官方提供的插件成熟度不够,有一些 Native 已有的 API 在 Flutter 上不支持;
  • 目前接入 Flutter 地图插件的应用很少,我们需要去蹚雷。
  • 由于官方适配的是纯 Flutter 项目,混合工程可能遇到很多未知棘手问题。

直接在 Flutter 页面上展示 Native 的地图

  • Native 地图成熟,不会遇到很大的坑;
  • 主要问题在于业务在 Flutter上,Flutter 需要大量的和地图组件进行交互、请求数据、联动。需要通过大量的桥方法去传递操作数据;
  • 要嵌套 Native 地图需要定制容器,Android 和 IOS 上各自得实现一遍桥、容器和地图逻辑,增加了维护成本。

考虑维护成本、权衡再三我们还是选择接入 Flutter 地图插件。为了能更好的定制一些 API 和更快速的修复一些官方没有及时更新的问题。我们采用的是源码接入 Flutter 地图插件。本文将重点突出基于 flutter-boost 的混合工程,单引擎模式下接入 Flutter 地图插件遇到的问题和解决方案。

二、如何源码集成

在混合项目中集成插件主要分 flutter 和原生两侧,集成 Flutter 插件时,官方 demo 中可以直接下载到插件的源码。本文以接入 flutter 地图插件 3.3.1 版本示例。

2.1 Flutter 端集成

获取到官方 demo 后在该目录下执行 flutter pub get,然后去 flutter SDK 下找到 pub-cache 依赖缓存文件目录,根据业务需要将每个插件 src 文件下的代码导入到 flutter 工程中。

2.2 IOS 端集成

执行完 flutter pub get 后,根据需要将每个插件 iOS/Classes/ 目录下的代码导入工程中。

2.3 Android 端集成

Android 的 Native 侧的集成和 IOS 端是类似的。在 Native 工程中新建一个地图 Module。把地图 Demo 中的地图插件源码 Android 部分放入工程即可。

三、地图插件实现原理:platformView

地图插件按功能分为 Map、Search、Util 等模块,其基本实现类似,使用 MethodChannel 与 native 通信,我们以 Map 为例分析其实现。插件使用了 PlatformView 将原生地图嵌入到 flutter 页面中,在 flutter 层为 UIKitView、AndroidView,native 在生成地图后根据 viewId 初始化 BMFMapViewController,包含对应的 MethodChannel。BMFMapViewController 聚合了对地图操作,派发到不同模块调用地图 native 方法。

3.1 什么是PlatformView

PlatformView 是允许原生组件嵌入到 Flutter 页面的一种技术,能够让我们将一些原生成熟组件、flutter UI 框架难以实现的地图、WebView 等组件展示在 flutter 页面中。

Flutter 提供了 Virtual Display、Hybrid Composition 两种方式实现 PlatformView。Virtual Display 模式将 native view 加载到内存当中,随着 flutter Widget 一起渲染出来。Hybrid Composition 模式是直接将 native view 添加到 flutter view 图层上。iOS采用了 Hybrid Composition 模式,Android 采用了 Virtual Display 和 Hybrid Composition 两种模式。

3.2 PlatformView 实现原理

1)flutter 渲染流程

在介绍 Hybrid Composition 实现之前,先通过下图大致了解下 flutter 的渲染流程。

在收到 VSync 信号之后,Dart 层在 UI Thread 完成 Widget Tree、Element Tree、RenderObject Tree 三棵树的更新与生成,然后生成包含绘制信息的 layer Tree 交给 Engine 去渲染,最后在 GPU Thread 经历 Compositor、Skia 将 flutter 视图渲染出来。

2)Hybrid Composition 模式分析

以 iOS 为例逐步分析 Hybird Composition 模式执行流程。首先 Dart 层提供了 UIKitView 组件来展示 native view,didChangeDependencies 方法中通过 channel 初始化一次 native view,生成唯一标识 native view 的 viewId,并将 native view 缓存在 root_views_ 中。在实际组装 layer 层时,dart 层会传输给 engine 展示 native view 的坐标和大小,并生成一个 PlatformViewLayer,也就是说 native view 的位置、大小信息是由 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]); // 缓存
}

生成当前帧的 Layer Tree 之后,会进入到 Rasterizer 流程。首先会调用 BeginFrame 渲染一帧,触发 PlatformViewLayer::Preroll,PlatformViewLayer 标记出当前帧有 PlatformView ,然后调用 FlutterPlatformViewsController::PrerollCompositeEmbeddedView 更新 view_params_,包含 Platform View 坐标、size 等信息,最后在 SubmitFrame 方法中取出 native view 添加到 flutter view 中,完成渲染。

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 Engine 中有 platform、ui、raster、io四个线程,native view 是在 Platform Thread(主线程)渲染,而 flutter 渲染正常情况在 Raster Thread 执行的,flutter 又是如何保证帧同步的呢? 

flutter 解决帧同步是通过线程合并的方案。上图 Raster 流程 PostPrerollAction 方法中,会判断如果有 PlatformView 存在,在接下来的绘制过程中 Raster Thread 与 Platform Thread 会合并,将 Raster 队列任务放到 Platform 队列中。这样所有的渲染任务都在 Platform Thread 中执行,保证了画面的同步。

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;
}

四、问题及解决方案

4.1 IOS 页面切换 Map 组件白屏问题

在使用 flutter_boost 混合开发时,当 A 页面中使用 platformview,开启新容器跳转到 flutter B 页面,platformView 会出现短暂的白屏,从 A 页面跳转 native 页面不会出现。根据表象首先猜测是单引擎导致的。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页面在创建新容器 push 到 flutter B 页面时,首先会触发 viewDidLayoutSubviews,方法内部会修改 engine 对应的 viewController flutterView,SceneBuilder::pushTransform 是在 viewDidLayoutSubviews 之后还会触发,而 platformView 是在 native 渲染,重新渲染 A 页面时就找不到对应的 platformView,导致白屏的问题。push 到非 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,但是在 release 环境中会出现卡死的现象。另一方案是 [super bridge_viewWillAppear:animated]; 改为 [super viewWillAppear:animated];  [super viewWillAppear:animated]; 会调用父类的方法,父类方法又会调用 sufaceUpdated,就可以解决白屏的问题。

4.2 Android 地图卡死不能操作问题

1)问题描述

A 页面内嵌地图,跳转到 B 页面。然后返回 A 页面,地图就不能滑动。

结合上文提到的 Flutter 地图插件其实是通过 MathodChannel 将操作传递到 Native 的地图视图处理的。我们调试 Native 的代码发现 PlatformViewsController 类里面的 onTouch()方法中,context 报了一个Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference。

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

2)分析问题

由于 context 对象被回收,造成的报错。现在我们只有分析出来为什么 context 对象会被回收掉了就能找出问题了,读源码发现只有在 detach() 方法中才会回收 context 对象。

public void detach() {
    context = null;
  }

结合日志输出,确实发现回到 A 页面是执行了 attach() 方法,但是马上又执行了 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.shouldAttachEng.NEToActivity()) {
      if (host.getActivity().isChangingConfigurations()) {
flutterEngine.getActivityControlSurface().detachFromactivityForConfigChanges();
} else {
flutterEngine.getActivityControlSurface().detachFromActivity();
      }
    }

3)解决问题

设置 shouldAttachEngineToActivity 返回 flase 使得 Flutter 引擎将在应用程序的整个生命周期内持久化存在,并独立于 Activity,当 Activity 被销毁时,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 提示。查看 leaks 内存溢出的堆栈信息。是由于 SingleViewPresentation 一直持有了容器 TripFlutterActivity  的 context 对象。怀疑是 MapView 的生命周期有问题。是不是没有执行 dispose。调试下来的情况 PlatformViewsHandler handler 对象空了,后面的流程都不会执行。

3)解决问题

查看源码只有 PaltformViewsController detach() 方法会把 handler 设置为 null。

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

调试下来 FlutterActivity 容器结束,调用了 onDestroy() 方法的时候  PaltformViewsController detach() 就已经被执行了。容器的 onDestroy() 在 MapView 的 dispos e之前,造成了 handler 对象空了。

解决问题的思路很简单,在 onDestroy() 的时候先保留 handler 对象,然后找个时机清除一下。采用 viewIdSet 自己维护一份 View 的数据。在 creat 方法中  disposeArgs.get("id")  执行过 dispose 方法的就删除掉 viewIdSet.remove(viewId)。setPlatformViewsHandler 为空的情况判断一下,有没有执行 dispose 的 view handler  先不回收。如下:

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

目前是执行 dispose 的时候 needReset 为 true 时会将 handler 设置为 null。为什么官方的 Demo 是没有问题的呢?主要原因还是我们接入了 FlutterBoost 默认是单引擎的,官方 Demo 是的纯 Flutter 项目多引擎。页面结束,通过销毁 engine 把问题覆盖了,所以内存回收表现的很平滑。 

五、自定义文本 BitMap Marker

地图业务中自定义 marker 是比较常见的需求,由于地图是通过 PlatformView 实现的,最容易想到的做法是,通过 Channel 传入 marker 对应的样式 Id 和展示所需数据,在各端绘制 marker,这种做法会增加人工成本,样式也可能存在不一致的情况,失去了 flutter 框架的优势。

地图插件在 v3.0(v3.0 之前需要自己实现)提供了 iconData 参数传入图片 data 信息,在 flutter 侧将文本、图片绘制出来生成一张图,将生成图片 Data 传递给原生,该实现并不需要改动各端代码,绘制时要注意视图大小是物理像素点,而不是逻辑像素点。

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 debug 环境调用 toImage 进程会被终止。flutter 升级之后对弱引用指针调用做了线程检查,创建和使用不是在同一线程在 debug 环境进程会被终止。toImage() 方法内使用了 fml::WeakPtr<SnapshotDelegate> snapshot_delegate 弱引用指针,由于 snapshot_delegate 在 raster 线程中被创建,正常调用也应该是在 raster 线程,当在 flutter 页面中嵌入 PlatformView 时,为了保证渲染的一致性,会将 raster 线程与主线程合并,造成了 snapshot_delegate 在主线程调用的情况,触发了线程检查终止进程,但并不影响 release 环境。

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 << "'";  // 实际执行线程
}

六、自定义让 Marker 展示在可见范围

在地图上添加 marker 之后,将已添加的 marker 全部展示在可视范围内也是常见的需求。插件提供了支持 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));
}

随着业务的迭代,需要将大地图融合到列表中。为了将大地图与小地图切换动画更加流畅,当小地图被加载时,地图 size 实际已经渲染成和大地图同样大小,下半部分被列表遮挡。这意味小地图需要设置可见范围的偏移量,但 inserts 参数 iOS、Android 计算方式不一样,iOS 是根据 point 计算,Android 是通过 pixel 计算,要区分平台做一次转换。

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);
}

七、总结

Flutter 地图插件基于Native地图 Android 和 iOS SDK 二次封装而成,通过在 Flutter 使用MethodChannel交互实现地图的显示、交互、覆盖物绘制和事件响应等功能。混合项目接入Flutter地图容易发生问题的点,基本集中在PlatformView这一块。通常是容器和View的事件、生命周期同步问题。

本文主要介绍FlutterBoost的混合工程,在接入Flutter地图插件遇到的各种问题和解决方案。阐述了PlatformView的工作原理,方便我们更好的理解Flutter地图插件。同时也介绍了如何用Android Studio 自带的工具直观地看内存异常。并且推荐leakcanary定位内存溢出的类和方法,希望对你接入Flutter地图插件有一定的帮助。



Tags:Flutter   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  Search: Flutter  点击:(47)  评论:(0)  加入收藏
Flutter 中 onTap 事件的 5 条规则让你脱颖而出
小事情决定了你的熟练程度,这些小细节的有趣之处在于它们的丰富性。您将在代码库中的数百个位置遇到 onTap 事件。增强它们可以对代码的可维护性和最终用户体验产生重大的...【详细内容】
2023-11-04  Search: Flutter  点击:(182)  评论:(0)  加入收藏
Flutter 地图在携程的最佳实践
作者简介Leo,携程高级移动开发工程师,关注跨端技术,致力于高效、高性能开发。Jarmon,携程高级移动开发工程师,专注 Flutter、iOS 开发。本文将重点突出基于 flutter-boost 的混...【详细内容】
2023-07-07  Search: Flutter  点击:(251)  评论:(0)  加入收藏
一篇带你了解跨平台的 UI 工具包—Flutter
Flutter是Google开发的一套全新的跨平台、开源UI框架,支持iOS、Android系统开发,并且是未来新操作系统Fuchsia的默认开发套件。自从2017年5月发布第一个版本以来,目前Flutter已...【详细内容】
2023-03-29  Search: Flutter  点击:(177)  评论:(0)  加入收藏
快速掌握 Flutter 图片开发核心技能
大家好,我是 17。在 Flutter 中使用图片是最基础能力之一。17 做了精心准备,满满的都是干货!本文介绍如何在 Flutter 中使用图片,尽量详细,示例完整,包会!使用网络图片使用网络图片...【详细内容】
2023-03-06  Search: Flutter  点击:(262)  评论:(0)  加入收藏
混合开发架构|Android工程集成React Native、Flutter、ReactJs
架构设计说明该篇文章,介绍并记录在大前端混合架构开发中的重要细节和流程。通过在安卓原生工程中集成两大主流混合框架React Native、Flutter,以及ReactJs[Vue],集成三类模块m...【详细内容】
2023-03-06  Search: Flutter  点击:(212)  评论:(0)  加入收藏
跨平台开发,Flutter还是React Native?
作者 | 胥磊审校 | 孙淑娟随着移动应用的不断普及,各个公司都在寻找可以在多种设备上运行的跨平台应用解决方案,这里跨平台主要是指安卓和iOS。统计数据显示:截止2021年6月,安卓...【详细内容】
2023-02-09  Search: Flutter  点击:(152)  评论:(0)  加入收藏
基于 Flutter 构建的高性能 Web 渲染引擎 Kraken
《开源精选》是我们分享Github、Gitee等开源社区中优质项目的栏目,包括技术、学习、实用与各种有趣的内容。本期推荐的是一个阿里开源基于 Flutter 进行渲染的高性能引擎&mda...【详细内容】
2022-11-03  Search: Flutter  点击:(371)  评论:(0)  加入收藏
Flutter 焦点管理 FocusScope 组件
前言更改用户交互中的文本字段颜色。预览 当选择一个文本字段并接受输入时,它被称为具有“焦点”通常,用户通过点击将焦点转移到文本字段,开发人员通过使用本菜谱中描述的工具...【详细内容】
2022-11-02  Search: Flutter  点击:(422)  评论:(0)  加入收藏
Flutter 系列 - 环境搭建
Flutter 作为火热的跨端工具包,在 github 上超过 120k 的关注量,可见一斑。基于目前本人正在学习 Flutter 的路上,会将整个学习的过程记录下来。本博文主要讲解环境的搭建,先把...【详细内容】
2022-10-18  Search: Flutter  点击:(239)  评论:(0)  加入收藏
▌简易百科推荐
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(6)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(13)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(9)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(5)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(11)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(9)  评论:(0)  加入收藏
为什么都说 HashMap 是线程不安全的?
做Java开发的人,应该都用过 HashMap 这种集合。今天就和大家来聊聊,为什么 HashMap 是线程不安全的。1.HashMap 数据结构简单来说,HashMap 基于哈希表实现。它使用键的哈希码来...【详细内容】
2024-03-22  Java技术指北  微信公众号  Tags:HashMap   点击:(11)  评论:(0)  加入收藏
如何从头开始编写LoRA代码,这有一份教程
选自 lightning.ai作者:Sebastian Raschka机器之心编译编辑:陈萍作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大...【详细内容】
2024-03-21  机器之心Pro    Tags:LoRA   点击:(12)  评论:(0)  加入收藏
这样搭建日志中心,传统的ELK就扔了吧!
最近客户有个新需求,就是想查看网站的访问情况。由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的...【详细内容】
2024-03-20  dbaplus社群    Tags:日志   点击:(4)  评论:(0)  加入收藏
Kubernetes 究竟有没有 LTS?
从一个有趣的问题引出很多人都在关注的 Kubernetes LTS 的问题。有趣的问题2019 年,一个名为 apiserver LoopbackClient Server cert expired after 1 year[1] 的 issue 中提...【详细内容】
2024-03-15  云原生散修  微信公众号  Tags:Kubernetes   点击:(6)  评论:(0)  加入收藏
站内最新
站内热门
站内头条