[{"data":1,"prerenderedAt":7465},["ShallowReactive",2],{"notes-flutter-interview":3},{"id":4,"title":5,"body":6,"category":7453,"date":7454,"description":7455,"extension":7456,"meta":7457,"navigation":235,"order":7453,"path":7458,"seo":7459,"seoDescription":7460,"seoTitle":7461,"slug":7462,"stem":7463,"__hash__":7464},"notes\u002Fnotes\u002F2026-05-03-flutter-interview.md","Flutter 中高级工程师面试题与详解",{"type":7,"value":8,"toc":7382},"minimark",[9,12,16,93,95,99,113,120,195,267,279,281,289,293,296,335,342,401,408,433,435,443,447,514,519,614,624,648,654,673,675,686,690,696,745,751,846,851,853,865,869,876,919,926,932,974,976,980,984,988,991,999,1004,1025,1027,1031,1035,1106,1112,1114,1122,1126,1131,1216,1221,1235,1240,1248,1258,1260,1264,1268,1272,1278,1365,1370,1409,1414,1425,1427,1435,1439,1445,1526,1531,1595,1600,1634,1636,1644,1648,1654,1659,1831,1833,1837,1841,1845,1959,1964,2077,2082,2171,2173,2184,2188,2193,2198,2230,2323,2328,2342,2353,2355,2359,2363,2367,2374,2380,2385,2402,2451,2456,2499,2501,2515,2519,2571,2576,2605,2610,2731,2736,2771,2773,2784,2788,2793,2896,2898,2902,2906,2910,2915,3065,3073,3098,3103,3172,3177,3206,3211,3269,3271,3275,3279,3284,3303,3306,3317,3322,3375,3380,3432,3437,3466,3468,3472,3476,3480,3486,3541,3546,3612,3697,3702,3759,3761,3765,3769,3838,3899,3904,3915,3917,3921,3925,3929,3934,3987,3990,4001,4006,4105,4110,4232,4234,4238,4242,4246,4308,4313,4471,4476,4506,4511,4615,4617,4621,4625,4628,4710,4753,4758,4773,4775,4779,4783,4787,4792,4798,4941,4946,5121,5123,5127,5131,5136,5219,5224,5270,5275,5331,5333,5337,5341,5345,5413,5417,5473,5479,5484,5495,5497,5501,5505,5539,5544,5575,5642,5667,5673,5675,5679,5683,5687,6133,6135,6139,6143,6300,6305,6347,6362,6364,6368,6372,6639,6641,6645,6649,6713,6753,6902,6962,7008,7010,7014,7018,7023,7114,7179,7242,7275,7280,7297,7299,7303,7378],[10,11],"hr",{},[13,14,15],"h2",{"id":15},"目录",[17,18,19,27,33,39,45,51,57,63,69,75,81,87],"ol",{},[20,21,22],"li",{},[23,24,26],"a",{"href":25},"#1-dart-%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80","Dart 语言基础",[20,28,29],{},[23,30,32],{"href":31},"#2-flutter-%E6%B8%B2%E6%9F%93%E6%9C%BA%E5%88%B6","Flutter 渲染机制",[20,34,35],{},[23,36,38],{"href":37},"#3-widget--element--renderobject","Widget \u002F Element \u002F RenderObject",[20,40,41],{},[23,42,44],{"href":43},"#4-%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86","状态管理",[20,46,47],{},[23,48,50],{"href":49},"#5-%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B","异步编程",[20,52,53],{},[23,54,56],{"href":55},"#6-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96","性能优化",[20,58,59],{},[23,60,62],{"href":61},"#7-%E5%B9%B3%E5%8F%B0%E9%80%9A%E4%BF%A1platform-channel","平台通信（Platform Channel）",[20,64,65],{},[23,66,68],{"href":67},"#8-%E8%B7%AF%E7%94%B1%E4%B8%8E%E5%AF%BC%E8%88%AA","路由与导航",[20,70,71],{},[23,72,74],{"href":73},"#9-%E6%B5%8B%E8%AF%95","测试",[20,76,77],{},[23,78,80],{"href":79},"#10-%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1","架构设计",[20,82,83],{},[23,84,86],{"href":85},"#11-%E5%8C%85%E7%AE%A1%E7%90%86%E4%B8%8E%E7%BC%96%E8%AF%91","包管理与编译",[20,88,89],{},[23,90,92],{"href":91},"#12-%E5%AE%9E%E6%88%98%E5%9C%BA%E6%99%AF%E9%A2%98","实战场景题",[10,94],{},[13,96,98],{"id":97},"_1-dart-语言基础","1. Dart 语言基础",[100,101,103,104,108,109,112],"h3",{"id":102},"q11-dart-中-const-和-final-的区别是什么","Q1.1: Dart 中 ",[105,106,107],"code",{},"const"," 和 ",[105,110,111],{},"final"," 的区别是什么？",[114,115,116],"p",{},[117,118,119],"strong",{},"答：",[121,122,123,141],"table",{},[124,125,126],"thead",{},[127,128,129,133,137],"tr",{},[130,131,132],"th",{},"特性",[130,134,135],{},[105,136,111],{},[130,138,139],{},[105,140,107],{},[142,143,144,156,167,181],"tbody",{},[127,145,146,150,153],{},[147,148,149],"td",{},"赋值时机",[147,151,152],{},"运行时确定，只能赋值一次",[147,154,155],{},"编译时确定，必须是编译期常量",[127,157,158,161,164],{},[147,159,160],{},"对象可变性",[147,162,163],{},"引用不可变，对象内部状态可变",[147,165,166],{},"引用和对象都不可变（深度不可变）",[127,168,169,172,175],{},[147,170,171],{},"类成员",[147,173,174],{},"可作为实例成员",[147,176,177,178],{},"只能作为 ",[105,179,180],{},"static const",[127,182,183,186,189],{},[147,184,185],{},"构造函数",[147,187,188],{},"无特殊要求",[147,190,191,192,194],{},"需要 ",[105,193,107],{}," 构造函数",[196,197,202],"pre",{"className":198,"code":199,"language":200,"meta":201,"style":201},"language-dart shiki shiki-themes github-light github-dark","\u002F\u002F final: 运行时确定\nfinal now = DateTime.now(); \u002F\u002F OK\nfinal list = [1, 2, 3];\nlist.add(4); \u002F\u002F OK，可以修改列表内容\n\n\u002F\u002F const: 编译时确定\nconst pi = 3.14; \u002F\u002F OK\nconst now = DateTime.now(); \u002F\u002F 错误！DateTime.now() 不是编译期常量\nconst list = [1, 2, 3];\nlist.add(4); \u002F\u002F 运行时报错，不可修改\n","dart","",[105,203,204,212,218,224,230,237,243,249,255,261],{"__ignoreMap":201},[205,206,209],"span",{"class":207,"line":208},"line",1,[205,210,211],{},"\u002F\u002F final: 运行时确定\n",[205,213,215],{"class":207,"line":214},2,[205,216,217],{},"final now = DateTime.now(); \u002F\u002F OK\n",[205,219,221],{"class":207,"line":220},3,[205,222,223],{},"final list = [1, 2, 3];\n",[205,225,227],{"class":207,"line":226},4,[205,228,229],{},"list.add(4); \u002F\u002F OK，可以修改列表内容\n",[205,231,233],{"class":207,"line":232},5,[205,234,236],{"emptyLinePlaceholder":235},true,"\n",[205,238,240],{"class":207,"line":239},6,[205,241,242],{},"\u002F\u002F const: 编译时确定\n",[205,244,246],{"class":207,"line":245},7,[205,247,248],{},"const pi = 3.14; \u002F\u002F OK\n",[205,250,252],{"class":207,"line":251},8,[205,253,254],{},"const now = DateTime.now(); \u002F\u002F 错误！DateTime.now() 不是编译期常量\n",[205,256,258],{"class":207,"line":257},9,[205,259,260],{},"const list = [1, 2, 3];\n",[205,262,264],{"class":207,"line":263},10,[205,265,266],{},"list.add(4); \u002F\u002F 运行时报错，不可修改\n",[114,268,269,272,273,275,276,278],{},[117,270,271],{},"深入点："," Flutter 中大量使用 ",[105,274,107],{}," 构造函数来创建 Widget，因为 ",[105,277,107],{}," 对象在编译时就确定了，可以被复用，避免重复创建，从而提升性能。",[10,280],{},[100,282,284,285,288],{"id":283},"q12-dart-的空安全null-safety机制是怎样的late-关键字有什么作用和风险","Q1.2: Dart 的空安全（Null Safety）机制是怎样的？",[105,286,287],{},"late"," 关键字有什么作用和风险？",[114,290,291],{},[117,292,119],{},[114,294,295],{},"Dart 2.12 引入了 Sound Null Safety，类型系统区分可空和非空类型：",[196,297,299],{"className":198,"code":298,"language":200,"meta":201,"style":201},"String name = 'hello';   \u002F\u002F 非空，不能赋值 null\nString? name = null;      \u002F\u002F 可空\n\n\u002F\u002F 常用操作符\nname?.length;      \u002F\u002F 空安全调用\nname ?? 'default'; \u002F\u002F 空合并\nname!;             \u002F\u002F 强制解包（断言非空，为 null 时抛异常）\n",[105,300,301,306,311,315,320,325,330],{"__ignoreMap":201},[205,302,303],{"class":207,"line":208},[205,304,305],{},"String name = 'hello';   \u002F\u002F 非空，不能赋值 null\n",[205,307,308],{"class":207,"line":214},[205,309,310],{},"String? name = null;      \u002F\u002F 可空\n",[205,312,313],{"class":207,"line":220},[205,314,236],{"emptyLinePlaceholder":235},[205,316,317],{"class":207,"line":226},[205,318,319],{},"\u002F\u002F 常用操作符\n",[205,321,322],{"class":207,"line":232},[205,323,324],{},"name?.length;      \u002F\u002F 空安全调用\n",[205,326,327],{"class":207,"line":239},[205,328,329],{},"name ?? 'default'; \u002F\u002F 空合并\n",[205,331,332],{"class":207,"line":245},[205,333,334],{},"name!;             \u002F\u002F 强制解包（断言非空，为 null 时抛异常）\n",[114,336,337],{},[117,338,339,341],{},[105,340,287],{}," 关键字：",[196,343,345],{"className":198,"code":344,"language":200,"meta":201,"style":201},"class MyWidget {\n  \u002F\u002F 延迟初始化：告诉编译器\"我保证在使用前会赋值\"\n  late final String title;\n\n  \u002F\u002F 懒加载：首次访问时才执行初始化\n  late final db = DatabaseHelper.open();\n\n  void init(String t) {\n    title = t;\n  }\n}\n",[105,346,347,352,357,362,366,371,376,380,385,390,395],{"__ignoreMap":201},[205,348,349],{"class":207,"line":208},[205,350,351],{},"class MyWidget {\n",[205,353,354],{"class":207,"line":214},[205,355,356],{},"  \u002F\u002F 延迟初始化：告诉编译器\"我保证在使用前会赋值\"\n",[205,358,359],{"class":207,"line":220},[205,360,361],{},"  late final String title;\n",[205,363,364],{"class":207,"line":226},[205,365,236],{"emptyLinePlaceholder":235},[205,367,368],{"class":207,"line":232},[205,369,370],{},"  \u002F\u002F 懒加载：首次访问时才执行初始化\n",[205,372,373],{"class":207,"line":239},[205,374,375],{},"  late final db = DatabaseHelper.open();\n",[205,377,378],{"class":207,"line":245},[205,379,236],{"emptyLinePlaceholder":235},[205,381,382],{"class":207,"line":251},[205,383,384],{},"  void init(String t) {\n",[205,386,387],{"class":207,"line":257},[205,388,389],{},"    title = t;\n",[205,391,392],{"class":207,"line":263},[205,393,394],{},"  }\n",[205,396,398],{"class":207,"line":397},11,[205,399,400],{},"}\n",[114,402,403],{},[117,404,405,407],{},[105,406,287],{}," 的风险：",[409,410,411,420,423],"ul",{},[20,412,413,414,416,417],{},"如果在赋值前访问 ",[105,415,287],{}," 变量，会抛出 ",[105,418,419],{},"LateInitializationError",[20,421,422],{},"编译器无法在编译时捕获这个错误，相当于把空安全的保护推迟到了运行时",[20,424,425,426,428,429,432],{},"应尽量避免滥用 ",[105,427,287],{},"，可以考虑用可空类型 + ",[105,430,431],{},"??"," 替代",[10,434],{},[100,436,438,439,442],{"id":437},"q13-dart-中的-mixin-是什么和抽象类接口有什么区别","Q1.3: Dart 中的 ",[105,440,441],{},"mixin"," 是什么？和抽象类、接口有什么区别？",[114,444,445],{},[117,446,119],{},[196,448,450],{"className":198,"code":449,"language":200,"meta":201,"style":201},"\u002F\u002F Mixin：用 mixin 关键字定义，提供代码复用\nmixin Swimming {\n  void swim() => print('Swimming');\n}\n\nmixin Flying {\n  void fly() => print('Flying');\n}\n\n\u002F\u002F 使用 with 混入\nclass Duck extends Animal with Swimming, Flying {\n  \u002F\u002F Duck 同时拥有 swim() 和 fly()\n}\n",[105,451,452,457,462,467,471,475,480,485,489,493,498,503,509],{"__ignoreMap":201},[205,453,454],{"class":207,"line":208},[205,455,456],{},"\u002F\u002F Mixin：用 mixin 关键字定义，提供代码复用\n",[205,458,459],{"class":207,"line":214},[205,460,461],{},"mixin Swimming {\n",[205,463,464],{"class":207,"line":220},[205,465,466],{},"  void swim() => print('Swimming');\n",[205,468,469],{"class":207,"line":226},[205,470,400],{},[205,472,473],{"class":207,"line":232},[205,474,236],{"emptyLinePlaceholder":235},[205,476,477],{"class":207,"line":239},[205,478,479],{},"mixin Flying {\n",[205,481,482],{"class":207,"line":245},[205,483,484],{},"  void fly() => print('Flying');\n",[205,486,487],{"class":207,"line":251},[205,488,400],{},[205,490,491],{"class":207,"line":257},[205,492,236],{"emptyLinePlaceholder":235},[205,494,495],{"class":207,"line":263},[205,496,497],{},"\u002F\u002F 使用 with 混入\n",[205,499,500],{"class":207,"line":397},[205,501,502],{},"class Duck extends Animal with Swimming, Flying {\n",[205,504,506],{"class":207,"line":505},12,[205,507,508],{},"  \u002F\u002F Duck 同时拥有 swim() 和 fly()\n",[205,510,512],{"class":207,"line":511},13,[205,513,400],{},[114,515,516],{},[117,517,518],{},"三者对比：",[121,520,521,536],{},[124,522,523],{},[127,524,525,527,530,533],{},[130,526,132],{},[130,528,529],{},"抽象类 (abstract class)",[130,531,532],{},"接口 (implicit interface)",[130,534,535],{},"Mixin",[142,537,538,550,573,587,600],{},[127,539,540,543,546,548],{},[147,541,542],{},"实例化",[147,544,545],{},"不能直接实例化",[147,547,545],{},[147,549,545],{},[127,551,552,555,561,567],{},[147,553,554],{},"继承方式",[147,556,557,560],{},[105,558,559],{},"extends","（单继承）",[147,562,563,566],{},[105,564,565],{},"implements","（多实现）",[147,568,569,572],{},[105,570,571],{},"with","（多混入）",[127,574,575,577,580,582],{},[147,576,185],{},[147,578,579],{},"可以有",[147,581,579],{},[147,583,584,185],{},[117,585,586],{},"不能有",[127,588,589,592,595,598],{},[147,590,591],{},"方法实现",[147,593,594],{},"可以有默认实现",[147,596,597],{},"需要全部重写",[147,599,594],{},[127,601,602,605,608,611],{},[147,603,604],{},"限制",[147,606,607],{},"只能继承一个",[147,609,610],{},"可以实现多个",[147,612,613],{},"可以混入多个",[114,615,616,619,620,623],{},[117,617,618],{},"Mixin 的线性化（Linearization）："," 当多个 mixin 有同名方法时，",[117,621,622],{},"最后混入的优先","：",[196,625,627],{"className":198,"code":626,"language":200,"meta":201,"style":201},"mixin A { String greet() => 'A'; }\nmixin B { String greet() => 'B'; }\n\nclass C with A, B {} \u002F\u002F C().greet() 返回 'B'\n",[105,628,629,634,639,643],{"__ignoreMap":201},[205,630,631],{"class":207,"line":208},[205,632,633],{},"mixin A { String greet() => 'A'; }\n",[205,635,636],{"class":207,"line":214},[205,637,638],{},"mixin B { String greet() => 'B'; }\n",[205,640,641],{"class":207,"line":220},[205,642,236],{"emptyLinePlaceholder":235},[205,644,645],{"class":207,"line":226},[205,646,647],{},"class C with A, B {} \u002F\u002F C().greet() 返回 'B'\n",[114,649,650,653],{},[105,651,652],{},"mixin on"," 可以限制 mixin 只能用于特定类型：",[196,655,657],{"className":198,"code":656,"language":200,"meta":201,"style":201},"mixin Draggable on Widget {\n  \u002F\u002F 只能被 Widget 的子类使用\n}\n",[105,658,659,664,669],{"__ignoreMap":201},[205,660,661],{"class":207,"line":208},[205,662,663],{},"mixin Draggable on Widget {\n",[205,665,666],{"class":207,"line":214},[205,667,668],{},"  \u002F\u002F 只能被 Widget 的子类使用\n",[205,670,671],{"class":207,"line":220},[205,672,400],{},[10,674],{},[100,676,678,679,682,683],{"id":677},"q14-解释-dart-中的-extension-方法和-sealed-class","Q1.4: 解释 Dart 中的 ",[105,680,681],{},"Extension"," 方法和 ",[105,684,685],{},"sealed class",[114,687,688],{},[117,689,119],{},[114,691,692,695],{},[117,693,694],{},"Extension 方法（Dart 2.7+）："," 在不修改原始类的情况下添加方法：",[196,697,699],{"className":198,"code":698,"language":200,"meta":201,"style":201},"extension StringX on String {\n  bool get isEmail => RegExp(r'^[\\w-.]+@([\\w-]+\\.)+[\\w-]{2,4}$').hasMatch(this);\n  String capitalize() => '${this[0].toUpperCase()}${substring(1)}';\n}\n\n\u002F\u002F 使用\n'test@email.com'.isEmail; \u002F\u002F true\n'hello'.capitalize();      \u002F\u002F 'Hello'\n",[105,700,701,706,711,716,720,724,729,737],{"__ignoreMap":201},[205,702,703],{"class":207,"line":208},[205,704,705],{},"extension StringX on String {\n",[205,707,708],{"class":207,"line":214},[205,709,710],{},"  bool get isEmail => RegExp(r'^[\\w-.]+@([\\w-]+\\.)+[\\w-]{2,4}$').hasMatch(this);\n",[205,712,713],{"class":207,"line":220},[205,714,715],{},"  String capitalize() => '${this[0].toUpperCase()}${substring(1)}';\n",[205,717,718],{"class":207,"line":226},[205,719,400],{},[205,721,722],{"class":207,"line":232},[205,723,236],{"emptyLinePlaceholder":235},[205,725,726],{"class":207,"line":239},[205,727,728],{},"\u002F\u002F 使用\n",[205,730,731,734],{"class":207,"line":245},[205,732,733],{},"'test@email.com'.isEmail;",[205,735,736],{}," \u002F\u002F true\n",[205,738,739,742],{"class":207,"line":251},[205,740,741],{},"'hello'.capitalize();",[205,743,744],{},"      \u002F\u002F 'Hello'\n",[114,746,747,750],{},[117,748,749],{},"Sealed Class（Dart 3.0+）："," 限制类的继承范围，必须在同一个文件中定义子类，编译器可以做穷举检查：",[196,752,754],{"className":198,"code":753,"language":200,"meta":201,"style":201},"sealed class Shape {}\n\nclass Circle extends Shape {\n  final double radius;\n  Circle(this.radius);\n}\n\nclass Square extends Shape {\n  final double side;\n  Square(this.side);\n}\n\n\u002F\u002F switch 穷举检查（不需要 default）\ndouble area(Shape shape) => switch (shape) {\n  Circle(radius: var r) => 3.14 * r * r,\n  Square(side: var s) => s * s,\n  \u002F\u002F 如果遗漏了某个子类，编译器会报错\n};\n",[105,755,756,761,765,770,775,780,784,788,793,798,803,807,811,816,822,828,834,840],{"__ignoreMap":201},[205,757,758],{"class":207,"line":208},[205,759,760],{},"sealed class Shape {}\n",[205,762,763],{"class":207,"line":214},[205,764,236],{"emptyLinePlaceholder":235},[205,766,767],{"class":207,"line":220},[205,768,769],{},"class Circle extends Shape {\n",[205,771,772],{"class":207,"line":226},[205,773,774],{},"  final double radius;\n",[205,776,777],{"class":207,"line":232},[205,778,779],{},"  Circle(this.radius);\n",[205,781,782],{"class":207,"line":239},[205,783,400],{},[205,785,786],{"class":207,"line":245},[205,787,236],{"emptyLinePlaceholder":235},[205,789,790],{"class":207,"line":251},[205,791,792],{},"class Square extends Shape {\n",[205,794,795],{"class":207,"line":257},[205,796,797],{},"  final double side;\n",[205,799,800],{"class":207,"line":263},[205,801,802],{},"  Square(this.side);\n",[205,804,805],{"class":207,"line":397},[205,806,400],{},[205,808,809],{"class":207,"line":505},[205,810,236],{"emptyLinePlaceholder":235},[205,812,813],{"class":207,"line":511},[205,814,815],{},"\u002F\u002F switch 穷举检查（不需要 default）\n",[205,817,819],{"class":207,"line":818},14,[205,820,821],{},"double area(Shape shape) => switch (shape) {\n",[205,823,825],{"class":207,"line":824},15,[205,826,827],{},"  Circle(radius: var r) => 3.14 * r * r,\n",[205,829,831],{"class":207,"line":830},16,[205,832,833],{},"  Square(side: var s) => s * s,\n",[205,835,837],{"class":207,"line":836},17,[205,838,839],{},"  \u002F\u002F 如果遗漏了某个子类，编译器会报错\n",[205,841,843],{"class":207,"line":842},18,[205,844,845],{},"};\n",[114,847,848,850],{},[105,849,685],{}," 非常适合用于状态建模（如 BLoC 的 State）。",[10,852],{},[100,854,856,857,860,861,864],{"id":855},"q15-dart-中的泛型协变covariance是什么为什么-listcat-可以赋值给-listanimal","Q1.5: Dart 中的泛型协变（Covariance）是什么？为什么 ",[105,858,859],{},"List\u003CCat>"," 可以赋值给 ",[105,862,863],{},"List\u003CAnimal>","？",[114,866,867],{},[117,868,119],{},[114,870,871,872,875],{},"Dart 的泛型是",[117,873,874],{},"协变（covariant）的","，这与 Java（不变）不同：",[196,877,879],{"className":198,"code":878,"language":200,"meta":201,"style":201},"class Animal {}\nclass Cat extends Animal {}\n\nList\u003CCat> cats = [Cat()];\nList\u003CAnimal> animals = cats; \u002F\u002F Dart 中合法！\n\n\u002F\u002F 但这会带来运行时风险：\nanimals.add(Dog()); \u002F\u002F 运行时报错！因为底层实际是 List\u003CCat>\n",[105,880,881,886,891,895,900,905,909,914],{"__ignoreMap":201},[205,882,883],{"class":207,"line":208},[205,884,885],{},"class Animal {}\n",[205,887,888],{"class":207,"line":214},[205,889,890],{},"class Cat extends Animal {}\n",[205,892,893],{"class":207,"line":220},[205,894,236],{"emptyLinePlaceholder":235},[205,896,897],{"class":207,"line":226},[205,898,899],{},"List\u003CCat> cats = [Cat()];\n",[205,901,902],{"class":207,"line":232},[205,903,904],{},"List\u003CAnimal> animals = cats; \u002F\u002F Dart 中合法！\n",[205,906,907],{"class":207,"line":239},[205,908,236],{"emptyLinePlaceholder":235},[205,910,911],{"class":207,"line":245},[205,912,913],{},"\u002F\u002F 但这会带来运行时风险：\n",[205,915,916],{"class":207,"line":251},[205,917,918],{},"animals.add(Dog()); \u002F\u002F 运行时报错！因为底层实际是 List\u003CCat>\n",[114,920,921,922,925],{},"Dart 选择了协变，在编译时允许这种赋值，但通过运行时检查来保证类型安全。这是一种",[117,923,924],{},"实用主义的折衷","。",[114,927,928,931],{},[105,929,930],{},"covariant"," 关键字用于显式声明参数协变：",[196,933,935],{"className":198,"code":934,"language":200,"meta":201,"style":201},"class Animal {\n  void chase(covariant Animal other) {} \u002F\u002F 子类可以缩窄参数类型\n}\n\nclass Cat extends Animal {\n  @override\n  void chase(Mouse other) {} \u002F\u002F 参数从 Animal 缩窄为 Mouse\n}\n",[105,936,937,942,947,951,955,960,965,970],{"__ignoreMap":201},[205,938,939],{"class":207,"line":208},[205,940,941],{},"class Animal {\n",[205,943,944],{"class":207,"line":214},[205,945,946],{},"  void chase(covariant Animal other) {} \u002F\u002F 子类可以缩窄参数类型\n",[205,948,949],{"class":207,"line":220},[205,950,400],{},[205,952,953],{"class":207,"line":226},[205,954,236],{"emptyLinePlaceholder":235},[205,956,957],{"class":207,"line":232},[205,958,959],{},"class Cat extends Animal {\n",[205,961,962],{"class":207,"line":239},[205,963,964],{},"  @override\n",[205,966,967],{"class":207,"line":245},[205,968,969],{},"  void chase(Mouse other) {} \u002F\u002F 参数从 Animal 缩窄为 Mouse\n",[205,971,972],{"class":207,"line":251},[205,973,400],{},[10,975],{},[13,977,979],{"id":978},"_2-flutter-渲染机制","2. Flutter 渲染机制",[100,981,983],{"id":982},"q21-flutter-的渲染流水线rendering-pipeline是怎样的一帧是如何绘制到屏幕上的","Q2.1: Flutter 的渲染流水线（Rendering Pipeline）是怎样的？一帧是如何绘制到屏幕上的？",[114,985,986],{},[117,987,119],{},[114,989,990],{},"Flutter 渲染流水线的核心流程（每帧约 16.6ms @60fps）：",[196,992,997],{"className":993,"code":995,"language":996},[994],"language-text","用户输入 \u002F 动画 Ticker\n       ↓\n┌──────────────────────────────────────────────┐\n│  1. Build Phase（构建阶段）                    │\n│     - 调用 Widget.build()                     │\n│     - 生成\u002F更新 Element Tree                   │\n│     - 标记需要更新的 Element (dirty)            │\n├──────────────────────────────────────────────┤\n│  2. Layout Phase（布局阶段）                   │\n│     - RenderObject.performLayout()            │\n│     - 父节点向子节点传递 Constraints            │\n│     - 子节点向父节点返回 Size                   │\n│     - 确定每个节点的大小和位置                   │\n├──────────────────────────────────────────────┤\n│  3. Paint Phase（绘制阶段）                    │\n│     - RenderObject.paint()                    │\n│     - 绘制到 Layer Tree                        │\n│     - 生成绘制指令                              │\n├──────────────────────────────────────────────┤\n│  4. Compositing（合成阶段）                    │\n│     - Layer Tree 发送到 Engine                 │\n│     - Skia\u002FImpeller 执行光栅化                  │\n│     - GPU 渲染到屏幕                            │\n└──────────────────────────────────────────────┘\n","text",[105,998,995],{"__ignoreMap":201},[114,1000,1001],{},[117,1002,1003],{},"关键概念：",[409,1005,1006,1012,1019],{},[20,1007,1008,1011],{},[117,1009,1010],{},"Constraints go down, Sizes go up, Parent sets position","：约束从父到子传递，尺寸从子到父返回，最终由父节点决定子节点的位置",[20,1013,1014,1015,1018],{},"Flutter 使用 ",[117,1016,1017],{},"单次遍历布局算法","（O(N)），非常高效",[20,1020,1021,1024],{},[117,1022,1023],{},"Relayout Boundary","：限制重新布局的范围，避免整棵树重新计算",[10,1026],{},[100,1028,1030],{"id":1029},"q22-什么是-impeller它和-skia-有什么区别","Q2.2: 什么是 Impeller？它和 Skia 有什么区别？",[114,1032,1033],{},[117,1034,119],{},[121,1036,1037,1049],{},[124,1038,1039],{},[127,1040,1041,1043,1046],{},[130,1042,132],{},[130,1044,1045],{},"Skia",[130,1047,1048],{},"Impeller",[142,1050,1051,1062,1073,1084,1095],{},[127,1052,1053,1056,1059],{},[147,1054,1055],{},"来源",[147,1057,1058],{},"Google 通用 2D 图形库",[147,1060,1061],{},"Flutter 团队专门为 Flutter 开发",[127,1063,1064,1067,1070],{},[147,1065,1066],{},"Shader 编译",[147,1068,1069],{},"运行时编译（JIT）",[147,1071,1072],{},"构建时预编译（AOT）",[127,1074,1075,1078,1081],{},[147,1076,1077],{},"首帧卡顿（Jank）",[147,1079,1080],{},"存在 Shader compilation jank",[147,1082,1083],{},"几乎消除",[127,1085,1086,1089,1092],{},[147,1087,1088],{},"平台支持",[147,1090,1091],{},"全平台",[147,1093,1094],{},"iOS（默认），Android（已稳定）",[127,1096,1097,1100,1103],{},[147,1098,1099],{},"渲染后端",[147,1101,1102],{},"OpenGL、Vulkan、Metal",[147,1104,1105],{},"Metal（iOS）、Vulkan\u002FOpenGL（Android）",[114,1107,1108,1111],{},[117,1109,1110],{},"Impeller 解决的核心问题："," Skia 在首次使用某种绘制效果时需要运行时编译 Shader，导致掉帧（shader compilation jank）。Impeller 在构建时就预编译了所有 Shader，从根本上消除了这个问题。",[10,1113],{},[100,1115,1117,1118,1121],{"id":1116},"q23-解释-flutter-中的-repaintboundary-的作用和使用场景","Q2.3: 解释 Flutter 中的 ",[105,1119,1120],{},"RepaintBoundary"," 的作用和使用场景",[114,1123,1124],{},[117,1125,119],{},[114,1127,1128,1130],{},[105,1129,1120],{}," 将子树隔离到独立的 Layer 中，当子树需要重绘时，不会影响到父级或兄弟节点。",[196,1132,1134],{"className":198,"code":1133,"language":200,"meta":201,"style":201},"\u002F\u002F 没有 RepaintBoundary：整个区域一起重绘\nColumn(\n  children: [\n    StaticHeader(),     \u002F\u002F 每次都跟着重绘\n    AnimatedCounter(),  \u002F\u002F 频繁变化\n  ],\n)\n\n\u002F\u002F 有 RepaintBoundary：AnimatedCounter 重绘时不影响 Header\nColumn(\n  children: [\n    StaticHeader(),\n    RepaintBoundary(\n      child: AnimatedCounter(), \u002F\u002F 独立图层，独立重绘\n    ),\n  ],\n)\n",[105,1135,1136,1141,1146,1151,1156,1161,1166,1171,1175,1180,1184,1188,1193,1198,1203,1208,1212],{"__ignoreMap":201},[205,1137,1138],{"class":207,"line":208},[205,1139,1140],{},"\u002F\u002F 没有 RepaintBoundary：整个区域一起重绘\n",[205,1142,1143],{"class":207,"line":214},[205,1144,1145],{},"Column(\n",[205,1147,1148],{"class":207,"line":220},[205,1149,1150],{},"  children: [\n",[205,1152,1153],{"class":207,"line":226},[205,1154,1155],{},"    StaticHeader(),     \u002F\u002F 每次都跟着重绘\n",[205,1157,1158],{"class":207,"line":232},[205,1159,1160],{},"    AnimatedCounter(),  \u002F\u002F 频繁变化\n",[205,1162,1163],{"class":207,"line":239},[205,1164,1165],{},"  ],\n",[205,1167,1168],{"class":207,"line":245},[205,1169,1170],{},")\n",[205,1172,1173],{"class":207,"line":251},[205,1174,236],{"emptyLinePlaceholder":235},[205,1176,1177],{"class":207,"line":257},[205,1178,1179],{},"\u002F\u002F 有 RepaintBoundary：AnimatedCounter 重绘时不影响 Header\n",[205,1181,1182],{"class":207,"line":263},[205,1183,1145],{},[205,1185,1186],{"class":207,"line":397},[205,1187,1150],{},[205,1189,1190],{"class":207,"line":505},[205,1191,1192],{},"    StaticHeader(),\n",[205,1194,1195],{"class":207,"line":511},[205,1196,1197],{},"    RepaintBoundary(\n",[205,1199,1200],{"class":207,"line":818},[205,1201,1202],{},"      child: AnimatedCounter(), \u002F\u002F 独立图层，独立重绘\n",[205,1204,1205],{"class":207,"line":824},[205,1206,1207],{},"    ),\n",[205,1209,1210],{"class":207,"line":830},[205,1211,1165],{},[205,1213,1214],{"class":207,"line":836},[205,1215,1170],{},[114,1217,1218],{},[117,1219,1220],{},"适合使用的场景：",[409,1222,1223,1226,1229],{},[20,1224,1225],{},"频繁重绘的动画组件（如进度条、计数器）",[20,1227,1228],{},"复杂但静态的子树（如复杂地图、图表的静态部分）",[20,1230,1231,1234],{},[105,1232,1233],{},"CustomPaint"," 中复杂的绑定逻辑",[114,1236,1237],{},[117,1238,1239],{},"不适合使用的场景：",[409,1241,1242,1245],{},[20,1243,1244],{},"子树本身就很简单，创建额外 Layer 的开销反而更大",[20,1246,1247],{},"子树和父级总是一起变化",[114,1249,1250,1253,1254,1257],{},[117,1251,1252],{},"调试工具："," 可以使用 ",[105,1255,1256],{},"debugRepaintRainbowEnabled = true"," 来可视化重绘区域。",[10,1259],{},[13,1261,1263],{"id":1262},"_3-widget-element-renderobject","3. Widget \u002F Element \u002F RenderObject",[100,1265,1267],{"id":1266},"q31-详解-widgetelementrenderobject-三棵树的关系和职责","Q3.1: 详解 Widget、Element、RenderObject 三棵树的关系和职责",[114,1269,1270],{},[117,1271,119],{},[196,1273,1276],{"className":1274,"code":1275,"language":996},[994],"Widget Tree          Element Tree           RenderObject Tree\n(配置\u002F蓝图)          (实例\u002F生命周期)         (布局\u002F绘制)\n\nContainer ──────► ComponentElement\n  │                    │\n  ├─ Padding ────► SingleChildRenderObjectElement ──► RenderPadding\n  │                    │\n  └─ Text ──────► LeafRenderObjectElement ──────────► RenderParagraph\n",[105,1277,1275],{"__ignoreMap":201},[121,1279,1280,1295],{},[124,1281,1282],{},[127,1283,1284,1286,1289,1292],{},[130,1285],{},[130,1287,1288],{},"Widget",[130,1290,1291],{},"Element",[130,1293,1294],{},"RenderObject",[142,1296,1297,1313,1329,1349],{},[127,1298,1299,1304,1307,1310],{},[147,1300,1301],{},[117,1302,1303],{},"职责",[147,1305,1306],{},"描述 UI 配置（不可变）",[147,1308,1309],{},"管理生命周期、持有状态",[147,1311,1312],{},"实际布局和绘制",[127,1314,1315,1320,1323,1326],{},[147,1316,1317],{},[117,1318,1319],{},"可变性",[147,1321,1322],{},"不可变（immutable）",[147,1324,1325],{},"可变、长寿命",[147,1327,1328],{},"可变",[127,1330,1331,1336,1343,1346],{},[147,1332,1333],{},[117,1334,1335],{},"创建频率",[147,1337,1338,1339,1342],{},"每次 ",[105,1340,1341],{},"build()"," 都可能重建",[147,1344,1345],{},"尽量复用（通过 canUpdate）",[147,1347,1348],{},"跟随 Element 复用",[127,1350,1351,1356,1359,1362],{},[147,1352,1353],{},[117,1354,1355],{},"类比",[147,1357,1358],{},"HTML 模板",[147,1360,1361],{},"虚拟 DOM 节点",[147,1363,1364],{},"浏览器 DOM 节点",[114,1366,1367],{},[117,1368,1369],{},"核心流程：",[17,1371,1372,1379,1382,1406],{},[20,1373,1374,1375,1378],{},"Widget 是 ",[117,1376,1377],{},"轻量的配置对象","，描述\"我想要什么\"",[20,1380,1381],{},"Flutter 框架根据 Widget 创建或更新 Element",[20,1383,1384,1385,1388,1389],{},"Element 通过 ",[105,1386,1387],{},"Widget.canUpdate(oldWidget, newWidget)"," 判断是否复用：\n",[409,1390,1391,1403],{},[20,1392,1393,108,1396,1399,1400],{},[105,1394,1395],{},"runtimeType",[105,1397,1398],{},"key"," 都相同 → 复用 Element，调用 ",[105,1401,1402],{},"update()",[20,1404,1405],{},"不同 → 销毁旧 Element，创建新的",[20,1407,1408],{},"RenderObjectElement 持有对应的 RenderObject，负责实际的布局和绘制",[114,1410,1411],{},[117,1412,1413],{},"为什么这样设计？",[409,1415,1416,1419,1422],{},[20,1417,1418],{},"Widget 不可变 + 频繁重建 → 声明式 UI 简洁易用",[20,1420,1421],{},"Element 复用 → 保持状态、避免不必要的重建",[20,1423,1424],{},"RenderObject 分离 → 只在真正需要时才重新布局和绘制",[10,1426],{},[100,1428,1430,1431,1434],{"id":1429},"q32-key-的作用是什么什么时候必须使用-key","Q3.2: ",[105,1432,1433],{},"Key"," 的作用是什么？什么时候必须使用 Key？",[114,1436,1437],{},[117,1438,119],{},[114,1440,1441,1442,1444],{},"Key 影响 Element 的复用策略。没有 Key 时，Flutter 只根据 ",[105,1443,1395],{}," 和在列表中的位置来匹配：",[196,1446,1448],{"className":198,"code":1447,"language":200,"meta":201,"style":201},"\u002F\u002F 问题场景：交换两个带状态的 Widget\n\u002F\u002F 没有 Key 时，Element 不会交换，只是更新配置 → 状态错乱\nColumn(\n  children: [\n    TodoTile(todo: todos[0]), \u002F\u002F Element 0 保持原位\n    TodoTile(todo: todos[1]), \u002F\u002F Element 1 保持原位\n    \u002F\u002F 交换 todos 后，Element 复用了，但状态（勾选状态）没跟着走\n  ],\n)\n\n\u002F\u002F 加 Key 后，Flutter 能正确匹配并移动 Element\nColumn(\n  children: [\n    TodoTile(key: ValueKey(todos[0].id), todo: todos[0]),\n    TodoTile(key: ValueKey(todos[1].id), todo: todos[1]),\n  ],\n)\n",[105,1449,1450,1455,1460,1464,1468,1473,1478,1483,1487,1491,1495,1500,1504,1508,1513,1518,1522],{"__ignoreMap":201},[205,1451,1452],{"class":207,"line":208},[205,1453,1454],{},"\u002F\u002F 问题场景：交换两个带状态的 Widget\n",[205,1456,1457],{"class":207,"line":214},[205,1458,1459],{},"\u002F\u002F 没有 Key 时，Element 不会交换，只是更新配置 → 状态错乱\n",[205,1461,1462],{"class":207,"line":220},[205,1463,1145],{},[205,1465,1466],{"class":207,"line":226},[205,1467,1150],{},[205,1469,1470],{"class":207,"line":232},[205,1471,1472],{},"    TodoTile(todo: todos[0]), \u002F\u002F Element 0 保持原位\n",[205,1474,1475],{"class":207,"line":239},[205,1476,1477],{},"    TodoTile(todo: todos[1]), \u002F\u002F Element 1 保持原位\n",[205,1479,1480],{"class":207,"line":245},[205,1481,1482],{},"    \u002F\u002F 交换 todos 后，Element 复用了，但状态（勾选状态）没跟着走\n",[205,1484,1485],{"class":207,"line":251},[205,1486,1165],{},[205,1488,1489],{"class":207,"line":257},[205,1490,1170],{},[205,1492,1493],{"class":207,"line":263},[205,1494,236],{"emptyLinePlaceholder":235},[205,1496,1497],{"class":207,"line":397},[205,1498,1499],{},"\u002F\u002F 加 Key 后，Flutter 能正确匹配并移动 Element\n",[205,1501,1502],{"class":207,"line":505},[205,1503,1145],{},[205,1505,1506],{"class":207,"line":511},[205,1507,1150],{},[205,1509,1510],{"class":207,"line":818},[205,1511,1512],{},"    TodoTile(key: ValueKey(todos[0].id), todo: todos[0]),\n",[205,1514,1515],{"class":207,"line":824},[205,1516,1517],{},"    TodoTile(key: ValueKey(todos[1].id), todo: todos[1]),\n",[205,1519,1520],{"class":207,"line":830},[205,1521,1165],{},[205,1523,1524],{"class":207,"line":836},[205,1525,1170],{},[114,1527,1528],{},[117,1529,1530],{},"Key 的类型：",[121,1532,1533,1543],{},[124,1534,1535],{},[127,1536,1537,1540],{},[130,1538,1539],{},"Key 类型",[130,1541,1542],{},"用途",[142,1544,1545,1555,1565,1575,1585],{},[127,1546,1547,1552],{},[147,1548,1549],{},[105,1550,1551],{},"ValueKey",[147,1553,1554],{},"基于值（如 id）匹配",[127,1556,1557,1562],{},[147,1558,1559],{},[105,1560,1561],{},"ObjectKey",[147,1563,1564],{},"基于对象引用匹配",[127,1566,1567,1572],{},[147,1568,1569],{},[105,1570,1571],{},"UniqueKey",[147,1573,1574],{},"强制不复用（每次创建新 Element）",[127,1576,1577,1582],{},[147,1578,1579],{},[105,1580,1581],{},"GlobalKey",[147,1583,1584],{},"跨树访问 Element\u002FState，可跨父节点移动",[127,1586,1587,1592],{},[147,1588,1589],{},[105,1590,1591],{},"PageStorageKey",[147,1593,1594],{},"保存页面滚动位置等",[114,1596,1597],{},[117,1598,1599],{},"必须使用 Key 的场景：",[17,1601,1602,1616,1621,1629],{},[20,1603,1604,1607,1608,1611,1612,1615],{},[117,1605,1606],{},"列表项会增删或重排","（如 ",[105,1609,1610],{},"ListView"," + ",[105,1613,1614],{},"reorder","）",[20,1617,1618],{},[117,1619,1620],{},"同类型有状态 Widget 的顺序会变化",[20,1622,1623,1626,1627,1615],{},[117,1624,1625],{},"需要在不同位置保持同一个 Widget 的状态","（",[105,1628,1581],{},[20,1630,1631],{},[117,1632,1633],{},"AnimatedSwitcher 等需要识别新旧子 Widget 的动画组件",[10,1635],{},[100,1637,1639,1640,1643],{"id":1638},"q33-statefulwidget-的完整生命周期是怎样的","Q3.3: ",[105,1641,1642],{},"StatefulWidget"," 的完整生命周期是怎样的？",[114,1645,1646],{},[117,1647,119],{},[196,1649,1652],{"className":1650,"code":1651,"language":996},[994],"createState()\n     ↓\ninitState()          ← 只调用一次，初始化状态\n     ↓\ndidChangeDependencies()  ← InheritedWidget 变化时也会调用\n     ↓\nbuild()              ← 返回 Widget 树\n     ↓\n ┌── didUpdateWidget()  ← 父 Widget 重建且 canUpdate 返回 true 时\n │        ↓\n └── build()\n     ↓\ndeactivate()         ← Element 从树中移除（可能是临时的）\n     ↓\ndispose()            ← 永久移除，释放资源\n",[105,1653,1651],{"__ignoreMap":201},[114,1655,1656],{},[117,1657,1658],{},"各阶段注意事项：",[196,1660,1662],{"className":198,"code":1661,"language":200,"meta":201,"style":201},"class _MyWidgetState extends State\u003CMyWidget> {\n  late final AnimationController _controller;\n\n  @override\n  void initState() {\n    super.initState(); \u002F\u002F 必须调用 super\n    _controller = AnimationController(vsync: this);\n    \u002F\u002F 不能在这里访问 InheritedWidget（context 还没准备好）\n    \u002F\u002F 不能在这里调用 setState()\n  }\n\n  @override\n  void didChangeDependencies() {\n    super.didChangeDependencies();\n    \u002F\u002F 可以安全地访问 InheritedWidget\n    final theme = Theme.of(context);\n  }\n\n  @override\n  void didUpdateWidget(covariant MyWidget oldWidget) {\n    super.didUpdateWidget(oldWidget);\n    \u002F\u002F 父 Widget 重建时调用，可以对比新旧 widget 的属性\n    if (widget.id != oldWidget.id) {\n      _fetchData();\n    }\n  }\n\n  @override\n  void dispose() {\n    _controller.dispose(); \u002F\u002F 释放资源，避免内存泄漏\n    super.dispose(); \u002F\u002F 必须调用 super\n  }\n}\n",[105,1663,1664,1669,1674,1678,1682,1687,1692,1697,1702,1707,1711,1715,1719,1724,1729,1734,1739,1743,1747,1752,1758,1764,1770,1776,1782,1788,1793,1798,1803,1809,1815,1821,1826],{"__ignoreMap":201},[205,1665,1666],{"class":207,"line":208},[205,1667,1668],{},"class _MyWidgetState extends State\u003CMyWidget> {\n",[205,1670,1671],{"class":207,"line":214},[205,1672,1673],{},"  late final AnimationController _controller;\n",[205,1675,1676],{"class":207,"line":220},[205,1677,236],{"emptyLinePlaceholder":235},[205,1679,1680],{"class":207,"line":226},[205,1681,964],{},[205,1683,1684],{"class":207,"line":232},[205,1685,1686],{},"  void initState() {\n",[205,1688,1689],{"class":207,"line":239},[205,1690,1691],{},"    super.initState(); \u002F\u002F 必须调用 super\n",[205,1693,1694],{"class":207,"line":245},[205,1695,1696],{},"    _controller = AnimationController(vsync: this);\n",[205,1698,1699],{"class":207,"line":251},[205,1700,1701],{},"    \u002F\u002F 不能在这里访问 InheritedWidget（context 还没准备好）\n",[205,1703,1704],{"class":207,"line":257},[205,1705,1706],{},"    \u002F\u002F 不能在这里调用 setState()\n",[205,1708,1709],{"class":207,"line":263},[205,1710,394],{},[205,1712,1713],{"class":207,"line":397},[205,1714,236],{"emptyLinePlaceholder":235},[205,1716,1717],{"class":207,"line":505},[205,1718,964],{},[205,1720,1721],{"class":207,"line":511},[205,1722,1723],{},"  void didChangeDependencies() {\n",[205,1725,1726],{"class":207,"line":818},[205,1727,1728],{},"    super.didChangeDependencies();\n",[205,1730,1731],{"class":207,"line":824},[205,1732,1733],{},"    \u002F\u002F 可以安全地访问 InheritedWidget\n",[205,1735,1736],{"class":207,"line":830},[205,1737,1738],{},"    final theme = Theme.of(context);\n",[205,1740,1741],{"class":207,"line":836},[205,1742,394],{},[205,1744,1745],{"class":207,"line":842},[205,1746,236],{"emptyLinePlaceholder":235},[205,1748,1750],{"class":207,"line":1749},19,[205,1751,964],{},[205,1753,1755],{"class":207,"line":1754},20,[205,1756,1757],{},"  void didUpdateWidget(covariant MyWidget oldWidget) {\n",[205,1759,1761],{"class":207,"line":1760},21,[205,1762,1763],{},"    super.didUpdateWidget(oldWidget);\n",[205,1765,1767],{"class":207,"line":1766},22,[205,1768,1769],{},"    \u002F\u002F 父 Widget 重建时调用，可以对比新旧 widget 的属性\n",[205,1771,1773],{"class":207,"line":1772},23,[205,1774,1775],{},"    if (widget.id != oldWidget.id) {\n",[205,1777,1779],{"class":207,"line":1778},24,[205,1780,1781],{},"      _fetchData();\n",[205,1783,1785],{"class":207,"line":1784},25,[205,1786,1787],{},"    }\n",[205,1789,1791],{"class":207,"line":1790},26,[205,1792,394],{},[205,1794,1796],{"class":207,"line":1795},27,[205,1797,236],{"emptyLinePlaceholder":235},[205,1799,1801],{"class":207,"line":1800},28,[205,1802,964],{},[205,1804,1806],{"class":207,"line":1805},29,[205,1807,1808],{},"  void dispose() {\n",[205,1810,1812],{"class":207,"line":1811},30,[205,1813,1814],{},"    _controller.dispose(); \u002F\u002F 释放资源，避免内存泄漏\n",[205,1816,1818],{"class":207,"line":1817},31,[205,1819,1820],{},"    super.dispose(); \u002F\u002F 必须调用 super\n",[205,1822,1824],{"class":207,"line":1823},32,[205,1825,394],{},[205,1827,1829],{"class":207,"line":1828},33,[205,1830,400],{},[10,1832],{},[13,1834,1836],{"id":1835},"_4-状态管理","4. 状态管理",[100,1838,1840],{"id":1839},"q41-对比-flutter-常用的状态管理方案","Q4.1: 对比 Flutter 常用的状态管理方案",[114,1842,1843],{},[117,1844,119],{},[121,1846,1847,1863],{},[124,1848,1849],{},[127,1850,1851,1854,1857,1860],{},[130,1852,1853],{},"方案",[130,1855,1856],{},"核心理念",[130,1858,1859],{},"复杂度",[130,1861,1862],{},"适用场景",[142,1864,1865,1881,1897,1913,1928,1944],{},[127,1866,1867,1872,1875,1878],{},[147,1868,1869],{},[105,1870,1871],{},"setState",[147,1873,1874],{},"局部状态直接修改",[147,1876,1877],{},"低",[147,1879,1880],{},"单个 Widget 内部状态",[127,1882,1883,1888,1891,1894],{},[147,1884,1885],{},[105,1886,1887],{},"InheritedWidget",[147,1889,1890],{},"沿树向下传递数据",[147,1892,1893],{},"中",[147,1895,1896],{},"框架基础，其他方案的底层",[127,1898,1899,1904,1907,1910],{},[147,1900,1901],{},[105,1902,1903],{},"Provider",[147,1905,1906],{},"InheritedWidget 的封装",[147,1908,1909],{},"低-中",[147,1911,1912],{},"中小项目，官方推荐入门",[127,1914,1915,1920,1923,1925],{},[147,1916,1917],{},[105,1918,1919],{},"Riverpod",[147,1921,1922],{},"编译安全、无 context 依赖",[147,1924,1893],{},[147,1926,1927],{},"中大型项目",[127,1929,1930,1935,1938,1941],{},[147,1931,1932],{},[105,1933,1934],{},"BLoC",[147,1936,1937],{},"事件驱动、流式响应",[147,1939,1940],{},"高",[147,1942,1943],{},"大型项目、团队协作",[127,1945,1946,1951,1954,1956],{},[147,1947,1948],{},[105,1949,1950],{},"GetX",[147,1952,1953],{},"极简 API、响应式",[147,1955,1877],{},[147,1957,1958],{},"快速开发（但争议较大）",[114,1960,1961],{},[117,1962,1963],{},"BLoC 模式详解：",[196,1965,1967],{"className":198,"code":1966,"language":200,"meta":201,"style":201},"\u002F\u002F Event\nsealed class CounterEvent {}\nclass Increment extends CounterEvent {}\nclass Decrement extends CounterEvent {}\n\n\u002F\u002F State\nclass CounterState {\n  final int count;\n  const CounterState(this.count);\n}\n\n\u002F\u002F BLoC\nclass CounterBloc extends Bloc\u003CCounterEvent, CounterState> {\n  CounterBloc() : super(const CounterState(0)) {\n    on\u003CIncrement>((event, emit) => emit(CounterState(state.count + 1)));\n    on\u003CDecrement>((event, emit) => emit(CounterState(state.count - 1)));\n  }\n}\n\n\u002F\u002F UI\nBlocBuilder\u003CCounterBloc, CounterState>(\n  builder: (context, state) => Text('${state.count}'),\n)\n",[105,1968,1969,1974,1979,1984,1989,1993,1998,2003,2008,2013,2017,2021,2026,2031,2036,2041,2046,2050,2054,2058,2063,2068,2073],{"__ignoreMap":201},[205,1970,1971],{"class":207,"line":208},[205,1972,1973],{},"\u002F\u002F Event\n",[205,1975,1976],{"class":207,"line":214},[205,1977,1978],{},"sealed class CounterEvent {}\n",[205,1980,1981],{"class":207,"line":220},[205,1982,1983],{},"class Increment extends CounterEvent {}\n",[205,1985,1986],{"class":207,"line":226},[205,1987,1988],{},"class Decrement extends CounterEvent {}\n",[205,1990,1991],{"class":207,"line":232},[205,1992,236],{"emptyLinePlaceholder":235},[205,1994,1995],{"class":207,"line":239},[205,1996,1997],{},"\u002F\u002F State\n",[205,1999,2000],{"class":207,"line":245},[205,2001,2002],{},"class CounterState {\n",[205,2004,2005],{"class":207,"line":251},[205,2006,2007],{},"  final int count;\n",[205,2009,2010],{"class":207,"line":257},[205,2011,2012],{},"  const CounterState(this.count);\n",[205,2014,2015],{"class":207,"line":263},[205,2016,400],{},[205,2018,2019],{"class":207,"line":397},[205,2020,236],{"emptyLinePlaceholder":235},[205,2022,2023],{"class":207,"line":505},[205,2024,2025],{},"\u002F\u002F BLoC\n",[205,2027,2028],{"class":207,"line":511},[205,2029,2030],{},"class CounterBloc extends Bloc\u003CCounterEvent, CounterState> {\n",[205,2032,2033],{"class":207,"line":818},[205,2034,2035],{},"  CounterBloc() : super(const CounterState(0)) {\n",[205,2037,2038],{"class":207,"line":824},[205,2039,2040],{},"    on\u003CIncrement>((event, emit) => emit(CounterState(state.count + 1)));\n",[205,2042,2043],{"class":207,"line":830},[205,2044,2045],{},"    on\u003CDecrement>((event, emit) => emit(CounterState(state.count - 1)));\n",[205,2047,2048],{"class":207,"line":836},[205,2049,394],{},[205,2051,2052],{"class":207,"line":842},[205,2053,400],{},[205,2055,2056],{"class":207,"line":1749},[205,2057,236],{"emptyLinePlaceholder":235},[205,2059,2060],{"class":207,"line":1754},[205,2061,2062],{},"\u002F\u002F UI\n",[205,2064,2065],{"class":207,"line":1760},[205,2066,2067],{},"BlocBuilder\u003CCounterBloc, CounterState>(\n",[205,2069,2070],{"class":207,"line":1766},[205,2071,2072],{},"  builder: (context, state) => Text('${state.count}'),\n",[205,2074,2075],{"class":207,"line":1772},[205,2076,1170],{},[114,2078,2079],{},[117,2080,2081],{},"Riverpod 示例：",[196,2083,2085],{"className":198,"code":2084,"language":200,"meta":201,"style":201},"\u002F\u002F 定义 Provider（全局，但编译安全）\nfinal counterProvider = StateNotifierProvider\u003CCounterNotifier, int>((ref) {\n  return CounterNotifier();\n});\n\nclass CounterNotifier extends StateNotifier\u003Cint> {\n  CounterNotifier() : super(0);\n  void increment() => state++;\n}\n\n\u002F\u002F UI（不需要 context）\nclass MyWidget extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final count = ref.watch(counterProvider);\n    return Text('$count');\n  }\n}\n",[105,2086,2087,2092,2097,2102,2107,2111,2116,2121,2126,2130,2134,2139,2144,2148,2153,2158,2163,2167],{"__ignoreMap":201},[205,2088,2089],{"class":207,"line":208},[205,2090,2091],{},"\u002F\u002F 定义 Provider（全局，但编译安全）\n",[205,2093,2094],{"class":207,"line":214},[205,2095,2096],{},"final counterProvider = StateNotifierProvider\u003CCounterNotifier, int>((ref) {\n",[205,2098,2099],{"class":207,"line":220},[205,2100,2101],{},"  return CounterNotifier();\n",[205,2103,2104],{"class":207,"line":226},[205,2105,2106],{},"});\n",[205,2108,2109],{"class":207,"line":232},[205,2110,236],{"emptyLinePlaceholder":235},[205,2112,2113],{"class":207,"line":239},[205,2114,2115],{},"class CounterNotifier extends StateNotifier\u003Cint> {\n",[205,2117,2118],{"class":207,"line":245},[205,2119,2120],{},"  CounterNotifier() : super(0);\n",[205,2122,2123],{"class":207,"line":251},[205,2124,2125],{},"  void increment() => state++;\n",[205,2127,2128],{"class":207,"line":257},[205,2129,400],{},[205,2131,2132],{"class":207,"line":263},[205,2133,236],{"emptyLinePlaceholder":235},[205,2135,2136],{"class":207,"line":397},[205,2137,2138],{},"\u002F\u002F UI（不需要 context）\n",[205,2140,2141],{"class":207,"line":505},[205,2142,2143],{},"class MyWidget extends ConsumerWidget {\n",[205,2145,2146],{"class":207,"line":511},[205,2147,964],{},[205,2149,2150],{"class":207,"line":818},[205,2151,2152],{},"  Widget build(BuildContext context, WidgetRef ref) {\n",[205,2154,2155],{"class":207,"line":824},[205,2156,2157],{},"    final count = ref.watch(counterProvider);\n",[205,2159,2160],{"class":207,"line":830},[205,2161,2162],{},"    return Text('$count');\n",[205,2164,2165],{"class":207,"line":836},[205,2166,394],{},[205,2168,2169],{"class":207,"line":842},[205,2170,400],{},[10,2172],{},[100,2174,2176,2177,2179,2180,2183],{"id":2175},"q42-inheritedwidget-是如何工作的为什么-themeofcontext-能获取到主题数据","Q4.2: ",[105,2178,1887],{}," 是如何工作的？为什么 ",[105,2181,2182],{},"Theme.of(context)"," 能获取到主题数据？",[114,2185,2186],{},[117,2187,119],{},[114,2189,2190,2192],{},[105,2191,1887],{}," 是 Flutter 中沿 Widget 树向下传递数据的机制。",[114,2194,2195],{},[117,2196,2197],{},"工作原理：",[17,2199,2200,2205,2223],{},[20,2201,2202,2204],{},[105,2203,1887],{}," 存储在 Element 树中",[20,2206,2207,2208,2211,2212],{},"当子 Widget 调用 ",[105,2209,2210],{},"context.dependOnInheritedWidgetOfExactType\u003CT>()"," 时：\n",[409,2213,2214,2217],{},[20,2215,2216],{},"沿 Element 树向上查找最近的 T 类型的 InheritedElement",[20,2218,2219,2220],{},"将当前 Element ",[117,2221,2222],{},"注册为依赖者",[20,2224,2225,2226,2229],{},"当 InheritedWidget 更新时，所有注册的依赖者都会收到通知（触发 ",[105,2227,2228],{},"didChangeDependencies()","，然后 rebuild）",[196,2231,2233],{"className":198,"code":2232,"language":200,"meta":201,"style":201},"class MyTheme extends InheritedWidget {\n  final Color primaryColor;\n\n  const MyTheme({\n    required this.primaryColor,\n    required super.child,\n  });\n\n  \u002F\u002F 便捷方法\n  static MyTheme of(BuildContext context) {\n    return context.dependOnInheritedWidgetOfExactType\u003CMyTheme>()!;\n  }\n\n  @override\n  bool updateShouldNotify(MyTheme oldWidget) {\n    return primaryColor != oldWidget.primaryColor;\n    \u002F\u002F 返回 false 则不通知依赖者，即使自身重建了\n  }\n}\n",[105,2234,2235,2240,2245,2249,2254,2259,2264,2269,2273,2278,2283,2288,2292,2296,2300,2305,2310,2315,2319],{"__ignoreMap":201},[205,2236,2237],{"class":207,"line":208},[205,2238,2239],{},"class MyTheme extends InheritedWidget {\n",[205,2241,2242],{"class":207,"line":214},[205,2243,2244],{},"  final Color primaryColor;\n",[205,2246,2247],{"class":207,"line":220},[205,2248,236],{"emptyLinePlaceholder":235},[205,2250,2251],{"class":207,"line":226},[205,2252,2253],{},"  const MyTheme({\n",[205,2255,2256],{"class":207,"line":232},[205,2257,2258],{},"    required this.primaryColor,\n",[205,2260,2261],{"class":207,"line":239},[205,2262,2263],{},"    required super.child,\n",[205,2265,2266],{"class":207,"line":245},[205,2267,2268],{},"  });\n",[205,2270,2271],{"class":207,"line":251},[205,2272,236],{"emptyLinePlaceholder":235},[205,2274,2275],{"class":207,"line":257},[205,2276,2277],{},"  \u002F\u002F 便捷方法\n",[205,2279,2280],{"class":207,"line":263},[205,2281,2282],{},"  static MyTheme of(BuildContext context) {\n",[205,2284,2285],{"class":207,"line":397},[205,2286,2287],{},"    return context.dependOnInheritedWidgetOfExactType\u003CMyTheme>()!;\n",[205,2289,2290],{"class":207,"line":505},[205,2291,394],{},[205,2293,2294],{"class":207,"line":511},[205,2295,236],{"emptyLinePlaceholder":235},[205,2297,2298],{"class":207,"line":818},[205,2299,964],{},[205,2301,2302],{"class":207,"line":824},[205,2303,2304],{},"  bool updateShouldNotify(MyTheme oldWidget) {\n",[205,2306,2307],{"class":207,"line":830},[205,2308,2309],{},"    return primaryColor != oldWidget.primaryColor;\n",[205,2311,2312],{"class":207,"line":836},[205,2313,2314],{},"    \u002F\u002F 返回 false 则不通知依赖者，即使自身重建了\n",[205,2316,2317],{"class":207,"line":842},[205,2318,394],{},[205,2320,2321],{"class":207,"line":1749},[205,2322,400],{},[114,2324,2325],{},[117,2326,2327],{},"关键区别：",[409,2329,2330,2336],{},[20,2331,2332,2335],{},[105,2333,2334],{},"dependOnInheritedWidgetOfExactType","：注册依赖，会自动重建",[20,2337,2338,2341],{},[105,2339,2340],{},"getInheritedWidgetOfExactType","：只读取，不注册依赖，不会自动重建",[114,2343,2344,2346,2347,2349,2350,2352],{},[105,2345,2182],{}," 内部就是调用了 ",[105,2348,2334],{},"，所以当 Theme 变化时，所有使用了 ",[105,2351,2182],{}," 的 Widget 都会自动重建。",[10,2354],{},[13,2356,2358],{"id":2357},"_5-异步编程","5. 异步编程",[100,2360,2362],{"id":2361},"q51-dart-是单线程的那它是如何处理异步操作的","Q5.1: Dart 是单线程的，那它是如何处理异步操作的？",[114,2364,2365],{},[117,2366,119],{},[114,2368,2369,2370,2373],{},"Dart 使用 ",[117,2371,2372],{},"事件循环（Event Loop）"," 机制，类似 JavaScript：",[196,2375,2378],{"className":2376,"code":2377,"language":996},[994],"┌─────────────────────────────────────────┐\n│             Event Loop                   │\n│                                         │\n│  ┌──────────────────────────────┐       │\n│  │  Microtask Queue（微任务队列）│       │\n│  │  - Future.then() 回调        │ ← 优先 │\n│  │  - scheduleMicrotask()       │       │\n│  └──────────────────────────────┘       │\n│                 ↓                        │\n│  ┌──────────────────────────────┐       │\n│  │  Event Queue（事件队列）      │       │\n│  │  - I\u002FO 完成回调              │       │\n│  │  - Timer 回调                │       │\n│  │  - UI 事件（点击、滑动）      │       │\n│  └──────────────────────────────┘       │\n└─────────────────────────────────────────┘\n",[105,2379,2377],{"__ignoreMap":201},[114,2381,2382],{},[117,2383,2384],{},"执行顺序：",[17,2386,2387,2390,2393,2396,2399],{},[20,2388,2389],{},"执行同步代码直到完成",[20,2391,2392],{},"清空所有 Microtask Queue",[20,2394,2395],{},"从 Event Queue 取出一个事件执行",[20,2397,2398],{},"再次清空 Microtask Queue",[20,2400,2401],{},"重复 3-4",[196,2403,2405],{"className":198,"code":2404,"language":200,"meta":201,"style":201},"void main() {\n  print('1'); \u002F\u002F 同步\n  Future(() => print('2'));             \u002F\u002F Event Queue\n  Future.microtask(() => print('3'));   \u002F\u002F Microtask Queue\n  Future(() => print('4'));             \u002F\u002F Event Queue\n  Future.microtask(() => print('5'));   \u002F\u002F Microtask Queue\n  print('6'); \u002F\u002F 同步\n}\n\u002F\u002F 输出顺序：1, 6, 3, 5, 2, 4\n",[105,2406,2407,2412,2417,2422,2427,2432,2437,2442,2446],{"__ignoreMap":201},[205,2408,2409],{"class":207,"line":208},[205,2410,2411],{},"void main() {\n",[205,2413,2414],{"class":207,"line":214},[205,2415,2416],{},"  print('1'); \u002F\u002F 同步\n",[205,2418,2419],{"class":207,"line":220},[205,2420,2421],{},"  Future(() => print('2'));             \u002F\u002F Event Queue\n",[205,2423,2424],{"class":207,"line":226},[205,2425,2426],{},"  Future.microtask(() => print('3'));   \u002F\u002F Microtask Queue\n",[205,2428,2429],{"class":207,"line":232},[205,2430,2431],{},"  Future(() => print('4'));             \u002F\u002F Event Queue\n",[205,2433,2434],{"class":207,"line":239},[205,2435,2436],{},"  Future.microtask(() => print('5'));   \u002F\u002F Microtask Queue\n",[205,2438,2439],{"class":207,"line":245},[205,2440,2441],{},"  print('6'); \u002F\u002F 同步\n",[205,2443,2444],{"class":207,"line":251},[205,2445,400],{},[205,2447,2448],{"class":207,"line":257},[205,2449,2450],{},"\u002F\u002F 输出顺序：1, 6, 3, 5, 2, 4\n",[114,2452,2453],{},[117,2454,2455],{},"真正的并行 → Isolate：",[196,2457,2459],{"className":198,"code":2458,"language":200,"meta":201,"style":201},"\u002F\u002F Isolate：独立内存空间，通过消息传递通信\nfinal result = await Isolate.run(() {\n  \u002F\u002F 在独立 Isolate 中执行 CPU 密集型任务\n  return heavyComputation();\n});\n\n\u002F\u002F compute() 是 Flutter 提供的便捷方法\nfinal result = await compute(parseJson, rawData);\n",[105,2460,2461,2466,2471,2476,2481,2485,2489,2494],{"__ignoreMap":201},[205,2462,2463],{"class":207,"line":208},[205,2464,2465],{},"\u002F\u002F Isolate：独立内存空间，通过消息传递通信\n",[205,2467,2468],{"class":207,"line":214},[205,2469,2470],{},"final result = await Isolate.run(() {\n",[205,2472,2473],{"class":207,"line":220},[205,2474,2475],{},"  \u002F\u002F 在独立 Isolate 中执行 CPU 密集型任务\n",[205,2477,2478],{"class":207,"line":226},[205,2479,2480],{},"  return heavyComputation();\n",[205,2482,2483],{"class":207,"line":232},[205,2484,2106],{},[205,2486,2487],{"class":207,"line":239},[205,2488,236],{"emptyLinePlaceholder":235},[205,2490,2491],{"class":207,"line":245},[205,2492,2493],{},"\u002F\u002F compute() 是 Flutter 提供的便捷方法\n",[205,2495,2496],{"class":207,"line":251},[205,2497,2498],{},"final result = await compute(parseJson, rawData);\n",[10,2500],{},[100,2502,2504,2505,108,2508,112,2511,2514],{"id":2503},"q52-future-和-stream-的区别是什么streamcontroller-怎么用","Q5.2: ",[105,2506,2507],{},"Future",[105,2509,2510],{},"Stream",[105,2512,2513],{},"StreamController"," 怎么用？",[114,2516,2517],{},[117,2518,119],{},[121,2520,2521,2531],{},[124,2522,2523],{},[127,2524,2525,2527,2529],{},[130,2526],{},[130,2528,2507],{},[130,2530,2510],{},[142,2532,2533,2544,2560],{},[127,2534,2535,2538,2541],{},[147,2536,2537],{},"值的数量",[147,2539,2540],{},"单个异步值",[147,2542,2543],{},"多个异步值序列",[127,2545,2546,2548,2554],{},[147,2547,1355],{},[147,2549,2550,2553],{},[105,2551,2552],{},"Promise"," (JS)",[147,2555,2556,2559],{},[105,2557,2558],{},"Observable"," (RxJS)",[127,2561,2562,2565,2568],{},[147,2563,2564],{},"完成",[147,2566,2567],{},"一次性完成或失败",[147,2569,2570],{},"可持续发送数据，直到关闭",[114,2572,2573],{},[117,2574,2575],{},"Stream 的两种类型：",[196,2577,2579],{"className":198,"code":2578,"language":200,"meta":201,"style":201},"\u002F\u002F 1. 单订阅流（Single-subscription）：只能 listen 一次\nfinal controller = StreamController\u003Cint>();\n\n\u002F\u002F 2. 广播流（Broadcast）：可以多次 listen\nfinal controller = StreamController\u003Cint>.broadcast();\n",[105,2580,2581,2586,2591,2595,2600],{"__ignoreMap":201},[205,2582,2583],{"class":207,"line":208},[205,2584,2585],{},"\u002F\u002F 1. 单订阅流（Single-subscription）：只能 listen 一次\n",[205,2587,2588],{"class":207,"line":214},[205,2589,2590],{},"final controller = StreamController\u003Cint>();\n",[205,2592,2593],{"class":207,"line":220},[205,2594,236],{"emptyLinePlaceholder":235},[205,2596,2597],{"class":207,"line":226},[205,2598,2599],{},"\u002F\u002F 2. 广播流（Broadcast）：可以多次 listen\n",[205,2601,2602],{"class":207,"line":232},[205,2603,2604],{},"final controller = StreamController\u003Cint>.broadcast();\n",[114,2606,2607],{},[117,2608,2609],{},"StreamController 使用：",[196,2611,2613],{"className":198,"code":2612,"language":200,"meta":201,"style":201},"class CounterService {\n  final _controller = StreamController\u003Cint>.broadcast();\n  int _count = 0;\n\n  Stream\u003Cint> get countStream => _controller.stream;\n\n  void increment() {\n    _count++;\n    _controller.sink.add(_count);  \u002F\u002F 发送数据\n  }\n\n  void dispose() {\n    _controller.close(); \u002F\u002F 必须关闭，否则内存泄漏\n  }\n}\n\n\u002F\u002F 在 Widget 中使用\nStreamBuilder\u003Cint>(\n  stream: counterService.countStream,\n  initialData: 0,\n  builder: (context, snapshot) {\n    if (snapshot.hasError) return Text('Error: ${snapshot.error}');\n    return Text('Count: ${snapshot.data}');\n  },\n)\n",[105,2614,2615,2620,2625,2630,2634,2639,2643,2648,2653,2658,2662,2666,2670,2675,2679,2683,2687,2692,2697,2702,2707,2712,2717,2722,2727],{"__ignoreMap":201},[205,2616,2617],{"class":207,"line":208},[205,2618,2619],{},"class CounterService {\n",[205,2621,2622],{"class":207,"line":214},[205,2623,2624],{},"  final _controller = StreamController\u003Cint>.broadcast();\n",[205,2626,2627],{"class":207,"line":220},[205,2628,2629],{},"  int _count = 0;\n",[205,2631,2632],{"class":207,"line":226},[205,2633,236],{"emptyLinePlaceholder":235},[205,2635,2636],{"class":207,"line":232},[205,2637,2638],{},"  Stream\u003Cint> get countStream => _controller.stream;\n",[205,2640,2641],{"class":207,"line":239},[205,2642,236],{"emptyLinePlaceholder":235},[205,2644,2645],{"class":207,"line":245},[205,2646,2647],{},"  void increment() {\n",[205,2649,2650],{"class":207,"line":251},[205,2651,2652],{},"    _count++;\n",[205,2654,2655],{"class":207,"line":257},[205,2656,2657],{},"    _controller.sink.add(_count);  \u002F\u002F 发送数据\n",[205,2659,2660],{"class":207,"line":263},[205,2661,394],{},[205,2663,2664],{"class":207,"line":397},[205,2665,236],{"emptyLinePlaceholder":235},[205,2667,2668],{"class":207,"line":505},[205,2669,1808],{},[205,2671,2672],{"class":207,"line":511},[205,2673,2674],{},"    _controller.close(); \u002F\u002F 必须关闭，否则内存泄漏\n",[205,2676,2677],{"class":207,"line":818},[205,2678,394],{},[205,2680,2681],{"class":207,"line":824},[205,2682,400],{},[205,2684,2685],{"class":207,"line":830},[205,2686,236],{"emptyLinePlaceholder":235},[205,2688,2689],{"class":207,"line":836},[205,2690,2691],{},"\u002F\u002F 在 Widget 中使用\n",[205,2693,2694],{"class":207,"line":842},[205,2695,2696],{},"StreamBuilder\u003Cint>(\n",[205,2698,2699],{"class":207,"line":1749},[205,2700,2701],{},"  stream: counterService.countStream,\n",[205,2703,2704],{"class":207,"line":1754},[205,2705,2706],{},"  initialData: 0,\n",[205,2708,2709],{"class":207,"line":1760},[205,2710,2711],{},"  builder: (context, snapshot) {\n",[205,2713,2714],{"class":207,"line":1766},[205,2715,2716],{},"    if (snapshot.hasError) return Text('Error: ${snapshot.error}');\n",[205,2718,2719],{"class":207,"line":1772},[205,2720,2721],{},"    return Text('Count: ${snapshot.data}');\n",[205,2723,2724],{"class":207,"line":1778},[205,2725,2726],{},"  },\n",[205,2728,2729],{"class":207,"line":1784},[205,2730,1170],{},[114,2732,2733],{},[117,2734,2735],{},"Stream 常用变换操作：",[196,2737,2739],{"className":198,"code":2738,"language":200,"meta":201,"style":201},"stream\n  .where((value) => value > 0)        \u002F\u002F 过滤\n  .map((value) => value * 2)          \u002F\u002F 映射\n  .distinct()                         \u002F\u002F 去重\n  .debounceTime(Duration(ms: 300))    \u002F\u002F 防抖（需 rxdart）\n  .listen((value) => print(value));\n",[105,2740,2741,2746,2751,2756,2761,2766],{"__ignoreMap":201},[205,2742,2743],{"class":207,"line":208},[205,2744,2745],{},"stream\n",[205,2747,2748],{"class":207,"line":214},[205,2749,2750],{},"  .where((value) => value > 0)        \u002F\u002F 过滤\n",[205,2752,2753],{"class":207,"line":220},[205,2754,2755],{},"  .map((value) => value * 2)          \u002F\u002F 映射\n",[205,2757,2758],{"class":207,"line":226},[205,2759,2760],{},"  .distinct()                         \u002F\u002F 去重\n",[205,2762,2763],{"class":207,"line":232},[205,2764,2765],{},"  .debounceTime(Duration(ms: 300))    \u002F\u002F 防抖（需 rxdart）\n",[205,2767,2768],{"class":207,"line":239},[205,2769,2770],{},"  .listen((value) => print(value));\n",[10,2772],{},[100,2774,2776,2777,108,2780,2783],{"id":2775},"q53-解释-async-和-yield-的用法","Q5.3: 解释 ",[105,2778,2779],{},"async*",[105,2781,2782],{},"yield"," 的用法",[114,2785,2786],{},[117,2787,119],{},[114,2789,2790,2792],{},[105,2791,2779],{}," 用于创建异步生成器，返回一个 Stream：",[196,2794,2796],{"className":198,"code":2795,"language":200,"meta":201,"style":201},"\u002F\u002F 异步生成器\nStream\u003Cint> countDown(int from) async* {\n  for (var i = from; i >= 0; i--) {\n    await Future.delayed(Duration(seconds: 1));\n    yield i; \u002F\u002F 每次 yield 发送一个值到 Stream\n  }\n}\n\n\u002F\u002F yield* 委托到另一个 Stream\nStream\u003Cint> fullSequence() async* {\n  yield* countDown(3);   \u002F\u002F 先倒计时\n  yield -1;              \u002F\u002F 然后发送 -1\n  yield* countDown(2);   \u002F\u002F 再倒计时\n}\n\n\u002F\u002F 同步生成器用 sync* + Iterable\nIterable\u003Cint> range(int start, int end) sync* {\n  for (var i = start; i \u003C= end; i++) {\n    yield i;\n  }\n}\n",[105,2797,2798,2803,2808,2813,2818,2823,2827,2831,2835,2840,2845,2850,2855,2860,2864,2868,2873,2878,2883,2888,2892],{"__ignoreMap":201},[205,2799,2800],{"class":207,"line":208},[205,2801,2802],{},"\u002F\u002F 异步生成器\n",[205,2804,2805],{"class":207,"line":214},[205,2806,2807],{},"Stream\u003Cint> countDown(int from) async* {\n",[205,2809,2810],{"class":207,"line":220},[205,2811,2812],{},"  for (var i = from; i >= 0; i--) {\n",[205,2814,2815],{"class":207,"line":226},[205,2816,2817],{},"    await Future.delayed(Duration(seconds: 1));\n",[205,2819,2820],{"class":207,"line":232},[205,2821,2822],{},"    yield i; \u002F\u002F 每次 yield 发送一个值到 Stream\n",[205,2824,2825],{"class":207,"line":239},[205,2826,394],{},[205,2828,2829],{"class":207,"line":245},[205,2830,400],{},[205,2832,2833],{"class":207,"line":251},[205,2834,236],{"emptyLinePlaceholder":235},[205,2836,2837],{"class":207,"line":257},[205,2838,2839],{},"\u002F\u002F yield* 委托到另一个 Stream\n",[205,2841,2842],{"class":207,"line":263},[205,2843,2844],{},"Stream\u003Cint> fullSequence() async* {\n",[205,2846,2847],{"class":207,"line":397},[205,2848,2849],{},"  yield* countDown(3);   \u002F\u002F 先倒计时\n",[205,2851,2852],{"class":207,"line":505},[205,2853,2854],{},"  yield -1;              \u002F\u002F 然后发送 -1\n",[205,2856,2857],{"class":207,"line":511},[205,2858,2859],{},"  yield* countDown(2);   \u002F\u002F 再倒计时\n",[205,2861,2862],{"class":207,"line":818},[205,2863,400],{},[205,2865,2866],{"class":207,"line":824},[205,2867,236],{"emptyLinePlaceholder":235},[205,2869,2870],{"class":207,"line":830},[205,2871,2872],{},"\u002F\u002F 同步生成器用 sync* + Iterable\n",[205,2874,2875],{"class":207,"line":836},[205,2876,2877],{},"Iterable\u003Cint> range(int start, int end) sync* {\n",[205,2879,2880],{"class":207,"line":842},[205,2881,2882],{},"  for (var i = start; i \u003C= end; i++) {\n",[205,2884,2885],{"class":207,"line":1749},[205,2886,2887],{},"    yield i;\n",[205,2889,2890],{"class":207,"line":1754},[205,2891,394],{},[205,2893,2894],{"class":207,"line":1760},[205,2895,400],{},[10,2897],{},[13,2899,2901],{"id":2900},"_6-性能优化","6. 性能优化",[100,2903,2905],{"id":2904},"q61-flutter-性能优化有哪些关键策略","Q6.1: Flutter 性能优化有哪些关键策略？",[114,2907,2908],{},[117,2909,119],{},[114,2911,2912],{},[117,2913,2914],{},"1. 减少不必要的 rebuild：",[196,2916,2918],{"className":198,"code":2917,"language":200,"meta":201,"style":201},"\u002F\u002F ❌ 整棵树在动画每帧都重建\nAnimatedBuilder(\n  animation: _controller,\n  builder: (context, child) {\n    return Column(\n      children: [\n        Transform.rotate(\n          angle: _controller.value * 2 * pi,\n          child: const Icon(Icons.refresh), \u002F\u002F 每帧都重建\n        ),\n        const ExpensiveWidget(), \u002F\u002F 每帧都重建！\n      ],\n    );\n  },\n)\n\n\u002F\u002F ✅ 使用 child 参数，ExpensiveWidget 只构建一次\nAnimatedBuilder(\n  animation: _controller,\n  child: const ExpensiveWidget(), \u002F\u002F 只构建一次，缓存在这里\n  builder: (context, child) {\n    return Column(\n      children: [\n        Transform.rotate(\n          angle: _controller.value * 2 * pi,\n          child: const Icon(Icons.refresh),\n        ),\n        child!, \u002F\u002F 复用缓存的 Widget\n      ],\n    );\n  },\n)\n",[105,2919,2920,2925,2930,2935,2940,2945,2950,2955,2960,2965,2970,2975,2980,2985,2989,2993,2997,3002,3006,3010,3015,3019,3023,3027,3031,3035,3040,3044,3049,3053,3057,3061],{"__ignoreMap":201},[205,2921,2922],{"class":207,"line":208},[205,2923,2924],{},"\u002F\u002F ❌ 整棵树在动画每帧都重建\n",[205,2926,2927],{"class":207,"line":214},[205,2928,2929],{},"AnimatedBuilder(\n",[205,2931,2932],{"class":207,"line":220},[205,2933,2934],{},"  animation: _controller,\n",[205,2936,2937],{"class":207,"line":226},[205,2938,2939],{},"  builder: (context, child) {\n",[205,2941,2942],{"class":207,"line":232},[205,2943,2944],{},"    return Column(\n",[205,2946,2947],{"class":207,"line":239},[205,2948,2949],{},"      children: [\n",[205,2951,2952],{"class":207,"line":245},[205,2953,2954],{},"        Transform.rotate(\n",[205,2956,2957],{"class":207,"line":251},[205,2958,2959],{},"          angle: _controller.value * 2 * pi,\n",[205,2961,2962],{"class":207,"line":257},[205,2963,2964],{},"          child: const Icon(Icons.refresh), \u002F\u002F 每帧都重建\n",[205,2966,2967],{"class":207,"line":263},[205,2968,2969],{},"        ),\n",[205,2971,2972],{"class":207,"line":397},[205,2973,2974],{},"        const ExpensiveWidget(), \u002F\u002F 每帧都重建！\n",[205,2976,2977],{"class":207,"line":505},[205,2978,2979],{},"      ],\n",[205,2981,2982],{"class":207,"line":511},[205,2983,2984],{},"    );\n",[205,2986,2987],{"class":207,"line":818},[205,2988,2726],{},[205,2990,2991],{"class":207,"line":824},[205,2992,1170],{},[205,2994,2995],{"class":207,"line":830},[205,2996,236],{"emptyLinePlaceholder":235},[205,2998,2999],{"class":207,"line":836},[205,3000,3001],{},"\u002F\u002F ✅ 使用 child 参数，ExpensiveWidget 只构建一次\n",[205,3003,3004],{"class":207,"line":842},[205,3005,2929],{},[205,3007,3008],{"class":207,"line":1749},[205,3009,2934],{},[205,3011,3012],{"class":207,"line":1754},[205,3013,3014],{},"  child: const ExpensiveWidget(), \u002F\u002F 只构建一次，缓存在这里\n",[205,3016,3017],{"class":207,"line":1760},[205,3018,2939],{},[205,3020,3021],{"class":207,"line":1766},[205,3022,2944],{},[205,3024,3025],{"class":207,"line":1772},[205,3026,2949],{},[205,3028,3029],{"class":207,"line":1778},[205,3030,2954],{},[205,3032,3033],{"class":207,"line":1784},[205,3034,2959],{},[205,3036,3037],{"class":207,"line":1790},[205,3038,3039],{},"          child: const Icon(Icons.refresh),\n",[205,3041,3042],{"class":207,"line":1795},[205,3043,2969],{},[205,3045,3046],{"class":207,"line":1800},[205,3047,3048],{},"        child!, \u002F\u002F 复用缓存的 Widget\n",[205,3050,3051],{"class":207,"line":1805},[205,3052,2979],{},[205,3054,3055],{"class":207,"line":1811},[205,3056,2984],{},[205,3058,3059],{"class":207,"line":1817},[205,3060,2726],{},[205,3062,3063],{"class":207,"line":1823},[205,3064,1170],{},[114,3066,3067],{},[117,3068,3069,3070,3072],{},"2. 使用 ",[105,3071,107],{}," 构造函数：",[196,3074,3076],{"className":198,"code":3075,"language":200,"meta":201,"style":201},"\u002F\u002F ✅ const Widget 在编译时创建，可以被复用\nconst Text('Hello')\nconst SizedBox(height: 16)\nconst EdgeInsets.all(8)\n",[105,3077,3078,3083,3088,3093],{"__ignoreMap":201},[205,3079,3080],{"class":207,"line":208},[205,3081,3082],{},"\u002F\u002F ✅ const Widget 在编译时创建，可以被复用\n",[205,3084,3085],{"class":207,"line":214},[205,3086,3087],{},"const Text('Hello')\n",[205,3089,3090],{"class":207,"line":220},[205,3091,3092],{},"const SizedBox(height: 16)\n",[205,3094,3095],{"class":207,"line":226},[205,3096,3097],{},"const EdgeInsets.all(8)\n",[114,3099,3100],{},[117,3101,3102],{},"3. ListView 优化：",[196,3104,3106],{"className":198,"code":3105,"language":200,"meta":201,"style":201},"\u002F\u002F ❌ 一次性构建所有子项\nListView(children: items.map((i) => ItemWidget(i)).toList())\n\n\u002F\u002F ✅ 懒加载，只构建可见区域\nListView.builder(\n  itemCount: items.length,\n  itemBuilder: (context, index) => ItemWidget(items[index]),\n)\n\n\u002F\u002F ✅ 固定高度项可以跳过布局计算\nListView.builder(\n  itemExtent: 72.0, \u002F\u002F 已知每项高度\n  itemBuilder: (context, index) => ItemWidget(items[index]),\n)\n",[105,3107,3108,3113,3118,3122,3127,3132,3137,3142,3146,3150,3155,3159,3164,3168],{"__ignoreMap":201},[205,3109,3110],{"class":207,"line":208},[205,3111,3112],{},"\u002F\u002F ❌ 一次性构建所有子项\n",[205,3114,3115],{"class":207,"line":214},[205,3116,3117],{},"ListView(children: items.map((i) => ItemWidget(i)).toList())\n",[205,3119,3120],{"class":207,"line":220},[205,3121,236],{"emptyLinePlaceholder":235},[205,3123,3124],{"class":207,"line":226},[205,3125,3126],{},"\u002F\u002F ✅ 懒加载，只构建可见区域\n",[205,3128,3129],{"class":207,"line":232},[205,3130,3131],{},"ListView.builder(\n",[205,3133,3134],{"class":207,"line":239},[205,3135,3136],{},"  itemCount: items.length,\n",[205,3138,3139],{"class":207,"line":245},[205,3140,3141],{},"  itemBuilder: (context, index) => ItemWidget(items[index]),\n",[205,3143,3144],{"class":207,"line":251},[205,3145,1170],{},[205,3147,3148],{"class":207,"line":257},[205,3149,236],{"emptyLinePlaceholder":235},[205,3151,3152],{"class":207,"line":263},[205,3153,3154],{},"\u002F\u002F ✅ 固定高度项可以跳过布局计算\n",[205,3156,3157],{"class":207,"line":397},[205,3158,3131],{},[205,3160,3161],{"class":207,"line":505},[205,3162,3163],{},"  itemExtent: 72.0, \u002F\u002F 已知每项高度\n",[205,3165,3166],{"class":207,"line":511},[205,3167,3141],{},[205,3169,3170],{"class":207,"line":818},[205,3171,1170],{},[114,3173,3174],{},[117,3175,3176],{},"4. 图片优化：",[196,3178,3180],{"className":198,"code":3179,"language":200,"meta":201,"style":201},"\u002F\u002F 指定 cacheWidth\u002FcacheHeight，避免解码全分辨率\nImage.asset(\n  'assets\u002Flarge_image.png',\n  cacheWidth: 200, \u002F\u002F 解码到指定大小，节省内存\n)\n",[105,3181,3182,3187,3192,3197,3202],{"__ignoreMap":201},[205,3183,3184],{"class":207,"line":208},[205,3185,3186],{},"\u002F\u002F 指定 cacheWidth\u002FcacheHeight，避免解码全分辨率\n",[205,3188,3189],{"class":207,"line":214},[205,3190,3191],{},"Image.asset(\n",[205,3193,3194],{"class":207,"line":220},[205,3195,3196],{},"  'assets\u002Flarge_image.png',\n",[205,3198,3199],{"class":207,"line":226},[205,3200,3201],{},"  cacheWidth: 200, \u002F\u002F 解码到指定大小，节省内存\n",[205,3203,3204],{"class":207,"line":232},[205,3205,1170],{},[114,3207,3208],{},[117,3209,3210],{},"5. 合理拆分 Widget：",[196,3212,3214],{"className":198,"code":3213,"language":200,"meta":201,"style":201},"\u002F\u002F ❌ 一个巨大的 build 方法\nWidget build(BuildContext context) {\n  return Column(children: [\n    \u002F\u002F 200 行 UI 代码...\n  ]);\n}\n\n\u002F\u002F ✅ 拆分为独立的 Widget 类（不是方法！）\n\u002F\u002F Widget 类可以独立 rebuild，方法提取不行\nclass HeaderSection extends StatelessWidget { ... }\nclass ContentSection extends StatelessWidget { ... }\n",[105,3215,3216,3221,3226,3231,3236,3241,3245,3249,3254,3259,3264],{"__ignoreMap":201},[205,3217,3218],{"class":207,"line":208},[205,3219,3220],{},"\u002F\u002F ❌ 一个巨大的 build 方法\n",[205,3222,3223],{"class":207,"line":214},[205,3224,3225],{},"Widget build(BuildContext context) {\n",[205,3227,3228],{"class":207,"line":220},[205,3229,3230],{},"  return Column(children: [\n",[205,3232,3233],{"class":207,"line":226},[205,3234,3235],{},"    \u002F\u002F 200 行 UI 代码...\n",[205,3237,3238],{"class":207,"line":232},[205,3239,3240],{},"  ]);\n",[205,3242,3243],{"class":207,"line":239},[205,3244,400],{},[205,3246,3247],{"class":207,"line":245},[205,3248,236],{"emptyLinePlaceholder":235},[205,3250,3251],{"class":207,"line":251},[205,3252,3253],{},"\u002F\u002F ✅ 拆分为独立的 Widget 类（不是方法！）\n",[205,3255,3256],{"class":207,"line":257},[205,3257,3258],{},"\u002F\u002F Widget 类可以独立 rebuild，方法提取不行\n",[205,3260,3261],{"class":207,"line":263},[205,3262,3263],{},"class HeaderSection extends StatelessWidget { ... }\n",[205,3265,3266],{"class":207,"line":397},[205,3267,3268],{},"class ContentSection extends StatelessWidget { ... }\n",[10,3270],{},[100,3272,3274],{"id":3273},"q62-如何使用-devtools-诊断性能问题","Q6.2: 如何使用 DevTools 诊断性能问题？",[114,3276,3277],{},[117,3278,119],{},[114,3280,3281],{},[117,3282,3283],{},"Performance Overlay：",[196,3285,3287],{"className":198,"code":3286,"language":200,"meta":201,"style":201},"MaterialApp(\n  showPerformanceOverlay: true, \u002F\u002F 显示 GPU\u002FUI 线程帧率\n)\n",[105,3288,3289,3294,3299],{"__ignoreMap":201},[205,3290,3291],{"class":207,"line":208},[205,3292,3293],{},"MaterialApp(\n",[205,3295,3296],{"class":207,"line":214},[205,3297,3298],{},"  showPerformanceOverlay: true, \u002F\u002F 显示 GPU\u002FUI 线程帧率\n",[205,3300,3301],{"class":207,"line":220},[205,3302,1170],{},[114,3304,3305],{},"两行图表：",[409,3307,3308,3311,3314],{},[20,3309,3310],{},"上方（UI Thread）：build\u002Flayout\u002Fpaint 耗时",[20,3312,3313],{},"下方（Raster Thread）：合成和光栅化耗时",[20,3315,3316],{},"红色柱体 = 该帧超过 16ms，发生掉帧",[114,3318,3319],{},[117,3320,3321],{},"Flutter DevTools 关键面板：",[121,3323,3324,3333],{},[124,3325,3326],{},[127,3327,3328,3331],{},[130,3329,3330],{},"面板",[130,3332,1542],{},[142,3334,3335,3343,3351,3359,3367],{},[127,3336,3337,3340],{},[147,3338,3339],{},"Performance",[147,3341,3342],{},"帧耗时分析，识别卡顿帧",[127,3344,3345,3348],{},[147,3346,3347],{},"CPU Profiler",[147,3349,3350],{},"函数级别耗时分析",[127,3352,3353,3356],{},[147,3354,3355],{},"Memory",[147,3357,3358],{},"内存分配、泄漏检测",[127,3360,3361,3364],{},[147,3362,3363],{},"Widget Inspector",[147,3365,3366],{},"Widget 树结构、rebuild 统计",[127,3368,3369,3372],{},[147,3370,3371],{},"Network",[147,3373,3374],{},"HTTP 请求监控",[114,3376,3377],{},[117,3378,3379],{},"常用调试标志：",[196,3381,3383],{"className":198,"code":3382,"language":200,"meta":201,"style":201},"import 'package:flutter\u002Frendering.dart';\n\n\u002F\u002F 显示重绘区域（彩虹色边框）\ndebugRepaintRainbowEnabled = true;\n\n\u002F\u002F 显示布局边界\ndebugPaintSizeEnabled = true;\n\n\u002F\u002F 打印重建的 Widget\ndebugPrintRebuildDirtyWidgets = true;\n",[105,3384,3385,3390,3394,3399,3404,3408,3413,3418,3422,3427],{"__ignoreMap":201},[205,3386,3387],{"class":207,"line":208},[205,3388,3389],{},"import 'package:flutter\u002Frendering.dart';\n",[205,3391,3392],{"class":207,"line":214},[205,3393,236],{"emptyLinePlaceholder":235},[205,3395,3396],{"class":207,"line":220},[205,3397,3398],{},"\u002F\u002F 显示重绘区域（彩虹色边框）\n",[205,3400,3401],{"class":207,"line":226},[205,3402,3403],{},"debugRepaintRainbowEnabled = true;\n",[205,3405,3406],{"class":207,"line":232},[205,3407,236],{"emptyLinePlaceholder":235},[205,3409,3410],{"class":207,"line":239},[205,3411,3412],{},"\u002F\u002F 显示布局边界\n",[205,3414,3415],{"class":207,"line":245},[205,3416,3417],{},"debugPaintSizeEnabled = true;\n",[205,3419,3420],{"class":207,"line":251},[205,3421,236],{"emptyLinePlaceholder":235},[205,3423,3424],{"class":207,"line":257},[205,3425,3426],{},"\u002F\u002F 打印重建的 Widget\n",[205,3428,3429],{"class":207,"line":263},[205,3430,3431],{},"debugPrintRebuildDirtyWidgets = true;\n",[114,3433,3434],{},[117,3435,3436],{},"Timeline 使用：",[196,3438,3440],{"className":198,"code":3439,"language":200,"meta":201,"style":201},"import 'dart:developer';\n\nTimeline.startSync('MyExpensiveOperation');\n\u002F\u002F ... 耗时操作\nTimeline.finishSync();\n",[105,3441,3442,3447,3451,3456,3461],{"__ignoreMap":201},[205,3443,3444],{"class":207,"line":208},[205,3445,3446],{},"import 'dart:developer';\n",[205,3448,3449],{"class":207,"line":214},[205,3450,236],{"emptyLinePlaceholder":235},[205,3452,3453],{"class":207,"line":220},[205,3454,3455],{},"Timeline.startSync('MyExpensiveOperation');\n",[205,3457,3458],{"class":207,"line":226},[205,3459,3460],{},"\u002F\u002F ... 耗时操作\n",[205,3462,3463],{"class":207,"line":232},[205,3464,3465],{},"Timeline.finishSync();\n",[10,3467],{},[13,3469,3471],{"id":3470},"_7-平台通信platform-channel","7. 平台通信（Platform Channel）",[100,3473,3475],{"id":3474},"q71-flutter-的-platform-channel-有哪几种类型分别适用于什么场景","Q7.1: Flutter 的 Platform Channel 有哪几种类型？分别适用于什么场景？",[114,3477,3478],{},[117,3479,119],{},[196,3481,3484],{"className":3482,"code":3483,"language":996},[994],"Flutter (Dart)  ←──── Platform Channel ────→  Native (iOS\u002FAndroid)\n",[105,3485,3483],{"__ignoreMap":201},[121,3487,3488,3500],{},[124,3489,3490],{},[127,3491,3492,3495,3498],{},[130,3493,3494],{},"Channel 类型",[130,3496,3497],{},"通信方式",[130,3499,1862],{},[142,3501,3502,3515,3528],{},[127,3503,3504,3509,3512],{},[147,3505,3506],{},[105,3507,3508],{},"MethodChannel",[147,3510,3511],{},"异步方法调用（请求-响应）",[147,3513,3514],{},"调用原生 API（相机、蓝牙等）",[127,3516,3517,3522,3525],{},[147,3518,3519],{},[105,3520,3521],{},"EventChannel",[147,3523,3524],{},"原生向 Dart 持续发送事件流",[147,3526,3527],{},"传感器数据、位置更新",[127,3529,3530,3535,3538],{},[147,3531,3532],{},[105,3533,3534],{},"BasicMessageChannel",[147,3536,3537],{},"双向消息传递",[147,3539,3540],{},"自定义编解码、简单数据交换",[114,3542,3543],{},[117,3544,3545],{},"MethodChannel 示例：",[196,3547,3549],{"className":198,"code":3548,"language":200,"meta":201,"style":201},"\u002F\u002F Dart 端\nclass BatteryService {\n  static const _channel = MethodChannel('com.example\u002Fbattery');\n\n  Future\u003Cint> getBatteryLevel() async {\n    try {\n      final level = await _channel.invokeMethod\u003Cint>('getBatteryLevel');\n      return level ?? -1;\n    } on PlatformException catch (e) {\n      throw Exception('Failed to get battery level: ${e.message}');\n    }\n  }\n}\n",[105,3550,3551,3556,3561,3566,3570,3575,3580,3585,3590,3595,3600,3604,3608],{"__ignoreMap":201},[205,3552,3553],{"class":207,"line":208},[205,3554,3555],{},"\u002F\u002F Dart 端\n",[205,3557,3558],{"class":207,"line":214},[205,3559,3560],{},"class BatteryService {\n",[205,3562,3563],{"class":207,"line":220},[205,3564,3565],{},"  static const _channel = MethodChannel('com.example\u002Fbattery');\n",[205,3567,3568],{"class":207,"line":226},[205,3569,236],{"emptyLinePlaceholder":235},[205,3571,3572],{"class":207,"line":232},[205,3573,3574],{},"  Future\u003Cint> getBatteryLevel() async {\n",[205,3576,3577],{"class":207,"line":239},[205,3578,3579],{},"    try {\n",[205,3581,3582],{"class":207,"line":245},[205,3583,3584],{},"      final level = await _channel.invokeMethod\u003Cint>('getBatteryLevel');\n",[205,3586,3587],{"class":207,"line":251},[205,3588,3589],{},"      return level ?? -1;\n",[205,3591,3592],{"class":207,"line":257},[205,3593,3594],{},"    } on PlatformException catch (e) {\n",[205,3596,3597],{"class":207,"line":263},[205,3598,3599],{},"      throw Exception('Failed to get battery level: ${e.message}');\n",[205,3601,3602],{"class":207,"line":397},[205,3603,1787],{},[205,3605,3606],{"class":207,"line":505},[205,3607,394],{},[205,3609,3610],{"class":207,"line":511},[205,3611,400],{},[196,3613,3617],{"className":3614,"code":3615,"language":3616,"meta":201,"style":201},"language-kotlin shiki shiki-themes github-light github-dark","\u002F\u002F Android (Kotlin) 端\nclass MainActivity : FlutterActivity() {\n    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {\n        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, \"com.example\u002Fbattery\")\n            .setMethodCallHandler { call, result ->\n                when (call.method) {\n                    \"getBatteryLevel\" -> {\n                        val level = getBatteryLevel()\n                        if (level != -1) result.success(level)\n                        else result.error(\"UNAVAILABLE\", \"Battery level not available\", null)\n                    }\n                    else -> result.notImplemented()\n                }\n            }\n    }\n}\n","kotlin",[105,3618,3619,3624,3629,3634,3639,3644,3649,3654,3659,3664,3669,3674,3679,3684,3689,3693],{"__ignoreMap":201},[205,3620,3621],{"class":207,"line":208},[205,3622,3623],{},"\u002F\u002F Android (Kotlin) 端\n",[205,3625,3626],{"class":207,"line":214},[205,3627,3628],{},"class MainActivity : FlutterActivity() {\n",[205,3630,3631],{"class":207,"line":220},[205,3632,3633],{},"    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {\n",[205,3635,3636],{"class":207,"line":226},[205,3637,3638],{},"        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, \"com.example\u002Fbattery\")\n",[205,3640,3641],{"class":207,"line":232},[205,3642,3643],{},"            .setMethodCallHandler { call, result ->\n",[205,3645,3646],{"class":207,"line":239},[205,3647,3648],{},"                when (call.method) {\n",[205,3650,3651],{"class":207,"line":245},[205,3652,3653],{},"                    \"getBatteryLevel\" -> {\n",[205,3655,3656],{"class":207,"line":251},[205,3657,3658],{},"                        val level = getBatteryLevel()\n",[205,3660,3661],{"class":207,"line":257},[205,3662,3663],{},"                        if (level != -1) result.success(level)\n",[205,3665,3666],{"class":207,"line":263},[205,3667,3668],{},"                        else result.error(\"UNAVAILABLE\", \"Battery level not available\", null)\n",[205,3670,3671],{"class":207,"line":397},[205,3672,3673],{},"                    }\n",[205,3675,3676],{"class":207,"line":505},[205,3677,3678],{},"                    else -> result.notImplemented()\n",[205,3680,3681],{"class":207,"line":511},[205,3682,3683],{},"                }\n",[205,3685,3686],{"class":207,"line":818},[205,3687,3688],{},"            }\n",[205,3690,3691],{"class":207,"line":824},[205,3692,1787],{},[205,3694,3695],{"class":207,"line":830},[205,3696,400],{},[114,3698,3699],{},[117,3700,3701],{},"EventChannel 示例：",[196,3703,3705],{"className":198,"code":3704,"language":200,"meta":201,"style":201},"\u002F\u002F Dart 端：接收原生传感器数据流\nclass AccelerometerService {\n  static const _channel = EventChannel('com.example\u002Faccelerometer');\n\n  Stream\u003CAccelerometerEvent> get events {\n    return _channel.receiveBroadcastStream().map((data) {\n      final list = data as List;\n      return AccelerometerEvent(list[0], list[1], list[2]);\n    });\n  }\n}\n",[105,3706,3707,3712,3717,3722,3726,3731,3736,3741,3746,3751,3755],{"__ignoreMap":201},[205,3708,3709],{"class":207,"line":208},[205,3710,3711],{},"\u002F\u002F Dart 端：接收原生传感器数据流\n",[205,3713,3714],{"class":207,"line":214},[205,3715,3716],{},"class AccelerometerService {\n",[205,3718,3719],{"class":207,"line":220},[205,3720,3721],{},"  static const _channel = EventChannel('com.example\u002Faccelerometer');\n",[205,3723,3724],{"class":207,"line":226},[205,3725,236],{"emptyLinePlaceholder":235},[205,3727,3728],{"class":207,"line":232},[205,3729,3730],{},"  Stream\u003CAccelerometerEvent> get events {\n",[205,3732,3733],{"class":207,"line":239},[205,3734,3735],{},"    return _channel.receiveBroadcastStream().map((data) {\n",[205,3737,3738],{"class":207,"line":245},[205,3739,3740],{},"      final list = data as List;\n",[205,3742,3743],{"class":207,"line":251},[205,3744,3745],{},"      return AccelerometerEvent(list[0], list[1], list[2]);\n",[205,3747,3748],{"class":207,"line":257},[205,3749,3750],{},"    });\n",[205,3752,3753],{"class":207,"line":263},[205,3754,394],{},[205,3756,3757],{"class":207,"line":397},[205,3758,400],{},[10,3760],{},[100,3762,3764],{"id":3763},"q72-什么是-ffiforeign-function-interface什么时候用-ffi-代替-platform-channel","Q7.2: 什么是 FFI（Foreign Function Interface）？什么时候用 FFI 代替 Platform Channel？",[114,3766,3767],{},[117,3768,119],{},[121,3770,3771,3783],{},[124,3772,3773],{},[127,3774,3775,3777,3780],{},[130,3776],{},[130,3778,3779],{},"Platform Channel",[130,3781,3782],{},"FFI",[142,3784,3785,3795,3806,3817,3827],{},[127,3786,3787,3789,3792],{},[147,3788,3497],{},[147,3790,3791],{},"异步消息传递",[147,3793,3794],{},"直接函数调用（同步）",[127,3796,3797,3800,3803],{},[147,3798,3799],{},"性能",[147,3801,3802],{},"有序列化\u002F反序列化开销",[147,3804,3805],{},"接近原生调用性能",[127,3807,3808,3811,3814],{},[147,3809,3810],{},"适用语言",[147,3812,3813],{},"Java\u002FKotlin、ObjC\u002FSwift",[147,3815,3816],{},"C\u002FC++",[127,3818,3819,3821,3824],{},[147,3820,1859],{},[147,3822,3823],{},"较低",[147,3825,3826],{},"较高",[127,3828,3829,3832,3835],{},[147,3830,3831],{},"典型场景",[147,3833,3834],{},"平台 API 调用",[147,3836,3837],{},"高性能计算、复用 C 库",[196,3839,3841],{"className":198,"code":3840,"language":200,"meta":201,"style":201},"\u002F\u002F FFI 示例：调用 C 函数\nimport 'dart:ffi';\n\n\u002F\u002F 定义 C 函数签名\ntypedef NativeAdd = Int32 Function(Int32 a, Int32 b);\ntypedef DartAdd = int Function(int a, int b);\n\nvoid main() {\n  final dylib = DynamicLibrary.open('libnative.so');\n  final add = dylib.lookupFunction\u003CNativeAdd, DartAdd>('add');\n  print(add(3, 4)); \u002F\u002F 7，同步调用\n}\n",[105,3842,3843,3848,3853,3857,3862,3867,3872,3876,3880,3885,3890,3895],{"__ignoreMap":201},[205,3844,3845],{"class":207,"line":208},[205,3846,3847],{},"\u002F\u002F FFI 示例：调用 C 函数\n",[205,3849,3850],{"class":207,"line":214},[205,3851,3852],{},"import 'dart:ffi';\n",[205,3854,3855],{"class":207,"line":220},[205,3856,236],{"emptyLinePlaceholder":235},[205,3858,3859],{"class":207,"line":226},[205,3860,3861],{},"\u002F\u002F 定义 C 函数签名\n",[205,3863,3864],{"class":207,"line":232},[205,3865,3866],{},"typedef NativeAdd = Int32 Function(Int32 a, Int32 b);\n",[205,3868,3869],{"class":207,"line":239},[205,3870,3871],{},"typedef DartAdd = int Function(int a, int b);\n",[205,3873,3874],{"class":207,"line":245},[205,3875,236],{"emptyLinePlaceholder":235},[205,3877,3878],{"class":207,"line":251},[205,3879,2411],{},[205,3881,3882],{"class":207,"line":257},[205,3883,3884],{},"  final dylib = DynamicLibrary.open('libnative.so');\n",[205,3886,3887],{"class":207,"line":263},[205,3888,3889],{},"  final add = dylib.lookupFunction\u003CNativeAdd, DartAdd>('add');\n",[205,3891,3892],{"class":207,"line":397},[205,3893,3894],{},"  print(add(3, 4)); \u002F\u002F 7，同步调用\n",[205,3896,3897],{"class":207,"line":505},[205,3898,400],{},[114,3900,3901],{},[117,3902,3903],{},"选择 FFI 的场景：",[409,3905,3906,3909,3912],{},[20,3907,3908],{},"需要调用现有的 C\u002FC++ 库（如 SQLite、OpenCV）",[20,3910,3911],{},"对性能要求极高，不能接受 Channel 的序列化开销",[20,3913,3914],{},"需要同步调用（Platform Channel 只支持异步）",[10,3916],{},[13,3918,3920],{"id":3919},"_8-路由与导航","8. 路由与导航",[100,3922,3924],{"id":3923},"q81-navigator-10-和-navigator-20router-api有什么区别","Q8.1: Navigator 1.0 和 Navigator 2.0（Router API）有什么区别？",[114,3926,3927],{},[117,3928,119],{},[114,3930,3931],{},[117,3932,3933],{},"Navigator 1.0（命令式）：",[196,3935,3937],{"className":198,"code":3936,"language":200,"meta":201,"style":201},"\u002F\u002F 入栈\nNavigator.push(context, MaterialPageRoute(builder: (_) => DetailPage()));\n\u002F\u002F 或命名路由\nNavigator.pushNamed(context, '\u002Fdetail', arguments: {'id': 42});\n\n\u002F\u002F 出栈\nNavigator.pop(context);\n\n\u002F\u002F 替换\nNavigator.pushReplacement(context, MaterialPageRoute(builder: (_) => HomePage()));\n",[105,3938,3939,3944,3949,3954,3959,3963,3968,3973,3977,3982],{"__ignoreMap":201},[205,3940,3941],{"class":207,"line":208},[205,3942,3943],{},"\u002F\u002F 入栈\n",[205,3945,3946],{"class":207,"line":214},[205,3947,3948],{},"Navigator.push(context, MaterialPageRoute(builder: (_) => DetailPage()));\n",[205,3950,3951],{"class":207,"line":220},[205,3952,3953],{},"\u002F\u002F 或命名路由\n",[205,3955,3956],{"class":207,"line":226},[205,3957,3958],{},"Navigator.pushNamed(context, '\u002Fdetail', arguments: {'id': 42});\n",[205,3960,3961],{"class":207,"line":232},[205,3962,236],{"emptyLinePlaceholder":235},[205,3964,3965],{"class":207,"line":239},[205,3966,3967],{},"\u002F\u002F 出栈\n",[205,3969,3970],{"class":207,"line":245},[205,3971,3972],{},"Navigator.pop(context);\n",[205,3974,3975],{"class":207,"line":251},[205,3976,236],{"emptyLinePlaceholder":235},[205,3978,3979],{"class":207,"line":257},[205,3980,3981],{},"\u002F\u002F 替换\n",[205,3983,3984],{"class":207,"line":263},[205,3985,3986],{},"Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => HomePage()));\n",[114,3988,3989],{},"优点：简单直观\n缺点：",[409,3991,3992,3995,3998],{},[20,3993,3994],{},"难以处理深层链接（Deep Link）",[20,3996,3997],{},"难以从 URL 恢复导航状态（Web）",[20,3999,4000],{},"路由栈不透明，难以声明式管理",[114,4002,4003],{},[117,4004,4005],{},"Navigator 2.0（声明式 \u002F Router API）：",[196,4007,4009],{"className":198,"code":4008,"language":200,"meta":201,"style":201},"MaterialApp.router(\n  routerConfig: GoRouter(\n    routes: [\n      GoRoute(\n        path: '\u002F',\n        builder: (context, state) => HomePage(),\n        routes: [\n          GoRoute(\n            path: 'detail\u002F:id',\n            builder: (context, state) {\n              final id = state.pathParameters['id']!;\n              return DetailPage(id: id);\n            },\n          ),\n        ],\n      ),\n    ],\n  ),\n)\n",[105,4010,4011,4016,4021,4026,4031,4036,4041,4046,4051,4056,4061,4066,4071,4076,4081,4086,4091,4096,4101],{"__ignoreMap":201},[205,4012,4013],{"class":207,"line":208},[205,4014,4015],{},"MaterialApp.router(\n",[205,4017,4018],{"class":207,"line":214},[205,4019,4020],{},"  routerConfig: GoRouter(\n",[205,4022,4023],{"class":207,"line":220},[205,4024,4025],{},"    routes: [\n",[205,4027,4028],{"class":207,"line":226},[205,4029,4030],{},"      GoRoute(\n",[205,4032,4033],{"class":207,"line":232},[205,4034,4035],{},"        path: '\u002F',\n",[205,4037,4038],{"class":207,"line":239},[205,4039,4040],{},"        builder: (context, state) => HomePage(),\n",[205,4042,4043],{"class":207,"line":245},[205,4044,4045],{},"        routes: [\n",[205,4047,4048],{"class":207,"line":251},[205,4049,4050],{},"          GoRoute(\n",[205,4052,4053],{"class":207,"line":257},[205,4054,4055],{},"            path: 'detail\u002F:id',\n",[205,4057,4058],{"class":207,"line":263},[205,4059,4060],{},"            builder: (context, state) {\n",[205,4062,4063],{"class":207,"line":397},[205,4064,4065],{},"              final id = state.pathParameters['id']!;\n",[205,4067,4068],{"class":207,"line":505},[205,4069,4070],{},"              return DetailPage(id: id);\n",[205,4072,4073],{"class":207,"line":511},[205,4074,4075],{},"            },\n",[205,4077,4078],{"class":207,"line":818},[205,4079,4080],{},"          ),\n",[205,4082,4083],{"class":207,"line":824},[205,4084,4085],{},"        ],\n",[205,4087,4088],{"class":207,"line":830},[205,4089,4090],{},"      ),\n",[205,4092,4093],{"class":207,"line":836},[205,4094,4095],{},"    ],\n",[205,4097,4098],{"class":207,"line":842},[205,4099,4100],{},"  ),\n",[205,4102,4103],{"class":207,"line":1749},[205,4104,1170],{},[114,4106,4107],{},[117,4108,4109],{},"go_router（官方推荐的 Router 封装）：",[196,4111,4113],{"className":198,"code":4112,"language":200,"meta":201,"style":201},"\u002F\u002F 声明式导航\ncontext.go('\u002Fdetail\u002F42');       \u002F\u002F 替换整个栈\ncontext.push('\u002Fdetail\u002F42');     \u002F\u002F 入栈\n\n\u002F\u002F 重定向\nGoRouter(\n  redirect: (context, state) {\n    final isLoggedIn = authService.isLoggedIn;\n    if (!isLoggedIn && state.matchedLocation != '\u002Flogin') {\n      return '\u002Flogin';\n    }\n    return null; \u002F\u002F 不重定向\n  },\n)\n\n\u002F\u002F ShellRoute：共享布局（如底部导航栏）\nShellRoute(\n  builder: (context, state, child) {\n    return ScaffoldWithBottomNav(child: child);\n  },\n  routes: [\n    GoRoute(path: '\u002Fhome', builder: ...),\n    GoRoute(path: '\u002Fsettings', builder: ...),\n  ],\n)\n",[105,4114,4115,4120,4125,4130,4134,4139,4144,4149,4154,4159,4164,4168,4173,4177,4181,4185,4190,4195,4200,4205,4209,4214,4219,4224,4228],{"__ignoreMap":201},[205,4116,4117],{"class":207,"line":208},[205,4118,4119],{},"\u002F\u002F 声明式导航\n",[205,4121,4122],{"class":207,"line":214},[205,4123,4124],{},"context.go('\u002Fdetail\u002F42');       \u002F\u002F 替换整个栈\n",[205,4126,4127],{"class":207,"line":220},[205,4128,4129],{},"context.push('\u002Fdetail\u002F42');     \u002F\u002F 入栈\n",[205,4131,4132],{"class":207,"line":226},[205,4133,236],{"emptyLinePlaceholder":235},[205,4135,4136],{"class":207,"line":232},[205,4137,4138],{},"\u002F\u002F 重定向\n",[205,4140,4141],{"class":207,"line":239},[205,4142,4143],{},"GoRouter(\n",[205,4145,4146],{"class":207,"line":245},[205,4147,4148],{},"  redirect: (context, state) {\n",[205,4150,4151],{"class":207,"line":251},[205,4152,4153],{},"    final isLoggedIn = authService.isLoggedIn;\n",[205,4155,4156],{"class":207,"line":257},[205,4157,4158],{},"    if (!isLoggedIn && state.matchedLocation != '\u002Flogin') {\n",[205,4160,4161],{"class":207,"line":263},[205,4162,4163],{},"      return '\u002Flogin';\n",[205,4165,4166],{"class":207,"line":397},[205,4167,1787],{},[205,4169,4170],{"class":207,"line":505},[205,4171,4172],{},"    return null; \u002F\u002F 不重定向\n",[205,4174,4175],{"class":207,"line":511},[205,4176,2726],{},[205,4178,4179],{"class":207,"line":818},[205,4180,1170],{},[205,4182,4183],{"class":207,"line":824},[205,4184,236],{"emptyLinePlaceholder":235},[205,4186,4187],{"class":207,"line":830},[205,4188,4189],{},"\u002F\u002F ShellRoute：共享布局（如底部导航栏）\n",[205,4191,4192],{"class":207,"line":836},[205,4193,4194],{},"ShellRoute(\n",[205,4196,4197],{"class":207,"line":842},[205,4198,4199],{},"  builder: (context, state, child) {\n",[205,4201,4202],{"class":207,"line":1749},[205,4203,4204],{},"    return ScaffoldWithBottomNav(child: child);\n",[205,4206,4207],{"class":207,"line":1754},[205,4208,2726],{},[205,4210,4211],{"class":207,"line":1760},[205,4212,4213],{},"  routes: [\n",[205,4215,4216],{"class":207,"line":1766},[205,4217,4218],{},"    GoRoute(path: '\u002Fhome', builder: ...),\n",[205,4220,4221],{"class":207,"line":1772},[205,4222,4223],{},"    GoRoute(path: '\u002Fsettings', builder: ...),\n",[205,4225,4226],{"class":207,"line":1778},[205,4227,1165],{},[205,4229,4230],{"class":207,"line":1784},[205,4231,1170],{},[10,4233],{},[13,4235,4237],{"id":4236},"_9-测试","9. 测试",[100,4239,4241],{"id":4240},"q91-flutter-中有哪些测试类型如何编写-widget-测试","Q9.1: Flutter 中有哪些测试类型？如何编写 Widget 测试？",[114,4243,4244],{},[117,4245,119],{},[121,4247,4248,4264],{},[124,4249,4250],{},[127,4251,4252,4255,4258,4261],{},[130,4253,4254],{},"类型",[130,4256,4257],{},"速度",[130,4259,4260],{},"范围",[130,4262,4263],{},"依赖",[142,4265,4266,4280,4294],{},[127,4267,4268,4271,4274,4277],{},[147,4269,4270],{},"Unit Test",[147,4272,4273],{},"极快",[147,4275,4276],{},"单个函数\u002F类",[147,4278,4279],{},"无",[127,4281,4282,4285,4288,4291],{},[147,4283,4284],{},"Widget Test",[147,4286,4287],{},"快",[147,4289,4290],{},"单个 Widget",[147,4292,4293],{},"Flutter 测试框架",[127,4295,4296,4299,4302,4305],{},[147,4297,4298],{},"Integration Test",[147,4300,4301],{},"慢",[147,4303,4304],{},"完整应用",[147,4306,4307],{},"真实设备\u002F模拟器",[114,4309,4310],{},[117,4311,4312],{},"Widget 测试示例：",[196,4314,4316],{"className":198,"code":4315,"language":200,"meta":201,"style":201},"import 'package:flutter_test\u002Fflutter_test.dart';\n\nvoid main() {\n  testWidgets('Counter increments', (WidgetTester tester) async {\n    \u002F\u002F 构建 Widget\n    await tester.pumpWidget(const MaterialApp(home: CounterPage()));\n\n    \u002F\u002F 验证初始状态\n    expect(find.text('0'), findsOneWidget);\n    expect(find.text('1'), findsNothing);\n\n    \u002F\u002F 模拟交互\n    await tester.tap(find.byIcon(Icons.add));\n    await tester.pump(); \u002F\u002F 触发一帧重建\n\n    \u002F\u002F 验证结果\n    expect(find.text('0'), findsNothing);\n    expect(find.text('1'), findsOneWidget);\n  });\n\n  testWidgets('shows loading then data', (tester) async {\n    await tester.pumpWidget(MyApp());\n\n    \u002F\u002F 验证 loading 状态\n    expect(find.byType(CircularProgressIndicator), findsOneWidget);\n\n    \u002F\u002F 等待异步操作完成\n    await tester.pumpAndSettle(); \u002F\u002F 持续 pump 直到没有待处理的帧\n\n    \u002F\u002F 验证数据状态\n    expect(find.text('Data loaded'), findsOneWidget);\n  });\n}\n",[105,4317,4318,4323,4327,4331,4336,4341,4346,4350,4355,4360,4365,4369,4374,4379,4384,4388,4393,4398,4403,4407,4411,4416,4421,4425,4430,4435,4439,4444,4449,4453,4458,4463,4467],{"__ignoreMap":201},[205,4319,4320],{"class":207,"line":208},[205,4321,4322],{},"import 'package:flutter_test\u002Fflutter_test.dart';\n",[205,4324,4325],{"class":207,"line":214},[205,4326,236],{"emptyLinePlaceholder":235},[205,4328,4329],{"class":207,"line":220},[205,4330,2411],{},[205,4332,4333],{"class":207,"line":226},[205,4334,4335],{},"  testWidgets('Counter increments', (WidgetTester tester) async {\n",[205,4337,4338],{"class":207,"line":232},[205,4339,4340],{},"    \u002F\u002F 构建 Widget\n",[205,4342,4343],{"class":207,"line":239},[205,4344,4345],{},"    await tester.pumpWidget(const MaterialApp(home: CounterPage()));\n",[205,4347,4348],{"class":207,"line":245},[205,4349,236],{"emptyLinePlaceholder":235},[205,4351,4352],{"class":207,"line":251},[205,4353,4354],{},"    \u002F\u002F 验证初始状态\n",[205,4356,4357],{"class":207,"line":257},[205,4358,4359],{},"    expect(find.text('0'), findsOneWidget);\n",[205,4361,4362],{"class":207,"line":263},[205,4363,4364],{},"    expect(find.text('1'), findsNothing);\n",[205,4366,4367],{"class":207,"line":397},[205,4368,236],{"emptyLinePlaceholder":235},[205,4370,4371],{"class":207,"line":505},[205,4372,4373],{},"    \u002F\u002F 模拟交互\n",[205,4375,4376],{"class":207,"line":511},[205,4377,4378],{},"    await tester.tap(find.byIcon(Icons.add));\n",[205,4380,4381],{"class":207,"line":818},[205,4382,4383],{},"    await tester.pump(); \u002F\u002F 触发一帧重建\n",[205,4385,4386],{"class":207,"line":824},[205,4387,236],{"emptyLinePlaceholder":235},[205,4389,4390],{"class":207,"line":830},[205,4391,4392],{},"    \u002F\u002F 验证结果\n",[205,4394,4395],{"class":207,"line":836},[205,4396,4397],{},"    expect(find.text('0'), findsNothing);\n",[205,4399,4400],{"class":207,"line":842},[205,4401,4402],{},"    expect(find.text('1'), findsOneWidget);\n",[205,4404,4405],{"class":207,"line":1749},[205,4406,2268],{},[205,4408,4409],{"class":207,"line":1754},[205,4410,236],{"emptyLinePlaceholder":235},[205,4412,4413],{"class":207,"line":1760},[205,4414,4415],{},"  testWidgets('shows loading then data', (tester) async {\n",[205,4417,4418],{"class":207,"line":1766},[205,4419,4420],{},"    await tester.pumpWidget(MyApp());\n",[205,4422,4423],{"class":207,"line":1772},[205,4424,236],{"emptyLinePlaceholder":235},[205,4426,4427],{"class":207,"line":1778},[205,4428,4429],{},"    \u002F\u002F 验证 loading 状态\n",[205,4431,4432],{"class":207,"line":1784},[205,4433,4434],{},"    expect(find.byType(CircularProgressIndicator), findsOneWidget);\n",[205,4436,4437],{"class":207,"line":1790},[205,4438,236],{"emptyLinePlaceholder":235},[205,4440,4441],{"class":207,"line":1795},[205,4442,4443],{},"    \u002F\u002F 等待异步操作完成\n",[205,4445,4446],{"class":207,"line":1800},[205,4447,4448],{},"    await tester.pumpAndSettle(); \u002F\u002F 持续 pump 直到没有待处理的帧\n",[205,4450,4451],{"class":207,"line":1805},[205,4452,236],{"emptyLinePlaceholder":235},[205,4454,4455],{"class":207,"line":1811},[205,4456,4457],{},"    \u002F\u002F 验证数据状态\n",[205,4459,4460],{"class":207,"line":1817},[205,4461,4462],{},"    expect(find.text('Data loaded'), findsOneWidget);\n",[205,4464,4465],{"class":207,"line":1823},[205,4466,2268],{},[205,4468,4469],{"class":207,"line":1828},[205,4470,400],{},[114,4472,4473],{},[117,4474,4475],{},"常用 Finder：",[196,4477,4479],{"className":198,"code":4478,"language":200,"meta":201,"style":201},"find.text('Hello');                    \u002F\u002F 按文本查找\nfind.byType(ElevatedButton);          \u002F\u002F 按类型查找\nfind.byIcon(Icons.add);               \u002F\u002F 按图标查找\nfind.byKey(const Key('submit_btn'));   \u002F\u002F 按 Key 查找\nfind.byWidgetPredicate((w) => ...);   \u002F\u002F 自定义条件\n",[105,4480,4481,4486,4491,4496,4501],{"__ignoreMap":201},[205,4482,4483],{"class":207,"line":208},[205,4484,4485],{},"find.text('Hello');                    \u002F\u002F 按文本查找\n",[205,4487,4488],{"class":207,"line":214},[205,4489,4490],{},"find.byType(ElevatedButton);          \u002F\u002F 按类型查找\n",[205,4492,4493],{"class":207,"line":220},[205,4494,4495],{},"find.byIcon(Icons.add);               \u002F\u002F 按图标查找\n",[205,4497,4498],{"class":207,"line":226},[205,4499,4500],{},"find.byKey(const Key('submit_btn'));   \u002F\u002F 按 Key 查找\n",[205,4502,4503],{"class":207,"line":232},[205,4504,4505],{},"find.byWidgetPredicate((w) => ...);   \u002F\u002F 自定义条件\n",[114,4507,4508],{},[117,4509,4510],{},"Mock 依赖（使用 mockito\u002Fmocktail）：",[196,4512,4514],{"className":198,"code":4513,"language":200,"meta":201,"style":201},"class MockAuthRepo extends Mock implements AuthRepository {}\n\ntestWidgets('shows error on login failure', (tester) async {\n  final mockRepo = MockAuthRepo();\n  when(() => mockRepo.login(any(), any()))\n      .thenThrow(AuthException('Invalid'));\n\n  await tester.pumpWidget(\n    ProviderScope(\n      overrides: [authRepoProvider.overrideWithValue(mockRepo)],\n      child: const MyApp(),\n    ),\n  );\n\n  await tester.enterText(find.byKey(Key('email')), 'test@test.com');\n  await tester.enterText(find.byKey(Key('password')), 'wrong');\n  await tester.tap(find.text('Login'));\n  await tester.pumpAndSettle();\n\n  expect(find.text('Invalid'), findsOneWidget);\n});\n",[105,4515,4516,4521,4525,4530,4535,4540,4545,4549,4554,4559,4564,4569,4573,4578,4582,4587,4592,4597,4602,4606,4611],{"__ignoreMap":201},[205,4517,4518],{"class":207,"line":208},[205,4519,4520],{},"class MockAuthRepo extends Mock implements AuthRepository {}\n",[205,4522,4523],{"class":207,"line":214},[205,4524,236],{"emptyLinePlaceholder":235},[205,4526,4527],{"class":207,"line":220},[205,4528,4529],{},"testWidgets('shows error on login failure', (tester) async {\n",[205,4531,4532],{"class":207,"line":226},[205,4533,4534],{},"  final mockRepo = MockAuthRepo();\n",[205,4536,4537],{"class":207,"line":232},[205,4538,4539],{},"  when(() => mockRepo.login(any(), any()))\n",[205,4541,4542],{"class":207,"line":239},[205,4543,4544],{},"      .thenThrow(AuthException('Invalid'));\n",[205,4546,4547],{"class":207,"line":245},[205,4548,236],{"emptyLinePlaceholder":235},[205,4550,4551],{"class":207,"line":251},[205,4552,4553],{},"  await tester.pumpWidget(\n",[205,4555,4556],{"class":207,"line":257},[205,4557,4558],{},"    ProviderScope(\n",[205,4560,4561],{"class":207,"line":263},[205,4562,4563],{},"      overrides: [authRepoProvider.overrideWithValue(mockRepo)],\n",[205,4565,4566],{"class":207,"line":397},[205,4567,4568],{},"      child: const MyApp(),\n",[205,4570,4571],{"class":207,"line":505},[205,4572,1207],{},[205,4574,4575],{"class":207,"line":511},[205,4576,4577],{},"  );\n",[205,4579,4580],{"class":207,"line":818},[205,4581,236],{"emptyLinePlaceholder":235},[205,4583,4584],{"class":207,"line":824},[205,4585,4586],{},"  await tester.enterText(find.byKey(Key('email')), 'test@test.com');\n",[205,4588,4589],{"class":207,"line":830},[205,4590,4591],{},"  await tester.enterText(find.byKey(Key('password')), 'wrong');\n",[205,4593,4594],{"class":207,"line":836},[205,4595,4596],{},"  await tester.tap(find.text('Login'));\n",[205,4598,4599],{"class":207,"line":842},[205,4600,4601],{},"  await tester.pumpAndSettle();\n",[205,4603,4604],{"class":207,"line":1749},[205,4605,236],{"emptyLinePlaceholder":235},[205,4607,4608],{"class":207,"line":1754},[205,4609,4610],{},"  expect(find.text('Invalid'), findsOneWidget);\n",[205,4612,4613],{"class":207,"line":1760},[205,4614,2106],{},[10,4616],{},[100,4618,4620],{"id":4619},"q92-什么是-golden-test怎么使用","Q9.2: 什么是 Golden Test？怎么使用？",[114,4622,4623],{},[117,4624,119],{},[114,4626,4627],{},"Golden Test（黄金测试 \u002F 快照测试）将 Widget 渲染结果与预存的参考图片进行像素级对比：",[196,4629,4631],{"className":198,"code":4630,"language":200,"meta":201,"style":201},"testWidgets('MyButton golden test', (tester) async {\n  await tester.pumpWidget(\n    MaterialApp(\n      home: Scaffold(\n        body: Center(\n          child: MyCustomButton(label: 'Click Me'),\n        ),\n      ),\n    ),\n  );\n\n  \u002F\u002F 首次运行生成参考图，之后每次运行对比\n  await expectLater(\n    find.byType(MyCustomButton),\n    matchesGoldenFile('goldens\u002Fmy_button.png'),\n  );\n});\n",[105,4632,4633,4638,4642,4647,4652,4657,4662,4666,4670,4674,4678,4682,4687,4692,4697,4702,4706],{"__ignoreMap":201},[205,4634,4635],{"class":207,"line":208},[205,4636,4637],{},"testWidgets('MyButton golden test', (tester) async {\n",[205,4639,4640],{"class":207,"line":214},[205,4641,4553],{},[205,4643,4644],{"class":207,"line":220},[205,4645,4646],{},"    MaterialApp(\n",[205,4648,4649],{"class":207,"line":226},[205,4650,4651],{},"      home: Scaffold(\n",[205,4653,4654],{"class":207,"line":232},[205,4655,4656],{},"        body: Center(\n",[205,4658,4659],{"class":207,"line":239},[205,4660,4661],{},"          child: MyCustomButton(label: 'Click Me'),\n",[205,4663,4664],{"class":207,"line":245},[205,4665,2969],{},[205,4667,4668],{"class":207,"line":251},[205,4669,4090],{},[205,4671,4672],{"class":207,"line":257},[205,4673,1207],{},[205,4675,4676],{"class":207,"line":263},[205,4677,4577],{},[205,4679,4680],{"class":207,"line":397},[205,4681,236],{"emptyLinePlaceholder":235},[205,4683,4684],{"class":207,"line":505},[205,4685,4686],{},"  \u002F\u002F 首次运行生成参考图，之后每次运行对比\n",[205,4688,4689],{"class":207,"line":511},[205,4690,4691],{},"  await expectLater(\n",[205,4693,4694],{"class":207,"line":818},[205,4695,4696],{},"    find.byType(MyCustomButton),\n",[205,4698,4699],{"class":207,"line":824},[205,4700,4701],{},"    matchesGoldenFile('goldens\u002Fmy_button.png'),\n",[205,4703,4704],{"class":207,"line":830},[205,4705,4577],{},[205,4707,4708],{"class":207,"line":836},[205,4709,2106],{},[196,4711,4715],{"className":4712,"code":4713,"language":4714,"meta":201,"style":201},"language-bash shiki shiki-themes github-light github-dark","# 生成\u002F更新 golden 文件\nflutter test --update-goldens\n\n# 正常测试（对比）\nflutter test\n","bash",[105,4716,4717,4723,4737,4741,4746],{"__ignoreMap":201},[205,4718,4719],{"class":207,"line":208},[205,4720,4722],{"class":4721},"sJ8bj","# 生成\u002F更新 golden 文件\n",[205,4724,4725,4729,4733],{"class":207,"line":214},[205,4726,4728],{"class":4727},"sScJk","flutter",[205,4730,4732],{"class":4731},"sZZnC"," test",[205,4734,4736],{"class":4735},"sj4cs"," --update-goldens\n",[205,4738,4739],{"class":207,"line":220},[205,4740,236],{"emptyLinePlaceholder":235},[205,4742,4743],{"class":207,"line":226},[205,4744,4745],{"class":4721},"# 正常测试（对比）\n",[205,4747,4748,4750],{"class":207,"line":232},[205,4749,4728],{"class":4727},[205,4751,4752],{"class":4731}," test\n",[114,4754,4755],{},[117,4756,4757],{},"注意事项：",[409,4759,4760,4763,4770],{},[20,4761,4762],{},"不同平台渲染可能有细微差异，建议在 CI 中固定平台",[20,4764,4765,4766,4769],{},"字体渲染差异可能导致误报，可使用 ",[105,4767,4768],{},"flutter_test"," 提供的默认字体",[20,4771,4772],{},"适合用于设计系统组件库、自定义绘制组件的回归测试",[10,4774],{},[13,4776,4778],{"id":4777},"_10-架构设计","10. 架构设计",[100,4780,4782],{"id":4781},"q101-介绍-flutter-中常用的架构模式","Q10.1: 介绍 Flutter 中常用的架构模式",[114,4784,4785],{},[117,4786,119],{},[114,4788,4789],{},[117,4790,4791],{},"1. Clean Architecture（推荐用于大型项目）：",[196,4793,4796],{"className":4794,"code":4795,"language":996},[994],"lib\u002F\n├── core\u002F                  # 公共工具、常量、错误处理\n├── features\u002F\n│   └── auth\u002F\n│       ├── data\u002F          # 数据层\n│       │   ├── datasources\u002F    # API、本地数据库\n│       │   ├── models\u002F         # JSON 序列化模型（DTO）\n│       │   └── repositories\u002F   # Repository 实现\n│       ├── domain\u002F        # 领域层（纯 Dart，无 Flutter 依赖）\n│       │   ├── entities\u002F       # 业务实体\n│       │   ├── repositories\u002F   # Repository 接口（抽象类）\n│       │   └── usecases\u002F       # 用例（业务逻辑）\n│       └── presentation\u002F  # 表现层\n│           ├── bloc\u002F           # BLoC \u002F Cubit\n│           ├── pages\u002F          # 页面\n│           └── widgets\u002F        # UI 组件\n",[105,4797,4795],{"__ignoreMap":201},[196,4799,4801],{"className":198,"code":4800,"language":200,"meta":201,"style":201},"\u002F\u002F Domain 层：纯业务逻辑，不依赖任何框架\nabstract class AuthRepository {\n  Future\u003CEither\u003CFailure, User>> login(String email, String password);\n}\n\nclass LoginUseCase {\n  final AuthRepository repository;\n  LoginUseCase(this.repository);\n\n  Future\u003CEither\u003CFailure, User>> call(String email, String password) {\n    return repository.login(email, password);\n  }\n}\n\n\u002F\u002F Data 层：实现 Repository 接口\nclass AuthRepositoryImpl implements AuthRepository {\n  final AuthRemoteDataSource remote;\n  final AuthLocalDataSource local;\n\n  @override\n  Future\u003CEither\u003CFailure, User>> login(String email, String password) async {\n    try {\n      final model = await remote.login(email, password);\n      await local.cacheToken(model.token);\n      return Right(model.toEntity());\n    } on ServerException catch (e) {\n      return Left(ServerFailure(e.message));\n    }\n  }\n}\n",[105,4802,4803,4808,4813,4818,4822,4826,4831,4836,4841,4845,4850,4855,4859,4863,4867,4872,4877,4882,4887,4891,4895,4900,4904,4909,4914,4919,4924,4929,4933,4937],{"__ignoreMap":201},[205,4804,4805],{"class":207,"line":208},[205,4806,4807],{},"\u002F\u002F Domain 层：纯业务逻辑，不依赖任何框架\n",[205,4809,4810],{"class":207,"line":214},[205,4811,4812],{},"abstract class AuthRepository {\n",[205,4814,4815],{"class":207,"line":220},[205,4816,4817],{},"  Future\u003CEither\u003CFailure, User>> login(String email, String password);\n",[205,4819,4820],{"class":207,"line":226},[205,4821,400],{},[205,4823,4824],{"class":207,"line":232},[205,4825,236],{"emptyLinePlaceholder":235},[205,4827,4828],{"class":207,"line":239},[205,4829,4830],{},"class LoginUseCase {\n",[205,4832,4833],{"class":207,"line":245},[205,4834,4835],{},"  final AuthRepository repository;\n",[205,4837,4838],{"class":207,"line":251},[205,4839,4840],{},"  LoginUseCase(this.repository);\n",[205,4842,4843],{"class":207,"line":257},[205,4844,236],{"emptyLinePlaceholder":235},[205,4846,4847],{"class":207,"line":263},[205,4848,4849],{},"  Future\u003CEither\u003CFailure, User>> call(String email, String password) {\n",[205,4851,4852],{"class":207,"line":397},[205,4853,4854],{},"    return repository.login(email, password);\n",[205,4856,4857],{"class":207,"line":505},[205,4858,394],{},[205,4860,4861],{"class":207,"line":511},[205,4862,400],{},[205,4864,4865],{"class":207,"line":818},[205,4866,236],{"emptyLinePlaceholder":235},[205,4868,4869],{"class":207,"line":824},[205,4870,4871],{},"\u002F\u002F Data 层：实现 Repository 接口\n",[205,4873,4874],{"class":207,"line":830},[205,4875,4876],{},"class AuthRepositoryImpl implements AuthRepository {\n",[205,4878,4879],{"class":207,"line":836},[205,4880,4881],{},"  final AuthRemoteDataSource remote;\n",[205,4883,4884],{"class":207,"line":842},[205,4885,4886],{},"  final AuthLocalDataSource local;\n",[205,4888,4889],{"class":207,"line":1749},[205,4890,236],{"emptyLinePlaceholder":235},[205,4892,4893],{"class":207,"line":1754},[205,4894,964],{},[205,4896,4897],{"class":207,"line":1760},[205,4898,4899],{},"  Future\u003CEither\u003CFailure, User>> login(String email, String password) async {\n",[205,4901,4902],{"class":207,"line":1766},[205,4903,3579],{},[205,4905,4906],{"class":207,"line":1772},[205,4907,4908],{},"      final model = await remote.login(email, password);\n",[205,4910,4911],{"class":207,"line":1778},[205,4912,4913],{},"      await local.cacheToken(model.token);\n",[205,4915,4916],{"class":207,"line":1784},[205,4917,4918],{},"      return Right(model.toEntity());\n",[205,4920,4921],{"class":207,"line":1790},[205,4922,4923],{},"    } on ServerException catch (e) {\n",[205,4925,4926],{"class":207,"line":1795},[205,4927,4928],{},"      return Left(ServerFailure(e.message));\n",[205,4930,4931],{"class":207,"line":1800},[205,4932,1787],{},[205,4934,4935],{"class":207,"line":1805},[205,4936,394],{},[205,4938,4939],{"class":207,"line":1811},[205,4940,400],{},[114,4942,4943],{},[117,4944,4945],{},"2. MVVM（适合中型项目）：",[196,4947,4949],{"className":198,"code":4948,"language":200,"meta":201,"style":201},"\u002F\u002F ViewModel\nclass LoginViewModel extends ChangeNotifier {\n  final AuthRepository _repo;\n  bool isLoading = false;\n  String? error;\n\n  Future\u003Cvoid> login(String email, String password) async {\n    isLoading = true;\n    notifyListeners();\n\n    final result = await _repo.login(email, password);\n    result.fold(\n      (failure) => error = failure.message,\n      (user) => { \u002F* navigate *\u002F },\n    );\n\n    isLoading = false;\n    notifyListeners();\n  }\n}\n\n\u002F\u002F View\nclass LoginPage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return ChangeNotifierProvider(\n      create: (_) => LoginViewModel(context.read\u003CAuthRepository>()),\n      child: Consumer\u003CLoginViewModel>(\n        builder: (context, vm, _) {\n          if (vm.isLoading) return CircularProgressIndicator();\n          \u002F\u002F ...\n        },\n      ),\n    );\n  }\n}\n",[105,4950,4951,4956,4961,4966,4971,4976,4980,4985,4990,4995,4999,5004,5009,5014,5019,5023,5027,5032,5036,5040,5044,5048,5053,5058,5062,5067,5072,5077,5082,5087,5092,5097,5102,5106,5111,5116],{"__ignoreMap":201},[205,4952,4953],{"class":207,"line":208},[205,4954,4955],{},"\u002F\u002F ViewModel\n",[205,4957,4958],{"class":207,"line":214},[205,4959,4960],{},"class LoginViewModel extends ChangeNotifier {\n",[205,4962,4963],{"class":207,"line":220},[205,4964,4965],{},"  final AuthRepository _repo;\n",[205,4967,4968],{"class":207,"line":226},[205,4969,4970],{},"  bool isLoading = false;\n",[205,4972,4973],{"class":207,"line":232},[205,4974,4975],{},"  String? error;\n",[205,4977,4978],{"class":207,"line":239},[205,4979,236],{"emptyLinePlaceholder":235},[205,4981,4982],{"class":207,"line":245},[205,4983,4984],{},"  Future\u003Cvoid> login(String email, String password) async {\n",[205,4986,4987],{"class":207,"line":251},[205,4988,4989],{},"    isLoading = true;\n",[205,4991,4992],{"class":207,"line":257},[205,4993,4994],{},"    notifyListeners();\n",[205,4996,4997],{"class":207,"line":263},[205,4998,236],{"emptyLinePlaceholder":235},[205,5000,5001],{"class":207,"line":397},[205,5002,5003],{},"    final result = await _repo.login(email, password);\n",[205,5005,5006],{"class":207,"line":505},[205,5007,5008],{},"    result.fold(\n",[205,5010,5011],{"class":207,"line":511},[205,5012,5013],{},"      (failure) => error = failure.message,\n",[205,5015,5016],{"class":207,"line":818},[205,5017,5018],{},"      (user) => { \u002F* navigate *\u002F },\n",[205,5020,5021],{"class":207,"line":824},[205,5022,2984],{},[205,5024,5025],{"class":207,"line":830},[205,5026,236],{"emptyLinePlaceholder":235},[205,5028,5029],{"class":207,"line":836},[205,5030,5031],{},"    isLoading = false;\n",[205,5033,5034],{"class":207,"line":842},[205,5035,4994],{},[205,5037,5038],{"class":207,"line":1749},[205,5039,394],{},[205,5041,5042],{"class":207,"line":1754},[205,5043,400],{},[205,5045,5046],{"class":207,"line":1760},[205,5047,236],{"emptyLinePlaceholder":235},[205,5049,5050],{"class":207,"line":1766},[205,5051,5052],{},"\u002F\u002F View\n",[205,5054,5055],{"class":207,"line":1772},[205,5056,5057],{},"class LoginPage extends StatelessWidget {\n",[205,5059,5060],{"class":207,"line":1778},[205,5061,964],{},[205,5063,5064],{"class":207,"line":1784},[205,5065,5066],{},"  Widget build(BuildContext context) {\n",[205,5068,5069],{"class":207,"line":1790},[205,5070,5071],{},"    return ChangeNotifierProvider(\n",[205,5073,5074],{"class":207,"line":1795},[205,5075,5076],{},"      create: (_) => LoginViewModel(context.read\u003CAuthRepository>()),\n",[205,5078,5079],{"class":207,"line":1800},[205,5080,5081],{},"      child: Consumer\u003CLoginViewModel>(\n",[205,5083,5084],{"class":207,"line":1805},[205,5085,5086],{},"        builder: (context, vm, _) {\n",[205,5088,5089],{"class":207,"line":1811},[205,5090,5091],{},"          if (vm.isLoading) return CircularProgressIndicator();\n",[205,5093,5094],{"class":207,"line":1817},[205,5095,5096],{},"          \u002F\u002F ...\n",[205,5098,5099],{"class":207,"line":1823},[205,5100,5101],{},"        },\n",[205,5103,5104],{"class":207,"line":1828},[205,5105,4090],{},[205,5107,5109],{"class":207,"line":5108},34,[205,5110,2984],{},[205,5112,5114],{"class":207,"line":5113},35,[205,5115,394],{},[205,5117,5119],{"class":207,"line":5118},36,[205,5120,400],{},[10,5122],{},[100,5124,5126],{"id":5125},"q102-如何实现依赖注入di","Q10.2: 如何实现依赖注入（DI）？",[114,5128,5129],{},[117,5130,119],{},[114,5132,5133],{},[117,5134,5135],{},"1. get_it（Service Locator 模式）：",[196,5137,5139],{"className":198,"code":5138,"language":200,"meta":201,"style":201},"final sl = GetIt.instance;\n\nvoid setupDependencies() {\n  \u002F\u002F 单例\n  sl.registerLazySingleton\u003CApiClient>(() => ApiClient());\n  sl.registerLazySingleton\u003CAuthRepository>(\n    () => AuthRepositoryImpl(sl\u003CApiClient>()),\n  );\n\n  \u002F\u002F 工厂（每次获取新实例）\n  sl.registerFactory\u003CLoginBloc>(\n    () => LoginBloc(sl\u003CAuthRepository>()),\n  );\n}\n\n\u002F\u002F 使用\nfinal bloc = sl\u003CLoginBloc>();\n",[105,5140,5141,5146,5150,5155,5160,5165,5170,5175,5179,5183,5188,5193,5198,5202,5206,5210,5214],{"__ignoreMap":201},[205,5142,5143],{"class":207,"line":208},[205,5144,5145],{},"final sl = GetIt.instance;\n",[205,5147,5148],{"class":207,"line":214},[205,5149,236],{"emptyLinePlaceholder":235},[205,5151,5152],{"class":207,"line":220},[205,5153,5154],{},"void setupDependencies() {\n",[205,5156,5157],{"class":207,"line":226},[205,5158,5159],{},"  \u002F\u002F 单例\n",[205,5161,5162],{"class":207,"line":232},[205,5163,5164],{},"  sl.registerLazySingleton\u003CApiClient>(() => ApiClient());\n",[205,5166,5167],{"class":207,"line":239},[205,5168,5169],{},"  sl.registerLazySingleton\u003CAuthRepository>(\n",[205,5171,5172],{"class":207,"line":245},[205,5173,5174],{},"    () => AuthRepositoryImpl(sl\u003CApiClient>()),\n",[205,5176,5177],{"class":207,"line":251},[205,5178,4577],{},[205,5180,5181],{"class":207,"line":257},[205,5182,236],{"emptyLinePlaceholder":235},[205,5184,5185],{"class":207,"line":263},[205,5186,5187],{},"  \u002F\u002F 工厂（每次获取新实例）\n",[205,5189,5190],{"class":207,"line":397},[205,5191,5192],{},"  sl.registerFactory\u003CLoginBloc>(\n",[205,5194,5195],{"class":207,"line":505},[205,5196,5197],{},"    () => LoginBloc(sl\u003CAuthRepository>()),\n",[205,5199,5200],{"class":207,"line":511},[205,5201,4577],{},[205,5203,5204],{"class":207,"line":818},[205,5205,400],{},[205,5207,5208],{"class":207,"line":824},[205,5209,236],{"emptyLinePlaceholder":235},[205,5211,5212],{"class":207,"line":830},[205,5213,728],{},[205,5215,5216],{"class":207,"line":836},[205,5217,5218],{},"final bloc = sl\u003CLoginBloc>();\n",[114,5220,5221],{},[117,5222,5223],{},"2. Riverpod（推荐，编译安全的 DI）：",[196,5225,5227],{"className":198,"code":5226,"language":200,"meta":201,"style":201},"final apiClientProvider = Provider((ref) => ApiClient());\n\nfinal authRepoProvider = Provider((ref) {\n  return AuthRepositoryImpl(ref.read(apiClientProvider));\n});\n\nfinal loginBlocProvider = Provider.autoDispose((ref) {\n  return LoginBloc(ref.read(authRepoProvider));\n});\n",[105,5228,5229,5234,5238,5243,5248,5252,5256,5261,5266],{"__ignoreMap":201},[205,5230,5231],{"class":207,"line":208},[205,5232,5233],{},"final apiClientProvider = Provider((ref) => ApiClient());\n",[205,5235,5236],{"class":207,"line":214},[205,5237,236],{"emptyLinePlaceholder":235},[205,5239,5240],{"class":207,"line":220},[205,5241,5242],{},"final authRepoProvider = Provider((ref) {\n",[205,5244,5245],{"class":207,"line":226},[205,5246,5247],{},"  return AuthRepositoryImpl(ref.read(apiClientProvider));\n",[205,5249,5250],{"class":207,"line":232},[205,5251,2106],{},[205,5253,5254],{"class":207,"line":239},[205,5255,236],{"emptyLinePlaceholder":235},[205,5257,5258],{"class":207,"line":245},[205,5259,5260],{},"final loginBlocProvider = Provider.autoDispose((ref) {\n",[205,5262,5263],{"class":207,"line":251},[205,5264,5265],{},"  return LoginBloc(ref.read(authRepoProvider));\n",[205,5267,5268],{"class":207,"line":257},[205,5269,2106],{},[114,5271,5272],{},[117,5273,5274],{},"3. Injectable + get_it（代码生成）：",[196,5276,5278],{"className":198,"code":5277,"language":200,"meta":201,"style":201},"@injectable\nclass AuthRepositoryImpl implements AuthRepository {\n  final ApiClient client;\n\n  @factoryMethod\n  AuthRepositoryImpl(this.client);\n}\n\n\u002F\u002F 自动生成注册代码\n@InjectableInit()\nvoid configureDependencies() => getIt.init();\n",[105,5279,5280,5285,5289,5294,5298,5303,5308,5312,5316,5321,5326],{"__ignoreMap":201},[205,5281,5282],{"class":207,"line":208},[205,5283,5284],{},"@injectable\n",[205,5286,5287],{"class":207,"line":214},[205,5288,4876],{},[205,5290,5291],{"class":207,"line":220},[205,5292,5293],{},"  final ApiClient client;\n",[205,5295,5296],{"class":207,"line":226},[205,5297,236],{"emptyLinePlaceholder":235},[205,5299,5300],{"class":207,"line":232},[205,5301,5302],{},"  @factoryMethod\n",[205,5304,5305],{"class":207,"line":239},[205,5306,5307],{},"  AuthRepositoryImpl(this.client);\n",[205,5309,5310],{"class":207,"line":245},[205,5311,400],{},[205,5313,5314],{"class":207,"line":251},[205,5315,236],{"emptyLinePlaceholder":235},[205,5317,5318],{"class":207,"line":257},[205,5319,5320],{},"\u002F\u002F 自动生成注册代码\n",[205,5322,5323],{"class":207,"line":263},[205,5324,5325],{},"@InjectableInit()\n",[205,5327,5328],{"class":207,"line":397},[205,5329,5330],{},"void configureDependencies() => getIt.init();\n",[10,5332],{},[13,5334,5336],{"id":5335},"_11-包管理与编译","11. 包管理与编译",[100,5338,5340],{"id":5339},"q111-flutter-的编译模式有哪些debug-和-release-有什么区别","Q11.1: Flutter 的编译模式有哪些？Debug 和 Release 有什么区别？",[114,5342,5343],{},[117,5344,119],{},[121,5346,5347,5366],{},[124,5348,5349],{},[127,5350,5351,5354,5357,5360,5363],{},[130,5352,5353],{},"模式",[130,5355,5356],{},"编译方式",[130,5358,5359],{},"JIT",[130,5361,5362],{},"AOT",[130,5364,5365],{},"特点",[142,5367,5368,5385,5399],{},[127,5369,5370,5373,5376,5379,5382],{},[147,5371,5372],{},"Debug",[147,5374,5375],{},"Kernel JIT",[147,5377,5378],{},"✅",[147,5380,5381],{},"❌",[147,5383,5384],{},"Hot Reload、断言启用、性能差",[127,5386,5387,5390,5392,5394,5396],{},[147,5388,5389],{},"Profile",[147,5391,5362],{},[147,5393,5381],{},[147,5395,5378],{},[147,5397,5398],{},"性能分析、DevTools 可用",[127,5400,5401,5404,5406,5408,5410],{},[147,5402,5403],{},"Release",[147,5405,5362],{},[147,5407,5381],{},[147,5409,5378],{},[147,5411,5412],{},"最高性能、无调试信息、Tree Shaking",[114,5414,5415],{},[117,5416,2327],{},[196,5418,5420],{"className":198,"code":5419,"language":200,"meta":201,"style":201},"\u002F\u002F 条件编译\nif (kDebugMode) {\n  print('Debug only log');\n}\n\nif (kReleaseMode) {\n  \u002F\u002F Release 专有逻辑\n}\n\n\u002F\u002F assert 只在 Debug 模式执行\nassert(value != null, 'Value should not be null');\n",[105,5421,5422,5427,5432,5437,5441,5445,5450,5455,5459,5463,5468],{"__ignoreMap":201},[205,5423,5424],{"class":207,"line":208},[205,5425,5426],{},"\u002F\u002F 条件编译\n",[205,5428,5429],{"class":207,"line":214},[205,5430,5431],{},"if (kDebugMode) {\n",[205,5433,5434],{"class":207,"line":220},[205,5435,5436],{},"  print('Debug only log');\n",[205,5438,5439],{"class":207,"line":226},[205,5440,400],{},[205,5442,5443],{"class":207,"line":232},[205,5444,236],{"emptyLinePlaceholder":235},[205,5446,5447],{"class":207,"line":239},[205,5448,5449],{},"if (kReleaseMode) {\n",[205,5451,5452],{"class":207,"line":245},[205,5453,5454],{},"  \u002F\u002F Release 专有逻辑\n",[205,5456,5457],{"class":207,"line":251},[205,5458,400],{},[205,5460,5461],{"class":207,"line":257},[205,5462,236],{"emptyLinePlaceholder":235},[205,5464,5465],{"class":207,"line":263},[205,5466,5467],{},"\u002F\u002F assert 只在 Debug 模式执行\n",[205,5469,5470],{"class":207,"line":397},[205,5471,5472],{},"assert(value != null, 'Value should not be null');\n",[114,5474,5475,5478],{},[117,5476,5477],{},"Tree Shaking："," Release 模式下，编译器会移除未使用的代码，减小包体积。",[114,5480,5481],{},[117,5482,5483],{},"Hot Reload vs Hot Restart：",[409,5485,5486,5489],{},[20,5487,5488],{},"Hot Reload：保持应用状态，只更新修改的代码（亚秒级）",[20,5490,5491,5492],{},"Hot Restart：重置应用状态，重新执行 ",[105,5493,5494],{},"main()",[10,5496],{},[100,5498,5500],{"id":5499},"q112-如何减小-flutter-应用的包体积","Q11.2: 如何减小 Flutter 应用的包体积？",[114,5502,5503],{},[117,5504,119],{},[196,5506,5508],{"className":4712,"code":5507,"language":4714,"meta":201,"style":201},"# 1. 分析包体积\nflutter build apk --analyze-size\nflutter build ios --analyze-size\n",[105,5509,5510,5515,5528],{"__ignoreMap":201},[205,5511,5512],{"class":207,"line":208},[205,5513,5514],{"class":4721},"# 1. 分析包体积\n",[205,5516,5517,5519,5522,5525],{"class":207,"line":214},[205,5518,4728],{"class":4727},[205,5520,5521],{"class":4731}," build",[205,5523,5524],{"class":4731}," apk",[205,5526,5527],{"class":4735}," --analyze-size\n",[205,5529,5530,5532,5534,5537],{"class":207,"line":220},[205,5531,4728],{"class":4727},[205,5533,5521],{"class":4731},[205,5535,5536],{"class":4731}," ios",[205,5538,5527],{"class":4735},[114,5540,5541],{},[117,5542,5543],{},"关键策略：",[196,5545,5549],{"className":5546,"code":5547,"language":5548,"meta":201,"style":201},"language-yaml shiki shiki-themes github-light github-dark","# 2. 使用 --split-debug-info 分离调试信息\n# flutter build apk --split-debug-info=debug-info\u002F\n\n# 3. 启用混淆\n# flutter build apk --obfuscate --split-debug-info=debug-info\u002F\n","yaml",[105,5550,5551,5556,5561,5565,5570],{"__ignoreMap":201},[205,5552,5553],{"class":207,"line":208},[205,5554,5555],{"class":4721},"# 2. 使用 --split-debug-info 分离调试信息\n",[205,5557,5558],{"class":207,"line":214},[205,5559,5560],{"class":4721},"# flutter build apk --split-debug-info=debug-info\u002F\n",[205,5562,5563],{"class":207,"line":220},[205,5564,236],{"emptyLinePlaceholder":235},[205,5566,5567],{"class":207,"line":226},[205,5568,5569],{"class":4721},"# 3. 启用混淆\n",[205,5571,5572],{"class":207,"line":232},[205,5573,5574],{"class":4721},"# flutter build apk --obfuscate --split-debug-info=debug-info\u002F\n",[196,5576,5578],{"className":198,"code":5577,"language":200,"meta":201,"style":201},"\u002F\u002F 4. 按需加载资源\n\u002F\u002F pubspec.yaml 中只声明需要的资源\nflutter:\n  assets:\n    - assets\u002Fimages\u002F   # 不要包含不需要的大图\n\n\u002F\u002F 5. 使用 deferred loading（延迟加载）\nimport 'package:my_app\u002Fheavy_feature.dart' deferred as heavy;\n\nFuture\u003Cvoid> loadFeature() async {\n  await heavy.loadLibrary();\n  heavy.showFeature();\n}\n",[105,5579,5580,5585,5590,5595,5600,5605,5609,5614,5619,5623,5628,5633,5638],{"__ignoreMap":201},[205,5581,5582],{"class":207,"line":208},[205,5583,5584],{},"\u002F\u002F 4. 按需加载资源\n",[205,5586,5587],{"class":207,"line":214},[205,5588,5589],{},"\u002F\u002F pubspec.yaml 中只声明需要的资源\n",[205,5591,5592],{"class":207,"line":220},[205,5593,5594],{},"flutter:\n",[205,5596,5597],{"class":207,"line":226},[205,5598,5599],{},"  assets:\n",[205,5601,5602],{"class":207,"line":232},[205,5603,5604],{},"    - assets\u002Fimages\u002F   # 不要包含不需要的大图\n",[205,5606,5607],{"class":207,"line":239},[205,5608,236],{"emptyLinePlaceholder":235},[205,5610,5611],{"class":207,"line":245},[205,5612,5613],{},"\u002F\u002F 5. 使用 deferred loading（延迟加载）\n",[205,5615,5616],{"class":207,"line":251},[205,5617,5618],{},"import 'package:my_app\u002Fheavy_feature.dart' deferred as heavy;\n",[205,5620,5621],{"class":207,"line":257},[205,5622,236],{"emptyLinePlaceholder":235},[205,5624,5625],{"class":207,"line":263},[205,5626,5627],{},"Future\u003Cvoid> loadFeature() async {\n",[205,5629,5630],{"class":207,"line":397},[205,5631,5632],{},"  await heavy.loadLibrary();\n",[205,5634,5635],{"class":207,"line":505},[205,5636,5637],{},"  heavy.showFeature();\n",[205,5639,5640],{"class":207,"line":511},[205,5641,400],{},[196,5643,5645],{"className":5546,"code":5644,"language":5548,"meta":201,"style":201},"# 6. 精简依赖，移除不使用的包\ndependencies:\n  # 定期检查是否所有依赖都在使用\n",[105,5646,5647,5652,5662],{"__ignoreMap":201},[205,5648,5649],{"class":207,"line":208},[205,5650,5651],{"class":4721},"# 6. 精简依赖，移除不使用的包\n",[205,5653,5654,5658],{"class":207,"line":214},[205,5655,5657],{"class":5656},"s9eBZ","dependencies",[205,5659,5661],{"class":5660},"sVt8B",":\n",[205,5663,5664],{"class":207,"line":220},[205,5665,5666],{"class":4721},"  # 定期检查是否所有依赖都在使用\n",[196,5668,5671],{"className":5669,"code":5670,"language":996},[994],"# 7. 使用 App Bundle（Android）\nflutter build appbundle  # 比 APK 小约 20%\n",[105,5672,5670],{"__ignoreMap":201},[10,5674],{},[13,5676,5678],{"id":5677},"_12-实战场景题","12. 实战场景题",[100,5680,5682],{"id":5681},"q121-如何实现一个无限滚动列表并处理加载状态和错误状态","Q12.1: 如何实现一个无限滚动列表，并处理加载状态和错误状态？",[114,5684,5685],{},[117,5686,119],{},[196,5688,5690],{"className":198,"code":5689,"language":200,"meta":201,"style":201},"class InfiniteListPage extends StatefulWidget {\n  @override\n  State\u003CInfiniteListPage> createState() => _InfiniteListPageState();\n}\n\nclass _InfiniteListPageState extends State\u003CInfiniteListPage> {\n  final _scrollController = ScrollController();\n  final List\u003CItem> _items = [];\n  bool _isLoading = false;\n  bool _hasMore = true;\n  String? _error;\n  int _page = 1;\n\n  @override\n  void initState() {\n    super.initState();\n    _loadMore();\n    _scrollController.addListener(_onScroll);\n  }\n\n  void _onScroll() {\n    if (_scrollController.position.pixels >=\n        _scrollController.position.maxScrollExtent - 200) {\n      _loadMore();\n    }\n  }\n\n  Future\u003Cvoid> _loadMore() async {\n    if (_isLoading || !_hasMore) return;\n\n    setState(() {\n      _isLoading = true;\n      _error = null;\n    });\n\n    try {\n      final newItems = await api.fetchItems(page: _page, limit: 20);\n      setState(() {\n        _items.addAll(newItems);\n        _page++;\n        _hasMore = newItems.length == 20;\n        _isLoading = false;\n      });\n    } catch (e) {\n      setState(() {\n        _error = e.toString();\n        _isLoading = false;\n      });\n    }\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return RefreshIndicator(\n      onRefresh: () async {\n        _page = 1;\n        _items.clear();\n        _hasMore = true;\n        await _loadMore();\n      },\n      child: ListView.builder(\n        controller: _scrollController,\n        itemCount: _items.length + (_hasMore ? 1 : 0),\n        itemBuilder: (context, index) {\n          if (index == _items.length) {\n            if (_error != null) {\n              return _ErrorTile(\n                error: _error!,\n                onRetry: _loadMore,\n              );\n            }\n            return const Center(child: CircularProgressIndicator());\n          }\n          return ItemTile(item: _items[index]);\n        },\n      ),\n    );\n  }\n\n  @override\n  void dispose() {\n    _scrollController.dispose();\n    super.dispose();\n  }\n}\n",[105,5691,5692,5697,5701,5706,5710,5714,5719,5724,5729,5734,5739,5744,5749,5753,5757,5761,5766,5771,5776,5780,5784,5789,5794,5799,5804,5808,5812,5816,5821,5826,5830,5835,5840,5845,5849,5853,5857,5863,5869,5875,5881,5887,5893,5899,5905,5910,5916,5921,5926,5931,5936,5941,5946,5951,5957,5963,5969,5975,5981,5987,5993,5999,6005,6011,6017,6023,6029,6035,6041,6047,6053,6058,6064,6070,6076,6081,6086,6091,6096,6101,6106,6111,6117,6123,6128],{"__ignoreMap":201},[205,5693,5694],{"class":207,"line":208},[205,5695,5696],{},"class InfiniteListPage extends StatefulWidget {\n",[205,5698,5699],{"class":207,"line":214},[205,5700,964],{},[205,5702,5703],{"class":207,"line":220},[205,5704,5705],{},"  State\u003CInfiniteListPage> createState() => _InfiniteListPageState();\n",[205,5707,5708],{"class":207,"line":226},[205,5709,400],{},[205,5711,5712],{"class":207,"line":232},[205,5713,236],{"emptyLinePlaceholder":235},[205,5715,5716],{"class":207,"line":239},[205,5717,5718],{},"class _InfiniteListPageState extends State\u003CInfiniteListPage> {\n",[205,5720,5721],{"class":207,"line":245},[205,5722,5723],{},"  final _scrollController = ScrollController();\n",[205,5725,5726],{"class":207,"line":251},[205,5727,5728],{},"  final List\u003CItem> _items = [];\n",[205,5730,5731],{"class":207,"line":257},[205,5732,5733],{},"  bool _isLoading = false;\n",[205,5735,5736],{"class":207,"line":263},[205,5737,5738],{},"  bool _hasMore = true;\n",[205,5740,5741],{"class":207,"line":397},[205,5742,5743],{},"  String? _error;\n",[205,5745,5746],{"class":207,"line":505},[205,5747,5748],{},"  int _page = 1;\n",[205,5750,5751],{"class":207,"line":511},[205,5752,236],{"emptyLinePlaceholder":235},[205,5754,5755],{"class":207,"line":818},[205,5756,964],{},[205,5758,5759],{"class":207,"line":824},[205,5760,1686],{},[205,5762,5763],{"class":207,"line":830},[205,5764,5765],{},"    super.initState();\n",[205,5767,5768],{"class":207,"line":836},[205,5769,5770],{},"    _loadMore();\n",[205,5772,5773],{"class":207,"line":842},[205,5774,5775],{},"    _scrollController.addListener(_onScroll);\n",[205,5777,5778],{"class":207,"line":1749},[205,5779,394],{},[205,5781,5782],{"class":207,"line":1754},[205,5783,236],{"emptyLinePlaceholder":235},[205,5785,5786],{"class":207,"line":1760},[205,5787,5788],{},"  void _onScroll() {\n",[205,5790,5791],{"class":207,"line":1766},[205,5792,5793],{},"    if (_scrollController.position.pixels >=\n",[205,5795,5796],{"class":207,"line":1772},[205,5797,5798],{},"        _scrollController.position.maxScrollExtent - 200) {\n",[205,5800,5801],{"class":207,"line":1778},[205,5802,5803],{},"      _loadMore();\n",[205,5805,5806],{"class":207,"line":1784},[205,5807,1787],{},[205,5809,5810],{"class":207,"line":1790},[205,5811,394],{},[205,5813,5814],{"class":207,"line":1795},[205,5815,236],{"emptyLinePlaceholder":235},[205,5817,5818],{"class":207,"line":1800},[205,5819,5820],{},"  Future\u003Cvoid> _loadMore() async {\n",[205,5822,5823],{"class":207,"line":1805},[205,5824,5825],{},"    if (_isLoading || !_hasMore) return;\n",[205,5827,5828],{"class":207,"line":1811},[205,5829,236],{"emptyLinePlaceholder":235},[205,5831,5832],{"class":207,"line":1817},[205,5833,5834],{},"    setState(() {\n",[205,5836,5837],{"class":207,"line":1823},[205,5838,5839],{},"      _isLoading = true;\n",[205,5841,5842],{"class":207,"line":1828},[205,5843,5844],{},"      _error = null;\n",[205,5846,5847],{"class":207,"line":5108},[205,5848,3750],{},[205,5850,5851],{"class":207,"line":5113},[205,5852,236],{"emptyLinePlaceholder":235},[205,5854,5855],{"class":207,"line":5118},[205,5856,3579],{},[205,5858,5860],{"class":207,"line":5859},37,[205,5861,5862],{},"      final newItems = await api.fetchItems(page: _page, limit: 20);\n",[205,5864,5866],{"class":207,"line":5865},38,[205,5867,5868],{},"      setState(() {\n",[205,5870,5872],{"class":207,"line":5871},39,[205,5873,5874],{},"        _items.addAll(newItems);\n",[205,5876,5878],{"class":207,"line":5877},40,[205,5879,5880],{},"        _page++;\n",[205,5882,5884],{"class":207,"line":5883},41,[205,5885,5886],{},"        _hasMore = newItems.length == 20;\n",[205,5888,5890],{"class":207,"line":5889},42,[205,5891,5892],{},"        _isLoading = false;\n",[205,5894,5896],{"class":207,"line":5895},43,[205,5897,5898],{},"      });\n",[205,5900,5902],{"class":207,"line":5901},44,[205,5903,5904],{},"    } catch (e) {\n",[205,5906,5908],{"class":207,"line":5907},45,[205,5909,5868],{},[205,5911,5913],{"class":207,"line":5912},46,[205,5914,5915],{},"        _error = e.toString();\n",[205,5917,5919],{"class":207,"line":5918},47,[205,5920,5892],{},[205,5922,5924],{"class":207,"line":5923},48,[205,5925,5898],{},[205,5927,5929],{"class":207,"line":5928},49,[205,5930,1787],{},[205,5932,5934],{"class":207,"line":5933},50,[205,5935,394],{},[205,5937,5939],{"class":207,"line":5938},51,[205,5940,236],{"emptyLinePlaceholder":235},[205,5942,5944],{"class":207,"line":5943},52,[205,5945,964],{},[205,5947,5949],{"class":207,"line":5948},53,[205,5950,5066],{},[205,5952,5954],{"class":207,"line":5953},54,[205,5955,5956],{},"    return RefreshIndicator(\n",[205,5958,5960],{"class":207,"line":5959},55,[205,5961,5962],{},"      onRefresh: () async {\n",[205,5964,5966],{"class":207,"line":5965},56,[205,5967,5968],{},"        _page = 1;\n",[205,5970,5972],{"class":207,"line":5971},57,[205,5973,5974],{},"        _items.clear();\n",[205,5976,5978],{"class":207,"line":5977},58,[205,5979,5980],{},"        _hasMore = true;\n",[205,5982,5984],{"class":207,"line":5983},59,[205,5985,5986],{},"        await _loadMore();\n",[205,5988,5990],{"class":207,"line":5989},60,[205,5991,5992],{},"      },\n",[205,5994,5996],{"class":207,"line":5995},61,[205,5997,5998],{},"      child: ListView.builder(\n",[205,6000,6002],{"class":207,"line":6001},62,[205,6003,6004],{},"        controller: _scrollController,\n",[205,6006,6008],{"class":207,"line":6007},63,[205,6009,6010],{},"        itemCount: _items.length + (_hasMore ? 1 : 0),\n",[205,6012,6014],{"class":207,"line":6013},64,[205,6015,6016],{},"        itemBuilder: (context, index) {\n",[205,6018,6020],{"class":207,"line":6019},65,[205,6021,6022],{},"          if (index == _items.length) {\n",[205,6024,6026],{"class":207,"line":6025},66,[205,6027,6028],{},"            if (_error != null) {\n",[205,6030,6032],{"class":207,"line":6031},67,[205,6033,6034],{},"              return _ErrorTile(\n",[205,6036,6038],{"class":207,"line":6037},68,[205,6039,6040],{},"                error: _error!,\n",[205,6042,6044],{"class":207,"line":6043},69,[205,6045,6046],{},"                onRetry: _loadMore,\n",[205,6048,6050],{"class":207,"line":6049},70,[205,6051,6052],{},"              );\n",[205,6054,6056],{"class":207,"line":6055},71,[205,6057,3688],{},[205,6059,6061],{"class":207,"line":6060},72,[205,6062,6063],{},"            return const Center(child: CircularProgressIndicator());\n",[205,6065,6067],{"class":207,"line":6066},73,[205,6068,6069],{},"          }\n",[205,6071,6073],{"class":207,"line":6072},74,[205,6074,6075],{},"          return ItemTile(item: _items[index]);\n",[205,6077,6079],{"class":207,"line":6078},75,[205,6080,5101],{},[205,6082,6084],{"class":207,"line":6083},76,[205,6085,4090],{},[205,6087,6089],{"class":207,"line":6088},77,[205,6090,2984],{},[205,6092,6094],{"class":207,"line":6093},78,[205,6095,394],{},[205,6097,6099],{"class":207,"line":6098},79,[205,6100,236],{"emptyLinePlaceholder":235},[205,6102,6104],{"class":207,"line":6103},80,[205,6105,964],{},[205,6107,6109],{"class":207,"line":6108},81,[205,6110,1808],{},[205,6112,6114],{"class":207,"line":6113},82,[205,6115,6116],{},"    _scrollController.dispose();\n",[205,6118,6120],{"class":207,"line":6119},83,[205,6121,6122],{},"    super.dispose();\n",[205,6124,6126],{"class":207,"line":6125},84,[205,6127,394],{},[205,6129,6131],{"class":207,"line":6130},85,[205,6132,400],{},[10,6134],{},[100,6136,6138],{"id":6137},"q122-如何处理-flutter-中的深层链接deep-linking","Q12.2: 如何处理 Flutter 中的深层链接（Deep Linking）？",[114,6140,6141],{},[117,6142,119],{},[196,6144,6146],{"className":198,"code":6145,"language":200,"meta":201,"style":201},"\u002F\u002F 使用 go_router 处理深层链接\nfinal router = GoRouter(\n  routes: [\n    GoRoute(\n      path: '\u002F',\n      builder: (_, __) => HomePage(),\n    ),\n    GoRoute(\n      path: '\u002Fproduct\u002F:id',\n      builder: (_, state) {\n        final id = state.pathParameters['id']!;\n        return ProductPage(id: id);\n      },\n    ),\n    GoRoute(\n      path: '\u002Forder\u002F:orderId\u002Ftracking',\n      builder: (_, state) {\n        return TrackingPage(orderId: state.pathParameters['orderId']!);\n      },\n    ),\n  ],\n  \u002F\u002F 认证守卫\n  redirect: (context, state) {\n    final isLoggedIn = ref.read(authProvider).isLoggedIn;\n    final isLoginRoute = state.matchedLocation == '\u002Flogin';\n\n    if (!isLoggedIn && !isLoginRoute) return '\u002Flogin';\n    if (isLoggedIn && isLoginRoute) return '\u002F';\n    return null;\n  },\n  \u002F\u002F 错误页\n  errorBuilder: (_, __) => NotFoundPage(),\n);\n",[105,6147,6148,6153,6158,6162,6167,6172,6177,6181,6185,6190,6195,6200,6205,6209,6213,6217,6222,6226,6231,6235,6239,6243,6248,6252,6257,6262,6266,6271,6276,6281,6285,6290,6295],{"__ignoreMap":201},[205,6149,6150],{"class":207,"line":208},[205,6151,6152],{},"\u002F\u002F 使用 go_router 处理深层链接\n",[205,6154,6155],{"class":207,"line":214},[205,6156,6157],{},"final router = GoRouter(\n",[205,6159,6160],{"class":207,"line":220},[205,6161,4213],{},[205,6163,6164],{"class":207,"line":226},[205,6165,6166],{},"    GoRoute(\n",[205,6168,6169],{"class":207,"line":232},[205,6170,6171],{},"      path: '\u002F',\n",[205,6173,6174],{"class":207,"line":239},[205,6175,6176],{},"      builder: (_, __) => HomePage(),\n",[205,6178,6179],{"class":207,"line":245},[205,6180,1207],{},[205,6182,6183],{"class":207,"line":251},[205,6184,6166],{},[205,6186,6187],{"class":207,"line":257},[205,6188,6189],{},"      path: '\u002Fproduct\u002F:id',\n",[205,6191,6192],{"class":207,"line":263},[205,6193,6194],{},"      builder: (_, state) {\n",[205,6196,6197],{"class":207,"line":397},[205,6198,6199],{},"        final id = state.pathParameters['id']!;\n",[205,6201,6202],{"class":207,"line":505},[205,6203,6204],{},"        return ProductPage(id: id);\n",[205,6206,6207],{"class":207,"line":511},[205,6208,5992],{},[205,6210,6211],{"class":207,"line":818},[205,6212,1207],{},[205,6214,6215],{"class":207,"line":824},[205,6216,6166],{},[205,6218,6219],{"class":207,"line":830},[205,6220,6221],{},"      path: '\u002Forder\u002F:orderId\u002Ftracking',\n",[205,6223,6224],{"class":207,"line":836},[205,6225,6194],{},[205,6227,6228],{"class":207,"line":842},[205,6229,6230],{},"        return TrackingPage(orderId: state.pathParameters['orderId']!);\n",[205,6232,6233],{"class":207,"line":1749},[205,6234,5992],{},[205,6236,6237],{"class":207,"line":1754},[205,6238,1207],{},[205,6240,6241],{"class":207,"line":1760},[205,6242,1165],{},[205,6244,6245],{"class":207,"line":1766},[205,6246,6247],{},"  \u002F\u002F 认证守卫\n",[205,6249,6250],{"class":207,"line":1772},[205,6251,4148],{},[205,6253,6254],{"class":207,"line":1778},[205,6255,6256],{},"    final isLoggedIn = ref.read(authProvider).isLoggedIn;\n",[205,6258,6259],{"class":207,"line":1784},[205,6260,6261],{},"    final isLoginRoute = state.matchedLocation == '\u002Flogin';\n",[205,6263,6264],{"class":207,"line":1790},[205,6265,236],{"emptyLinePlaceholder":235},[205,6267,6268],{"class":207,"line":1795},[205,6269,6270],{},"    if (!isLoggedIn && !isLoginRoute) return '\u002Flogin';\n",[205,6272,6273],{"class":207,"line":1800},[205,6274,6275],{},"    if (isLoggedIn && isLoginRoute) return '\u002F';\n",[205,6277,6278],{"class":207,"line":1805},[205,6279,6280],{},"    return null;\n",[205,6282,6283],{"class":207,"line":1811},[205,6284,2726],{},[205,6286,6287],{"class":207,"line":1817},[205,6288,6289],{},"  \u002F\u002F 错误页\n",[205,6291,6292],{"class":207,"line":1823},[205,6293,6294],{},"  errorBuilder: (_, __) => NotFoundPage(),\n",[205,6296,6297],{"class":207,"line":1828},[205,6298,6299],{},");\n",[114,6301,6302],{},[117,6303,6304],{},"平台配置：",[196,6306,6310],{"className":6307,"code":6308,"language":6309,"meta":201,"style":201},"language-xml shiki shiki-themes github-light github-dark","\u003C!-- Android: AndroidManifest.xml -->\n\u003Cintent-filter>\n  \u003Caction android:name=\"android.intent.action.VIEW\" \u002F>\n  \u003Ccategory android:name=\"android.intent.category.DEFAULT\" \u002F>\n  \u003Ccategory android:name=\"android.intent.category.BROWSABLE\" \u002F>\n  \u003Cdata android:scheme=\"https\" android:host=\"myapp.com\" \u002F>\n\u003C\u002Fintent-filter>\n","xml",[105,6311,6312,6317,6322,6327,6332,6337,6342],{"__ignoreMap":201},[205,6313,6314],{"class":207,"line":208},[205,6315,6316],{},"\u003C!-- Android: AndroidManifest.xml -->\n",[205,6318,6319],{"class":207,"line":214},[205,6320,6321],{},"\u003Cintent-filter>\n",[205,6323,6324],{"class":207,"line":220},[205,6325,6326],{},"  \u003Caction android:name=\"android.intent.action.VIEW\" \u002F>\n",[205,6328,6329],{"class":207,"line":226},[205,6330,6331],{},"  \u003Ccategory android:name=\"android.intent.category.DEFAULT\" \u002F>\n",[205,6333,6334],{"class":207,"line":232},[205,6335,6336],{},"  \u003Ccategory android:name=\"android.intent.category.BROWSABLE\" \u002F>\n",[205,6338,6339],{"class":207,"line":239},[205,6340,6341],{},"  \u003Cdata android:scheme=\"https\" android:host=\"myapp.com\" \u002F>\n",[205,6343,6344],{"class":207,"line":245},[205,6345,6346],{},"\u003C\u002Fintent-filter>\n",[196,6348,6350],{"className":6307,"code":6349,"language":6309,"meta":201,"style":201},"\u003C!-- iOS: Info.plist + Associated Domains -->\n\u003C!-- 添加 applinks:myapp.com 到 Associated Domains -->\n",[105,6351,6352,6357],{"__ignoreMap":201},[205,6353,6354],{"class":207,"line":208},[205,6355,6356],{},"\u003C!-- iOS: Info.plist + Associated Domains -->\n",[205,6358,6359],{"class":207,"line":214},[205,6360,6361],{},"\u003C!-- 添加 applinks:myapp.com 到 Associated Domains -->\n",[10,6363],{},[100,6365,6367],{"id":6366},"q123-如何实现多主题dark-mode支持","Q12.3: 如何实现多主题（Dark Mode）支持？",[114,6369,6370],{},[117,6371,119],{},[196,6373,6375],{"className":198,"code":6374,"language":200,"meta":201,"style":201},"\u002F\u002F 1. 定义主题\nclass AppTheme {\n  static ThemeData light = ThemeData(\n    brightness: Brightness.light,\n    colorScheme: ColorScheme.fromSeed(\n      seedColor: Colors.blue,\n      brightness: Brightness.light,\n    ),\n    textTheme: _textTheme,\n    appBarTheme: const AppBarTheme(elevation: 0),\n  );\n\n  static ThemeData dark = ThemeData(\n    brightness: Brightness.dark,\n    colorScheme: ColorScheme.fromSeed(\n      seedColor: Colors.blue,\n      brightness: Brightness.dark,\n    ),\n    textTheme: _textTheme,\n  );\n\n  static const _textTheme = TextTheme(\n    headlineLarge: TextStyle(fontWeight: FontWeight.bold),\n  );\n}\n\n\u002F\u002F 2. 使用 Riverpod 管理主题状态\nfinal themeModeProvider = StateProvider\u003CThemeMode>((ref) => ThemeMode.system);\n\n\u002F\u002F 3. 应用主题\nclass MyApp extends ConsumerWidget {\n  @override\n  Widget build(BuildContext context, WidgetRef ref) {\n    final themeMode = ref.watch(themeModeProvider);\n    return MaterialApp(\n      theme: AppTheme.light,\n      darkTheme: AppTheme.dark,\n      themeMode: themeMode,\n      home: HomePage(),\n    );\n  }\n}\n\n\u002F\u002F 4. 在 Widget 中使用语义化颜色\nContainer(\n  color: Theme.of(context).colorScheme.surface,\n  child: Text(\n    'Hello',\n    style: Theme.of(context).textTheme.headlineLarge,\n  ),\n)\n\n\u002F\u002F 5. 自定义扩展（超出 Theme 范围的颜色）\nextension CustomColors on ThemeData {\n  Color get successColor =>\n      brightness == Brightness.light ? Colors.green : Colors.greenAccent;\n}\n",[105,6376,6377,6382,6387,6392,6397,6402,6407,6412,6416,6421,6426,6430,6434,6439,6444,6448,6452,6457,6461,6465,6469,6473,6478,6483,6487,6491,6495,6500,6505,6509,6514,6519,6523,6527,6532,6537,6542,6547,6552,6557,6561,6565,6569,6573,6578,6583,6588,6593,6598,6603,6607,6611,6615,6620,6625,6630,6635],{"__ignoreMap":201},[205,6378,6379],{"class":207,"line":208},[205,6380,6381],{},"\u002F\u002F 1. 定义主题\n",[205,6383,6384],{"class":207,"line":214},[205,6385,6386],{},"class AppTheme {\n",[205,6388,6389],{"class":207,"line":220},[205,6390,6391],{},"  static ThemeData light = ThemeData(\n",[205,6393,6394],{"class":207,"line":226},[205,6395,6396],{},"    brightness: Brightness.light,\n",[205,6398,6399],{"class":207,"line":232},[205,6400,6401],{},"    colorScheme: ColorScheme.fromSeed(\n",[205,6403,6404],{"class":207,"line":239},[205,6405,6406],{},"      seedColor: Colors.blue,\n",[205,6408,6409],{"class":207,"line":245},[205,6410,6411],{},"      brightness: Brightness.light,\n",[205,6413,6414],{"class":207,"line":251},[205,6415,1207],{},[205,6417,6418],{"class":207,"line":257},[205,6419,6420],{},"    textTheme: _textTheme,\n",[205,6422,6423],{"class":207,"line":263},[205,6424,6425],{},"    appBarTheme: const AppBarTheme(elevation: 0),\n",[205,6427,6428],{"class":207,"line":397},[205,6429,4577],{},[205,6431,6432],{"class":207,"line":505},[205,6433,236],{"emptyLinePlaceholder":235},[205,6435,6436],{"class":207,"line":511},[205,6437,6438],{},"  static ThemeData dark = ThemeData(\n",[205,6440,6441],{"class":207,"line":818},[205,6442,6443],{},"    brightness: Brightness.dark,\n",[205,6445,6446],{"class":207,"line":824},[205,6447,6401],{},[205,6449,6450],{"class":207,"line":830},[205,6451,6406],{},[205,6453,6454],{"class":207,"line":836},[205,6455,6456],{},"      brightness: Brightness.dark,\n",[205,6458,6459],{"class":207,"line":842},[205,6460,1207],{},[205,6462,6463],{"class":207,"line":1749},[205,6464,6420],{},[205,6466,6467],{"class":207,"line":1754},[205,6468,4577],{},[205,6470,6471],{"class":207,"line":1760},[205,6472,236],{"emptyLinePlaceholder":235},[205,6474,6475],{"class":207,"line":1766},[205,6476,6477],{},"  static const _textTheme = TextTheme(\n",[205,6479,6480],{"class":207,"line":1772},[205,6481,6482],{},"    headlineLarge: TextStyle(fontWeight: FontWeight.bold),\n",[205,6484,6485],{"class":207,"line":1778},[205,6486,4577],{},[205,6488,6489],{"class":207,"line":1784},[205,6490,400],{},[205,6492,6493],{"class":207,"line":1790},[205,6494,236],{"emptyLinePlaceholder":235},[205,6496,6497],{"class":207,"line":1795},[205,6498,6499],{},"\u002F\u002F 2. 使用 Riverpod 管理主题状态\n",[205,6501,6502],{"class":207,"line":1800},[205,6503,6504],{},"final themeModeProvider = StateProvider\u003CThemeMode>((ref) => ThemeMode.system);\n",[205,6506,6507],{"class":207,"line":1805},[205,6508,236],{"emptyLinePlaceholder":235},[205,6510,6511],{"class":207,"line":1811},[205,6512,6513],{},"\u002F\u002F 3. 应用主题\n",[205,6515,6516],{"class":207,"line":1817},[205,6517,6518],{},"class MyApp extends ConsumerWidget {\n",[205,6520,6521],{"class":207,"line":1823},[205,6522,964],{},[205,6524,6525],{"class":207,"line":1828},[205,6526,2152],{},[205,6528,6529],{"class":207,"line":5108},[205,6530,6531],{},"    final themeMode = ref.watch(themeModeProvider);\n",[205,6533,6534],{"class":207,"line":5113},[205,6535,6536],{},"    return MaterialApp(\n",[205,6538,6539],{"class":207,"line":5118},[205,6540,6541],{},"      theme: AppTheme.light,\n",[205,6543,6544],{"class":207,"line":5859},[205,6545,6546],{},"      darkTheme: AppTheme.dark,\n",[205,6548,6549],{"class":207,"line":5865},[205,6550,6551],{},"      themeMode: themeMode,\n",[205,6553,6554],{"class":207,"line":5871},[205,6555,6556],{},"      home: HomePage(),\n",[205,6558,6559],{"class":207,"line":5877},[205,6560,2984],{},[205,6562,6563],{"class":207,"line":5883},[205,6564,394],{},[205,6566,6567],{"class":207,"line":5889},[205,6568,400],{},[205,6570,6571],{"class":207,"line":5895},[205,6572,236],{"emptyLinePlaceholder":235},[205,6574,6575],{"class":207,"line":5901},[205,6576,6577],{},"\u002F\u002F 4. 在 Widget 中使用语义化颜色\n",[205,6579,6580],{"class":207,"line":5907},[205,6581,6582],{},"Container(\n",[205,6584,6585],{"class":207,"line":5912},[205,6586,6587],{},"  color: Theme.of(context).colorScheme.surface,\n",[205,6589,6590],{"class":207,"line":5918},[205,6591,6592],{},"  child: Text(\n",[205,6594,6595],{"class":207,"line":5923},[205,6596,6597],{},"    'Hello',\n",[205,6599,6600],{"class":207,"line":5928},[205,6601,6602],{},"    style: Theme.of(context).textTheme.headlineLarge,\n",[205,6604,6605],{"class":207,"line":5933},[205,6606,4100],{},[205,6608,6609],{"class":207,"line":5938},[205,6610,1170],{},[205,6612,6613],{"class":207,"line":5943},[205,6614,236],{"emptyLinePlaceholder":235},[205,6616,6617],{"class":207,"line":5948},[205,6618,6619],{},"\u002F\u002F 5. 自定义扩展（超出 Theme 范围的颜色）\n",[205,6621,6622],{"class":207,"line":5953},[205,6623,6624],{},"extension CustomColors on ThemeData {\n",[205,6626,6627],{"class":207,"line":5959},[205,6628,6629],{},"  Color get successColor =>\n",[205,6631,6632],{"class":207,"line":5965},[205,6633,6634],{},"      brightness == Brightness.light ? Colors.green : Colors.greenAccent;\n",[205,6636,6637],{"class":207,"line":5971},[205,6638,400],{},[10,6640],{},[100,6642,6644],{"id":6643},"q124-如何做-flutter-应用的国际化i18n","Q12.4: 如何做 Flutter 应用的国际化（i18n）？",[114,6646,6647],{},[117,6648,119],{},[196,6650,6652],{"className":5546,"code":6651,"language":5548,"meta":201,"style":201},"# pubspec.yaml\ndependencies:\n  flutter_localizations:\n    sdk: flutter\n  intl: any\n\nflutter:\n  generate: true\n",[105,6653,6654,6659,6665,6672,6683,6693,6697,6703],{"__ignoreMap":201},[205,6655,6656],{"class":207,"line":208},[205,6657,6658],{"class":4721},"# pubspec.yaml\n",[205,6660,6661,6663],{"class":207,"line":214},[205,6662,5657],{"class":5656},[205,6664,5661],{"class":5660},[205,6666,6667,6670],{"class":207,"line":220},[205,6668,6669],{"class":5656},"  flutter_localizations",[205,6671,5661],{"class":5660},[205,6673,6674,6677,6680],{"class":207,"line":226},[205,6675,6676],{"class":5656},"    sdk",[205,6678,6679],{"class":5660},": ",[205,6681,6682],{"class":4731},"flutter\n",[205,6684,6685,6688,6690],{"class":207,"line":232},[205,6686,6687],{"class":5656},"  intl",[205,6689,6679],{"class":5660},[205,6691,6692],{"class":4731},"any\n",[205,6694,6695],{"class":207,"line":239},[205,6696,236],{"emptyLinePlaceholder":235},[205,6698,6699,6701],{"class":207,"line":245},[205,6700,4728],{"class":5656},[205,6702,5661],{"class":5660},[205,6704,6705,6708,6710],{"class":207,"line":251},[205,6706,6707],{"class":5656},"  generate",[205,6709,6679],{"class":5660},[205,6711,6712],{"class":4735},"true\n",[196,6714,6716],{"className":5546,"code":6715,"language":5548,"meta":201,"style":201},"# l10n.yaml\narb-dir: lib\u002Fl10n\ntemplate-arb-file: app_en.arb\noutput-localization-file: app_localizations.dart\n",[105,6717,6718,6723,6733,6743],{"__ignoreMap":201},[205,6719,6720],{"class":207,"line":208},[205,6721,6722],{"class":4721},"# l10n.yaml\n",[205,6724,6725,6728,6730],{"class":207,"line":214},[205,6726,6727],{"class":5656},"arb-dir",[205,6729,6679],{"class":5660},[205,6731,6732],{"class":4731},"lib\u002Fl10n\n",[205,6734,6735,6738,6740],{"class":207,"line":220},[205,6736,6737],{"class":5656},"template-arb-file",[205,6739,6679],{"class":5660},[205,6741,6742],{"class":4731},"app_en.arb\n",[205,6744,6745,6748,6750],{"class":207,"line":226},[205,6746,6747],{"class":5656},"output-localization-file",[205,6749,6679],{"class":5660},[205,6751,6752],{"class":4731},"app_localizations.dart\n",[196,6754,6758],{"className":6755,"code":6756,"language":6757,"meta":201,"style":201},"language-json shiki shiki-themes github-light github-dark","\u002F\u002F lib\u002Fl10n\u002Fapp_en.arb\n{\n  \"@@locale\": \"en\",\n  \"appTitle\": \"My App\",\n  \"greeting\": \"Hello, {name}!\",\n  \"@greeting\": {\n    \"placeholders\": {\n      \"name\": { \"type\": \"String\" }\n    }\n  },\n  \"itemCount\": \"{count, plural, =0{No items} =1{1 item} other{{count} items}}\",\n  \"@itemCount\": {\n    \"placeholders\": {\n      \"count\": { \"type\": \"int\" }\n    }\n  }\n}\n","json",[105,6759,6760,6765,6770,6783,6795,6807,6815,6822,6841,6845,6849,6861,6868,6874,6890,6894,6898],{"__ignoreMap":201},[205,6761,6762],{"class":207,"line":208},[205,6763,6764],{"class":4721},"\u002F\u002F lib\u002Fl10n\u002Fapp_en.arb\n",[205,6766,6767],{"class":207,"line":214},[205,6768,6769],{"class":5660},"{\n",[205,6771,6772,6775,6777,6780],{"class":207,"line":220},[205,6773,6774],{"class":4735},"  \"@@locale\"",[205,6776,6679],{"class":5660},[205,6778,6779],{"class":4731},"\"en\"",[205,6781,6782],{"class":5660},",\n",[205,6784,6785,6788,6790,6793],{"class":207,"line":226},[205,6786,6787],{"class":4735},"  \"appTitle\"",[205,6789,6679],{"class":5660},[205,6791,6792],{"class":4731},"\"My App\"",[205,6794,6782],{"class":5660},[205,6796,6797,6800,6802,6805],{"class":207,"line":232},[205,6798,6799],{"class":4735},"  \"greeting\"",[205,6801,6679],{"class":5660},[205,6803,6804],{"class":4731},"\"Hello, {name}!\"",[205,6806,6782],{"class":5660},[205,6808,6809,6812],{"class":207,"line":239},[205,6810,6811],{"class":4735},"  \"@greeting\"",[205,6813,6814],{"class":5660},": {\n",[205,6816,6817,6820],{"class":207,"line":245},[205,6818,6819],{"class":4735},"    \"placeholders\"",[205,6821,6814],{"class":5660},[205,6823,6824,6827,6830,6833,6835,6838],{"class":207,"line":251},[205,6825,6826],{"class":4735},"      \"name\"",[205,6828,6829],{"class":5660},": { ",[205,6831,6832],{"class":4735},"\"type\"",[205,6834,6679],{"class":5660},[205,6836,6837],{"class":4731},"\"String\"",[205,6839,6840],{"class":5660}," }\n",[205,6842,6843],{"class":207,"line":257},[205,6844,1787],{"class":5660},[205,6846,6847],{"class":207,"line":263},[205,6848,2726],{"class":5660},[205,6850,6851,6854,6856,6859],{"class":207,"line":397},[205,6852,6853],{"class":4735},"  \"itemCount\"",[205,6855,6679],{"class":5660},[205,6857,6858],{"class":4731},"\"{count, plural, =0{No items} =1{1 item} other{{count} items}}\"",[205,6860,6782],{"class":5660},[205,6862,6863,6866],{"class":207,"line":505},[205,6864,6865],{"class":4735},"  \"@itemCount\"",[205,6867,6814],{"class":5660},[205,6869,6870,6872],{"class":207,"line":511},[205,6871,6819],{"class":4735},[205,6873,6814],{"class":5660},[205,6875,6876,6879,6881,6883,6885,6888],{"class":207,"line":818},[205,6877,6878],{"class":4735},"      \"count\"",[205,6880,6829],{"class":5660},[205,6882,6832],{"class":4735},[205,6884,6679],{"class":5660},[205,6886,6887],{"class":4731},"\"int\"",[205,6889,6840],{"class":5660},[205,6891,6892],{"class":207,"line":824},[205,6893,1787],{"class":5660},[205,6895,6896],{"class":207,"line":830},[205,6897,394],{"class":5660},[205,6899,6900],{"class":207,"line":836},[205,6901,400],{"class":5660},[196,6903,6905],{"className":6755,"code":6904,"language":6757,"meta":201,"style":201},"\u002F\u002F lib\u002Fl10n\u002Fapp_zh.arb\n{\n  \"@@locale\": \"zh\",\n  \"appTitle\": \"我的应用\",\n  \"greeting\": \"你好, {name}!\",\n  \"itemCount\": \"{count, plural, =0{没有项目} other{{count} 个项目}}\"\n}\n",[105,6906,6907,6912,6916,6927,6938,6949,6958],{"__ignoreMap":201},[205,6908,6909],{"class":207,"line":208},[205,6910,6911],{"class":4721},"\u002F\u002F lib\u002Fl10n\u002Fapp_zh.arb\n",[205,6913,6914],{"class":207,"line":214},[205,6915,6769],{"class":5660},[205,6917,6918,6920,6922,6925],{"class":207,"line":220},[205,6919,6774],{"class":4735},[205,6921,6679],{"class":5660},[205,6923,6924],{"class":4731},"\"zh\"",[205,6926,6782],{"class":5660},[205,6928,6929,6931,6933,6936],{"class":207,"line":226},[205,6930,6787],{"class":4735},[205,6932,6679],{"class":5660},[205,6934,6935],{"class":4731},"\"我的应用\"",[205,6937,6782],{"class":5660},[205,6939,6940,6942,6944,6947],{"class":207,"line":232},[205,6941,6799],{"class":4735},[205,6943,6679],{"class":5660},[205,6945,6946],{"class":4731},"\"你好, {name}!\"",[205,6948,6782],{"class":5660},[205,6950,6951,6953,6955],{"class":207,"line":239},[205,6952,6853],{"class":4735},[205,6954,6679],{"class":5660},[205,6956,6957],{"class":4731},"\"{count, plural, =0{没有项目} other{{count} 个项目}}\"\n",[205,6959,6960],{"class":207,"line":245},[205,6961,400],{"class":5660},[196,6963,6965],{"className":198,"code":6964,"language":200,"meta":201,"style":201},"\u002F\u002F 配置\nMaterialApp(\n  localizationsDelegates: AppLocalizations.localizationsDelegates,\n  supportedLocales: AppLocalizations.supportedLocales,\n)\n\n\u002F\u002F 使用\nText(AppLocalizations.of(context)!.greeting('Flutter'))\nText(AppLocalizations.of(context)!.itemCount(5))\n",[105,6966,6967,6972,6976,6981,6986,6990,6994,6998,7003],{"__ignoreMap":201},[205,6968,6969],{"class":207,"line":208},[205,6970,6971],{},"\u002F\u002F 配置\n",[205,6973,6974],{"class":207,"line":214},[205,6975,3293],{},[205,6977,6978],{"class":207,"line":220},[205,6979,6980],{},"  localizationsDelegates: AppLocalizations.localizationsDelegates,\n",[205,6982,6983],{"class":207,"line":226},[205,6984,6985],{},"  supportedLocales: AppLocalizations.supportedLocales,\n",[205,6987,6988],{"class":207,"line":232},[205,6989,1170],{},[205,6991,6992],{"class":207,"line":239},[205,6993,236],{"emptyLinePlaceholder":235},[205,6995,6996],{"class":207,"line":245},[205,6997,728],{},[205,6999,7000],{"class":207,"line":251},[205,7001,7002],{},"Text(AppLocalizations.of(context)!.greeting('Flutter'))\n",[205,7004,7005],{"class":207,"line":257},[205,7006,7007],{},"Text(AppLocalizations.of(context)!.itemCount(5))\n",[10,7009],{},[100,7011,7013],{"id":7012},"q125-如何处理-flutter-中的内存泄漏","Q12.5: 如何处理 Flutter 中的内存泄漏？",[114,7015,7016],{},[117,7017,119],{},[114,7019,7020],{},[117,7021,7022],{},"常见内存泄漏场景及解决方案：",[196,7024,7026],{"className":198,"code":7025,"language":200,"meta":201,"style":201},"\u002F\u002F ❌ 泄漏 1：未取消的 Stream 订阅\nclass _MyState extends State\u003CMyWidget> {\n  late StreamSubscription _sub;\n\n  @override\n  void initState() {\n    super.initState();\n    _sub = stream.listen((data) => setState(() {}));\n  }\n\n  \u002F\u002F ❌ 忘记取消订阅\n}\n\n\u002F\u002F ✅ 修复：在 dispose 中取消\n@override\nvoid dispose() {\n  _sub.cancel();\n  super.dispose();\n}\n",[105,7027,7028,7033,7038,7043,7047,7051,7055,7059,7064,7068,7072,7077,7081,7085,7090,7095,7100,7105,7110],{"__ignoreMap":201},[205,7029,7030],{"class":207,"line":208},[205,7031,7032],{},"\u002F\u002F ❌ 泄漏 1：未取消的 Stream 订阅\n",[205,7034,7035],{"class":207,"line":214},[205,7036,7037],{},"class _MyState extends State\u003CMyWidget> {\n",[205,7039,7040],{"class":207,"line":220},[205,7041,7042],{},"  late StreamSubscription _sub;\n",[205,7044,7045],{"class":207,"line":226},[205,7046,236],{"emptyLinePlaceholder":235},[205,7048,7049],{"class":207,"line":232},[205,7050,964],{},[205,7052,7053],{"class":207,"line":239},[205,7054,1686],{},[205,7056,7057],{"class":207,"line":245},[205,7058,5765],{},[205,7060,7061],{"class":207,"line":251},[205,7062,7063],{},"    _sub = stream.listen((data) => setState(() {}));\n",[205,7065,7066],{"class":207,"line":257},[205,7067,394],{},[205,7069,7070],{"class":207,"line":263},[205,7071,236],{"emptyLinePlaceholder":235},[205,7073,7074],{"class":207,"line":397},[205,7075,7076],{},"  \u002F\u002F ❌ 忘记取消订阅\n",[205,7078,7079],{"class":207,"line":505},[205,7080,400],{},[205,7082,7083],{"class":207,"line":511},[205,7084,236],{"emptyLinePlaceholder":235},[205,7086,7087],{"class":207,"line":818},[205,7088,7089],{},"\u002F\u002F ✅ 修复：在 dispose 中取消\n",[205,7091,7092],{"class":207,"line":824},[205,7093,7094],{},"@override\n",[205,7096,7097],{"class":207,"line":830},[205,7098,7099],{},"void dispose() {\n",[205,7101,7102],{"class":207,"line":836},[205,7103,7104],{},"  _sub.cancel();\n",[205,7106,7107],{"class":207,"line":842},[205,7108,7109],{},"  super.dispose();\n",[205,7111,7112],{"class":207,"line":1749},[205,7113,400],{},[196,7115,7117],{"className":198,"code":7116,"language":200,"meta":201,"style":201},"\u002F\u002F ❌ 泄漏 2：闭包持有 BuildContext 或 State\nvoid _fetchData() async {\n  final data = await api.getData();\n  \u002F\u002F 此时 Widget 可能已经被销毁\n  setState(() { _data = data; }); \u002F\u002F 可能报错或泄漏\n}\n\n\u002F\u002F ✅ 修复：检查 mounted\nvoid _fetchData() async {\n  final data = await api.getData();\n  if (!mounted) return; \u002F\u002F Widget 已销毁，直接返回\n  setState(() { _data = data; });\n}\n",[105,7118,7119,7124,7129,7134,7139,7144,7148,7152,7157,7161,7165,7170,7175],{"__ignoreMap":201},[205,7120,7121],{"class":207,"line":208},[205,7122,7123],{},"\u002F\u002F ❌ 泄漏 2：闭包持有 BuildContext 或 State\n",[205,7125,7126],{"class":207,"line":214},[205,7127,7128],{},"void _fetchData() async {\n",[205,7130,7131],{"class":207,"line":220},[205,7132,7133],{},"  final data = await api.getData();\n",[205,7135,7136],{"class":207,"line":226},[205,7137,7138],{},"  \u002F\u002F 此时 Widget 可能已经被销毁\n",[205,7140,7141],{"class":207,"line":232},[205,7142,7143],{},"  setState(() { _data = data; }); \u002F\u002F 可能报错或泄漏\n",[205,7145,7146],{"class":207,"line":239},[205,7147,400],{},[205,7149,7150],{"class":207,"line":245},[205,7151,236],{"emptyLinePlaceholder":235},[205,7153,7154],{"class":207,"line":251},[205,7155,7156],{},"\u002F\u002F ✅ 修复：检查 mounted\n",[205,7158,7159],{"class":207,"line":257},[205,7160,7128],{},[205,7162,7163],{"class":207,"line":263},[205,7164,7133],{},[205,7166,7167],{"class":207,"line":397},[205,7168,7169],{},"  if (!mounted) return; \u002F\u002F Widget 已销毁，直接返回\n",[205,7171,7172],{"class":207,"line":505},[205,7173,7174],{},"  setState(() { _data = data; });\n",[205,7176,7177],{"class":207,"line":511},[205,7178,400],{},[196,7180,7182],{"className":198,"code":7181,"language":200,"meta":201,"style":201},"\u002F\u002F ❌ 泄漏 3：AnimationController 未释放\nclass _MyState extends State\u003CMyWidget> with TickerProviderStateMixin {\n  late final controller = AnimationController(vsync: this);\n\n  \u002F\u002F ❌ 忘记 dispose\n\n  \u002F\u002F ✅ 修复\n  @override\n  void dispose() {\n    controller.dispose();\n    super.dispose();\n  }\n}\n",[105,7183,7184,7189,7194,7199,7203,7208,7212,7217,7221,7225,7230,7234,7238],{"__ignoreMap":201},[205,7185,7186],{"class":207,"line":208},[205,7187,7188],{},"\u002F\u002F ❌ 泄漏 3：AnimationController 未释放\n",[205,7190,7191],{"class":207,"line":214},[205,7192,7193],{},"class _MyState extends State\u003CMyWidget> with TickerProviderStateMixin {\n",[205,7195,7196],{"class":207,"line":220},[205,7197,7198],{},"  late final controller = AnimationController(vsync: this);\n",[205,7200,7201],{"class":207,"line":226},[205,7202,236],{"emptyLinePlaceholder":235},[205,7204,7205],{"class":207,"line":232},[205,7206,7207],{},"  \u002F\u002F ❌ 忘记 dispose\n",[205,7209,7210],{"class":207,"line":239},[205,7211,236],{"emptyLinePlaceholder":235},[205,7213,7214],{"class":207,"line":245},[205,7215,7216],{},"  \u002F\u002F ✅ 修复\n",[205,7218,7219],{"class":207,"line":251},[205,7220,964],{},[205,7222,7223],{"class":207,"line":257},[205,7224,1808],{},[205,7226,7227],{"class":207,"line":263},[205,7228,7229],{},"    controller.dispose();\n",[205,7231,7232],{"class":207,"line":397},[205,7233,6122],{},[205,7235,7236],{"class":207,"line":505},[205,7237,394],{},[205,7239,7240],{"class":207,"line":511},[205,7241,400],{},[196,7243,7245],{"className":198,"code":7244,"language":200,"meta":201,"style":201},"\u002F\u002F ❌ 泄漏 4：全局\u002F静态引用持有 Widget\nclass Cache {\n  static Widget? lastWidget; \u002F\u002F 永远不会被 GC\n}\n\n\u002F\u002F ✅ 修复：避免静态引用 Widget\u002FState\u002FBuildContext\n",[105,7246,7247,7252,7257,7262,7266,7270],{"__ignoreMap":201},[205,7248,7249],{"class":207,"line":208},[205,7250,7251],{},"\u002F\u002F ❌ 泄漏 4：全局\u002F静态引用持有 Widget\n",[205,7253,7254],{"class":207,"line":214},[205,7255,7256],{},"class Cache {\n",[205,7258,7259],{"class":207,"line":220},[205,7260,7261],{},"  static Widget? lastWidget; \u002F\u002F 永远不会被 GC\n",[205,7263,7264],{"class":207,"line":226},[205,7265,400],{},[205,7267,7268],{"class":207,"line":232},[205,7269,236],{"emptyLinePlaceholder":235},[205,7271,7272],{"class":207,"line":239},[205,7273,7274],{},"\u002F\u002F ✅ 修复：避免静态引用 Widget\u002FState\u002FBuildContext\n",[114,7276,7277],{},[117,7278,7279],{},"检测工具：",[409,7281,7282,7285,7294],{},[20,7283,7284],{},"DevTools Memory 面板：查看对象分配和 GC",[20,7286,7287,7290,7291],{},[105,7288,7289],{},"dart:developer"," 的 ",[105,7292,7293],{},"Service.getMemoryUsage()",[20,7295,7296],{},"LeakTracking（Flutter 3.18+）：自动检测某些类型的泄漏",[10,7298],{},[13,7300,7302],{"id":7301},"附录高频考察知识点速查","附录：高频考察知识点速查",[121,7304,7305,7315],{},[124,7306,7307],{},[127,7308,7309,7312],{},[130,7310,7311],{},"主题",[130,7313,7314],{},"关键词",[142,7316,7317,7325,7332,7339,7347,7355,7362,7370],{},[127,7318,7319,7322],{},[147,7320,7321],{},"渲染",[147,7323,7324],{},"三棵树、Constraints go down \u002F Sizes go up、RepaintBoundary、Impeller",[127,7326,7327,7329],{},[147,7328,44],{},[147,7330,7331],{},"InheritedWidget 原理、BLoC vs Riverpod、setState 粒度",[127,7333,7334,7336],{},[147,7335,3799],{},[147,7337,7338],{},"const Widget、ListView.builder、AnimatedBuilder child、Isolate",[127,7340,7341,7344],{},[147,7342,7343],{},"异步",[147,7345,7346],{},"Event Loop、Microtask vs Event Queue、Stream vs Future、Isolate",[127,7348,7349,7352],{},[147,7350,7351],{},"平台通信",[147,7353,7354],{},"MethodChannel、EventChannel、FFI",[127,7356,7357,7359],{},[147,7358,74],{},[147,7360,7361],{},"Widget Test、pumpAndSettle、Golden Test、Mock",[127,7363,7364,7367],{},[147,7365,7366],{},"架构",[147,7368,7369],{},"Clean Architecture、MVVM、依赖注入",[127,7371,7372,7375],{},[147,7373,7374],{},"Dart 语言",[147,7376,7377],{},"Null Safety、Mixin、Sealed Class、Extension、泛型协变",[7379,7380,7381],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":201,"searchDepth":214,"depth":214,"links":7383},[7384,7385,7397,7403,7410,7415,7422,7426,7430,7433,7437,7441,7445,7452],{"id":15,"depth":214,"text":15},{"id":97,"depth":214,"text":98,"children":7386},[7387,7389,7391,7393,7395],{"id":102,"depth":220,"text":7388},"Q1.1: Dart 中 const 和 final 的区别是什么？",{"id":283,"depth":220,"text":7390},"Q1.2: Dart 的空安全（Null Safety）机制是怎样的？late 关键字有什么作用和风险？",{"id":437,"depth":220,"text":7392},"Q1.3: Dart 中的 mixin 是什么？和抽象类、接口有什么区别？",{"id":677,"depth":220,"text":7394},"Q1.4: 解释 Dart 中的 Extension 方法和 sealed class",{"id":855,"depth":220,"text":7396},"Q1.5: Dart 中的泛型协变（Covariance）是什么？为什么 List\u003CCat> 可以赋值给 List\u003CAnimal>？",{"id":978,"depth":214,"text":979,"children":7398},[7399,7400,7401],{"id":982,"depth":220,"text":983},{"id":1029,"depth":220,"text":1030},{"id":1116,"depth":220,"text":7402},"Q2.3: 解释 Flutter 中的 RepaintBoundary 的作用和使用场景",{"id":1262,"depth":214,"text":1263,"children":7404},[7405,7406,7408],{"id":1266,"depth":220,"text":1267},{"id":1429,"depth":220,"text":7407},"Q3.2: Key 的作用是什么？什么时候必须使用 Key？",{"id":1638,"depth":220,"text":7409},"Q3.3: StatefulWidget 的完整生命周期是怎样的？",{"id":1835,"depth":214,"text":1836,"children":7411},[7412,7413],{"id":1839,"depth":220,"text":1840},{"id":2175,"depth":220,"text":7414},"Q4.2: InheritedWidget 是如何工作的？为什么 Theme.of(context) 能获取到主题数据？",{"id":2357,"depth":214,"text":2358,"children":7416},[7417,7418,7420],{"id":2361,"depth":220,"text":2362},{"id":2503,"depth":220,"text":7419},"Q5.2: Future 和 Stream 的区别是什么？StreamController 怎么用？",{"id":2775,"depth":220,"text":7421},"Q5.3: 解释 async* 和 yield 的用法",{"id":2900,"depth":214,"text":2901,"children":7423},[7424,7425],{"id":2904,"depth":220,"text":2905},{"id":3273,"depth":220,"text":3274},{"id":3470,"depth":214,"text":3471,"children":7427},[7428,7429],{"id":3474,"depth":220,"text":3475},{"id":3763,"depth":220,"text":3764},{"id":3919,"depth":214,"text":3920,"children":7431},[7432],{"id":3923,"depth":220,"text":3924},{"id":4236,"depth":214,"text":4237,"children":7434},[7435,7436],{"id":4240,"depth":220,"text":4241},{"id":4619,"depth":220,"text":4620},{"id":4777,"depth":214,"text":4778,"children":7438},[7439,7440],{"id":4781,"depth":220,"text":4782},{"id":5125,"depth":220,"text":5126},{"id":5335,"depth":214,"text":5336,"children":7442},[7443,7444],{"id":5339,"depth":220,"text":5340},{"id":5499,"depth":220,"text":5500},{"id":5677,"depth":214,"text":5678,"children":7446},[7447,7448,7449,7450,7451],{"id":5681,"depth":220,"text":5682},{"id":6137,"depth":220,"text":6138},{"id":6366,"depth":220,"text":6367},{"id":6643,"depth":220,"text":6644},{"id":7012,"depth":220,"text":7013},{"id":7301,"depth":214,"text":7302},null,"2026-05-03 10:10:00 CST","覆盖 Dart 语言、渲染机制、状态管理、异步编程、性能优化等 Flutter 中高级面试核心考点。","md",{},"\u002Fnotes\u002F2026-05-03-flutter-interview",{"title":5,"description":7455},"Flutter 中高级工程师面试题详解，涵盖 Dart 基础、渲染机制、Widget 树、状态管理、异步编程和性能优化。","Flutter 中高级工程师面试题与详解｜个人笔记","flutter-interview","notes\u002F2026-05-03-flutter-interview","nUaoweAKJHeLHnMa5K8lOJO5U2XVaaDZh-ecZG0o0sQ",1778291670668]