0%

​ 今天看耗子哥新文如何做一个有质量的技术分享,文末做了一个分享示例

For example, if you want to sharing a topic about Docker. the following outlines would be good one:

  • What’s the major problems need to solve. (Provision, Environment, Isolation etc.)
  • The Alternative solutions. (Puppet/Chef/Ansible, VM, LXC etc.)
  • The Best Solution – Docker. Why?
  • Docker’s key techniques – image, cgroup, union fs, namespace…
  • Docker’s Pros/Cons
  • Further reading list.

感觉吸收新知识也是同理,建立一个问题,引起好奇心,然后顺理成章往下推演,看现在的最佳解决方案(知识)是怎么经过筛选得来的。

下面是自己收集的一些tips供自己参考,持续更新:

  • teach someone else
  • learn in short bursts of time
  • take notes by hand
  • take a study nap
  • the human brain processes images 60,000 times faster than text, and 90 percent of information transmitted to the brain is visual

参考:learn anything faster

Humans Process Visual Data Better

​ 一开始想添加一个博客评论系统,方便交流,参考的是kid的博客,选择最简单的方案 utteranc.es,hexo已经有插件集成,使用的是npm安装。安装完成后在博客底部成功出现评论框

comment-box.jpg

点击github登录时出现了访问失败,找不到资源

image

经过测试,确定原因是自己域名没有https的原因,链接添加参数后默认以https访问出现请求失败情况。首先想到的是去阿里万网看自带的服务,价格有些小贵image

为了秉持互联网免费原则,确定了Netlify方案,根据教程配置完成后大概等了半天时间,https域名就可以正常使用了。

没有尝试的方案:

最后,感谢蓝伟洪小蘑菇配合测试评论系统。

​ 在纯flutter应用里打开网页,目前流行两个插件:

  • flutter_webview_plugin (以下简称三方web)

    • 特点

      1.实测中滑动相对流畅

      2.bug较少

      3.不在flutter的widget tree中,视图在app最顶层,通过channel通知来实现显示和隐藏,全局是一个单例

      4.非官方

  • webview_flutter(以下简称官方web)

    • 特点

      1.镶嵌在widget tree中,方便在flutter中控制

      2.键盘弹出有部分bug

      3.官方支持

由于历史原因和开发成本考虑,继续沿用三方web作为浏览器,遇到一个需求,就是需要打开网页的时候秒开,实现类似今日头条的效果,经过调研和结合flutter特性,最终方案定型为下图

image

image

​ 在实现iOS过程中比较顺利,在appdelegate启动的时候加载WebViewController放在后台待命,MainViewController新增一个导航在合适的时候来push WebViewController

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
    lazy var webViewEngine:FlutterEngine = {
let engine = FlutterEngine(name: "webViewEngineName")
engine.run(withEntrypoint: "webViewMain")
return engine
}()

lazy var webViewFlutterVC:FlutterViewController = {
let webFlutterVC = FlutterViewController(engine: self.webViewEngine, nibName: nil, bundle: nil)
webFlutterVC.view.backgroundColor = UIColor.white
return webFlutterVC
}()

lazy var registerWebFlutterVC: Void = {
CopiedPluginRegistrant.register(with: self.webViewFlutterVC)
}()


override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
//启动WebViewController
_ = self.registerWebFlutterVC

return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

同样的原理框架在实现安卓的时候因为实现原理不同,做了改动:

  1. 两个页面均由activity实现,在安卓app启动的时候需要指定一个activity作为单例launcher,把web activity作为launcher先启动,等内部webview预热完成,再启动main activity

manifest文件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<activity
android:name=".FlutterWebViewActivity"
android:launchMode="singleInstance"
android:theme="@style/activityTheme">

<!--启动先预热第二引擎页面,oncreate里面再切换回app主页-->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<!--启动的时候加背景图防止黑屏-->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />


</activity>

  1. 在预热web activity过程中,如果不注意两个activity engine注册插件先后顺序和冲突,会出现flutter和原生交互找不到method方法报错,需要按照下面的顺序来

    • 在Application onCreate方法里预热web引擎,automaticallyRegisterPlugins一定要传false

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public class MyApp extends FlutterApplication {
      @Override
      public void onCreate() {
      super.onCreate();

      //启动预加载一个引擎for 单例 webview 不自动注册插件
      FlutterEngine webEngine = new FlutterEngine(this,null,false);
      FlutterEngineCache.getInstance().put(WebEngine.ENGINE_ID,webEngine);

      }
      }
    • 继承FlutterActivity类,覆写获取engine方法,拿到已经预热好的engine

      1
      2
      3
      4
      5
      //加载预热的引擎,在my_app类中创建
      override fun provideFlutterEngine(context: Context): FlutterEngine? {
      //return super.provideFlutterEngine(context)
      return FlutterEngineCache.getInstance().get(WebEngine.ENGINE_ID)
      }
    • 执行指定的entry point

      1
      2
      3
      4
      override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
      runEntrypoint(flutterEngine)
      super.configureFlutterEngine(flutterEngine)
      }

这样,两端就都可以实现H5“秒开”体验。

完。

症状:

除了电源按钮,键盘和触摸板全部失灵,触摸板可以按下去,鼠标无法滑动,外接usb键盘鼠标可以使用

处理办法:

重置NVRAM

步骤:首先按住 option+commamd+P+R键,再按住电源键开机,等待听duang的开机声,听到四声后,松开按键,按电源键开机,恢复正常

参考:https://www.jianshu.com/p/51d6142b564a

完。

报错原文:

1
2
Cloning into '/var/folders/nd/n2mbp09n1lqbyyf586wntcww0000gn/T/d20210428-5764-pwxnoe'...
fatal: unable to access 'https://github.com/SDWebImage/SDWebImage.git/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443

原因分析:

本地git使用代理连接github,代理出了问题

问题解决:

让代理恢复正常或者不使用代理

1
2
git config --global --unset http.https://github.com.proxy
git config --global --unset https.https://github.com.proxy

去除了http 和 https的代理

完。

参考:https://www.jianshu.com/p/388c2914f22a

​ WK is run in a separate process so that it can draw on native Safari JavaScript optimizations,this means WK loads web pages faster than UIWeb,UIWeb uses traditional JS engines which are basic interpreters,Apple’s Nitro engine uses a 4 tier compiling strategy which can execute JS 40 times faster than a traditional JS interpreter.

Nitro means Nitromethane, is a kind of fuel for car engine.

source:

早上开车等红绿灯时想到几个月前写的一个算法,应该可以更简单,今天重新写了一遍

  • 寻找下一个数字,例如输入345,输出354

    思路:从最右边位和上一位对比,如果大,就往前移,移动完,尾数排序,从小到大,保证次大

  • 代码

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
//字符串分成数组
NSMutableArray *convertStringToArray(NSString *str){
NSMutableArray *marr = [NSMutableArray array];
for (NSInteger i = 0; i < str.length; i++) {
[marr addObject:[str substringWithRange:NSMakeRange(i, 1)]];
}
return marr;
}

//数组拼成字符串
NSString *convertArrayToString(NSArray *arr){
NSMutableString *mstr = [NSMutableString string];
for (NSString *str in arr) {
[mstr appendString:str];
}
return mstr;
}

//冒泡排序 从小到大
NSString *bubbleSort(NSString *str){
NSArray *sortedArray = convertStringToArray(str);
NSMutableArray *marr = [NSMutableArray arrayWithArray:sortedArray];
//最后一位的索引
unsigned long j = marr.count-1;
for (;j>0;j--) {

BOOL didSort = NO;
//第一轮结束
for (int i=0; i<j; i++) {
NSString *pre = marr[i];
NSString *next = marr[i+1];
if (pre.integerValue > next.integerValue) {
[marr exchangeObjectAtIndex:i+1 withObjectAtIndex:i];
didSort = YES;
}
}

if (!didSort) {
break;
}
}

NSMutableString *mstr = [NSMutableString string];
for (NSString *str in marr) {
[mstr appendString:str];
}
return mstr;

}


NSString *findNextLargerNum(NSString *oriNum){
NSString *resultStr = oriNum;
NSMutableArray *oriNumMArr =[NSMutableArray arrayWithArray:convertStringToArray(oriNum)];

for (int j = oriNumMArr.count-1; j>0; j--) {

BOOL getResult = NO;
NSString *last = oriNumMArr[j];
for (int i = j-1; i>=0; i--) {
NSString *pre = oriNumMArr[i];
if (last.integerValue > pre.integerValue) {
[oriNumMArr exchangeObjectAtIndex:i withObjectAtIndex:j];
NSRange range = NSMakeRange(i+1, oriNumMArr.count - 1 - i);
NSString *surfixStr = convertArrayToString([oriNumMArr subarrayWithRange:range]);
NSString *surResultStr = bubbleSort(surfixStr);
NSString *preStr = convertArrayToString([oriNumMArr subarrayWithRange:NSMakeRange(0,i+1)]);
resultStr = [NSString stringWithFormat:@"%@%@",preStr,surResultStr];
getResult = YES;
break;
}
}


if (getResult) {
break;
}

}
return resultStr;
}
使用:
1
2
3
4
NSString *str = findNextLargerNum(@"8976");
NSLog(@"======%@",str);
//打印结果
======9678

  • 抽取widget,尽量成为一个stateless widget

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class MainWidget extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return Expanded(
    child: Container(
    color: Colors.grey[700],
    child: Center(
    child: Text(
    'Hello Flutter',
    style: Theme.of(context).textTheme.display1,
    ),
    ),
    ),
    );
    }
    }
  • 更新属性值,用传值的方式

    • ChangeNotifier

      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
      //------ ChangeNotifier class ----//
      class MyColorNotifier extends ChangeNotifier {
      Color myColor = Colors.grey;
      Random _random = new Random();

      void changeColor() {
      int randomNumber = _random.nextInt(30);
      myColor = Colors.primaries[randomNumber % Colors.primaries.length];
      notifyListeners();
      }
      }
      //------ State class ----//

      class _MyHomePageState extends State<MyHomePage> {
      final _colorNotifier = MyColorNotifier();

      void _onPressed() {
      _colorNotifier.changeColor();
      }

      @override
      void dispose() {
      _colorNotifier.dispose();
      super.dispose();
      }

      @override
      Widget build(BuildContext context) {
      print('building `MyHomePage`');
      return Scaffold(
      floatingActionButton: FloatingActionButton(
      onPressed: _onPressed,
      child: Icon(Icons.colorize),
      ),
      body: Stack(
      children: [
      Positioned.fill(
      child: BackgroundWidget(),
      ),
      Center(
      child: AnimatedBuilder(
      animation: _colorNotifier,
      builder: (_, __) => Container(
      height: 150,
      width: 150,
      color: _colorNotifier.myColor,
      ),
      ),
      ),
      ],
      ),
      );
      }
      }
  • ValueNotifier

    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
    class _MyHomePageState extends State<MyHomePage> {
    final _colorNotifier = ValueNotifier<Color>(Colors.grey);
    Random _random = new Random();

    void _onPressed() {
    int randomNumber = _random.nextInt(30);
    _colorNotifier.value =
    Colors.primaries[randomNumber % Colors.primaries.length];
    }

    @override
    void dispose() {
    _colorNotifier.dispose();
    super.dispose();
    }

    @override
    Widget build(BuildContext context) {
    print('building `MyHomePage`');
    return Scaffold(
    floatingActionButton: FloatingActionButton(
    onPressed: _onPressed,
    child: Icon(Icons.colorize),
    ),
    body: Stack(
    children: [
    Positioned.fill(
    child: BackgroundWidget(),
    ),
    Center(
    child: ValueListenableBuilder(
    valueListenable: _colorNotifier,
    builder: (_, value, __) => Container(
    height: 150,
    width: 150,
    color: value,
    ),
    ),
    ),
    ],
    ),
    );
    }
    }
  • 使用const修饰stateless widget,set state不会重复刷新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class BackgroundWidget extends StatelessWidget {
    const BackgroundWidget();

    @override
    Widget build(BuildContext context) {
    print('building `BackgroundWidget`');
    return Image.network(
    'https://cdn.pixabay.com/photo/2017/08/30/01/05/milky-way-2695569_960_720.jpg',
    fit: BoxFit.cover,
    );
    }
    }
  • 生成列表的时候使用itemExtent属性告诉系统item高度,更高效的滑动

    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 MyHomePage extends StatelessWidget {
    final widgets = List.generate(
    10000,
    (index) => Container(
    color: Colors.primaries[index % Colors.primaries.length],
    child: ListTile(
    title: Text('Index: $index'),
    ),
    ),
    );

    final _scrollController = ScrollController();

    void _onPressed() async {
    _scrollController.jumpTo(
    _scrollController.position.maxScrollExtent,
    );
    }

    @override
    Widget build(BuildContext context) {
    return Scaffold(
    floatingActionButton: FloatingActionButton(
    onPressed: _onPressed,
    splashColor: Colors.red,
    child: Icon(Icons.slow_motion_video),
    ),
    body: ListView(
    controller: _scrollController,
    children: widgets,
    itemExtent: 200,
    ),
    );
    }
    }
  • 需要动画的子控件设置给AnimatedBuilder的child,避免重复绘制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @override
    Widget build(BuildContext context) {
    return Scaffold(
    floatingActionButton: FloatingActionButton(
    onPressed: _onPressed,
    splashColor: Colors.red,
    child: Icon(Icons.slow_motion_video),
    ),
    body: AnimatedBuilder(
    animation: _controller,
    child: CounterWidget(
    counter: counter,
    ),
    builder: (_, child) => Transform(
    alignment: Alignment.center,
    transform: Matrix4.identity()
    ..setEntry(3, 2, 0.001)
    ..rotateY(360 * _controller.value * (pi / 180.0)),
    child: child,
    ),
    ),
    );
    }
  • 合理使用opacity属性,减少调用setlayer方法,触发离屏渲染

    • 使用fadeTransition

      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
      class _MyHomePageState extends State<MyHomePage>
      with SingleTickerProviderStateMixin {
      AnimationController _controller;
      int counter = 0;

      void _onPressed() {
      setState(() {
      counter++;
      });
      _controller.forward(from: 0.0);
      }

      @override
      void initState() {
      _controller = AnimationController(
      vsync: this, duration: const Duration(milliseconds: 600));
      _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
      _controller.reverse();
      }
      });
      super.initState();
      }

      @override
      void dispose() {
      _controller.dispose();
      super.dispose();
      }

      @override
      Widget build(BuildContext context) {
      return Scaffold(
      floatingActionButton: FloatingActionButton(
      onPressed: _onPressed,
      splashColor: Colors.red,
      child: Icon(Icons.slow_motion_video),
      ),
      body: FadeTransition(
      opacity: Tween(begin: 1.0, end: 0.0).animate(_controller),
      child: CounterWidget(
      counter: counter,
      ),
      ),
      );
      }
      }
  • 使用animatedOpacity

    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
    const duration = const Duration(milliseconds: 600);

    class _MyHomePageState extends State<MyHomePage> {
    int counter = 0;
    double opacity = 1.0;

    void _onPressed() async {
    counter++;
    setState(() {
    opacity = 0.0;
    });
    await Future.delayed(duration);
    setState(() {
    opacity = 1.0;
    });
    }

    @override
    Widget build(BuildContext context) {
    return Scaffold(
    floatingActionButton: FloatingActionButton(
    onPressed: _onPressed,
    splashColor: Colors.red,
    child: Icon(Icons.slow_motion_video),
    ),
    body: AnimatedOpacity(
    opacity: opacity,
    duration: duration,
    child: CounterWidget(
    counter: counter,
    ),
    ),
    );
    }
    }

完。

sources:

问题描述:

flutter工程打包apk文件后

1
2
3
4
5
6
7
8
9
10
try {
val method = javaClass.getDeclaredMethod(call.method, MethodCall::class.java, MethodChannel.Result::class.java)
method.invoke(this, call, result)
} catch (e: NoSuchMethodException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.printStackTrace()
}

这段代码无法执行

原因分析

经过咨询,安卓release包会代码混淆,使用反射会让代码无法精确匹配

解决:

  • 使用反射的类不参与混淆,降低安全性
  • 不使用反射,改用if判断或者switch case

参考:1. https://stackoverflow.com/questions/41525867/reflection-not-working-on-android-release-apk-even-with-proguard-minify-disable

2.https://blog.csdn.net/guohesheng/article/details/53696652

3.https://www.jianshu.com/p/b5b2a5dfaaf4

感谢王志浩同学提供帮助!

  • avoid costly work in build() since build() can be invoked frequently when ancestor Widgets rebuild

  • avoid calling setState() high up in the tree

  • Use the lazy methods, with callbacks,when building large grids or lists, that way only the visible portion of the screen is built at startup time

  • avoid invoke saveLayer(),including

    • opacity widget
    • shaderMask
    • colorFilter
    • Clip
    • Text
    • addition:Clipping doesn’t call saveLayer() but is still costly

    Ways to avoid calls to saveLayer():

    • To implement fading in an image, consider using the FadeInImage
    • using the border Radius property instead of applying a clipping rectangle
  • build and display frames in 16ms

  • build subtree widget once and pass it as a child to the AnimatedBuilder

  • pre-clip image before animating it

  • avoid using constructors with a concrete List of children if most of the children are not visible on screen

source:https://flutter.dev/docs/perf/rendering/best-practices