Dart部分
String擴展一個方法
- 使用關(guān)鍵字
extension ... on為String定義一個擴展類 - 在為擴展類添加一個 新的方法
- String類型對象調(diào)用這個擴展的方法
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如何達(dá)到多繼承的效果?
Dart中使用Mixins,可以達(dá)到多繼承的效果
mixin混入有什么特點
- 作為mixins的類只能
繼承自O(shè)bject,不能繼承其他類 - 作為mixins的類
不能有構(gòu)造函數(shù) - 一個類可以mixins
多個mixins類 - mixins絕不是繼承,也不是接口,而是一種全新的特性
// 類D 繼承A和B 關(guān)鍵字 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);
}
混入相同方法的多個混入,最終會執(zhí)行哪一個?
后面的類中的方法將前面的類中相同的方法覆蓋
dart運行機制是什么樣的?
消息循環(huán)機制
- 兩個隊列,微任務(wù)隊列和事件隊列。
- microtask queue 的優(yōu)先級高于event queue。
- 在每一次事件循環(huán)中,Dart總是先去第一個microtask queue中查詢是否有可執(zhí)行的任務(wù),如果沒有,才會處理后續(xù)的event queue的流程。
如何向事件隊列插入任務(wù)?
Future就是將任務(wù)插入到事件隊列
向微任務(wù)隊列插入任務(wù)
Future.microtask()
scheduleMicrotask()
Stream中的執(zhí)行異步的模式就是scheduleMicrotask。因為microtask的優(yōu)先級又高于event。所以,如果 microtask 太多就可能會對觸摸、繪制等外部事件造成阻塞卡頓。
Future和Stream有什么區(qū)別?
- Future中的任務(wù)會加入下一輪事件循環(huán),而Stream中的任務(wù)則是加入微任務(wù)隊列
- Future 用于表示單個運算的結(jié)果,而 Stream 則表示多個結(jié)果的序列。
Stream 有同步流和異步流之分。它們的區(qū)別在于同步流會在執(zhí)行 add,addError 或 close 方法時立即向流的監(jiān)聽器 StreamSubscription 發(fā)送事件,而異步流總是在事件隊列中的代碼執(zhí)行完成后在發(fā)送事件。`
Stream訂閱模式有哪幾種?
Stream分為Single Subscription和Broadcast兩種類型, 前者只允許訂閱(listen)一次,后者允許多次訂閱。
單訂閱在訂閱者出現(xiàn)之前會持有數(shù)據(jù),在訂閱者出現(xiàn)之后就才轉(zhuǎn)交給它。
廣播訂閱可以同時有多個訂閱者,當(dāng)有數(shù)據(jù)時就會傳遞給所有的訂閱者,而不管當(dāng)前是否已有訂閱者存在。
Stream單訂閱,多次訂閱會出現(xiàn)什么結(jié)果?
會報錯,單訂閱只能有一次訂閱.
即使取消了第一個監(jiān)聽器,也不允許在單訂閱流上設(shè)置其他的監(jiān)聽器。
Stream 可以通過 transform() 方法(返回另一個 Stream)進(jìn)行連續(xù)調(diào)用。
通過 Stream.asBroadcastStream() 可以將一個單訂閱模式的 Stream 轉(zhuǎn)換成一個多訂閱模式的 Stream,isBroadcast 屬性可以判斷當(dāng)前 Stream 所處的模式。
dart是單線程還是多線程?
Dart是單線程模型。
Dart是如何實現(xiàn)多任務(wù)并行的
主要依賴dart的并發(fā)編程(Isolate)、異步和事件驅(qū)動機制
Completer
// 獲取圖片的寬高
Future<Size> getImageSize(String path) {
Completer<Size> completer = Completer<Size>();
Image image = Image.file(File.fromUri(Uri.parse(path)));
// 預(yù)先獲取圖片信息
image.image.resolve(const ImageConfiguration()).addListener(
ImageStreamListener((ImageInfo info, bool _) {
Size size = Size(info.image.width.toDouble(), info.image.height.toDouble());
completer.complete(size);
}));
return completer.future;
}
Isolate
- 使用場景:視頻的編碼轉(zhuǎn)碼,需要非常高的CPU計算
- 在dart中,一個Isolate對象其實就是一個Isolate執(zhí)行環(huán)境的引用,一般來說我們都是通過當(dāng)前的Isolate去控制其他的Isolate完成彼此之間的交互,而當(dāng)我們想要創(chuàng)建一個新的Isolate可以使用
Isolate.spawn方法獲取一個新的Isolate對象,兩個Isolate之間使用SendPort相互發(fā)送消息,而Isolate中也存在了一個與之對應(yīng)ReceivePort接收消息用來處理,但是我們需要注意的是SendPort和ReceivePort在每一個Isolate都有一對,只有同一個Isolate中的ReceivePort才能接受當(dāng)前類的SendPort發(fā)送的消息并且處理。 - Isolate可以把它理解為Dart中的線程。但它又不同于線程,更恰當(dāng)?shù)恼f應(yīng)該是微線程。它與線程最大的區(qū)別就是不能共享內(nèi)存,因此也不存在鎖競爭問題,兩個Isolate完全是兩條獨立的執(zhí)行線,且每個Isolate都有自己的事件循環(huán),它們之間只能通過發(fā)送消息通信,所以它的資源開銷低于線程。
創(chuàng)建Isolate的兩種方式:Isolate.spawn() 和 compute()
compute的使用還是有些限制,它沒有辦法多次返回結(jié)果,也沒有辦法持續(xù)性的傳值計算,每次調(diào)用,相當(dāng)于新建一個隔離,如果調(diào)用過多的話反而會適得其反。
@override
void initState() async {
super.initState();
// 主Isolate的ReceivePort
ReceivePort receivePort = ReceivePort();
SendPort? otherSendPort;
// 主Isolate接收到子Isolate中由主Isolate的SendPort發(fā)送過來的消息
receivePort.listen((message) {
if(message is SendPort){
otherSendPort = message;
otherSendPort.send(receivePort.sendPort());
}else{
// 處理消息
// ......
// 子Isolate的SendPort在主Isolate中向子Isolate發(fā)送消息
otherSendPort?.send('我是來自主Isolate的消息');
}
});
// 創(chuàng)建子Isolate
Isolate isolate = await Isolate.spawn((message) {
// message 是主Isolate的SendPort
// 在子Isolate中創(chuàng)建一個新的ReceivePort
ReceivePort recPort = ReceivePort();
// 主Isolate的SendPort
SendPort? mainSendPort;
// 運用主Isolate的SendPort將子Isolate的SendPort發(fā)送給主Isolate
message.send(recPort.sendPort);
// 子Isolate監(jiān)聽接收到主Isolate那邊發(fā)送的消息 誰發(fā)送?子Isolate的SendPort
recPort.listen((msg) {
if(msg is SendPort){
mainSendPort = msg;
}else{
// 主Isolate的SendPort向主Isolate發(fā)送消息
mainSendPort?.send('我是來自子Isolate的消息');
}
});
}, receivePort.sendPort);// 參數(shù)二 將主Isolate的SendPort傳遞給子Isolate
}
// 方式二
// 注冊主 isolate 的 SendPort
ReceivePort mainReceivePort = ReceivePort();
mainReceivePort.listen((state){
// 獲取上次執(zhí)行任務(wù)的時間戳
final lastExecutedTime = SpUtil().getStandLastTime(); // 從本地存儲或數(shù)據(jù)庫獲取
final now = DateTime.now().millisecondsSinceEpoch;
final interval = now - lastExecutedTime;
if (kDebugMode) {
print("Native called background interval: $interval");
}
// 判斷是否達(dá)到 1 小時
if (interval >= 3600000) {
// 執(zhí)行你的任務(wù)
/// 檢查步數(shù)是否在變化 沒有變化需要站立提醒
GlobalEvent().emit(GlobalName.wearStandCheck);
// 更新上次執(zhí)行時間
SpUtil().setStandLastTime(now); // 將 now 保存到本地存儲或數(shù)據(jù)庫
}
});
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系統(tǒng)限制 15分鐘執(zhí)行一次
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流中的數(shù)據(jù),然后執(zhí)行循環(huán)體中的操作。它一般用在直到stream什么時候完成,并且必須等待傳遞完成后才能使用,不然會阻塞。
Stream<String> stream = new Stream<String>.fromIterable(['1', '2', '3','4']);
main() async{
await for(String s in stream){
print(s);
}
}
使用場景:網(wǎng)絡(luò)請求流式返回
// http庫流式返回
Future<void> postStream({required String token,required String url, required Map<String, dynamic> body,required Function(String value) onMessage}) async {
try {
final request = http.Request('POST', Uri.parse(url));
request.headers['Content-Type'] = 'application/json; charset=UTF-8';
request.headers['Authorization'] = token;
request.body = jsonEncode(body);
final response = await request.send();
if (response.statusCode == 200) {
await for (var chunk in response.stream.transform(utf8.decoder)) {
onMessage(chunk);
}
} else {
throw Exception('服務(wù)器錯誤');
}
}catch (e) {
Logger.debugLog('Error: $e');
throw Exception('服務(wù)器錯誤');
}
}
// dio流式返回
Future<void> postStream({required String url, required Map<String, dynamic> body,required Function(String value) onMessage,CancelToken? cancelToken,}) async {
try {
Options requestOptions = Options(receiveTimeout: 60000,sendTimeout: 30000);
requestOptions.headers = {
..._dio!.options.headers,
'Authorization': Ability().getLoginToken(),
'Content-Type': 'application/json'
};
requestOptions.responseType = ResponseType.stream;
Response<dynamic> response = await _dio!.post(
url,
data: jsonEncode(body),
options: requestOptions,
cancelToken: cancelToken,
);
if(response.statusCode == 200){
await for (var chunk in response.data.stream) {
final jsonData = utf8.decode(chunk);
onMessage(jsonData);
}
}
} catch (e) {
// 處理請求失敗的情況
print("postStream Request failed: $e");
rethrow;
}
}
如何實現(xiàn)websocket穩(wěn)定連接?
- 定期發(fā)送心跳包,一般1秒一次
- 捕獲關(guān)閉連接事件并重連websocket
- 實現(xiàn)斷線重連
Flutter部分
A、B兩個組件在setState前修改背景顏色,是否會修改成功?
會
Flutter 渲染流程是什么?(GPU)
將dart語言的UI代碼轉(zhuǎn)換成skia能識別的數(shù)據(jù),進(jìn)行渲染。
Flutter向GPU提供視圖數(shù)據(jù)的過程。
Flutter只關(guān)心向 GPU提供視圖數(shù)據(jù),GPU的 VSync信號同步到 UI線程,UI線程使用 Dart來構(gòu)建抽象的視圖結(jié)構(gòu),這份數(shù)據(jù)結(jié)構(gòu)在 GPU線程進(jìn)行圖層合成,視圖數(shù)據(jù)提供給 Skia引擎渲染為 GPU數(shù)據(jù),這些數(shù)據(jù)通過 OpenGL或者 Vulkan提供給 GPU。


Widget、Element、RenderObject三者關(guān)系是什么?
Widget包含業(yè)務(wù)代碼,widget樹更龐大;Element是對widget的抽取,只包含build函數(shù),去除業(yè)務(wù)代碼。
簡述
widget是用于描述Element配置信息的,flutter中一切都是widget,尺寸、顏色、組件等都是widget
element是widget樹上特定位置的實例
renderobject是渲染樹上的一個對象
依賴關(guān)系:Element樹依賴Widget樹,渲染樹依賴Element樹,最終的UI樹是由獨立的Element節(jié)點構(gòu)成。
一個widget會創(chuàng)建一個element
一個element持有一個widget和render object,element
會對比widget的變化,將那寫需要更新和重建的widget,同步到render object樹,以最小的開銷來渲染
一、它們是什么?
- Widget:對一個Element配置的描述,刷新的過程中隨時會重建。(不參與真正的渲染,widget的屬性是不可以改變的,要想改變只能重新創(chuàng)建一個widget對象)
- Element:表示一個Widget樹中特定位置的實例,用于對比widget,找出需要更新和重建的widget,更新Element樹和RenderObject樹。
- RenderObject:渲染樹上的一個對象,用于界面的布局和繪制,負(fù)責(zé)真正的渲染,實例化一個 RenderObject 是非常耗能。
二、關(guān)系
- 一個Widget會創(chuàng)建一個Element對象,是通過createElement()創(chuàng)建的。
- 一個Element持有一個RenderObject和一個Widget。Element樹與Widget樹一一對應(yīng),每個Element負(fù)責(zé)管理一個Widget的配置和生命周期。
- Widget 具有不可變性,但 Element 卻是可變的。Element 樹將 Widget 樹的變化做了抽象,可以只將真正需要修改的部分同步到真實的 RenderObject 樹中,最大程度降低對真實渲染視圖的修改,提高渲染效率,而不是銷毀整個渲染視圖樹重建。
flutter生命周期,setstate會執(zhí)行哪些生命周期?


- setState()執(zhí)行后,會執(zhí)行
build() - 父Widget使用了InheritedWidget管理狀態(tài),子Widget使用了狀態(tài)數(shù)據(jù),set State時,子組件的
didChangeDependencies會調(diào)用。
https://juejin.cn/post/7348680291935862818?searchId=20240410133635350846A909709A89034C
- app的狀態(tài):AppLifecycleState
inactive:活躍可見
paused:關(guān)閉或者切換到后臺時,不可見的狀態(tài)
hidden:后臺運行狀態(tài)
resumed:切回到前臺可見狀態(tài)
detached:關(guān)閉狀態(tài)
Flutter SDK 3.13 之前的方式:
with WidgetsBindingObserver
在initState()中注冊 WidgetsBinding.instance.addObserver(this);
在dispose()移除 WidgetsBinding.instance.removeObserver(this);
在didChangeAppLifecycleState()回調(diào)中,檢測app的狀態(tài)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:可以調(diào)用多次
State 對象的生命周期方法
class _MyWidgetState extends State<MyWidget> {
@override
void initState() {
super.initState();
// 在 State 對象被插入樹中時調(diào)用,這個方法只會被調(diào)用一次。
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// 在 initState 之后調(diào)用,表示 State 對象的依賴關(guān)系發(fā)生變化。
// state對象依賴發(fā)生改變會調(diào)用
}
@override
Widget build(BuildContext context) {
// 在此構(gòu)建 Widget 樹
return Container();
}
@override
void didUpdateWidget(MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// 當(dāng)父 Widget 重建時,canUpdate 返回true時調(diào)用。
// 如果父 Widget 重建時需要重新配置子 Widget,則會調(diào)用此方法。
}
@override
void deactivate() {
// 在此處理 State 對象從樹中被移除的操作
super.deactivate();
}
@override
void dispose() {
// 當(dāng) State 對象被永久從樹中移除時調(diào)用
super.dispose();
}
}
// 打開頁面執(zhí)行:initState、didChangeDependencies
// 關(guān)閉頁面執(zhí)行:deactivate、dispose
StatelessWidget 的生命周期方法
class MyWidget extends StatefulWidget {
@override
Widget build(BuildContext context) {
return Widget;
}
}
StatelessWidget組件如何監(jiān)聽數(shù)據(jù)變化?
- context.watch<Counter>
- ChangeNotifierProvider + Consumer
Element的生命周期
initial 初始化
active 激活狀態(tài)
inactive 未激活狀態(tài)
defunct 失效狀態(tài)
如何監(jiān)聽頁面paused和resume狀態(tài)?
利用RouteObserver 在MaterialApp中注冊navigatorObservers,然后在頁面中注冊監(jiān)聽routeObserver.subscribe(this, ModalRoute.of(context)!),同時混入RouteAware,重寫didPopNext 和 didPushNext,可實現(xiàn)對頁面的監(jiān)聽。
setState()執(zhí)行做了什么事?
setState()過程主要工作是記錄所有的臟元素,會引起build函數(shù)執(zhí)行,更新widget樹、更新Element樹和RenderObject樹,最后重新渲染。
flutter中的key?
- 作用:比較兩個Widget是不是同一個Widget
- 分類:LocaleKey、GlobalKey
- LocaleKey:ValueKey、ObjectKey、UniqueKey。
statelesswidget和statefullwidget有什么區(qū)別?
StatelessWidget 沒有要管理的內(nèi)部狀態(tài).
無狀態(tài)widget的build方法通常只會在以下三種情況調(diào)用:
- 將widget插入樹中時
- 當(dāng)widget的父級更改其配置時
- 當(dāng)它依賴的InheritedWidget發(fā)生變化時
StatefullWidget是可變狀態(tài)的widget。 使用setState方法管理StatefulWidget的狀態(tài)的改變。調(diào)用setState告訴Flutter框架,某個狀態(tài)發(fā)生了變化,F(xiàn)lutter會重新運行build方法,以便應(yīng)用程序可以應(yīng)用最新狀態(tài)。
在有狀態(tài)類中編寫一個按鈕調(diào)用初始化生命周期(initState)方法,會發(fā)生什么?
報錯,但不影響布局;會報生命周期創(chuàng)建錯誤;
如何獲取控件的大小和位置?
- 使用Key拿到上下文取得findRenderObject拿內(nèi)容的尺寸數(shù)據(jù);
- 使用context取得findRenderObject拿內(nèi)容的尺寸數(shù)據(jù);
Flutter 是如何與原生Android、iOS進(jìn)行通信的?
PlatformChannel
BasicMessageChannel :用于傳遞字符串和半結(jié)構(gòu)化的信息。
MethodChannel :用于傳遞方法調(diào)用(method invocation)。
EventChannel : 用于數(shù)據(jù)流(event streams)的通信。
// 1. 創(chuàng)建java類。 實現(xiàn)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包下創(chuàng)建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狀態(tài)有哪些?
Widget的分類有三類。
- 組合類Widget,通過繼承StatelessWidget和StatefulWidget的類。
- 代理類Widget,如功能組件InheritedWidget。Theme、MediaQuery正是基于InheritedWidget實現(xiàn)的。
- 繪制類Widget,通過RenderObjectWidget實現(xiàn)的Widget,如Align、Padding、ConstrainedBox等。
Widget狀態(tài)有: StatelessWidget 和 StatefulWidget