干货 | Flutter 地图在携程的最佳实践

Flutter集成百度地图-Android集成 flutter Android 集成百度地图 踩坑攻略 阅读详情

作者简介

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

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

一、背景

随着各种多端技术的蓬勃发展,项目主体从纯 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 端集成

8198c3de18ce0bcefb796329b42076e3.png

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

2.2 IOS 端集成

1d30702a1fda25d9a6c6bd987e5f1513.png

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

2.3 Android 端集成

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

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

b55c1f333c1f8fcc8c87a5d9bd5b368d.png

地图插件按功能分为 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 的渲染流程。

ec5889806ba1665f7cbee8aa86eed4f7.png

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

2)Hybrid Composition 模式分析

1ac3a7de76715bc811b968cf6e6c4887.png

以 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 是如何实现帧同步?

61a79f71d74d4b12543024e475309487.jpeg

在原生开发中,我们知道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()

查看调用链:

4cbbf3f090f2f11777fad1795e15a377.png

逐个类读源码我们发现在 FlutterActivityAndFragmentDelegate的OnDetach() 方法中如果引擎的生命周期和 Activity 的生命周期是绑定的。页面结束时,引擎就会被销毁掉。

void onDetach() {
    if (host.shouldAttachEngineToActivity()) {
      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 可以很明显的看出来,每打开一次页面,内存占有都会上升,结束页面内存没有得到释放。

f9202bb3f23de4b124f7fa8e6b11c48f.png

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 对象空了,后面的流程都不会执行。

6b90771e5f491cad2025a5b8ba2447b2.png

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 计算,要区分平台做一次转换。

c1ad1bd11f29f97eeea849fb43f6a81f.png

0b93cde69181f6bc7cad4242dee3b032.png
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地图插件有一定的帮助。

【推荐阅读】

27f44fdf86eabaa5ee2b2c80306588c5.jpeg

 “携程技术”公众号

  分享,交流,成长

Flutter--高德地图使用指南 1.在高德官网申请Key 2.导入flutter包 dependencies: amap_flutter_map: ^3.0.0 3.初始化 官网的说明很混乱,经过实验。3.0.0版sdk后,初始化apikey这种操作不需要在原生代码中操作了,在你要创建地图时,应当采用如下做法: 注意:privacyStatement该参数表示已经给用户看过相关协议,权限要求等,不写拿不到数据,如果要上架必须要在执行该代码前用明显弹窗提示,具体内容可参考重要: SDK合规使用方案 | 高德地图API / 阅读详情

相关推荐

Flutter开发百度地图,踩坑无数,保姆级教程(1)

最近在用flutter开发百度地图,踩坑无数,可以说把能踩的坑都踩了一遍,特此记录下辛酸经历。官网里面把基本都把步骤写了,但是里面有很多不明确的点。刚接触到的人可能会不知道某些文件具体添加到哪里,代码添加也有可能出现问题。下面我将安装官网的例程步骤,一步一步进行配置。配置完成之后,一定是可以完美运行的。 一、引入flutter插件 二、 新建一个自定义的Application 三、混淆配置 四、AK配置(秘钥配置) 五、显示地图 六、最终效果图 遇到的其他问题 总结

o__o__o__o的博客 1万+

flutter插件中关于地图的使用flutter_map

关于flutter插件地图的使用flutter_map flutter_map A Dart implementation of Leaflet for Flutter apps. 一个基于leaflet的地图插件,也就是说flutter_map基于的是瓦片地图,那么在兼容性上可以说比较好用了。 横向对比 目前主要的地图插件主要有: flutter_map map_view``amap ...

fudong071234的博客 1万+

DeepSeek+知网研学:三步搞定文献矩阵与对比分析(实战指南)

本文提供了一套高效实用的文献管理分析实战指南,详细介绍了如何结合DeepSeek大模型与知网研学平台,通过三个核心步骤快速完成文献梳理。该方法能自动提炼文献核心要素、生成结构化对比矩阵,并辅助发现研究空白,显著提升学术研究者的文献分析效率与深度。

weixin_29061509的博客 491

使用 Flutter 制作地图应用

这里也为想要学习Flutter的朋友们准备了两份学习资料《Flutter Dart语言编程入门到精通》《Flutter实战》,从编程语言到项目实战,一条龙服务!!

bugyinyin的博客 1894

MTK pwm beeper配置记录

如果正常,系统启动后会在/dev/input下生成 一个event节点,使用方法参考标准linux使用。dts相关修改如下,全都在里面,注意一下位置。对应的配置文件中增加如下定义。

hclydao的专栏 727

MTK metadata中AF模块的移除与配置调整

本文详细介绍了在MTK平台Camera开发中如何移除metadata中的AF(自动对焦)模块。通过系统清理MTK_CONTROL_AF_REGIONS、MTK_CONTROL_MAX_REGIONS等关键配置项,并调整对焦模式及硬件特性声明,确保定制化设备在禁用自动对焦功能后的稳定运行。

q1w3e5r7t9y的博客 570

干货,Flutter 的一些常用库

字体图标生成http://fluttericon.com/ Flutter中文网https://flutterchina.club Flutter官网https://flutter.io Flutter中文开发者论坛http://flutter-dev.cn/ Flutter|Dart语言中文社区http://www.cndartlang....

Amoour的博客 2163

超声波 vs 红外 vs 激光雷达:扫地机器人避障传感器选型指南(实测对比)

本文深入对比了扫地机器人三种主流避障传感器——超声波、红外与激光雷达的核心原理与实测表现。文章基于成本、精度、环境适应性等关键指标,提供了清晰的选型决策树和典型场景配置方案,旨在帮助开发者与爱好者根据项目需求与预算,构建高效可靠的机器人避障系统。

h0i1j2k3l的博客 690

仿高德地图抽屉android源码_Flutter自定义View——仿高德三级联动Drawer

作者:吉哈达前言一直觉得高德地图的首页Drawer滑动起来很漂亮,还有一些科技感,之前用android实现了一遍,趁着最近不忙再用Flutter实现一遍。第一次看我文章的小伙伴可以关注一下我,顺便关注一下我的专栏:Android高级开发架构技术专栏,每天更新各种技术干货,分享更多最热程序员圈内事。Android高级开发架构技术专栏​zhuanlan.zhihu.com示意图为了方便区分布局结构,我...

weixin_39915820的博客 345

如何用Python打造PubMed API客户端:科研文献检索自动化实践

PubMed提供了官方的Entrez E-utilities API接口,支持程序化检索文献,但直接调用API需处理请求重试、参数封装、数据解析等问题。批量获取文献ID及结构化详情(标题、作者、DOI等)内置重试机制,应对网络波动或API限流统一数据输出格式,便于后续分析(如文献计量、AI文本处理)灵活适配不同检索需求(如指定数据库、结果数量)本文实现的PubMed API客户端,通过封装API请求、重试机制、数据解析,让科研文献检索从“手动点击”变为“代码自动化”。

ASKCOS博客 358

AI自动化时代:一人公司的崛起与商业机会

AI技术正在重塑商业格局,催生"一人公司"创业新模式。这种轻资产、高效率的商业模式通过AI自动化运营,让个人创业者能以极低成本挑战传统企业。核心机会包括:1)AI自主公司替代人力密集型业务;2)为企业提供AI转型解决方案;3)利用AI工具实现个人创业。创业者需快速验证创意,聚焦人机协作,把握当前AI发展窗口期。未来,AI赋能的"一人公司"将成为商业新常态,带来更高的专业化和灵活性。抓住AI红利,学会人机协作,是个人创业成功的关键。

这里汇聚了前沿的技术分享与实用的开发技巧,带你探索从创意到企业的技术创业之路。 518

flutter教程安装配置,androidauto百度地图

希望本文对你有所启发,有任何面试上的建议也欢迎留言分享给大家。好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。好了~如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。

2401_84407972的博客 707

Oracle EBS的帐套由“4C”构成,而华为MetaERP将其发展为“6C”

华为MetaERP从OracleEBS的"4C"架构升级为"6C",实现核算理念革新。核心突破在于将会计科目、核算维度和核算单元解耦,形成"科目表+维度库"的灵活架构。新架构优势显著:1)科目回归会计本质,管理维度独立扩展;2)支持无限维度的精细化核算;3)实现数据权限隔离和内部交易自动化;4)采用元数据驱动设计,为AI应用奠定基础。这一平台化架构解决了大型企业在全球化、多元化发展中的核算难题,相比传统架构具有更强的灵活性和扩展性。

金牌架构师 204

Flutter跨平台开发实战:从零构建鸿蒙印章店查询应用

跨平台移动开发是当前应用生态降本增效的关键路径。Flutter凭借自绘UI引擎与统一渲染机制,让一套Dart代码可同时运行于Android、iOS及鸿蒙系统,尤其在鸿蒙HarmonyOS NEXT逐步成熟的背景下,其适配可行性已成为开发者关注的高频话题。理解Flutter的渲染原理与组件通信方式,是驾驭跨端工程的基础。在实际业务场景中,诸如本地生活查询、地图展示等工具型应用,均可借助Flutter快速落地多端版本。本文以一个真实需求——全国文创印章店查询应用为例,系统拆解从环境搭建、数据层drift设计、U

weixin_31881651的博客 104

构建企业级AI税务顾问:优化税收策略与合规

本文深入探讨了构建企业级AI税务顾问的重要性及其在实际应用中的价值。通过分析当前税务领域的挑战和问题,本文详细介绍了AI税务顾问的基本原理、技术架构、核心功能以及实践应用。文章以逐步分析的方式,从背景介绍、核心概念与联系、算法原理讲解、系统分析与架构设计、项目实战到最佳实践与展望,系统性地展示了如何利用AI技术优化税收策略并确保税务合规。文章旨在为企业和税务从业者提供实用的指导,推动AI在税务领域的创新与发展。在当前全球经济一体化的背景下,企业面临着越来越复杂的税务环境。首先,税收法规的复杂性日益增加。

AI天才研究院 1153

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

2024年前端最全干货!如何在高德地图实现自动巡航效果?(1),2024年最新面试时手撕代码

移动模型,并更新NPC的位置和朝向、更新镜头的位置和朝向,这里使用了TWEEN做移动状态的控制器,它控制的是一整条路线(A-B-C-D…直接用线段连接LineA的终点和LineB的起点,会导致物体移动朝向异常,镜头转动也比较突兀,因此当两个端点距离大于某个阈值时,需要提供一个方法可以自动“焊接”两条线段。(2)更新镜头朝向,朝向其实就是矢量方向,两个点确定矢量,在这里取NPC当前坐标和后面第四个关键点的坐标确定朝向,也可以根据实际情况而定。5.随着模型移动,更新镜头的位置、以及镜头朝向的位置;

2401_84617821的博客 614

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

优快云日报190820:程序员养生指北

优快云日报来啦!给大家奉上当日最新鲜的技术干货! 热门项目|Google I / O 2019 Android应用程序 Google I / O是每年举办的一次开发者大会,有三天深入的技术内容,包括技术会议和开发人员展示其技术的数百个演示。 该项目是会议的Android应用程序。 该应用程序显示会议事件列表 - 会话,办公时间,应用程序评论,代码等等 - 并允许用户按事件类型和主题(Andro...

优快云 官方博客 2949

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

想转行当程序员的必看!一起看看这些大厂面试真题查漏补缺吧,满满干货指导

简介 首先,Android是不是真的找工作越来越难呢?这个可能是大家最关心的。这个受大的经济环境以及行业发展前景的影响,同时也和个人因素有关。 近期一方面是所在的公司招聘Java开发人员很难招到合适的,投简历的人很少;而另一方面,经常听身边的人说Android、iOS方面找工作不好找,特别是没什么经验的,经验比较少的!说是不好找,但在我家所在的吉林省省会长春,会Unity3D+Maya都可以拿到手一万+,而且是之前做Maya方面的,今年刚转Unity3D;同样是长春,c#有几年工作经验的工资也可以达到8K+

fanzhang_vip0723的博客 192

这里有份超全Android体系化进阶学习图谱,满满干货指导

开头 让我们一起来看看,字节跳动的第三面,面试官都问了什么?(第一二面的题目及答案已整理,需要的可以在文末领取) 从七月中旬开始,我前前后后差不多一共投递了八十份简历,到目前为止,我参加面试的面试有十二家(收到的面试通知) 字节跳动的面试我准备了一个多星期吧,因为比较重视。 从字节跳动面试官这些问题,可以看出,第三面他们考察的一个很重要的方向就是项目架构和性能优化。 “懒洋洋”甚至跟我吐槽:“我靠,现在大厂面试java后端都这么难的吗?” 大厂面试就是难,但是大厂的待遇是真的香!最近不是都在讨论华为的工资到

m0_56259669的博客 213

干货分享!Android开发者面试如何系统复习?分享PDF高清版

一、前言 刚拿到字节offer,经历了5次面试,其中4轮技术面,1轮HR面试。在这里分享一下自己的面试经验和学习心得。希望能够帮助更多的小伙伴。 我本科毕业于四川师范学院计算机系,刚毕业时也是小白,也是一步步成成起来的。需要提一点是,你将来是需要靠这个吃饭的,所以请对找工作保持十二分的热情,而且越早准备越好。 Android 基础 1.Activity 1、 什么是 Activity?2、 请描述一下 Activity 生命周期…… 2.Service 3.Broadcast Receiver32 4.

m0_57065039的博客 110

2026 Android中高级面试Flutter考点全解析:从渲染原理到混合工程

跨平台开发已成为移动应用的主流技术路线,Flutter凭借自绘引擎与高性能渲染方案,在Android中高级岗位中的考察比重逐年上升。理解Widget、Element、RenderObject三棵树的协作机制,掌握Dart异步模型与Isolate并发边界,是深入Flutter原理的基础。在混合工程实践中,MethodChannel、EventChannel与FFI的选型直接决定了原生与Flutter的通信效率;而状态管理方案的对比与离线数据同步架构的设计,更是区分中高级开发者的关键分水岭。性能优化与线上问题排

weixin_34122810的博客 235

开发干货

一:推荐Android15个常用的图表库,包含线性,条形柱状,饼状图,扇形,雷达,股票,折线,散点,仪表盘... https://blog.youkuaiyun.com/u014133119/article/details/80923327 HelloCharts: http://www.jcodecraeer.com/a/anzhuokaifa/andro...

笔片 450

Flutter×OpenHarmony跨端视频播放器推荐模块实战解析

跨端开发已成为移动应用降本增效的主流选择,而视频播放器作为重交互、重渲染的典型场景,对框架的底层能力与性能表现提出极高要求。Flutter凭借自绘渲染引擎与高效的列表复用机制,在动态推荐流、频繁滚动和视频封面预加载等场景中展现出明显优势。当业务需要适配OpenHarmony生态时,开发者还需面对PlatformView桥接、纹理生命周期管理、Impeller渲染引擎适配以及原生能力调用等工程难题。本文从推荐视频模块的需求拆解出发,围绕数据模型防御式设计、组件通信方案选型、列表分页与懒加载优化、内嵌播放器的三

weixin_29013129的博客 289

GitHub_Trending/pac/packages性能基准测试:插件性能对比与分析

还在为Flutter应用性能优化头疼吗?选择合适的官方插件是提升应用性能的关键一步!本文将为您深度解析Flutter官方包集合的性能表现,帮助您做出明智的技术选型。 ## 📊 性能测试概览 通过对[packages/](https://link.gitcode.com/i/2d9df65bbc8568ca42dad0d96a670940)目录下多个核心插件的分析,我们发现不同类别的插件在性能

gitblog_00983的博客 434

12年首次大改!真有人喜欢苹果的“液态玻璃”吗?至少Flutter开发者的噩梦开始了

目前加入共享计划的有:1. 清华、北大、人大、外经贸、中财、上财、复旦、交大、同济、浙大、武大、华科、中大、北航等等校友系列(排名不分先后,以时间为序,其他高校陆续开通中,欢迎一起共建共享)2. 海外名校系列(目前包括哈佛、耶鲁、斯坦福、卡内基梅隆、剑桥、牛津、帝国理工、港大、港中文、南洋理工、新国立等等,排名不分先后,以时间为序,欢迎海外海归学子一起共建共享)3. 金融人 帝都、魔都、湾区、长三角、京津冀、珠三角等地域系列。成员(3w+)覆盖国内外众多金融机构,投资机构的中高层,是覆盖最为广泛的校友群。

Java和Android架构 195

精彩回顾|I/O Extended 2022 活动干货分享

随着最后一场活动的顺利开展I/O Extended 2022 画上了圆满句号今年,共有来自 12 个城市的 GDG 社区为大家带来了丰富的 I/O Extended 活动其中包括多场久违的线下活动欢迎点击传送门,进一步了解各站活动详情厦门站、兰州站、上海站、武汉站、杭州站、深圳站、广州站、东莞站、西安站、重庆站、石家庄站、张家口站再次感谢各位嘉宾讲师、组织者们的精心筹备以...

谷歌开发者 723
上一篇: 干货 | 提速10倍+,StarRocks 指标平台在携程火车票的实践
下一篇: 干货 | 成本低误差小,携程基于 Kafka 的 Serverless 延迟队列的实践
携程技术
博客等级 码龄10年 3716粉丝 324原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值