-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.json
1 lines (1 loc) · 118 KB
/
index.json
1
[{"content":"文本及样式 Flutter.. 两个点语法含义 Dart中两个点..和三个点\u0026hellip;的用法\n文本及样式 常见属性 textAlign: 文本的对齐方式;可以选择左对齐、右对齐还是居中。注意,对齐的参考系是Text widget 本身, 只有 Text 宽度大于文本内容长度时指定此属性才有意义 maxLines、overflow: 指定文本显示的最大行数,默认情况下,文本是自动折行的,如果指定此参数,则文本最多不会超过指定的行。如果有多余的文本,可以通过overflow来指定截断方式,默认是直接截断,本例中指定的截断方式TextOverflow.ellipsis,它会将多余文本截断后以省略符“\u0026hellip;”表示;TextOverflow 的其他截断方式请参考 SDK 文档。 textScaleFactor: 代表文本相对于当前字体大小的缩放因子,相对于去设置文本的样式style属性的fontSize,它是调整字体大小的一个快捷方式。该属性的默认值可以通过MediaQueryData.textScaleFactor获得,如果没有MediaQuery,那么会默认值将为1.0。 TextStyle 1 2 3 4 5 6 7 8 9 10 11 Text(\u0026#34;Hello world\u0026#34;, style: TextStyle( color: Colors.blue, fontSize: 18.0, height: 1.2, fontFamily: \u0026#34;Courier\u0026#34;, background: Paint()..color=Colors.yellow, decoration:TextDecoration.underline, decorationStyle: TextDecorationStyle.dashed ), ); height:该属性用于指定行高,但它并不是一个绝对值,而是一个因子,具体的行高等于fontSize*height。 fontSize:该属性和 Text 的textScaleFactor都用于控制字体大小。但是有两个主要区别: fontSize可以精确指定字体大小,而textScaleFactor只能通过缩放比例来控制。 textScaleFactor主要是用于系统字体大小设置改变时对 Flutter 应用字体进行全局调整,而fontSize通常用于单个文本,字体大小不会跟随系统字体大小变化。 TextSpan 有点像富文本的展示方式\n1 2 3 4 5 6 const TextSpan({ TextStyle style, Sting text, List\u0026lt;TextSpan\u0026gt; children, GestureRecognizer recognizer, }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Text.rich(TextSpan( children: [ TextSpan( text: \u0026#34;Home: \u0026#34; ), TextSpan( text: \u0026#34;https://flutterchina.club\u0026#34;, style: TextStyle( color: Colors.blue ), recognizer: _tapRecognizer ), ] )) 我们通过 TextSpan 实现了一个基础文本片段和一个链接片段,然后通过Text.rich 方法将TextSpan 添加到 Text 中,之所以可以这样做,是因为 Text 其实就是 RichText 的一个包装,而RichText 是可以显示多种样式(富文本)的 widget ps: 在Flutter中经常会用用到..的语法糖 如下:\n1 2 3 state.clone() ..splashImg = action.img ..famousSentence = action.famousSentence; 等价于\n1 2 3 state.clone() state.splashImg = action.img state.famousSentence = action.famousSentence; 可以看成链式调用,但是和OC与java的链式调用不太一样 在OC/Java中链式调用有个规律,谁调用就返回谁,但是在dart中\u0026quot;..\u0026ldquo;不用在方法中返回调用主体,景观源码的实现方式也是通过set进去的,但是我们看到的就是Dart给我们提供的语法糖,因为Dart本身就是把成员变量的getter setter方法改成隐式的了\n三个点(\u0026hellip;) 是用来拼接集合 如list Map等\n1 2 3 4 5 6 7 8 9 10 class Test { Test() { //这里组合后 list就变成[ \u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;,\u0026#39;d\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;f\u0026#39;] var list2 = [\u0026#39;d\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;f\u0026#39;]; var list = [\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;, ...list2]; //这里组合后map就变成{\u0026#39;a\u0026#39;: \u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;: \u0026#39;b\u0026#39;,\u0026#39;c\u0026#39;: \u0026#39;c\u0026#39;, \u0026#39;d\u0026#39;: \u0026#39;d\u0026#39;} var map2 = {\u0026#39;a\u0026#39;: \u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;: \u0026#39;b\u0026#39;}; var map = {...map2, \u0026#39;c\u0026#39;: \u0026#39;c\u0026#39;, \u0026#39;d\u0026#39;: \u0026#39;d\u0026#39;}; } } DefaultTextStyle 在 Widget 树中,文本的样式默认是可以被继承的(子类文本类组件未指定具体样式时可以使用 Widget 树中父级设置的默认样式),因此,如果在 Widget 树的某一个节点处设置一个默认的文本样式,那么该节点的子树中所有文本都会默认使用这个样式,而DefaultTextStyle正是用于设置默认文本样式的 设置Widget树中子Widget的文本的样式, 如果这子Widget中设指定了对应文本样式的话(设置inherit: false, 则全部都不使用继承的默认样式),子widget的优先级会更高\n字体 在Flutter中使用字体分两步完成,首先在pubspec.yaml中声明他们,以确保会打包到应用中,然后通过TextStyle 属性使用字体\n在asset中声明 1 2 3 4 5 6 7 8 9 10 11 12 flutter: fonts: - family: Raleway fonts: - asset: assets/fonts/Raleway-Regular.ttf - asset: assets/fonts/Raleway-Medium.ttf weight: 500 - asset: assets/fonts/Raleway-SemiBold.ttf weight: 600 - family: AbrilFatface fonts: - asset: assets/fonts/abrilfatface/AbrilFatface-Regular.ttf 使用字体 1 2 3 4 5 6 7 8 9 10 // 声明文本样式 const textStyle = const TextStyle( fontFamily: \u0026#39;Raleway\u0026#39;, ); // 使用文本样式 var buttonText = const Text( \u0026#34;Use the font for this text\u0026#34;, style: textStyle, ); package中的字体 要使用 Package 中定义的字体,必须提供package参数。例如,假设上面的字体声明位于 my_package包中。然后创建 TextStyle 的过程如下,如果在 package 包内部使用它自己定义 的字体,也应该在创建文本样式时指定package参数 1 2 3 4 const textStyle = const TextStyle( fontFamily: \u0026#39;Raleway\u0026#39;, package: \u0026#39;my_package\u0026#39;, //指定包名 ); 一个包也可以只提供字体文件而不需要在 pubspec.yaml 中声明。 这些文件应该存放在包的lib/文件夹中。字体文件不会自动绑定到应用程序中,应用程序可以在声明字体时有选择地使用这些字体。假设一个名为my_package的包中有一个字体文件: lib/fonts/Raleway-Medium.ttf 然后再声明中声明\n1 2 3 4 5 6 7 flutter: fonts: - family: Raleway fonts: - asset: assets/fonts/Raleway-Regular.ttf - asset: packages/my_package/fonts/Raleway-Medium.ttf weight: 500 ps: lib/是隐含的,所以它不应该包含在 asset 路径中。\n在这种情况下,由于应用程序本地定义了字体,所以在创建TextStyle时可以不指定package参数:\n1 2 3 const textStyle = const TextStyle( fontFamily: \u0026#39;Raleway\u0026#39;, ); ","permalink":"https://akashark.github.io/posts/read/flutter%E5%AE%9E%E6%88%98/%E7%AC%AC%E4%B8%89%E7%AB%A0%E5%9F%BA%E7%A1%80%E7%BB%84%E4%BB%B6-%E4%B8%80/","summary":"文本及样式 Flutter.. 两个点语法含义 Dart中两个点..和三个点\u0026hellip;的用法 文本及样式 常见属性 textAlign: 文本的对齐方式;可以选择左对齐、右对齐还是居","title":"第三章基础组件 一"},{"content":"第二章\n调试Flutter应用 日志与断点 debugger() 声明 1 2 3 4 void someFunction(double offset) { debugger(when: offset \u0026gt; 30.0); // ... } print、debugPrint、flutter logs Dart print()功能将输出到系统控制台,我们可以使用flutter logs来查看它(基本上是一个包装adb logcat)。\n如果你一次输出太多,那么Android有时会丢弃一些日志行。为了避免这种情况,我们可以使用Flutter的foundation库中的debugPrint() (opens new window)。 这是一个封装print,它将输出限制在一个级别,避免被Android内核丢弃。\nFlutter框架中的许多类都有toString实现。按照惯例,这些输出通常包括对象的runtimeType单行输出,通常在表单中ClassName(more information about this instance…)。 树中使用的一些类也具有toStringDeep,从该点返回整个子树的多行描述。已一些具有详细信息toString的类会实现一个toStringShort,它只返回对象的类型或其他非常简短的(一个或两个单词)描述。\n调试模式断言 在Flutter应用调试过程中,Dart assert语句被启用,并且 Flutter 框架使用它来执行许多运行时检查来验证是否违反一些不可变的规则。当一个某个规则被违反时,就会在控制台打印错误日志,并带上一些上下文信息来帮助追踪问题的根源。\n要关闭调试模式并使用发布模式,请使用flutter run \u0026ndash;release运行我们的应用程序。 这也关闭了Observatory调试器。一个中间模式可以关闭除Observatory之外所有调试辅助工具的,称为“profile mode”,用\u0026ndash;profile替代\u0026ndash;release即可。\n断点 Vscode 或者 AS上自带的\n调试应用程序层 widget树 渲染树 Layer树 文档写的太少了而且没有实操,这个地方再找找资料补充下 官网上有对于DevTools的相关教程 教程\n异常捕获 Dart单线程模型 Dart线程运行过程,如上图中所示,入口函数 main() 执行完后,消息循环机制便启动了。首先会按照先进先出的顺序逐个执行微任务队列中的任务,事件任务执行完毕后程序便会退出,但是,在事件任务执行的过程中也可以插入新的微任务和事件任务,在这种情况下,整个线程的执行过程便是一直在循环,不会退出,而Flutter中,主线程的执行过程正是如此,永不终止。 在Dart中,所有的外部事件任务都在事件队列中,如IO、计时器、点击、以及绘制事件等,而微任务通常来源于Dart内部,并且微任务非常少,之所以如此,是因为微任务队列优先级高,如果微任务太多,执行时间总和就越久,事件队列任务的延迟也就越久,对于GUI应用来说最直观的表现就是比较卡,所以必须得保证微任务队列不会太长。值得注意的是,我们可以通过Future.microtask(…)方法向微任务队列插入一个任务。 在事件循环中,当某个任务发生异常并没有被捕获时,程序并不会退出,而直接导致的结果是当前任务的后续代码就不会被执行了,也就是说一个任务中的异常是不会影响其他任务执行的。\nFlutter框架异常捕获 onError是FlutterError的一个静态属性,它有一个默认的处理方法 dumpErrorToConsole,到这里就清晰了,如果我们想自己上报异常,只需要提供一个自定义的错误处理回调即可,如:\n1 2 3 4 5 6 void main() { FlutterError.onError = (FlutterErrorDetails details) { reportError(details); }; ... } 这样我们就可以处理那些Flutter为我们捕获的异常了\n其他异常捕获与日志收集 在Flutter中,还有一些Flutter没有为我们捕获的异常,如调用空对象方法异常、Future中的异常。在Dart中,异常分两类:同步异常和异步异常,同步异常可以通过try/catch捕获,而异步异常则比较麻烦,如下面的代码是捕获不了Future的异常的:\n1 2 3 4 5 try{ Future.delayed(Duration(seconds: 1)).then((e) =\u0026gt; Future.error(\u0026#34;xxx\u0026#34;)); }catch (e){ print(e) } Dart中有一个runZoned(\u0026hellip;) 方法,可以给执行对象指定一个Zone。Zone表示一个代码执行的环境范围,为了方便理解,读者可以将Zone类比为一个代码执行沙箱,不同沙箱的之间是隔离的,沙箱可以捕获、拦截或修改一些代码行为,如Zone中可以捕获日志输出、Timer创建、微任务调度的行为,同时Zone也可以捕获所有未处理的异常。下面我们看看runZoned(\u0026hellip;)方法定义:\n1 2 3 4 R runZoned\u0026lt;R\u0026gt;(R body(), { Map zoneValues, ZoneSpecification zoneSpecification, }) zoneValues: Zone 的私有数据,可以通过实例zone[key]获取,可以理解为每个“沙箱”的私有数据。 zoneSpecification:Zone的一些配置,可以自定义一些代码行为,比如拦截日志输出和错误等,举个例子: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 runZoned( () =\u0026gt; runApp(MyApp()), zoneSpecification: ZoneSpecification( // 拦截print 蜀西湖 print: (Zone self, ZoneDelegate parent, Zone zone, String line) { parent.print(zone, \u0026#34;Interceptor: $line\u0026#34;); }, // 拦截未处理的异步错误 handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, Object error, StackTrace stackTrace) { parent.print(zone, \u0026#39;${error.toString()} $stackTrace\u0026#39;); }, ), ); 这样一来,我们 APP 中所有调用print方法输出日志的行为都会被拦截,通过这种方式,我们也可以在应用中记录日志,等到应用触发未捕获的异常时,将异常信息和日志统一上报。 另外我们还拦截了未被捕获的异步错误,这样一来,结合上面的 FlutterError.onError 我们就可以捕获我们Flutter应用错误了并进行上报了!如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void collectLog(String line){ ... //收集日志 } void reportErrorAndLog(FlutterErrorDetails details){ ... //上报错误和日志逻辑 } FlutterErrorDetails makeDetails(Object obj, StackTrace stack){ ...// 构建错误信息 } void main() { // 已经捕获的异常 var onError = FlutterError.onError; //先将 onerror 保存起来 FlutterError.onError = (FlutterErrorDetails details) { onError?.call(details); //调用默认的onError reportErrorAndLog(details); //上报 }; runZoned( () =\u0026gt; runApp(MyApp()), zoneSpecification: ZoneSpecification( // 拦截print print: (Zone self, ZoneDelegate parent, Zone zone, String line) { collectLog(line); parent.print(zone, \u0026#34;Interceptor: $line\u0026#34;); }, // 拦截未处理的异步错误 handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, Object error, StackTrace stackTrace) { reportErrorAndLog(details); parent.print(zone, \u0026#39;${error.toString()} $stackTrace\u0026#39;); }, ), ); } ","permalink":"https://akashark.github.io/posts/read/flutter%E5%AE%9E%E6%88%98/%E7%AC%AC%E4%B8%80%E4%B8%AAflutter%E5%BA%94%E7%94%A8-%E5%9B%9B/","summary":"第二章 调试Flutter应用 日志与断点 debugger() 声明 1 2 3 4 void someFunction(double offset) { debugger(when: offset \u0026gt; 30.0); // ... } print、debugPrint、flutter logs Dart print()","title":"第一个Flutter应用 四"},{"content":"第二章\n路由管理 MaterialPageRoute 1 2 3 4 5 6 7 // 路由跳转 Navigator.push( context, MaterialPageRoute(builder: (context){ return const NewRoute(); }) ); MaterialPageRoute继承自PageRoute类,PageRoute类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,它还定义了路由构建及切换时过渡动画的相关接口及属性。MaterialPageRoute 是 Material组件库提供的组件,它可以针对不同平台,实现与平台页面切换动画风格一致的路由切换动画\n1 2 3 4 5 6 MaterialPageRoute({ WidgetBuilder builder, RouteSettings settings, bool maintainState = true, bool fullscreenDialog = false, }) MaterialPageRoute构造函数 (可以点进去看注释,注释写的也很清楚)\nbuilder 是一个WidgetBuilder类型的回调函数,它的作用是构建路由页面的具体内容,返回值是一个widget。我们通常要实现此回调,返回新路由的实例。 settings 包含路由的配置信息,如路由名称、是否初始路由(首页)。 maintainState:默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其所占用的所有资源,可以设置maintainState为 false。 fullscreenDialog表示新的路由页面是否是一个全屏的模态对话框,在 iOS 中,如果fullscreenDialog为true,新页面将会从屏幕底部滑入(而不是水平方向)。 Navigator Navigator是一个路由管理的组件,它提供了打开和退出路由页方法。Navigator通过一个栈来管理活动路由集合。通常当前屏幕显示的页面就是栈顶的路由。 常用方法\nFuture push(BuildContext context, Route route) 将给定的路由入栈(即打开新的页面),返回值是一个Future对象,用以接收新路由出栈(即关闭)时的 返回数据。 bool pop(BuildContext context, [ result ]) 将栈顶路由出栈,result 为页面关闭时返回给上一个页面的数据。 实例方法 Navigator类第一个参数为context的静态方法都对应一个Navigator的实例方法,比如 Navigator.push(BuildContext context, Route route)等价于 Navigator.of(context).push(Route route) ,下面命名路由相关的方法也是一样的。 Navigator 还有很多其他方法,如Navigator.replace、Navigator.popUntil等,详情请参考 API文档或SDK 源码注释,在此不再赘述。\n路由传值(非命名路由) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class TipRoute extends StatelessWidget { TipRoute({ Key key, required this.text, // 接收一个text参数 }) : super(key: key); final String text; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(\u0026#34;提示\u0026#34;), ), body: Padding( padding: EdgeInsets.all(18), child: Center( child: Column( children: \u0026lt;Widget\u0026gt;[ Text(text), ElevatedButton( onPressed: () =\u0026gt; Navigator.pop(context, \u0026#34;我是返回值\u0026#34;), child: Text(\u0026#34;返回\u0026#34;), ) ], ), ), ), ); } } class RouterTestRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: ElevatedButton( onPressed: () async { // 打开`TipRoute`,并等待返回结果 var result = await Navigator.push( context, MaterialPageRoute( builder: (context) { return TipRoute( // 路由参数 text: \u0026#34;我是提示xxxx\u0026#34;, ); }, ), ); //输出`TipRoute`路由返回结果 print(\u0026#34;路由返回值: $result\u0026#34;); }, child: Text(\u0026#34;打开提示页\u0026#34;), ), ); } } 命名路由 所谓命名路由,即有名字的路由,我们可以先给路由起一个名字,然后就可以通过路由名字直接打开新路由了,这为路由管理带来了一种直观、简单的方式。\n路由表 Map\u0026lt;String, WidgetBuilder\u0026gt; routes;他是一个Map,key为路由的名字,是一个字符串,value是个builder回调函数,用于生成相应的路由widget。\n注册路由表\n1 2 3 4 5 6 7 8 9 10 11 12 MaterialApp( title: \u0026#39;Flutter Demo\u0026#39;, initialRoute:\u0026#34;/\u0026#34;, //名为\u0026#34;/\u0026#34;的路由作为应用的home(首页) theme: ThemeData( primarySwatch: Colors.blue, ), //注册路由表 routes:{ \u0026#34;new_page\u0026#34;:(context) =\u0026gt; NewRoute(), \u0026#34;/\u0026#34;:(context) =\u0026gt; MyHomePage(title: \u0026#39;Flutter Demo Home Page\u0026#39;), //注册首页路由 } ); 跳转要通过路由名称来打开新路由,可以使用Navigator 的pushNamed方法: Future pushNamed(BuildContext context, String routeName,{Object arguments})\n传递参数 1 2 3 4 5 6 7 8 9 10 11 class EchoRoute extends StatelessWidget { @override Widget build(BuildContext context) { //获取路由参数 var args=ModalRoute.of(context).settings.arguments; //...省略无关代码 } } Navigator.of(context).pushNamed(\u0026#34;new_page\u0026#34;, arguments: \u0026#34;hi\u0026#34;); 对于有构造函数,并且构造函数需要传递参数的Widget我们可以使用下面的方式进行适配\n1 2 3 4 5 6 7 8 MaterialApp( ... //省略无关代码 routes: { \u0026#34;tip2\u0026#34;: (context){ return TipRoute(text: ModalRoute.of(context)!.settings.arguments); }, }, ); 路由生成钩子 MaterialApp有一个onGenerateRoute属性,它在打开命名路由时可能会被调用,之所以说可能,是因为当调用Navigator.pushNamed(\u0026hellip;)打开命名路由时,如果指定的路由名在路由表中已注册,则会调用路由表中的builder函数来生成路由组件;如果路由表中没有注册,才会调用onGenerateRoute来生成路由。onGenerateRoute回调签名如下: Route\u0026lt;dynamic\u0026gt; Function(RouteSettings settings)\n有了onGenerateRoute回调,要实现上面控制页面权限的功能就非常容易:我们放弃使用路由表,取而代之的是提供一个onGenerateRoute回调,然后在该回调中进行统一的权限控制,如:\n1 2 3 4 5 6 7 8 9 10 11 12 13 onGenerateRoute: (settings) { return MaterialPageRoute(builder: (context) { String? routeName = settings.name; switch (routeName) { case \u0026#34;/\u0026#34;: { return MyHomePage(title: \u0026#34;title\u0026#34;); } case \u0026#34;new\u0026#34;: { return NewRoute(titleStr: \u0026#34;titleStr\u0026#34;); } } return Scaffold(); }); 其中MaterialPageRoute\n1 2 3 4 5 6 7 8 9 10 MaterialPageRoute({ required this.builder, super.settings, this.maintainState = true, super.fullscreenDialog, }) : assert(builder != null), assert(maintainState != null), assert(fullscreenDialog != null) { assert(opaque); } 传入widgetBuild返回一个Route的子类\n总结 建议使用命名路由的形式,这将会带来如下好处:\n语义化更明确。 代码更好维护;如果使用匿名路由,则必须在调用Navigator.push的地方创建新路由页,这样不仅需要import新路由页的dart文件,而且这样的代码将会非常分散。 可以通过onGenerateRoute做一些全局的路由跳转前置处理逻辑。 包管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 name: flutter_in_action description: First Flutter Application. version: 1.0.0+1 dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.2 dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true name: 应用或者包名 description: 应用或包的描述、简介。 version:应用或包的版本号。 dependencies:应用或包依赖的其他包或插件。 dev_dependencies:开发环境依赖的工具包(而不是flutter应用本身依赖的包)。 flutter:flutter相关的配置选项。 如果我们的Flutter应用本身依赖某个包,我们需要将所依赖的包添加到dependencies下就可以了\nPub仓库 Pub(https://pub.dev/ )是 Google 官方的 Dart Packages 仓库,类似于 node 中的 npm仓库、Android中的 jcenter。我们可以在 Pub 上面查找我们需要的包和插件,也可以向 Pub 发布我们的包和插件。我们将在后面的章节中介绍如何向 Pub 发布我们的包和插件。\n我们可以使用IDE的功能或者手动运行flutter packages get 命令来下载依赖包。另外,需要注意dependencies和dev_dependencies的区别,前者的依赖包将作为App的源码的一部分参与编译,生成最终的安装包。而后者的依赖包只是作为开发阶段的一些工具包,主要是用于帮助我们提高开发、测试效率,比如 flutter 的自动化测试包等。\n本地依赖 如果我们正在本地开发一个包,包名为pkg1,我们可以通过下面方式依赖:\n1 2 3 dependencies: pkg1: path: ../../code/pkg1 git依赖 1 2 3 4 dependencies: pkg1: git: url: git://github.com/xxx/pkg1.git 1 2 3 4 5 dependencies: package1: git: url: git://github.com/flutter/packages.git path: packages/package1 问题他们这个不应该也有一个对于包的描述文件么,比如podspec之类的\n资源管理 指定 assets 1 2 3 4 flutter: assets: - assets/my_icon.png - assets/background.png assets指定应包含在应用程序中的文件, 每个 asset 都通过相对于pubspec.yaml文件所在的文件系统路径来标识自身的路径。asset 的声明顺序是无关紧要的,asset的实际目录可以是任意文件夹(在本示例中是assets 文件夹)\n加载文本assets 通过rootBundle (opens new window)对象加载:每个Flutter应用程序都有一个rootBundle (opens new window)对象, 通过它可以轻松访问主资源包,直接使用package:flutter/services.dart中全局静态的rootBundle对象来加载asset即可。 通过 DefaultAssetBundle (opens new window)加载:建议使用 DefaultAssetBundle (opens new window)来获取当前 BuildContext 的AssetBundle。 这种方法不是使用应用程序构建的默认 asset bundle,而是使父级 widget 在运行时动态替换的不同的 AssetBundle,这对于本地化或测试场景很有用。 通常,可以使用DefaultAssetBundle.of()在应用运行时来间接加载 asset(例如JSON文件),而在widget 上下文之外,或其他AssetBundle句柄不可用时,可以使用rootBundle直接加载这些 asset,例如:\n加载图片 主资源默认对应于1.0倍的分辨率图片。看一个例子:\n…/my_icon.png …/2.0x/my_icon.png …/3.0x/my_icon.png 在设备像素比率为1.8的设备上,\u0026hellip;/2.0x/my_icon.png 将被选择。对于2.7的设备像素比 率,\u0026hellip;/3.0x/my_icon.png将被选择。 如果未在Image widget上指定渲染图像的宽度和高度,那么Image widget将占用与主资源相同 的屏幕空间大小。 也就是说,如果\u0026hellip;/my_icon.png是72px乘72px,那么\u0026hellip;/3.0x/ my_icon.png应该是216px乘216px; 但如果未指定宽度和高度,它们都将渲染为72像素×72像素 (以逻辑像素为单位)。\npubspec.yaml中asset部分中的每一项都应与实际文件相对应,但主资源项除外。当主资源缺少某个资源时,会按分辨率从低到高的顺序去选择 ,也就是说1x中没有的话会在2x中找,2x中还没有的话就在3x中找。(可以不放1x的)\n1 2 3 4 5 6 7 8 9 Widget build(BuildContext context) { return DecoratedBox( decoration: BoxDecoration( image: DecorationImage( image: AssetImage(\u0026#39;graphics/background.png\u0026#39;), ), ), ); } 注意,AssetImage 并非是一个widget, 它实际上是一个ImageProvider,有些时候你可能期望直接得到一个显示图片的widget,那么你可以使用Image.asset()方法,如:\n1 2 3 Widget build(BuildContext context) { return Image.asset(\u0026#39;graphics/background.png\u0026#39;); } 使用默认的 asset bundle 加载资源时,内部会自动处理分辨率等,这些处理对开发者来说是无感知的。 (如果使用一些更低级别的类,如 ImageStream (opens new window)或 ImageCache (opens new window)时你会注意到有与缩放相关的参数)\n要加载依赖包中的图像,必须给AssetImage提供package参数。 例如,假设您的应用程序依赖于一个名为“my_icons”的包,它具有如下目录结构:\n…/pubspec.yaml …/icons/heart.png …/icons/1.5x/heart.png …/icons/2.0x/heart.png …etc. 然后加载图像,使用: AssetImage('icons/heart.png', package: 'my_icons') Image.asset('icons/heart.png', package: 'my_icons') 注意:包在使用本身的资源时也应该加上package参数来获取。 ps:\n与iOS中的类似,有Bundle类型,读取图片默认是处理scale,但是底层的API在处理图片的时候需要使用对应的scale处理 对于启动页和图标图片的使用均是在原生平台下进行使用的 多平台共享assets ","permalink":"https://akashark.github.io/posts/read/flutter%E5%AE%9E%E6%88%98/%E7%AC%AC%E4%B8%80%E4%B8%AAflutter%E5%BA%94%E7%94%A8-%E4%B8%89/","summary":"第二章 路由管理 MaterialPageRoute 1 2 3 4 5 6 7 // 路由跳转 Navigator.push( context, MaterialPageRoute(builder: (context){ return const NewRoute(); }) ); MaterialPageRoute继承自PageRoute类,PageRoute类","title":"第一个Flutter应用 三"},{"content":"第二章\n有状态与无状态组件 Stateful widget 可以拥有状态,这些状态在 widget 生命周期中是可以变的,而 Stateless widget 是不可变的。 Stateful widget 至少由两个类组成: 一个StatefulWidget类。 一个 State类; StatefulWidget类本身是不变的,但是State类中持有的状态在 widget 生命周期中可能会发生变化。 Widget 接口 Widget定义\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @immutable // 不可变的 abstract class Widget extends DiagnosticableTree { const Widget({ this.key }); final Key? key; @protected @factory Element createElement(); @override String toStringShort() { final String type = objectRuntimeType(this, \u0026#39;Widget\u0026#39;); return key == null ? type : \u0026#39;$type-$key\u0026#39;; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense; } @override @nonVirtual bool operator ==(Object other) =\u0026gt; super == other; @override @nonVirtual int get hashCode =\u0026gt; super.hashCode; static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType \u0026amp;\u0026amp; oldWidget.key == newWidget.key; } ... } @immutable 代表 Widget 是不可变的,这会限制 Widget 中定义的属性(即配置信息)必须是不可变的(final),为什么不允许 Widget 中定义的属性变化呢?这是因为,Flutter 中如果属性发生变化则会重新构建Widget树,即重新创建新的 Widget 实例来替换旧的 Widget 实例,所以允许 Widget 的属性变化是没有意义的,因为一旦 Widget 自己的属性变了自己就会被替换。这也是为什么 Widget 中定义的属性必须是 final 的原因。 widget类继承自DiagnosticableTree,DiagnosticableTree即“诊断树”,主要作用是提供调试信息。 Key: 这个key属性类似于 React/Vue 中的key,主要的作用是决定是否在下一次build时复用旧的 widget ,决定的条件在canUpdate()方法中。 createElement():正如前文所述“一个 widget 可以对应多个Element”;Flutter 框架在构建UI树时,会先调用此方法生成对应节点的Element对象。此方法是 Flutter 框架隐式调用的,在我们开发过程中基本不会调用到。 debugFillProperties(\u0026hellip;) 复写父类的方法,主要是设置诊断树的一些特性。 canUpdate(\u0026hellip;)是一个静态方法,它主要用于在 widget 树重新build时复用旧的 widget ,其实具体来说,应该是:是否用新的 widget 对象去更新旧UI树上所对应的Element对象的配置;通过其源码我们可以看到,只要newWidget与oldWidget的runtimeType和key同时相等时就会用new widget去更新Element对象的配置,否则就会创建新的Element。 Flutter 中的四棵树 Flutter渲染流程\n根据 Widget 树生成一个 Element 树,Element 树中的节点都继承自 Element 类。 根据 Element 树生成 Render 树(渲染树),渲染树中的节点都继承自RenderObject 类。 根据渲染树生成 Layer 树,然后上屏显示,Layer 树中的节点都继承自 Layer 类。 负责渲染和布局的是Render Tree\n1 2 3 4 5 6 7 8 9 Container( // 一个容器 widget color: Colors.blue, // 设置容器背景色 child: Row( // 可以将子widget沿水平方向排列 children: [ Image.network(\u0026#39;https://www.example.com/1.png\u0026#39;), // 显示图片的 widget const Text(\u0026#39;A\u0026#39;), ], ), ); 对于上面的代码会变成如下三棵树,其中对于容器设置back会变成cloredBox 三棵树中,Widget 和 Element 是一一对应的,但并不和 RenderObject 一一对应。比如 StatelessWidget 和 StatefulWidget 都没有对应的 RenderObject。 StatelessWidget Context statelessWidget是不需要记录状态的Widget,主要是方法是在build中构建UI配置,build方法有一个context参数,它是BuildContext类的一个实例,表示当前 widget 在 widget 树中的上下文,每一个 widget 都会对应一个 context 对象(因为每一个 widget 都是 widget 树上的一个节点)。实际上,context是当前 widget 在 widget 树中位置中执行”相关操作“的一个句柄(handle),比如它提供了从当前 widget 开始向上遍历 widget 树以及按照 widget 类型查找父级 widget 的方法。下面是在子树中获取父级 widget 的一个示例:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class ContextRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(\u0026#34;Context测试\u0026#34;), ), body: Container( child: Builder(builder: (context) { // 在 widget 树中向上查找最近的父级`Scaffold` widget Scaffold scaffold = context.findAncestorWidgetOfExactType\u0026lt;Scaffold\u0026gt;(); // 直接返回 AppBar的title, 此处实际上是Text(\u0026#34;Context测试\u0026#34;) return (scaffold.appBar as AppBar).title; }), ), ); } } StatefulWidget createState() 用于创建和 StatefulWidget 相关的状态,它在StatefulWidget 的生命周期中可能会被多次调用。例如,当一个 StatefulWidget 同时插入到 widget 树的多个位置时,Flutter 框架就会调用该方法为每一个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例。\nState 一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态,State中的保存的状态信息可以:\n在widget构建时被同步读取 在widget生命周期中可以被改变,当State被改变时,可以手动调用其setState()方法通知Flutter框架状态发生改变,Flutter框架收到状态改变的消息后,会重新调用其build方法构建widget树,从而更新UI State 中有两个常用的属性:\nwidget,它表示与该 State 实例关联的 widget 实例,由Flutter 框架动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI树上的某一个节点的 widget 实例在重新构建时可能会变化,但State实例只会在第一次插入到树中时被创建,当在重新构建时,如果 widget 被修改了,Flutter 框架会动态设置State. widget 为新的 widget 实例。 context。StatefulWidget对应的 BuildContext,作用同StatelessWidget 的BuildContext。 State 生命周期 initState: 当 widget 第一次插入到 widget 树时会被调用,对于每一个State对象,Flutter 框 架只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅 子树的事件通知等。不能在该回调中调用 BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在 widget 树上获取离当前 widget 最近的一个父级InheritedWidget,关于InheritedWidget 我们将在后面章节介绍),原因是在初始化完成后, widget 树中的InheritFrom widget也可能会发生变化,所以正确的做法应该在在build()方法或 didChangeDependencies()中调用它。 didChangeDependencies: 当State对象的依赖发生变化时会被调用;例如:在之前build() 中包含了一个 InheritedWidget (第七章介绍),然后在之后的build() 中Inherited widget发 生了变化,那么此时InheritedWidget的子 widget 的didChangeDependencies() 回调都会被调用。典型的场景是当系统语言 Locale 或应用主题改变时,Flutter 框架 会通知 widget 调用此回调。需要注意,组件第一次被创建后挂载的时候(包括重创建) 对应的didChangeDependencies也会被调用。 build: 此回调读者现在应该已经相当熟悉了,它主要是用于构建 widget 子树的,会在如下场景 被调用: 在调用initState()之后 在调用didUpdateWidget()之后 在调用setState()之后 在调用didChangeDependencies()之后 在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其他位置 reassemble: 此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。 didUpdateWidget: 在 widget 重新构建时,Flutter 框架会调用widget.canUpdate来检测 widget 树 中同一位置的新旧节点,然后决定是否需要更新,如果widget.canUpdate返回true则会 调用此回调。正如之前所述,widget.canUpdate会在新旧 widget 的 key 和 runtimeType 同时相等时会返回true,也就是说在在新旧 widget 的key和 runtimeType同时相等时didUpdateWidget()就会被调用。 deactivate: 当 State 对象从树中被移除时,会调用此回调。在一些场景下,Flutter 框架会将 State 对象重新插到树中,如包含此 State 对象的子树在树的一个位置移动到另一个位 置时(可以通过GlobalKey 来实现)。如果移除后没有重新插入到树中则紧接着会调用 dispose()方法。 dispose: 当 State 对象从树中被永久移除时调用;通常在此回调中释放资源。 ps: 在继承StatefulWidget重写其方法时,对于包含@mustCallSuper标注的父类方法,都要在子类方法中调用父类方法。\n在Widget树中获取State对象 context获取 context对象有一个findAncestorStateOfType()方法,该方法可以从当前节点沿着 widget 树向上查找指定类型的 StatefulWidget 对应的 State 对象。下面是实现打 开 SnackBar 的示例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class GetStateObjectRoute extends StatefulWidget { const GetStateObjectRoute({Key? key}) : super(key: key); @override State\u0026lt;GetStateObjectRoute\u0026gt; createState() =\u0026gt; _GetStateObjectRouteState(); } class _GetStateObjectRouteState extends State\u0026lt;GetStateObjectRoute\u0026gt; { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(\u0026#34;子树中获取State对象\u0026#34;), ), body: Center( child: Column( children: [ Builder(builder: (context) { return ElevatedButton( onPressed: () { // 查找父级最近的Scaffold对应的ScaffoldState对象 ScaffoldState _state = context.findAncestorStateOfType\u0026lt;ScaffoldState\u0026gt;()!; // 打开抽屉菜单 _state.openDrawer(); }, child: Text(\u0026#39;打开抽屉菜单1\u0026#39;), ); }), ], ), ), drawer: Drawer(), ); } } 在 Flutter 开发中便有了一个默认的约定:如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个of 静态方法来获取其 State 对象,开发者便可直接通过该方法来获取 1 2 3 4 5 6 7 8 9 10 11 Builder(builder: (context) { return ElevatedButton( onPressed: () { // 直接通过of静态方法来获取ScaffoldState ScaffoldState _state=Scaffold.of(context); // 打开抽屉菜单 _state.openDrawer(); }, child: Text(\u0026#39;打开抽屉菜单2\u0026#39;), ); }), 通过GolbalKey GlobalKey 是 Flutter 提供的一种在整个 App 中引用 element 的机制。如果一个 widget 设置了GlobalKey,那么我们便可以通过globalKey.currentWidget获得该 widget 对象、globalKey.currentElement来获得 widget 对应的element对象, 如果当前 widget 是StatefulWidget,则可以通过globalKey.currentState来获得 该 widget 对应的state对象。 ps: 使用 GlobalKey 开销较大,如果有其他可选方案,应尽量避免使用它。另外,同一 个 GlobalKey 在整个 widget 树中必须是唯一的,不能重复。 使用RenderObject定义Widget 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class CustomWidget extends LeafRenderObjectWidget{ @override RenderObject createRenderObject(BuildContext context) { // 创建 RenderObject return RenderCustomObject(); } @override void updateRenderObject(BuildContext context, RenderCustomObject renderObject) { // 更新 RenderObject super.updateRenderObject(context, renderObject); } } class RenderCustomObject extends RenderBox{ @override void performLayout() { // 实现布局逻辑 } @override void paint(PaintingContext context, Offset offset) { // 实现绘制 } } 常见组件 Text (opens new window):该组件可让您创建一个带格式的文本。 Row (opens new window)、 Column (opens new window): 这些具有弹性空间的布局类 widget 可让您在水平(Row)和垂直(Column)方向上创建灵活的布局。其设计是基于 Web 开发中的 Flexbox 布局模型。 Stack (opens new window): 取代线性布局 (译者语:和 Android 中的FrameLayout相似),[Stack](https://docs.flutter.dev/flutter/ widgets/Stack-class.html)允许子 widget 堆叠, 你可以使用 Positioned (opens new window)来定位他们相对于Stack的上下左右四条边的位置。Stacks是基于Web开发中的绝对定位(absolute positioning )布局模型设计的。 Container (opens new window): Container (opens new window)可让您创建矩形视觉元素。Container 可以装饰一个BoxDecoration (opens new window), 如 background、一个边框、或者一个阴影。 Container (opens new window)也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container (opens new window)可以使用矩阵在三维空间中对其进行变换。 Material组件 Cupertino组件 在Widget之上的两个库,是Flutter提供的两种风格的组件库\n","permalink":"https://akashark.github.io/posts/read/flutter%E5%AE%9E%E6%88%98/%E7%AC%AC%E4%B8%80%E4%B8%AAflutter%E5%BA%94%E7%94%A8-%E4%B8%80/","summary":"第二章 有状态与无状态组件 Stateful widget 可以拥有状态,这些状态在 widget 生命周期中是可以变的,而 Stateless widget 是不可变的。 Stateful widget 至少由两个类组成: 一个StatefulWi","title":"第一个Flutter应用 一"},{"content":"第二章\n状态管理 StatefulWidget的状态管理视情况被管理,通常有一下几种方式\nWidget 管理自己的状态 Widget 管理子widget状态 混合管理 (父Widget和子Widget都管理状态) 如何决定使用哪种管理方式 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 Widget 管理。 如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由 Widget 本身来管理。 如果某一个状态是不同 Widget 共享的则最好由它们共同的父 Widget 管理。 在 Widget 内部管理状态封装性会好一些,而在父 Widget 中管理会比较灵活。有些时 候,如果不确定到底该怎么管理状态,那么推荐的首选是在父 Widget 中管理(灵活会显 得更重要一些)。 Widget管理自身状态 _TapboxAState 类:\n管理TapboxA的状态。 定义_active:确定盒子的当前颜色的布尔值。 定义_handleTap()函数,该函数在点击该盒子时更新_active,并调用setState()更新UI。 实现widget的所有交互式行为 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 // TapboxA 管理自身状态. //------------------------- TapboxA ---------------------------------- class TapboxA extends StatefulWidget { TapboxA({Key? key}) : super(key: key); @override _TapboxAState createState() =\u0026gt; _TapboxAState(); } class _TapboxAState extends State\u0026lt;TapboxA\u0026gt; { bool _active = false; void _handleTap() { setState(() { _active = !_active; }); } Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( child: Center( child: Text( _active ? \u0026#39;Active\u0026#39; : \u0026#39;Inactive\u0026#39;, style: TextStyle(fontSize: 32.0, color: Colors.white), ), ), width: 200.0, height: 200.0, decoration: BoxDecoration( color: _active ? Colors.lightGreen[700] : Colors.grey[600], ), ), ); } } 父Widget管理子Widget的状态 在以下示例中,TapboxB通过回调将其状态导出到其父组件,状态由父组件管理,因此它的父组件为StatefulWidget。但是由于TapboxB不管理任何状态,所以TapboxB为StatelessWidget。 ParentWidgetState 类:\n为TapboxB 管理_active状态。 实现_handleTapboxChanged(),当盒子被点击时调用的方法。 当状态改变时,调用setState()更新UI。 TapboxB 类: 继承StatelessWidget类,因为所有状态都由其父组件处理。 当检测到点击时,它会通知父组件。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 // ParentWidget 为 TapboxB 管理状态. //------------------------ ParentWidget -------------------------------- class ParentWidget extends StatefulWidget { @override _ParentWidgetState createState() =\u0026gt; _ParentWidgetState(); } class _ParentWidgetState extends State\u0026lt;ParentWidget\u0026gt; { bool _active = false; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Container( child: TapboxB( active: _active, onChanged: _handleTapboxChanged, ), ); } } //------------------------- TapboxB ---------------------------------- class TapboxB extends StatelessWidget { TapboxB({Key? key, this.active: false, required this.onChanged}) : super(key: key); final bool active; final ValueChanged\u0026lt;bool\u0026gt; onChanged; void _handleTap() { onChanged(!active); } Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( child: Center( child: Text( active ? \u0026#39;Active\u0026#39; : \u0026#39;Inactive\u0026#39;, style: TextStyle(fontSize: 32.0, color: Colors.white), ), ), width: 200.0, height: 200.0, decoration: BoxDecoration( color: active ? Colors.lightGreen[700] : Colors.grey[600], ), ), ); } } 混合状态管理 对于一些组件来说,混合管理的方式会非常有用。在这种情况下,组件自身管理一些内部状态,而父组件管理一些其他外部状态。 在下面 TapboxC 示例中,手指按下时,盒子的周围会出现一个深绿色的边框,抬起时,边框消失。点击完成后,盒子的颜色改变。 TapboxC 将其_active状态导出到其父组件中,但在内部管理其_highlight状态。这个例子有两个状态对象_ParentWidgetState和_TapboxCState。 _ParentWidgetStateC类:\n管理_active 状态。 实现 _handleTapboxChanged() ,当盒子被点击时调用。 当点击盒子并且_active状态改变时调用setState()更新UI。 _TapboxCState 对象:\n管理_highlight 状态。 GestureDetector监听所有tap事件。当用户点下时,它添加高亮(深绿色边框);当用户释放时,会移除高亮。 当按下、抬起、或者取消点击时更新_highlight状态,调用setState()更新UI。 当点击时,将状态的改变传递给父组件。 整体Demo代码如下\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 //---------------------------- ParentWidget ---------------------------- class ParentWidgetC extends StatefulWidget { @override _ParentWidgetCState createState() =\u0026gt; _ParentWidgetCState(); } class _ParentWidgetCState extends State\u0026lt;ParentWidgetC\u0026gt; { bool _active = false; void _handleTapboxChanged(bool newValue) { setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Container( child: TapboxC( active: _active, onChanged: _handleTapboxChanged, ), ); } } //----------------------------- TapboxC ------------------------------ class TapboxC extends StatefulWidget { TapboxC({Key? key, this.active: false, required this.onChanged}) : super(key: key); final bool active; final ValueChanged\u0026lt;bool\u0026gt; onChanged; @override _TapboxCState createState() =\u0026gt; _TapboxCState(); } class _TapboxCState extends State\u0026lt;TapboxC\u0026gt; { bool _highlight = false; void _handleTapDown(TapDownDetails details) { setState(() { _highlight = true; }); } void _handleTapUp(TapUpDetails details) { setState(() { _highlight = false; }); } void _handleTapCancel() { setState(() { _highlight = false; }); } void _handleTap() { widget.onChanged(!widget.active); } @override Widget build(BuildContext context) { // 在按下时添加绿色边框,当抬起时,取消高亮 return GestureDetector( onTapDown: _handleTapDown, // 处理按下事件 onTapUp: _handleTapUp, // 处理抬起事件 onTap: _handleTap, onTapCancel: _handleTapCancel, child: Container( child: Center( child: Text( widget.active ? \u0026#39;Active\u0026#39; : \u0026#39;Inactive\u0026#39;, style: TextStyle(fontSize: 32.0, color: Colors.white), ), ), width: 200.0, height: 200.0, decoration: BoxDecoration( color: widget.active ? Colors.lightGreen[700] : Colors.grey[600], border: _highlight ? Border.all( color: Colors.teal[700], width: 10.0, ) : null, ), ), ); } } 全局状态管理 实现一个全局的事件总线,将语言状态改变对应为一个事件,然后在APP中依赖应用语言的组件的initState 方法中订阅语言改变的事件。当用户在设置页切换语言后,我们发布语言改变事件,而订阅了此事件的组件就会收到通知,收到通知后调用setState(\u0026hellip;)方法重新build一下自身即可。 使用一些专门用于状态管理的包,如 Provider、Redux,读者可以在 pub 上查看其详细信息。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 import \u0026#39;package:flutter/material.dart\u0026#39;; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: \u0026#39;Flutter Demo\u0026#39;, theme: ThemeData( // This is the theme of your application. // // Try running your application with \u0026#34;flutter run\u0026#34;. You\u0026#39;ll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // \u0026#34;hot reload\u0026#34; (press \u0026#34;r\u0026#34; in the console where you ran \u0026#34;flutter run\u0026#34;, // or simply save your changes to \u0026#34;hot reload\u0026#34; in a Flutter IDE). // Notice that the counter didn\u0026#39;t reset back to zero; the application // is not restarted. primarySwatch: Colors.blue, ), home: const MyHomePage(title: \u0026#39;Flutter Demo Home Page\u0026#39;), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect // how it looks. // This class is the configuration for the state. It holds the values (in this // case the title) provided by the parent (in this case the App widget) and // used by the build method of the State. Fields in a Widget subclass are // always marked \u0026#34;final\u0026#34;. final String title; // 对于StatefulWidget自动会执行creatState 但在生命周期的那个时刻呢 @override State\u0026lt;MyHomePage\u0026gt; createState() =\u0026gt; _MyHomePageState(); } class _MyHomePageState extends State\u0026lt;MyHomePage\u0026gt; { int _counter = 0; void _incrementCounter() { setState(() { // This call to setState tells the Flutter framework that something has // changed in this State, which causes it to rerun the build method below // so that the display can reflect the updated values. If we changed // _counter without calling setState(), then the build method would not be // called again, and so nothing would appear to happen. _counter++; }); } @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done // by the _incrementCounter method above. // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: Column( // Column is also a layout widget. It takes a list of children and // arranges them vertically. By default, it sizes itself to fit its // children horizontally, and tries to be as tall as its parent. // // Invoke \u0026#34;debug painting\u0026#34; (press \u0026#34;p\u0026#34; in the console, choose the // \u0026#34;Toggle Debug Paint\u0026#34; action from the Flutter Inspector in Android // Studio, or the \u0026#34;Toggle Debug Paint\u0026#34; command in Visual Studio Code) // to see the wireframe for each widget. // // Column has various properties to control how it sizes itself and // how it positions its children. Here we use mainAxisAlignment to // center the children vertically; the main axis here is the vertical // axis because Columns are vertical (the cross axis would be // horizontal). mainAxisAlignment: MainAxisAlignment.center, children: \u0026lt;Widget\u0026gt;[ TaboxA(), ParentWidget(), ParentWidgetC(), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: \u0026#39;Increment\u0026#39;, child: const Icon(Icons.add), ), ); } } class TaboxA extends StatefulWidget { const TaboxA({Key? key}) : super(key: key); @override State\u0026lt;TaboxA\u0026gt; createState() =\u0026gt; _TaboxAState(); } class _TaboxAState extends State\u0026lt;TaboxA\u0026gt; { bool _active = false; // Widget自己管理状态 _handleTap() { setState(() { _active = !_active; }); } @override Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( width: 200.0, height: 200.0, decoration: BoxDecoration( color: _active ? Colors.lightGreen[700] : Colors.grey[600], ), child: Center( child: Text( _active ? \u0026#34;Active\u0026#34; : \u0026#34;Inactive\u0026#34;, style: const TextStyle( fontSize: 32.0, color: Colors.white, ), ), ), ), ); } } // 交给父类管理状态 class ParentWidget extends StatefulWidget { const ParentWidget({Key? key}) : super(key: key); @override State\u0026lt;ParentWidget\u0026gt; createState() =\u0026gt; _ParentWidgetState(); } class _ParentWidgetState extends State\u0026lt;ParentWidget\u0026gt; { bool _active = false; _handleTapboxChanged(bool newValue) { if (newValue == _active) return; setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Container( child: TapBoxB( active: _active, onChanged: _handleTapboxChanged, ), ); } } class TapBoxB extends StatelessWidget { const TapBoxB({Key? key, required this.active, required this.onChanged}) : super(key: key); final bool active; final ValueChanged\u0026lt;bool\u0026gt; onChanged; _handleTap() { onChanged(!active); } @override Widget build(BuildContext context) { return GestureDetector( onTap: _handleTap, child: Container( width: 200.0, height: 200.0, decoration: BoxDecoration( color: active ? Colors.lightGreen[700] : Colors.grey[600], ), child: Center( child: Text( active ? \u0026#34;Active\u0026#34; : \u0026#34;Inactive\u0026#34;, style: const TextStyle( fontSize: 32.0, color: Colors.white, ), ), ), ), ); } } // 混合管理模式 class ParentWidgetC extends StatefulWidget { @override State\u0026lt;ParentWidgetC\u0026gt; createState() =\u0026gt; _ParentWidgetCState(); } // 管理active状态 class _ParentWidgetCState extends State\u0026lt;ParentWidgetC\u0026gt; { bool _active = false; _handleTapboxChanged(bool newValue) { if (newValue == _active) return ; setState(() { _active = newValue; }); } @override Widget build(BuildContext context) { return Container( child: TapBoxC( active: _active, activeOnChanged: _handleTapboxChanged, ), ); } } class TapBoxC extends StatefulWidget { const TapBoxC({Key? key, required this.active, required this.activeOnChanged}) : super(key: key); // 父widget管理的状态 final bool active; final ValueChanged\u0026lt;bool\u0026gt; activeOnChanged; @override State\u0026lt;TapBoxC\u0026gt; createState() =\u0026gt; _TapBoxCState(); } // 管理highlight状态 class _TapBoxCState extends State\u0026lt;TapBoxC\u0026gt; { bool _highlight = false; void _handleTapDown(TapDownDetails details) { setState(() { _highlight = true; }); } void _handleTapUp(TapUpDetails details) { setState(() { _highlight = false; }); } void _handleTapCancel() { setState(() { _highlight = false; }); } void _handleTap() { // 回调active状态 widget.activeOnChanged(!widget.active); } @override Widget build(BuildContext context) { return GestureDetector( onTapDown: _handleTapDown, // 处理按下事件 onTapUp: _handleTapUp, // 处理抬起事件 onTap: _handleTap, onTapCancel: _handleTapCancel, child: Container( width: 200.0, height: 200.0, decoration: BoxDecoration( color: widget.active ? Colors.lightGreen[700] : Colors.grey[600], border: _highlight ? Border.all( color: Colors.teal, width: 10.0 ): null, ), child: Center( child: Text( widget.active ? \u0026#39;Active\u0026#39; : \u0026#39;Inactive\u0026#39;, style: const TextStyle(fontSize: 32.0, color: Colors.white), ), ), ), ); } } ","permalink":"https://akashark.github.io/posts/read/flutter%E5%AE%9E%E6%88%98/%E7%AC%AC%E4%B8%80%E4%B8%AAflutter%E5%BA%94%E7%94%A8-%E4%BA%8C/","summary":"第二章 状态管理 StatefulWidget的状态管理视情况被管理,通常有一下几种方式 Widget 管理自己的状态 Widget 管理子widget状态 混合管理 (父Wi","title":"第一个Flutter应用 二"},{"content":"永澄:任务、目标和计划都是啥?它们之间啥关系?\n行动(Action) 行动,指的是通过一步操作即可完成的、不可细分的最小事项,活动,也可以理解为执行的最小单元。 对于任务规划目标来说只有行动才是可以被执行的,这是任务管理中的基本原则,任何任务,目标,计划都需要转化成行动才能被执行。 行动概念的用法,通常有两种:\n规范描述 当你提到行动时,其中必须包含动词 落实任务 用话术的方式来帮助自己思考某任务是否可以落地,话术为:\u0026ldquo;现在已经分解到行动层级了 么? 可以执行了么?\u0026rdquo; 在拆分好行动后问下自己,是否每个拆分出来的小任务都是可以被执 行的 ps: 一件事是否是行动,你需要真诚地对待自己,不需要假装自己很厉害,把所有事情都看的太简单,也不需要严格分解所有的事项,那样管理成本就太高了。让这件事处于一个合理的管理成本区间就够了。也就是说,管理成本区间决定着一件事对自己来说究竟是不是行动。\n任务(Task) 任务是由若干行动有机组合而形成的事件/活动集合,任务可以通过一定方式分解成行动。 只要不能一步完成的事项/活动都是任务\n分解任务\n若干行动,若干指的是不定量,一个或者多个,所以这里隐藏这一点,存在由一个行动构成的任务 有机组合的集合,不是说把任务放在一起就是任务了,任务有其整体性,我们用最简单的集合来理解: 把两条行动“吃饭”、“买票”放在一起组成一个任务“去餐厅吃饭”,那这个任务本身也是可以被理解的。如果行动组合成的任务不能被理解,那就不叫任务,比如说“一边在家吃饭,一边在电影院排队买票”就超出认知范围,这就不是“有机”组合。也可以这么说,任务是由行动构成的有机整体。正如上图展示的那样,组合在一起。 一定方式分解。任务是可以被分解成行动的,但是必须要通过一定方式来分解,不同人掌握的“方式”不同,把任务分解成行动(就是所谓的“任务分解”)的过程也是不同的,所以,即便同样的任务,不同人的处理方式也是不同的。另外,这一条还说明,如果你不具备“方式”,很可能有些任务是无法分解的,比如说“先赚一个亿”,不能分解就不能执行,就只能卡在那。引申一下:如果你发现自己卡住了,不要抱怨/低落/挫败,而是要去找到有效的“方式” 总结下: 从整体上看,任务是一个整体,打开他的表面,他还是若干任务构成的有机集合,这个集合可以通过某种方式分解成一个个的行动,之所以有任务,就是因为通过实施任务,可以得到结果,而那个结果是我们想要的目的(Purpose),任务只是稻城结果的执行载体。\n目标 目标是任务的固有属性 我理解就是一个任务的指定是有一个或者多个目标的,比如我吃饭目标就是晚上吃饱了 即便是执行同一件任务(行为), 因为目的(Purpose)不同,所以需要达成的目标也是不同的。\n目标是眼睛中可以看到的标准,用这个定义可以解释SMART原则; 目标很复杂,为降低管理成本,要从一个点切入理解,这个点是“任务和目标的关系”; 目标是任务的属性,任务和目标的关系是:任务是对象、目标是属性; 任务的目标属性有不同的值,任务之所以体现出不同的目标属性,是由执行任务的人背后的目的所决定的,目的不同,即便同一件任务,目标也不同。 PORT模型中的O-目标,其实蕴含在任务之中,根据目的不同,目标也不同。这就是“做任何事前必须要澄清目的”的底层逻辑。 ps: 任务和目标的关系,必须是现有目的,后有任务,再有目标,按照细分流程刻意联系,只有练习多了,就能提出整体目标。\n计划 计划是为了达成任务的整体目标而对任务分解出的行动进行排序的方式和结果。\n为什么要有计划?为了实现任务的整体目标。 计划是什么?是方法,作为动词使用,计划(安排)某某事情;也是结果,作为名词使用,做出一份计划。当它是动词使用时,主要对行动进行排序,排序之后的结果,就是名词形式。 排序是什么意思?通常来说要对行动的先后关系、重要程度、执行方式进行考虑,最终排列行动的串并行执行关系,就是排序。 总结 每个人都有自己想要的(想得到-期待导向、想逃避-问题导向),但是水平不同,效能不同:水平低的就会瞎做事情,以为自己可以得到想要的结果,这种无意识、无方法的人效能通常很低。\n来看看高水平的人——他是既知道自己要什么、还知道怎么去得到的人(做对的事,把事做对),他的做法是:先去分析如果想要实现目的,需要得到哪些结果;通过结果推导出任务以及任务所需要达成的目标;之后把任务分解至可以执行的行动;再对行动进行排序形成计划;按照计划去执行最终达成目的。\n这是一个完整的流程,具体执行的时候要根据目的和任务的大小来选择相应的标准,哪个环节要做成什么样的,哪个环节可以省略,这些标准的形成来自于刻意训练。这是所有自我效能提升类学科(目标、时间、任务、行动管理)的底层逻辑,对于上图标准的把握、对流程的应用能力,直接体现出一个人的自我管理能力,也决定了一个人的效能水平。\n","permalink":"https://akashark.github.io/posts/life/%E8%87%AA%E6%88%91%E6%88%90%E9%95%BF/%E4%BB%BB%E5%8A%A1%E7%9B%AE%E6%A0%87%E8%AE%A1%E5%88%92/","summary":"永澄:任务、目标和计划都是啥?它们之间啥关系? 行动(Action) 行动,指的是通过一步操作即可完成的、不可细分的最小事项,活动,也可以理解为","title":"任务目标计划"},{"content":"第一章 起步 弹射起步 变量声明 var 声明变量,但是类型后面不可以改变了,根据第一次赋值数据的类型来推断其类型,编译结束后其类型就已经被确定 dynamic 和 Object 声明动态变量,后面类型也可以改变,不同点在于dynamic可以调用可以用的属性(有运行时风险),Object 只能调用Object提供的属性 final const final是运行时常量,const是编译时常量,同时用final或者const修饰的变量可以不加类型 空类型 安全 1 2 3 4 5 6 7 int i = 8; //默认为不可空,必须在定义时初始化。 int? j; // 定义为可空类型,对于可空变量,我们在使用前必须判空。 // 如果我们预期变量不能为空,但在定义时不能确定其初始值,则可以加上late关键字, // 表示会稍后初始化,但是在正式使用它之前必须得保证初始化过了,否则会报错 late int k; k=9; 函数 函数式编程 Dart函数声明如果没有显式声明返回值类型时会默认当做dynamic处理,返回值没有类型推断 支持箭头函数,只有一个语句的函数,使用箭头函数简写 函数做为变量 1 2 3 4 var say = (str){ print(str); }; say(\u0026#34;hi world\u0026#34;); 函数作为参数传递 1 2 3 4 void execute(var callback) { callback(); } execute(() =\u0026gt; print(\u0026#34;xxx\u0026#34;)) 可选的位置参数 包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的最后面: 1 2 3 4 5 6 7 String say(String from, String msg, [String? device]) { var result = \u0026#39;$from says $msg\u0026#39;; if (device != null) { result = \u0026#39;$result with a $device\u0026#39;; } return result; } 可选的命名参数 定义函数时,使用{param1, param2, …},放在参数列表的最后面,用于指定命名参数。例如: 1 2 3 4 //设置[bold]和[hidden]标志 void enableFlags({bool bold, bool hidden}) { // ... } mixin Dart 是不支持多继承的,但是它支持 mixin,简单来讲 mixin 可以 “组合” 多个类,我们通过一个例子来理解。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Person { say() { print(\u0026#39;say\u0026#39;); } } mixin Eat { eat() { print(\u0026#39;eat\u0026#39;); } } mixin Walk { walk() { print(\u0026#39;walk\u0026#39;); } } mixin Code { code() { print(\u0026#39;key\u0026#39;); } } class Dog with Eat, Walk{} class Man extends Person with Eat, Walk, Code{} ps:\n不能同时使用位置参数和命名参数 我们定义了几个 mixin,然后通过 with 关键字将它们组合成不同的类。有一点需要注意:如果多个mixin 中有同名方法,with 时,会默认使用最后面的 mixin 的,mixin 方法中可以通过 super 关键字调用之前 mixin 或类中的方法。 异步支持 Future Future.then 为了方便示例,在本例中我们使用Future.delayed 创建了一个延时任务(实际场景会是一个真正的耗时任务,比如一次网络请求),即2秒后返回结果字符串\u0026quot;hi world!\u0026quot;,然后我们在then中接收异步结果并打印结果,代码如下:\n1 2 3 4 5 Future.delayed(Duration(seconds: 2),(){ return \u0026#34;hi world!\u0026#34;; }).then((data){ print(data); }); Future.catchError 1 2 3 4 5 6 7 8 9 10 Future.delayed(Duration(seconds: 2),(){ //return \u0026#34;hi world!\u0026#34;; throw AssertionError(\u0026#34;Error\u0026#34;); }).then((data){ //执行成功会走到这里 print(\u0026#34;success\u0026#34;); }).catchError((e){ //执行失败会走到这里 print(e); }); 或者使用then的onError可选参数\n1 2 3 4 5 6 7 8 Future.delayed(Duration(seconds: 2), () { //return \u0026#34;hi world!\u0026#34;; throw AssertionError(\u0026#34;Error\u0026#34;); }).then((data) { print(\u0026#34;success\u0026#34;); }, onError: (e) { print(e); }); Future.whenComplete 无论成功失败都走的块\n1 2 3 4 5 6 7 8 9 10 11 12 Future.delayed(Duration(seconds: 2),(){ //return \u0026#34;hi world!\u0026#34;; throw AssertionError(\u0026#34;Error\u0026#34;); }).then((data){ //执行成功会走到这里 print(data); }).catchError((e){ //执行失败会走到这里 print(e); }).whenComplete((){ //无论成功或失败都会走到这里 }); Future.wait 类似于GCD group\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 Future.wait([ // 2秒后返回结果 Future.delayed(Duration(seconds: 2), () { return \u0026#34;hello\u0026#34;; }), // 4秒后返回结果 Future.delayed(Duration(seconds: 4), () { return \u0026#34; world\u0026#34;; }) ]).then((results){ print(results[0]+results[1]); }).catchError((e){ print(e); }); 执行上面代码,4秒后你会在控制台中看到“hello world”。 里面的两个任务都执行完成后才会走到then\nasync/await 避免回调低于,用同步的写法写出异步的代码 回调地狱\n1 2 3 4 5 6 7 8 9 10 login(\u0026#34;alice\u0026#34;,\u0026#34;******\u0026#34;).then((id){ //登录成功后通过,id获取用户信息 getUserInfo(id).then((userInfo){ //获取用户信息后保存 saveUserInfo(userInfo).then((){ //保存用户信息,接下来执行其他操作 ... }); }); }) 使用Future写出 Callback Hell 1 2 3 4 5 6 7 8 9 10 login(\u0026#34;alice\u0026#34;,\u0026#34;******\u0026#34;).then((id){ return getUserInfo(id); }).then((userInfo){ return saveUserInfo(userInfo); }).then((e){ //执行接下来的操作 }).catchError((e){ //错误处理 print(e); }); Future 的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用” ,如果在then 中返回的是一个Future的话,该future会执行,执行结束后会触发后面的then回调,这样依次向下,就避免了层层嵌套。\n使用 async/await 消除callback hell 1 2 3 4 5 6 7 8 9 10 task() async { try{ String id = await login(\u0026#34;alice\u0026#34;,\u0026#34;******\u0026#34;); String userInfo = await getUserInfo(id); await saveUserInfo(userInfo); //执行接下来的操作 } catch(e){ //错误处理 print(e); } async用来表示函数是异步的,定义的函数会返回一个Future对象,可以使用 then 方法添加回调函数。 await 后面是一个Future,表示等待该异步任务完成,异步完成后才会往下走;await必须出现在 async 函数内部。 其实,无论是在 JavaScript 还是 Dart 中,async/await 都只是一个语法糖, 译器或解释器最终都会将其转化为一个 Promise(Future)的调用链。\nStream Stream 也是用于接收异步事件数据,和 Future 不同的是,它可以接收多个异步操作的结果(成功或失败)。 也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。 Stream 常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Stream.fromFutures([ // 1秒后返回结果 Future.delayed(Duration(seconds: 1), () { return \u0026#34;hello 1\u0026#34;; }), // 抛出一个异常 Future.delayed(Duration(seconds: 2),(){ throw AssertionError(\u0026#34;Error\u0026#34;); }), // 3秒后返回结果 Future.delayed(Duration(seconds: 3), () { return \u0026#34;hello 3\u0026#34;; }) ]).listen((data){ print(data); }, onError: (e){ print(e.message); },onDone: (){ }); 输出如下: I/flutter (17666): hello 1 I/flutter (17666): Error I/flutter (17666): hello 3\n","permalink":"https://akashark.github.io/posts/read/flutter%E5%AE%9E%E6%88%98/%E8%B5%B7%E6%AD%A5/","summary":"第一章 起步 弹射起步 变量声明 var 声明变量,但是类型后面不可以改变了,根据第一次赋值数据的类型来推断其类型,编译结束后其类型就已经被确定 dynamic 和 Object 声明","title":"起步"},{"content":"背景 最近有两件事情对我感触还是挺大的,第一件是在字节的转正实习转正,第二件是转正后2天的研究生导师组会。为啥这两件事情会对我感触比较大呢,总的来说这两件事情的本质其实是相同的,都是完成一部分任务,对于现在完成的情况遇到的问题细节等作输出汇报,汇报的对象一个是+1 +2的领导,一个是自己的研究生导师,在汇报的工程中,发现他们的思考方式竟然是一致的。这是有点颠覆我的认知的,我之前一直以为,大学的教授大部分是学院派,搞理论的那一套会比较厉害,企业中的领导更多的思考点是如何创造更大的价值。\n一些想法 他们的思考方式是一直的,在听我汇报的过程中问的问题也是相似的,基本上从几个维度,现在正在做的事情是什么,做的怎么样了,遇到了什么问题,预期的结果是什么,为什么这么做,衡量收益的指标是什么,下一步打算怎么做,预期的结果是什么,为什么这么做,打算怎么去规避一些问题。 一般问到现在正在做什么事情的时候我还能回答的很好,到了预期以及下一步怎么做的时候,我经常就有点拉胯了,说实在的之前并没有细想过,问到衡量指标我就彻底歇菜,但是我发现对于衡量指标老板们问的最多。\n现在回想起来这些问题其实他们想问的点也很清楚\n正在做的事情, 其实想了解当前事情的背景是什么 做的怎么样了, 遇到了什么问题, 其实想了解当前事件的进度如何,到了哪一步了 预期结果是什么, 其实是想了解当前这件事你自己搞清楚没有,自己想要完成什么样的目标 一下步打算怎么做,其实是想了解你对这件事情的规划,下一步的计划 为什么这么做,如何规避问题, 其实就是想看看你对整件事情的一个思考程度 衡量指标,你对这件事情的认知程度,事件的清晰程度 老板们可能并不清楚我做的事情,但这个简简单单的几个问题,就能确定清楚我的一些思考,以及这件事情的完成程度,老板们的思路是如果这件事,我思考的很全面了,并且清楚自己的每一步在干什么,其实八九不离十就可以做好,毕竟已经是研究生(或者通过面试进入公司)。\n后续的一些做法 但我确实对自己做的事情,没想的很细。。。我经常做事情就是一股脑的冲进去,但是这样缺少思考,感觉大部分的时间都是白白荒废掉了。 觉得老板们的思考还是很到位的,以后的思考方式也应该向老板们看起,做事情的时候首先将这几件事情想清楚\n为什么做这件事 (评估下这件事情的优先级,成本,大概的收益) 怎么做这件事情 (现状是什么,可能遇到的问题,卡点在哪里,现阶段要做什么) 对这件事情预期,目标 (目标是什么,做到什么程度,做完这件事情对于现状的提升,对于现状以及以后的影响) 5w2h分析法 5w2h 发明者以五个w开头的英语单词和两个以h开头的英语单词进行设问。发现解决问题的线索,寻找出创新和发明新项目的思路,更进一步进行设计构思,从而搞出新的发明项目,这就叫做5w2h法。\nWhy What When Who Where How to do How much 在发明和设计中,对问题不敏感,看不出毛病是与平时不善于提问有密切关系的。对一个问题追根刨底,才有可能发现新的知识和新的疑问。所以从根本上说, 会发明首先要学会提问,善于提问。阻碍提问的因素:一是怕提问多,被别人看成什么也不懂的傻瓜。二是随着年龄和知识的增长,提问欲望渐渐淡薄。如果提 得不到答复和鼓励,反而遭人讥讽,结果在人的潜意识中就形成了这种看法:好提问、好挑毛病的人事扰乱别人的讨厌鬼,最好紧闭嘴唇,不看、不闻、不问, 是这恰恰阻碍了人的创造性的发挥。\n在对5w2h模式进行思考的时候可以善用下思维导图,遇到问题多追问自己两个问题没准可以发现问题的本质原因.\n","permalink":"https://akashark.github.io/posts/life/%E6%80%9D%E8%80%83/20221001/","summary":"背景 最近有两件事情对我感触还是挺大的,第一件是在字节的转正实习转正,第二件是转正后2天的研究生导师组会。为啥这两件事情会对我感触比较大呢,总","title":"最近的一些感悟 (20221001)"},{"content":"What’s New in Xcode 14 What’s New in Xcode 14\nSingle Size App Icon 在WWDC22, Apple发布了最新的Xcode14,Xcode14中带来了大量的功能和性能的提高,包括了在源码编辑器和其他方面上很多比较Cool的东西,我将和你一起分享下你要知道的。\n让我们来一起过下这些主题\nCode Structure When Scrolling 单尺寸App icon对于开发者是一个非常好的功能,你再也不需要App Icon生成工具,你能使用单一的一个1024x1024px的App Icon 资源。 不但如此,这将减少你App的尺寸,你可以尝试下使用1024x1024px的App Icon资源,然后导入到Xcode的Assets中从右边的工具条上选择单尺寸,一旦你导出IPA,你将看出来不同 下面的图是一个app归档后的结果,他展示了选择App icon 单图片和多图片的不同 Code Structure When Scrolling 我第一次看到这个功能很是吃惊,我想起来了成组的table View的样式,我确信这个是一个有价值的功能,为我们在编辑器滑动的时候展示我们在那个方法。 你当然可以禁止或者开启这个功能在Setting中如下展示 同样的对于标记导航也有修改,你能通过标记导航看到你在代码前写的标记标签。 Memberwise Initializer Completion 完成成员变量的初始化,在之前版本的Xcode,你需要去右击成员变量初始化 在Xcode 14 提供了高效的方式, 创建成员变量初始化,你只需要写下init,然后你将看到最接近的成员变量初始化的方法 还有更多代码补全的提高,这些补全也更加精确 Optiuonal Unwrapping 我看到的一大进步是你不需要通过 if let 设置来创建不可变变量来检查可选变量。 Xcode Build TimeLine 在之前版本的 Xcode 中,我们将构建日志视为一个列表,就像您在左侧看到的以下列表一样。我们还知道正在构建哪个步骤以及需要多少时间。现在,使用 Xcode 14,我们可以将这些日志视为时间线。 Highlighted Other Features and Improvements in the Release Notes 还有很多高光的其他能力和提高在release文档中被提到\nSimulator now supports remote notifications in iOS 16 when running in macOS 13 on Mac computers with Apple silicon or T2 processors. Xcode 14 can now compile targets in parallel with their Swift target dependencies. You can now enable sandboxing for shell script build phases using the ENABLE_USER_SCRIPT_SANDBOXING build setting. Xcode now provides RECOMMENDED_MACOSX_DEPLOYMENT_TARGET, RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET, RECOMMENDED_TVOS_DEPLOYMENT_TARGET, RECOMMENDED_WATCHOS_DEPLOYMENT_TARGET, and RECOMMENDED_DRIVERKIT_DEPLOYMENT_TARGET build settings that indicate the recommended minimum deployment versions for each supported Xcode platform. Because bitcode is now deprecated, builds for iOS, tvOS, and watchOS no longer include bitcode by default. The Thread Performance Checker shows runtime performance issues in the Issue Navigator and the source editor while debugging an app. Interface Builder now updates scenes asynchronously. Wrapping code with an if statement now automatically reindents the block. ","permalink":"https://akashark.github.io/posts/tech/%E7%BF%BB%E8%AF%91/xcode14%E6%96%B0%E5%8A%9F%E8%83%BD/","summary":"What’s New in Xcode 14 What’s New in Xcode 14 Single Size App Icon 在WWDC22, Apple发布了最新的Xcode14,Xcode14中带来了大量的功能和性能","title":"Xcode14新功能"},{"content":"工厂模式 浅析设计模式1 —— 工厂模式\n设计模式分类 创建型模式是对垒的实例化过程进行抽象,从而将对象的穿件和使用分离开,工厂模式属于创建型模式的范畴\n基本概念 工厂模式的核心思想就是创建对象和使用对象的解耦,由工厂负责对象的创建,而用户只能通过接口来使用对象, 这样就可以灵活应对变化的业务需求,方便代码管理,避免代码重复\n简单工厂模式 顾名思义,简单工厂模式是最简单的一种工厂模式,它定义了一个负责生产对象的工厂类,使用者可以根据不同参数来创建并返回不同子类,这些子类都共用一个接口(即父类)。\n结构 简单工厂模式包含三种类,分别是抽象产品类、具体产品类、工厂类,下面分别对各类及它们之间的关系作进一步说明。 使用 有了上述的基本概念,我们将简单工厂模式的使用步骤概括为:\nstep1: 创建抽象产品类,并为具体产品定义好一个接口 step2: 创建具体产品类,其通过接口来集成抽象产品类,同时也要定义计划生产的每一个具体产品 step3: 创建工厂类,器创建的静态方法可以对传入的不同参数做出响应 step4: 外界使用这就能调用工厂类的静态方法了,通过传入不同参数来创建不同具体产品实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 //step1:创建抽象产品类,定义具体产品的公共接口 public abstract class Shirt{ public abstract void Show(); } //step2:创建具体产品类(继承抽象产品类),定义生产的具体产品 //具体产品类A,女款衬衫 public class WomenShirt extends Shirt{ @Override public void Show(){ System.out.println(\u0026#34;展示女款衬衫\u0026#34;); } } //具体产品类B,男款 public class MenShirt extends Shirt{ @Overside public void Show(){ System.out.println(\u0026#34;展示男款衬衫\u0026#34;); } } //step3:创建工厂类,通过静态方法处理不同传入参数,从而创建不同具体产品类的实例 public class Factory{ public static Shirt Exhibit(String ShirtName){ switch(ShirtName){ case \u0026#34;女款衬衫\u0026#34;: return new WomenShirt(); case \u0026#34;男款衬衫\u0026#34;: return new MenShirt(); default: return null; } } } //step4:外界调用工厂类的静态方法,传入不同参数创建不同具体产品类的实例 public class SimpleFactoryPattern{ public static void main(String[] args){ Factory exhibitFactory = new Factory(); //用户搜索女款衬衫 try{ //调用工厂类的静态方法,传入参数并创建实例 exhibitFactory.Exhibit(\u0026#34;女款衬衫\u0026#34;).Show(); }catch(NullPointerException e){ System.out.println(\u0026#34;没有找到商品\u0026#34;); } //用户搜索男款裤子 try{ exhibitFactory.Exhibit(\u0026#34;男款裤子\u0026#34;).Show(); }catch(NullPointerException e){ System.out.println(\u0026#34;没有找到商品\u0026#34;); } //用户搜索男款衬衫 try{ exhibitFactory.Exhibit(\u0026#34;男款衬衫\u0026#34;).Show(); }catch(NullPointerException e){ System.out.println(\u0026#34;没有找到商品\u0026#34;); } } } 优点 将对象的使用和创建过程分离开,实现解藕。客户端不需要关注对象是谁创建的、怎么创建的,只要通过工厂中的静态方法就可以直接获取其需要的对象。 将初始化实例的工作放到工厂里执行,代码易维护, 更符合面向对象的原则,做到面向接口编程,而不是面向实现编程。 缺点 工厂类中需要选择创建具体某个对象,所以一旦添加新产品则必须要对工厂中的选择逻辑进行修改,导致工厂逻辑过于复杂,违背开闭原则。 工厂类集合了所有实例(具体产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响。 静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。 场景 具体产品类比较少,使用简单工厂模式可以实现生产者与消费者的分离,而且也不会在工厂中定义太复杂的判断逻辑 使用者只需要知道工厂类的参数,不关系如何创建对象的逻辑时 工厂方法模式 工厂方法模式包含四种类,分别是抽象产品类,具体产品类,抽象工厂类,具体工厂类 使用 step1: 创建抽象工厂类,定义具体工厂的公共接口 step2: 创建抽象产品类,定义具体产品的公共接口 step3: 创建具体产品类(继承抽象产品类), 定义生产的具体产品 step4: 创建具体工厂类(集成抽象工厂类), 定义创建相应具体产品实例的方法 step5: 外界调用具体工厂类的方法,创建不同产品类的实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 //step1:创建抽象工厂类,定义具体工厂的公共接口 public abstract class Factory{ public abstract Shirt Exhibit(); } //step2:创建抽象产品类,定义具体产品的公共接口 public abstract class Shirt{ public abstract void Show(); } //step3:创建具体产品类(继承抽象产品类),定义生产的具体产品 //具体产品类A,女款衬衫 public class WomenShirt extends Shirt{ @Override public void Show(){ System.out.println(\u0026#34;展示女款衬衫\u0026#34;); } } //具体产品类B,男款衬衫 public class MenShirt extends Shirt{ @Overside public void Show(){ System.out.println(\u0026#34;展示男款衬衫\u0026#34;); } } //step4:创建具体工厂类,定义创建具体产品实例的方法 //具体工厂类A,展示女款衬衫类商品 public class WomenShirtFactory extends Factory{ @Overside public Shirt Exhibit(){ return new WomenShirt(); } } //具体工厂类B,展示男款衬衫类商品 public class MenShirtFactory extends Factory{ @Overside public Shirt Exhibit(){ return new MenShirt(); } } //step5:外界调用具体工厂类的方法,创建不同具体产品类的实例 public class FactoryPattern{ public static void main(String[] args){ //用户在店铺搜索女款衬衫 Factory exhibitWomenShirtFactory = new WomenShirtFactory(); exhibitWomenShirtFactory.Exhibit().Show(); //用户在店铺搜索男款衬衫 Factory exhibitMenShirtFactory = new MenShirtFactory(); exhibitMenShirtFactory.Exhibit().Show(); } } ps: 其实把if else 判断具体创建哪种产品交给了外部调用者来区分\n优点 符合开闭原则,新增一种产品时,只需要增加相应的具体产品类和工厂子类即可,可发方便的生产或者切换产品 符合单一原则,每个具体工厂类只负责创建对应的具体产品,而简单工厂中工厂类可能存在比较复杂的逻辑 相对于简单工厂模式,可形成基于继承的等级结构 缺点 一个具体工厂只能创建一种具体产品,添加新产品的时, 除增加新产品类外,还要提供与之对应的工厂类,类的个数成对增加,在一定程度是哪个增加了系统的复杂度,同时有更多的类需要加载与编译,给系统带来了额外的开销 由于考虑到系统的可扩展行,引入了抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,增加了系统的实现难度 虽然保证了工厂方法内的对修改的关闭,但对于使用工厂的类,如果需要更改另一种产品,仍需要修改实例化的具体工厂类 难以对于父类接口进行修改,因为一旦修改接口,就必须要对众多的子类进行修改 适用场景 一个类不确定他所必须创建的对象的类,在工厂方法模式长胖呢个,客户端不需要知道具体产品类的类名,只需要知道对应的工厂即可 你期望获得较高的扩展性 一个类希望由它的子类来指定它所创建的对象。在工厂方法模式中,对于抽象工厂类只需提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏替换原则,在程序运行时,子类对象将覆盖父类对象,从而使系统更容易扩展 当类将创建对象的职责委托给多个工厂子类中的一个,而且用户知道要使用那个一个子类(判断放在了客户端这里) 抽象工厂模式 抽象工厂模式,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类。抽象工厂模式与工厂方法模式最大的区别:抽象工厂中每个具体工厂可以创建多类具体产品;而工厂方法每个具体工厂只能创建一类具体产品。\n结构 抽象工厂模式包含五种类,分别是抽象产品族类、抽象产品类、具体产品类、抽象工厂类、具体工厂类 感觉就是又增加了一层抽象层 抽象产品族 使用 step1:创建抽象工厂类,定义具体工厂的公共接口; step2:创建抽象产品族类,定义抽象产品的公共接口; step3:创建抽象产品类(继承抽象产品族类),定义具体产品的公共接口; step4:创建具体产品类(继承抽象产品类),定义生产的具体产品; step5:创建具体工厂类(继承抽象工厂类),定义创建相应具体产品实例的方法; step6:外界调用具体工厂类的方法,创建不同具体产品类的实例。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 //step1:创建抽象工厂类,定义具体工厂的公共接口 public abstract class Factory{ public abstract Clothing ExhibitShirt(); public abstract Clothing ExhibitTrousers(); } //step2:创建抽象产品族类,定义抽象产品的公共接口 public abstract class Clothing{ public abstract void Show(); } //step3:创建抽象产品类,定义具体产品的公共接口 // 短袖抽象类 public abstract class Shirt extends Clothing{ @Override public abstract void Show(); } // 裤子抽象类 public abstract class Trousers extends Clothing{ @Override public abstract void Show(); } //step4:创建具体产品类(继承抽象产品类),定义生产的具体产品 //衬衫产品类A,淘宝衬衫 public class TBShirt extends Shirt{ @Override public void Show(){ System.out.println(\u0026#34;展示淘宝店铺衬衫\u0026#34;); } //衬衫产品类B,淘特衬衫 public class TTShirt extends Shirt{ @Overside public void Show(){ System.out.println(\u0026#34;展示淘特店铺衬衫\u0026#34;); } } //裤子产品类A,淘宝裤子 public class TBTrousers extends Trousers{ @Override public void Show(){ System.out.println(\u0026#34;展示淘宝店铺裤子\u0026#34;); } } //裤子产品类B,淘特裤子 public class TTTrousers extends Trousers{ @Overside public void Show(){ System.out.println(\u0026#34;展示陶特店铺裤子\u0026#34;); } } //step5:创建具体工厂类,定义创建具体产品实例的方法 //淘宝工厂类A,展示衬衫+裤子 public class TBFactory extends Factory{ @Overside public Clothing ExhibitShirt(){ return new TBShirt(); } @Overside public Clothing ExhibitTrousers(){ return new TBTrousers(); } } //淘特工厂类B,展示裤子+衬衫 public class TTFactory extends Factory{ @Overside public Clothing ExhibitShirt(){ return new TTShirt(); } @Overside public Clothing ExhibitTrousers(){ return new TTTrousers(); } } //step6:外界实例化具体工厂类,调用工厂类中创建不同目标产品的方法,创建不同具体产品类的实例 public class AbstractFactoryPattern{ public static void main(String[] args){ TBFactory exhibitTBFactory = new TBFactory(); TTFactory exhibitTTFactory = new TTFactory(); //淘宝用户搜索衬衫 exhibitTBFactory.ExhibitShirt().Show(); //淘宝用户搜索衬衫 exhibitTBFactory.ExhibitTrousers().Show(); //淘特用户搜索衬衫 exhibitTTFactory.ExhibitShirt().Show(); //淘特用户搜索衬衫 exhibitTTFactory.ExhibitTrousers().Show(); } } 优点 降低耦合度。抽象工厂模式将具体产品的创建延迟到具体工厂类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而降低系统耦合度,有利于后期的维护和扩展。 符合开闭原则。新增一种产品类时,只需增加相应的具体产品类和工厂子类即可,简单工厂模式需要修改工厂类的判断逻辑。 符合单一职责原则。每个具体工厂类只负责创建对应的产品,简单工厂模式中的工厂类需要进行复杂的 switch 逻辑判断。 不使用静态工厂方法,可以形成基于继承的等级结构。 便于添加更换产品族。因为具体产品都是由具体工厂创建的,所以在更换产品族的时候只要简单修改具体工厂即可。 具体产品的创建过程和客户端隔离。客户端通过操作抽象产品接口实现操作具体产品实例,具体产品的类名不会出现在客户端中。 缺点 难以支持新种类产品的变化。这是因为抽象工厂接口中已经确定了可被创建的产品集合,如果需要添加新产品,此时就必须去添加抽象产品接口,还要在抽象工厂接口中添加新方法,并在所有具体工厂中实现该新方法。这样就会改变抽象工厂类以及所有具体工厂子类的改变,违背开闭原则。 类图有点复杂,可读性没有工厂方法模式高。 场景 系统不要求依赖产品类实例如何被创建、组合和表达,这点也是所有工厂模式应用的前提。 系统要求提供一个产品类的库,所有产品以同样的接口出现,客户端不需要依赖具体实现。 系统中有多个产品族,但每次只使用其中某一族产品。(切换产品族只需修改具体工厂对象即可) 总结 简单工厂模式:让一个工厂类负责创建所有对象;但没有考虑后期扩展和维护,修改违背开闭原则,静态方法不能被继承。 工厂方法模式:主要思想是继承,修改符合开闭原则;但每个工厂只能创建一种类型的产品。 抽象工厂模式:主要思想是组合,本质是产品族,实际包含了很多工厂方法,修改符合开闭原则;但只适用于增加同类工厂这种横向扩展需求,不适合新增功能方法这种纵向扩展。 其实这三种工厂模式在形式和特点上都非常相似,甚至存在一定的内在联系,而且最终目的都是解耦。在使用时,我们不必去在意这个模式到底工厂方法模式还是抽象工厂模式,因为它们之间也是可以灵活转变的。比如你原本使用的是工厂方法模式,加入一个新方法后就可能会让具体产品类构成不同等级结构中的产品族,代码结构就变成抽象工厂模式了;而对于抽象工厂模式,当减少一个或多个具体产品类时,使原有产品族只剩下一个产品后,代码结构也就转变成了工厂方法模式。\n","permalink":"https://akashark.github.io/posts/tech/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/","summary":"工厂模式 浅析设计模式1 —— 工厂模式 设计模式分类 创建型模式是对垒的实例化过程进行抽象,从而将对象的穿件和使用分离开,工厂模式属于创建型模式的范","title":"工厂模式"},{"content":"HTTP发展历程 [TOC]\nHTTP协议版本 HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2.0 HTTP/3.0 HTTP/0.9 0.9版本的HTTP协议他不涉及数据包传输(仅仅是HTLM通过ASCII编码后传递),主要是规定了客户端和服务端之间的通信格式,默认使用80端口,这个版本只有一条命令GET,而且不支持处理HTML以外任何的文件格式。 缺点\n只支持简单的文本传输,并没有更多丰富的元素 支持的行为比较少,只支持GET HTTP/1.0 相对于0.9版本增加了如下\n支持响应支持HTTP头(HTTP Header), 支持响应含状态行,增加状态码 支持HEAD,POST等方法 支持HTML以外的文件格式 包括了图片,视频,二进制文件等 相对于0.9版本1.0版本,为了支持互联网的变化增加了许多内容,丰富了网络提供的基础服务 缺点 客户端必须为每一个待请求的对象建立并维护一个新的连接,也就是说当页面存在多个对象,HTTP1.0建立非持久连接,使得一个页面下载十分缓慢,增加了网络传输负担 HTTP/1.1 相对于之前的版本1.1版本增加了如下\n长连接与管道化: HTTP1.1支持长连接和请求的流水线处理,在一个TCP连接上可以传送很多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTPP1.1中默认开启Connection: keey-alive 缓存处理: 在 HTTP1.0 中主要使用 header 里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。 带宽优化: HTTP1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1 则在请求头引入了range 头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。 错误状态码: 在 HTTP1.1 中新增了24 个错误状态响应码,如 409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。 Host头处理: 在 HTTP1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。HTTP1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request)。 缺点 虽然 1.1 版允许复用 TCP 连接,但是同一个 TCP 连接里面,所有的数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。要是前面的回应特别慢,后面就会有许多请求排队等着。 HTTP1.x 在传输数据时,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份,这在一定程度上无法保证数据的安全性。 HTTP1.x 在使用时,header 里携带的内容过大,在一定程度上增加了传输的成本,并且每次请求 header 基本不怎么变化,尤其在移动端增加用户流量。 虽然 HTTP1.x 支持了 keep-alive,来弥补多次创建连接产生的延迟,但是 keep-alive 使用多了同样会给服务端带来大量的性能压力,并且对于单个文件被不断请求的服务(例如图片存放网站),keep-alive 可能会极大的影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间。 SPDY协议 2009 年,谷歌公开了自行研发的 SPDY 协议,主要解决 HTTP/1.1 效率不高的问题。 这个协议在 Chrome 浏览器上证明可行以后,就被当作 HTTP/2 的基础,主要特性都在 HTTP/2 之中得到继承。SPDY 可以说是综合了 HTTPS 和 HTTP 两者有点于一体的传输协议,主要解决:\n降低延迟 SPDY采用了多路复用,多路复用通过多个请求Stream共享一个TCP连接的方式降低延迟的同时提高了带宽的利用率 请求优先级 多路复用带来了一个新的问题是,在连接共享的基础上可能会导致关键请求被阻塞,SPYD运行给每个request设置优先级,这样重要的请求会优先得到响应 Header压缩 SYPD采用了DEFLATE压缩算法对于HTTP的Header部分进行了压缩处理 服务端推送 采用了SPDY设计的网页可以接受服务端主动的推送,比如在请求css的时候服务端将js作为推送内容推送给浏览器,在网页请求js的时候可以直接使用浏览器的换从不用再次发起请求 默认增加SSL/LTS层来保证数据传递的安全性问题 HTTP/2.0 HTTP/2.0可以说是SPDY的升级版,主要与SPDY的不同在于\nHTTP2.0 支持明文传输HTTP传输,而SPDY强制使用HTTPS HTTP2.0 消息头的压缩算法采用了HPACK,而非SPDY采用的DEFLATE HTTP2.0的新特性包括了 二进制分帧 HTTP2.0的所有数据帧都采用二进制编码 多路复用 请求优先级 header压缩 服务端推送 二进制分帧 帧: 客户端与服务器通过交换帧来通信,帧是基于这个新协议通信的最小单元 消息: 逻辑上的HTTP消息。比如请求,响应等,由一个或者多个帧组成 流: 流是连接中的一个虚拟信道,可以承载双向消息(全双工),每个流都有一个唯一的整数标识符 HTTP/2 采用二进制格式传输数据,而非 HTTP 1.x 的文本格式,二进制协议解析起来更高效。 HTTP/1.x 的请求和响应报文,都是由起始行,首部和实体正文(可选)组成,各部分之间以文本换行 符分隔。HTTP/2 将请求和响应数据分割为更小的帧,并且它们采用二进制编码。 帧、流、消息的关系 每个数据流都以消息的形式发送,而消息又由一个或者多个帧组成,帧是流中的数据单元,一个数据报的header帧可以分成多个header帧,data帧可以分成多个data帧。 多路复用 多路复用运行同时通过单一的HTTP/2.0连接发起多重的请求-响应消息,每个request都可以复用共享的连接,每一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混合在一起,接受房可以根据request的id将request再归属到各自不同的服务端请求请求里 请求优先级 把 HTTP 消息分解为很多独立的帧之后,就可以通过优化这些帧的交错和传输顺序,每个流都可以带有一个 31 比特的优先值:0 表示最高优先级;2 的 31 次方-1 表示最低优先级。 服务器可以根据流的优先级,控制资源分配(CPU、内存、带宽),而在响应数据准备好之后,优先将最高优先级的帧发送给客户端。 header压缩 HTTP1.X的header带有大量信息,而且每次都要重复发送,HTTP/2.0使用encoder来减少需要传输的header的大小,并且通讯双方各自cache一份header field表,避免了重复header的传输问题,又减少了需要传输的大小,为了减少这部分的资源消耗并提升性能,HTTP/2.0对一下首部采取了压缩策略\nHTTP/2.0在客户端和服务端使用首部表来跟踪和存储之前发送的键值对(HTTP Header),不再重复发送Header 首部表在HTTP/2.0的连接存续期内始终存在,由客户端和服务端共同渐进地更新 针对Header的更新,如下图所示,根据已经缓存的Header Fields表来更新键值对 服务器推送 Server Push 即服务端能通过 push 的方式将客户端需要的内容预先推送过去,也叫“cache push”。 服务器可以对一个客户端请求发送多个响应。服务器向客户端推送资源无需客户端明确地请求,服务端可以提前给客户端推送必要的资源,这样可以减少请求延迟时间,例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不是等到 HTML 解析到资源时发送请求,大致过程如下图所示: HTTP/3.0 HTTP3.0可以看做HTTP协议的一次大版本的更新,HTTP3.0将传输层的协议由TCP更换成了QUIC协议 为什么会更换掉传输层的协议呢,因为在HTTP2.0中大家已经将HTTP队头阻塞的问题通过多路复用解决了,应用层的优化做尽了,没地方卷了,就开上了TCP队头阻塞的问题,打算从传输层下手。 哪有为什么选择UDP呢,传输层的协议替换是需要硬件层面运营商支持的,大规模的普及人力物力成本较大ROI较低,就很IPV6一样24年前的东西到现在也没普及多少,所以这帮人就盯上了UDP,UDP和TCP一样被大部分硬件支持,而且基于UDP的QUIC协议提供了可靠性的保证。 那么QUIC是怎么解决TCP队头问题的呢, 为了解决传输级别的队头阻塞问题,通过 QUIC 连接传输的数据被分为一些流。流是持久性 QUIC 连接中短暂、独立的“子连接”。每个流都处理自己的错误纠正和传递保证,但使用连接全局压缩和加密属性。每个客户端发起的 HTTP 请求都在单独的流上运行,因此丢失数据包不会影响其他流/请求的数据传输。\n补充 数字证书 证书生成规则\nCA机构拥有非对称加密的私钥和公钥。 CA机构对证书明文数据T进行hash。 对hash后的值用私钥加密,得到数字签名S。 明文和数字签名共同组成了数字证书,这样一份数字证书就可以颁发给网站了。 从上面的图也可以看出数字证书就是证书的明文信息+证书明文信息用hash签名再用CA的私钥加密后的签名\n证书验证规则 拿到证书,得到明文T1,数字签名S1。 用CA机构的公钥对S1解密,得到S2。 用证书里说明的hash算法对明文T1进行hash得到T2。 比较S2是否等于T2,等于则表明证书可信。 队头阻塞 TCP队头阻塞 TCP队头阻塞是因为TCP协议要求我们在每收到一个SYN包的时候就需要发送一个ACK作为回复,但是在 网络不好的情况下有可能发生丢包的现象,TCP的做法是在发送方将丢失的数据包重传之前,接受方是不 能将已经接受的数据包做回复的(已经接受的数据包将丢弃)。TCP的队头阻塞问题在移动网络上更加明 显。 HTTP队头阻塞 HTTP在1.1后默认开启了管线化,HTTP 管线化的意思就是客户端无需在发送后续 HTTP 请求之前等待 服务器响应请求。此功能可以更有效地利用带宽并减少延迟,但它的改进空间甚至更大。HTTP 管线化 仍要求服务器按照接收到的请求顺序进行响应,因此,如果管线化中的单个请求执行得很慢,则对客户 端的所有后续响应都将相应地延迟下去。这个问题被称为队头阻塞。 总结 从 http/0.9 到 http/2 的发展,有了很多的优化点如下:\n二进制分帧:HTTP/2 的所有帧都采用二进制编码 多路复用 (Multiplexing) 请求优先级 header 压缩 服务端推送 到了HTTP3.0更是有了长足的进步直接把传输层的协议改了不再依靠应用层去做一些花活了 ","permalink":"https://akashark.github.io/posts/tech/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/http%E5%8F%91%E5%B1%95%E5%8F%B2%E6%B5%85%E6%9E%90/","summary":"HTTP发展历程 [TOC] HTTP协议版本 HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2.0 HTTP/3.0 HTTP/0.9 0.9版本的HTTP协议他不涉及数据包传输(仅仅是HTLM通过ASCII编码后传递),主要是规","title":"HTTP发展史浅析"},{"content":"","permalink":"https://akashark.github.io/docs/network/http%E5%8F%91%E5%B1%95%E5%8F%B2%E6%B5%85%E6%9E%90/","summary":"","title":"HTTP发展史浅析"},{"content":"为什么要有虚拟内存? 虚拟内存 单道程序的操作系统直接引用的物理地址,无法同时运行两个程序,程序会因为内存地址错乱而崩溃,单道程序的操作系统使用队列,做完一个任务开始下一个任务。\n现代操作系统的做法是把进程所使用的地址「隔离」开来,即让操作系统为每个进程分配独立的一套「虚拟地址」,人人都有,大家自己玩自己的地址就行,互不干涉。但是有个前提每个进程都不能访问物理地址,至于虚拟地址最终怎么落到物理内存里,对进程来说是透明的,操作系统已经把这些都安排的明明白白了。 操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。\n程序中使用的地址: 虚拟内存地址 硬盘中使用的地址: 物理内存地址\n操作系统引入了虚拟内存,进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存,如下图所示: 操作系统使用内存分段和内存分页来管理虚拟地址与物理地址之间的关系\n补充: 虚拟内存有什么作用\n提高内存使用率: 虚拟内存可以使得进程运行内存超过物理内存大小,因为程序运行符合局部性原则,cpu访问内存会有很明显的重复访问的倾向性,对于那些没有被经常使用到的的内存,我们可以将他们换出物理内存外,到磁盘上的swap区域 进程隔离: 每个进程都有自己的页表,所以每个进程的虚拟内存相互独立,一个进程没有办法访问其他进程的页表,这些页表都是私有的,这就解决了多进程之间地址冲突的问题 安全性保证: 页表里面的页表项除了物理地址之外,还有一些标志属性,比如控制一个页的读写权限标记该页是否存在,在内存访问方面,操作系统为操作系统提供了更好的安全支持。 内存分段 程序是由若干个逻辑分段组成的,如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,所以就用分段(Segmentation)的形式把这些段分离出来。 分段机制下的虚拟地址由两部分组成,段选择因子和段内偏移量。\n段选择因子和段内偏移量:\n段选择子就保存在段寄存器里面。段选择子里面最重要的是段号,用作段表的索引。段表里面保存的是这个段的基地址、段的界限和特权等级等。 虚拟地址中的段内偏移量应该位于 0 和段界限之间,如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址。 问题\n内存碎片化问题 内存交换效率低 碎片化 外部内存碎片,产生了多个不连续的小物理内存,导致新的程序无法被装载 内部内存碎片,程序所有的内存都被装载到了物理内存,但是这个程序有部分的内存可能并不是很常使用,这也会导致内存的浪费; 内存交换,将部分内存写到磁盘上再次读取进来紧挨着已经被占用的防止出现外部碎片,这个内存交换空间,在 Linux 系统里,也就是我们常看到的 Swap 空间,这块空间是从硬盘划分出来的,用于内存与硬盘的空间交换。\n交换率低 对于多进程的系统来说,用分段的方式,内存碎片是很容易产生的,产生了内存碎片,那不得不重新 Swap 内存区域,这个过程会产生性能瓶颈。 因为硬盘的访问速度要比内存慢太多了,每一次内存交换,我们都需要把一大段连续的内存数据写到硬盘上。所以,如果内存交换的时候,交换的是一个占内存空间很大的程序,这样整个机器都会显得卡顿。\n内存分页 分段的好处就是能产生连续的内存空间,但是会出现内存碎片和内存交换的空间太大的问题。 要解决这些问题,那么就要想出能少出现一些内存碎片的办法。另外,当需要进行内存交换的时候,让需要交换写入或者从磁盘装载的数据更少一点,这样就可以解决问题了。这个办法,也就是内存分页(Paging)。\n分页是把整个虚拟和物理空间切成一段段固定尺寸的大小,这样一个连续的并且尺寸固定的空间我们叫做页。 虚拟地址和物理地址之间通过页表来映射 和分段管理的段表类似,分页管理的页表也存储在MMU(内存管理单元)中\n当进程访问的虚拟地址在页表查不到的时候,系统会产生一个缺页中断,进入系统内核空间分配物理内存,更新进程页表,最后返回用户空间,恢复进程的运行\n由于内存控件都是预先划分好的,也就不会像分段会产生非常细小的内存,这正是分段会产生内存碎片的原因,而采用了分页, 那么释放的内存都是以页为释放单元,也就不会产生无法给进程使用的小内存了\n如果内存空间不够,操作系统会把其他正在运行的进程中的「最近没被使用」的内存页面给释放掉,也就是暂时写在硬盘上,称为换出(Swap Out)。一旦需要的时候,再加载进来,称为换入(Swap In)。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,内存交换的效率就相对比较高。 在分页机制下,虚拟地址分为两部分,页号和页内偏移。页号作为页表的索引,页表包含物理页每页所在物理内存的基地址,这个基地址与页内偏移的组合就形成了物理内存地址,见下图。 内存地址转化三个步骤\n把虚拟内存地址,切分成页号和偏移量 根据页号去MMU中查找对应的物理页号 直接拿到物理页的页号(物理内存的基地址), 加上前面的偏移量, 就得到了物理内存地址 有空间上的缺陷。 因为操作系统是可以同时运行非常多的进程的,那这不就意味着页表会非常的庞大。 在 32 位的环境下,虚拟地址空间共有 4GB,假设一个页的大小是 4KB(2^12),那么就需要大约 100 万 (2^20) 个页,每个「页表项」需要 4 个字节大小来存储,那么整个 4GB 空间的映射就需要有 4MB 的内存来存储页表。 这 4MB 大小的页表,看起来也不是很大。但是要知道每个进程都是有自己的虚拟地址空间的,也就说都有自己的页表。 那么,100 个进程的话,就需要 400MB 的内存来存储页表,这是非常大的内存了,更别说 64 位的环境了。 ps: 内存分页和内存分段的逻辑和链表和数组有点相似,分页采用的不是连续的内存区域,分段采用的是连续的内存区域\n多级页表 如果使用了二级分页,一级页表就可以覆盖整个 4GB 虚拟地址空间,但如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表。做个简单的计算,假设只有 20% 的一级页表项被用到了,那么页表占用的内存空间就只有 4KB(一级页表) + 20% * 4MB(二级页表)= 0.804MB,这对比单级页表的 4MB 是不是一个巨大的节约?\n64位的系统二级分页不够使用,变成了四级\n全局页目录项 PGD(Page Global Directory); 上层页目录项 PUD(Page Upper Directory); 中间页目录项 PMD(Page Middle Directory); 页表项 PTE(Page Table Entry); 内存地址局部性原则 我们就可以利用这一特性,把最常访问的几个页表项存储到访问速度更快的硬件,于是计算机科学家们,就在 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache,这个 Cache 就是 TLB(Translation Lookaside Buffer) ,通常称为页表缓存、转址旁路缓存、快表等。 段页式内存管理 段页式内存管理的实现方式\n先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制 接着再将每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页 地址结构由段号,段内页号,页内偏移三个部分组成 段页式地址转化中要得到物理地址需要进过三次内存访问\n段表得到页表的起始地址 访问页表得到物理页号 物理页号与页内偏移组合得到物理地址 ps: 逻辑地址和线性地址\n程序中所使用的地址,通常是没有被段式内存管理映射的地址,称为逻辑地址 通过段式内存管理映射的地址,称为线性地址,也叫做虚拟地址 逻辑地址是「段式内存管理」转换前的地址,线性地址则是「页式内存管理」转换前的地址。 程序文件段(.text),包括二进制可执行代码; 已初始化数据段(.data),包括静态常量; 未初始化数据段(.bss),包括未初始化的静态变量; 堆段,包括动态分配的内存,从低地址开始向上增长; 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关 (opens new window)); 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小; 总结 为了在多进程环境下,使得进程之间的内存地址不受影响,相互隔离,于是操作系统就为每个进程独立分配一套虚拟地址空间,每个程序只关心自己的虚拟地址就可以,实际上大家的虚拟地址都是一样的,但分布到物理地址内存是不一样的。作为程序,也不用关心物理地址的事情。\n每个进程都有自己的虚拟空间,而物理内存只有一个,所以当启用了大量的进程,物理内存必然会很紧张,于是操作系统会通过内存交换技术,把不常使用的内存暂时存放到硬盘(换出),在需要的时候再装载回物理内存(换入)。\n那既然有了虚拟地址空间,那必然要把虚拟地址「映射」到物理地址,这个事情通常由操作系统来维护。\n那么对于虚拟地址与物理地址的映射关系,可以有分段和分页的方式,同时两者结合都是可以的。\n内存分段是根据程序的逻辑角度,分成了栈段、堆段、数据段、代码段等,这样可以分离出不同属性的段,同时是一块连续的空间。但是每个段的大小都不是统一的,这就会导致内存碎片和内存交换效率低的问题。\n于是,就出现了内存分页,把虚拟空间和物理空间分成大小固定的页,如在 Linux 系统中,每一页的大小为 4KB。由于分了页后,就不会产生细小的内存碎片。同时在内存交换的时候,写入硬盘也就一个页或几个页,这就大大提高了内存交换的效率。\n再来,为了解决简单分页产生的页表过大的问题,就有了多级页表,它解决了空间上的问题,但这就会导致 CPU 在寻址的过程中,需要有很多层表参与,加大了时间上的开销。于是根据程序的局部性原理,在 CPU 芯片中加入了 TLB,负责缓存最近常被访问的页表项,大大提高了地址的转换速度。\n","permalink":"https://akashark.github.io/posts/read/%E5%9B%BE%E8%A7%A3%E7%B3%BB%E7%BB%9F/%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E6%9C%89%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98/","summary":"为什么要有虚拟内存? 虚拟内存 单道程序的操作系统直接引用的物理地址,无法同时运行两个程序,程序会因为内存地址错乱而崩溃,单道程序的操作系统使用","title":"为什么要有虚拟内存"},{"content":"关于我\n英文名: Sharker 职业: 学生/程序员 喜好: 摆烂 ","permalink":"https://akashark.github.io/about/","summary":"关于我 英文名: Sharker 职业: 学生/程序员 喜好: 摆烂","title":"🙋🏻♂️关于"}]