Dart部分
String扩展一个方法
- 使用关键字
extension ... on
为String定义一个扩展类 - 在为扩展类添加一个 新的方法
- String类型对象调用这个扩展的方法
extension StringExt on String{
// 2. 扩展方法
int add(int x, int y){
return x + y;
}
}
void mian(){
String str = 'hello';
// 3. 使用扩展类方法
int result = str.add(3, 7);
debugPrint(result.toString());
}
// 单元测试
import 'package:flutter_start/extTest.dart';
import 'package:flutter_test/flutter_test.dart';
void main(){
test('StringExt', (){
String ext = 'ext';
expect(ext.add(3, 7), 10);
});
}
dart是单继承还是多继承?
单继承
dart如何达到多继承的效果?
Dart中使用Mixins
,可以达到多继承的效果
mixin混入有什么特点
- 作为mixins的类只能
继承自Object
,不能继承其他类 - 作为mixins的类
不能有构造函数
- 一个类可以mixins
多个mixins类
- mixins绝不是继承,也不是接口,而是一种全新的特性
// 类D 继承A和B 关键字 with
class D extends A with B{
}
// mixin 的使用
class A {
String info="this is A";
void printA(){
print("A");
}
void run(){
print("A Run");
}
}
class B {
void printB(){
print("B");
}
void run(){
print("B Run");
}
}
class C extends Person with B,A{
C(String name, num age) : super(name, age);
}
混入相同方法的多个混入,最终会执行哪一个?
后面的类中的方法将前面的类中相同的方法覆盖
dart运行机制是什么样的?
消息循环机制
- 两个队列,微任务队列和事件队列。
- microtask queue 的优先级高于event queue。
- 在每一次事件循环中,Dart总是先去第一个microtask queue中查询是否有可执行的任务,如果没有,才会处理后续的event queue的流程。
如何向事件队列插入任务?
Future就是将任务插入到事件队列
向微任务队列插入任务
Future.microtask()
scheduleMicrotask()
Stream
中的执行异步的模式就是scheduleMicrotask
。因为microtask的优先级又高于event。所以,如果 microtask 太多
就可能会对触摸、绘制等外部事件造成阻塞卡顿
。
Future和Stream有什么区别?
- Future中的任务会加入下一轮事件循环,而Stream中的任务则是加入微任务队列
- Future 用于表示单个运算的结果,而 Stream 则表示多个结果的序列。
Stream 有同步流和异步流
之分。它们的区别在于同步流
会在执行 add,addError 或 close 方法时立即向流的监听器 StreamSubscription 发送事件,而异步流
总是在事件队列中的代码执行完成后在发送事件。`
Stream订阅模式有哪几种?
Stream分为Single Subscription和Broadcast两种类型, 前者只允许订阅(listen)一次,后者允许多次订阅。
单订阅在订阅者出现之前会持有数据,在订阅者出现之后就才转交给它。
广播订阅可以同时有多个订阅者,当有数据时就会传递给所有的订阅者,而不管当前是否已有订阅者存在。
Stream单订阅,多次订阅会出现什么结果?
会报错,单订阅只能有一次订阅.
即使取消了第一个监听器,也不允许在单订阅流上设置其他的监听器。
Stream 可以通过 transform() 方法(返回另一个 Stream)进行连续调用。
通过 Stream.asBroadcastStream() 可以将一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream,isBroadcast 属性可以判断当前 Stream 所处的模式。
dart是单线程还是多线程?
Dart是单线程模型
。
Dart是如何实现多任务并行的
主要依赖dart的并发编程(Isolate)、异步和事件驱动机制
Isolate
- 使用场景:视频的编码转码,需要非常高的CPU计算
- 在dart中,一个Isolate对象其实就是一个Isolate执行环境的引用,一般来说我们都是通过当前的Isolate去控制其他的Isolate完成彼此之间的交互,而当我们想要创建一个新的Isolate可以使用
Isolate.spawn
方法获取一个新的Isolate对象,两个Isolate之间使用SendPort相互发送消息
,而Isolate中也存在了一个与之对应ReceivePort
接收消息用来处理,但是我们需要注意的是SendPort
和ReceivePort
在每一个Isolate都有一对,只有同一个Isolate中的ReceivePort才能接受当前类的SendPort发送的消息并且处理。 - Isolate可以把它理解为Dart中的线程。但它又不同于线程,更恰当的说应该是微线程。它与线程最大的区别就是不能共享内存,因此也不存在锁竞争问题,两个Isolate完全是两条独立的执行线,且每个Isolate都有自己的事件循环,它们之间只能通过发送消息通信,所以它的资源开销低于线程。
创建Isolate的两种方式:Isolate.spawn() 和 compute()
compute的使用还是有些限制,它没有办法多次返回结果,也没有办法持续性的传值计算,每次调用,相当于新建一个隔离,如果调用过多的话反而会适得其反。
@override
void initState() async {
super.initState();
// 主Isolate的ReceivePort
ReceivePort receivePort = ReceivePort();
SendPort? otherSendPort;
// 主Isolate接收到子Isolate中由主Isolate的SendPort发送过来的消息
receivePort.listen((message) {
if(message is SendPort){
otherSendPort = message;
otherSendPort.send(receivePort.sendPort());
}else{
// 处理消息
// ......
// 子Isolate的SendPort在主Isolate中向子Isolate发送消息
otherSendPort?.send('我是来自主Isolate的消息');
}
});
// 创建子Isolate
Isolate isolate = await Isolate.spawn((message) {
// message 是主Isolate的SendPort
// 在子Isolate中创建一个新的ReceivePort
ReceivePort recPort = ReceivePort();
// 主Isolate的SendPort
SendPort? mainSendPort;
// 运用主Isolate的SendPort将子Isolate的SendPort发送给主Isolate
message.send(recPort.sendPort);
// 子Isolate监听接收到主Isolate那边发送的消息 谁发送?子Isolate的SendPort
recPort.listen((msg) {
if(msg is SendPort){
mainSendPort = msg;
}else{
// 主Isolate的SendPort向主Isolate发送消息
mainSendPort?.send('我是来自子Isolate的消息');
}
});
}, receivePort.sendPort);// 参数二 将主Isolate的SendPort传递给子Isolate
}
// 方式二
// 注册主 isolate 的 SendPort
ReceivePort mainReceivePort = ReceivePort();
mainReceivePort.listen((state){
// 获取上次执行任务的时间戳
final lastExecutedTime = SpUtil().getStandLastTime(); // 从本地存储或数据库获取
final now = DateTime.now().millisecondsSinceEpoch;
final interval = now - lastExecutedTime;
if (kDebugMode) {
print("Native called background interval: $interval");
}
// 判断是否达到 1 小时
if (interval >= 3600000) {
// 执行你的任务
/// 检查步数是否在变化 没有变化需要站立提醒
GlobalEvent().emit(GlobalName.wearStandCheck);
// 更新上次执行时间
SpUtil().setStandLastTime(now); // 将 now 保存到本地存储或数据库
}
});
IsolateNameServer.registerPortWithName(mainReceivePort.sendPort, 'main_isolate');
Workmanager().initialize(
callbackDispatcher, // The top level function, aka callbackDispatcher
isInDebugMode: false // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
);
/// Android系统限制 15分钟执行一次
Workmanager().registerPeriodicTask(
"periodic-task-identifier",
"simplePeriodicTask",
inputData: <String, dynamic>{
'key': 'value',
},
// When no frequency is provided the default 15 minutes is set.
// Minimum frequency is 15 min. Android will automatically change your frequency to 15 min if you have configured a lower frequency.
frequency: const Duration(minutes: 15),
);
await for如何使用?
await for是用来不断获取stream流中的数据,然后执行循环体中的操作。它一般用在直到stream什么时候完成,并且必须等待传递完成后才能使用,不然会阻塞。
Stream<String> stream = new Stream<String>.fromIterable(['1', '2', '3','4']);
main() async{
await for(String s in stream){
print(s);
}
}
如何实现websocket稳定连接?
- 定期发送心跳包,一般1秒一次
- 捕获关闭连接事件并重连websocket
- 实现断线重连
Flutter部分
A、B两个组件在setState前修改背景颜色,是否会修改成功?
会
Flutter 渲染流程是什么?(GPU)
将dart语言的UI代码转换成skia能识别的数据,进行渲染。
Flutter向GPU提供视图数据的过程。
Flutter只关心向 GPU提供视图数据,GPU的 VSync信号同步到 UI线程,UI线程使用 Dart来构建抽象的视图结构,这份数据结构在 GPU线程进行图层合成,视图数据提供给 Skia引擎渲染为 GPU数据,这些数据通过 OpenGL或者 Vulkan提供给 GPU。
Widget、Element、RenderObject三者关系是什么?
Widget包含业务代码,widget树更庞大;Element是对widget的抽取,只包含build函数,去除业务代码。
简述
widget是用于描述Element配置信息的,flutter中一切都是widget,尺寸、颜色、组件等都是widget
element是widget树上特定位置的实例
renderobject是渲染树上的一个对象
依赖关系:Element树依赖Widget树,渲染树依赖Element树,最终的UI树是由独立的Element节点构成。
一个widget会创建一个element
一个element持有一个widget和render object,element
会对比widget的变化,将那写需要更新和重建的widget,同步到render object树,以最小的开销来渲染
一、它们是什么?
- Widget:对一个Element配置的描述,刷新的过程中随时会重建。(不参与真正的渲染,widget的属性是不可以改变的,要想改变只能重新创建一个widget对象)
- Element:表示一个Widget树中特定位置的实例,用于对比widget,找出需要更新和重建的widget,更新Element树和RenderObject树。
- RenderObject:渲染树上的一个对象,用于界面的布局和绘制,负责真正的渲染,实例化一个 RenderObject 是非常耗能。
二、关系
- 一个Widget会创建一个Element对象,是通过createElement()创建的。
- 一个Element持有一个RenderObject和一个Widget。Element树与Widget树一一对应,每个Element负责管理一个Widget的配置和生命周期。
- Widget 具有不可变性,但 Element 却是可变的。Element 树将 Widget 树的变化做了抽象,可以只将真正需要修改的部分同步到真实的 RenderObject 树中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。
flutter生命周期,setstate会执行哪些生命周期?
- setState()执行后,会执行
build()
- 父Widget使用了InheritedWidget管理状态,子Widget使用了状态数据,set State时,子组件的
didChangeDependencies
会调用。
https://juejin.cn/post/7348680291935862818?searchId=20240410133635350846A909709A89034C
- app的状态:AppLifecycleState
inactive
:活跃可见
paused
:关闭或者切换到后台时,不可见的状态
hidden
:后台运行状态
resumed
:切回到前台可见状态
detached
:关闭状态
Flutter SDK 3.13 之前的方式:
with WidgetsBindingObserver
在initState()
中注册 WidgetsBinding.instance.addObserver(this);
在dispose()
移除 WidgetsBinding.instance.removeObserver(this);
在didChangeAppLifecycleState()
回调中,检测app的状态Flutter SDK 3.13 之后的方式:
AppLifecycleListener
late final AppLifecycleListener _listener;
@override
void initState() {
super.initState();
// Initialize the AppLifecycleListener class and pass callbacks
_listener = AppLifecycleListener(
onStateChange: _onStateChanged,
);
}
@override
void dispose() {
// Do not forget to dispose the listener
_listener.dispose();
super.dispose();
}
// Listen to the app lifecycle state changes
void _onStateChanged(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.detached:
_onDetached();
case AppLifecycleState.resumed:
_onResumed();
case AppLifecycleState.inactive:
_onInactive();
case AppLifecycleState.hidden:
_onHidden();
case AppLifecycleState.paused:
_onPaused();
}
}
StatefulWidget 的生命周期方法
createState
:可以调用多次
State 对象的生命周期方法
class _MyWidgetState extends State<MyWidget> {
@override
void initState() {
super.initState();
// 在 State 对象被插入树中时调用,这个方法只会被调用一次。
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// 在 initState 之后调用,表示 State 对象的依赖关系发生变化。
// state对象依赖发生改变会调用
}
@override
Widget build(BuildContext context) {
// 在此构建 Widget 树
return Container();
}
@override
void didUpdateWidget(MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// 当父 Widget 重建时,canUpdate 返回true时调用。
// 如果父 Widget 重建时需要重新配置子 Widget,则会调用此方法。
}
@override
void deactivate() {
// 在此处理 State 对象从树中被移除的操作
super.deactivate();
}
@override
void dispose() {
// 当 State 对象被永久从树中移除时调用
super.dispose();
}
}
// 打开页面执行:initState、didChangeDependencies
// 关闭页面执行:deactivate、dispose
StatelessWidget 的生命周期方法
class MyWidget extends StatefulWidget {
@override
Widget build(BuildContext context) {
return Widget;
}
}
StatelessWidget组件如何监听数据变化?
- context.watch<Counter>
- ChangeNotifierProvider + Consumer
Element的生命周期
initial 初始化
active 激活状态
inactive 未激活状态
defunct 失效状态
如何监听页面paused和resume状态?
利用RouteObserver
在MaterialApp
中注册navigatorObservers
,然后在页面中注册监听routeObserver.subscribe(this, ModalRoute.of(context)!)
,同时混入RouteAware
,重写didPopNext 和 didPushNext
,可实现对页面的监听。
setState()执行做了什么事?
setState()过程主要工作是记录所有的脏元素,会引起build函数执行,更新widget树、更新Element树和RenderObject树,最后重新渲染。
flutter中的key?
- 作用:比较两个Widget是不是同一个Widget
- 分类:LocaleKey、GlobalKey
- LocaleKey:ValueKey、ObjectKey、UniqueKey。
statelesswidget和statefullwidget有什么区别?
StatelessWidget 没有要管理的内部状态.
无状态widget的build方法通常只会在以下三种情况调用:
- 将widget插入树中时
- 当widget的父级更改其配置时
- 当它依赖的InheritedWidget发生变化时
StatefullWidget是可变状态的widget。 使用setState方法管理StatefulWidget的状态的改变。调用setState告诉Flutter框架,某个状态发生了变化,Flutter会重新运行build方法,以便应用程序可以应用最新状态。
在有状态类中编写一个按钮调用初始化生命周期(initState)方法,会发生什么?
报错,但不影响布局;会报生命周期创建错误;
如何获取控件的大小和位置?
- 使用Key拿到上下文取得findRenderObject拿内容的尺寸数据;
- 使用context取得findRenderObject拿内容的尺寸数据;
Flutter 是如何与原生Android、iOS进行通信的?
PlatformChannel
BasicMessageChannel :用于传递字符串和半结构化的信息。
MethodChannel :用于传递方法调用(method invocation)。
EventChannel : 用于数据流(event streams)的通信。
// 1. 创建java类。 实现FlutterPlugin和MethodCallHandler
public class MsaOaidPlugin implements FlutterPlugin, MethodCallHandler{
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
Log.e("---------","==========onAttachedToEngine");
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "msa_oaid");
channel.setMethodCallHandler(this);
this.context = flutterPluginBinding.getApplicationContext();
System.loadLibrary("msaoaidsec");
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
Log.e("---------","==========onMethodCall");
if(call.method.equals("isSupport")){
new DemoHelper().getDeviceIds(context, new IIdentifierListener() {
@Override
public void onSupport(IdSupplier idSupplier) {
result.success(idSupplier.isSupported());
}
});
}else if(call.method.equals("getOaid")){
new DemoHelper().getDeviceIds(context, new IIdentifierListener() {
@Override
public void onSupport(IdSupplier idSupplier) {
result.success(idSupplier.getOAID());
}
});
}else{
result.notImplemented();
}
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
Log.e("---------","==========onDetachedFromEngine");
}
}
// 2. flutter lib包下创建dart类
class MsaOaid {
static const MethodChannel _channel = MethodChannel('msa_oaid');
static Future<bool> isSupport() async {
final bool support = await _channel.invokeMethod('isSupport');
return support;
}
static Future<String?> getOaid() async {
final String? oaid = await _channel.invokeMethod('getOaid');
return oaid;
}
}
Flutter中Widget的分类有哪些?Widget状态有哪些?
Widget的分类有三类。
- 组合类Widget,通过继承StatelessWidget和StatefulWidget的类。
- 代理类Widget,如功能组件InheritedWidget。Theme、MediaQuery正是基于InheritedWidget实现的。
- 绘制类Widget,通过RenderObjectWidget实现的Widget,如Align、Padding、ConstrainedBox等。
Widget状态有: StatelessWidget 和 StatefulWidget