三十而立
站在三十岁的第一天回头看,
生活就是一个七天接着一个七天。
人生短暂,
须臾模糊了发际线。
时间教会我的:
现在过的每一天,
都是余生中最年轻的一天。
。
。
。
没错,句子是东拼西凑抄来的╮( ̄▽ ̄)╭
站在三十岁的第一天回头看,
生活就是一个七天接着一个七天。
人生短暂,
须臾模糊了发际线。
时间教会我的:
现在过的每一天,
都是余生中最年轻的一天。
。
。
。
没错,句子是东拼西凑抄来的╮( ̄▽ ̄)╭
众所周知,官方提供了好几个办法来让我们在开发 Flutter app 的过程中可以使用查看 fps等性能数据,如devtools,具体见文档 Debugging Flutter apps 、Flutter performance profiling 等。
但是这些工具统计到的数据充其量只能算开发过程中的“试验室”数据,假如需要统计app 在线上在用户手机上的运行情况,该如何在 flutter 端代码里自己计算性能数据,比如 fps
这个值呢?
经过阅读源码,发现其实很简单,给window
对象注册 onReportTimings
即可,去看api文档。
1 | void main() { |
注意:当升级到Flutter 1.12.x 之后,onReportTimings
应该改成SchedulerBinding
的addTimingsCallback
1 | // 需监听fps时注册 |
考虑计算fps
,只需要保留最近 N
个FrameTiming
来计算即可,最好用类似stack的数据结构存起来,参考了文档,我们选用 Queue ,N 指定为 100
1 | const maxframes = 100; // 100 帧足够了,对于 60 fps 来说 |
1 | +---------------------------------------------------------------+ |
lastFrames
的头就是最后一帧,尾是队伍里最开始的一帧,现在你可以计算 FPS 了:
1 | double get fps { |
但,你会发现,这样算出来和官方工具算的对不上,而且错的离谱。
why ??
flutter 的崩溃日志收集主要有两个方面:
人写的代码是无数异常交织起来的偶然产物,代码发生异常才是正常情况。
除了在关键的地方加上 try-catch
让它们变成已知异常之外,抓到未知异常才是真本事。
比如下面的一段代码中的try-catch
是无效的:
1 | try { |
好在,Dart 有一个 Zone
的概念,有点类似sandbox
的意思。不同的 Zone 代码上下文是不同的互不影响,Zone 还可以创建新的子Zone。Zone 可以重新定义自己的print
、timers
、microtasks
还有最关键的how uncaught errors are handled
未捕获异常的处理
1 | runZoned(() { |
在 reportError
里即可以进行上报处理(详见后面介绍)。
注册 FlutterError.onError
回调,用于收集 Flutter framework 外抛的异常。
1 | FlutterError.onError = (FlutterErrorDetails details) { |
该 error 一般是由 Widget
在 build
的时候抛出,如下:
1 |
|
普遍的,不论大小Android应用都会配置 proguard
在release 编译的时候混淆自己的代码:
1 | android { |
但无论 Proguard
还是 R8
,他们的混淆字典默认都太简单了(too simple),只是 abcdefg
而已,反编译后还是很容易阅读的,如下所示:
1 | final class b { |
所幸,Proguard 支持自定义字典:
1 | -obfuscationdictionary dict.txt |
如果,有那么一个字典,里面都是形似“乱码”的字符,看起来不仅费眼睛,甚至电脑字体还没收录更佳(会显示成一个方框)。
万能的github 上还真有符合要求的。但是直接生成好的字典文件一直用也是有隐患的,举个例子,两个版本之间类、方法的个数差别不大,最终的混淆结果其实是很相似的,对比 mapping 之后,有可能一个方法前一个版本叫 aa,现在叫 ab 了。
而且翻看了部分实现方案,要么是字典文件里词汇量不够大,要么生成代码实现可能有其它bug。故而干脆自己撸起袖子几行代码搞定。
Add abiFilters to android/app/build.gradle
:
1 | android { |
Run command:
1 | $ flutter run |
Flutter app crashed by java.lang.UnsatisfiedLinkError
:
java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/xxxx/base.apk”],nativeLibraryDirectories=[/data/app/xxxx/lib/arm, /data/app/xxxx/base.apk!/lib/armeabi-v7a, /system/lib, /vendor/lib, /product/lib]]] couldn’t find “libflutter.so”
at java.lang.Runtime.loadLibrary0(Runtime.java:1011)
at java.lang.System.loadLibrary(System.java:1660)
at io.flutter.view.FlutterMain.startInitialization(FlutterMain.java:156)
at io.flutter.view.FlutterMain.startInitialization(FlutterMain.java:133)
at io.flutter.app.FlutterApplication.onCreate(FlutterApplication.java:22)…
As you can see, flutter run
seems like forgot to put “libflutter.so” into apk.
譬如你跟他说某开源项目很好值得深入学习,他可能点个star表示已阅,或者点个fork表示可以深入研究,clone代码那是万万不可能的。
但假如在某程序员聚集地说某项目源码泄漏了,那景象好比炸窝,蜂拥而上,生怕吃不上热的,star+fork+clone素质三连,忙得不亦乐乎。
费劲周折毕竟拿到代码了,也不管看不看得懂,像是对着名画,总要品头论足一番,凭着一知半解发表几点高论。
开动鹰眼扫代码要是发现点奇特的地方,那如同发现新大陆,喜不自胜,更丝毫不吝啬自己的言辞到处去说。
程序员真的是个热爱学习的群体。
Gradle 也可以用下面的方式声明使用的插件:
1 | // build.gradle |
其实是从 Gradle 官方的插件仓库 https://plugins.gradle.org/m2/ 下载的。
但是,众所周知的原因,某些地区会连不上,导致下载不到需要的插件,例如出现如下错误:
1 | * What went wrong: |
又或者,插件是不对外的,存在某个私有仓库的,该如何修改或者添加额外的私有仓库地址呢?
书接上文,上回提到 B 站Android团队为了解决组件化后协作上的问题,已经采用了大仓(monorepo)
的方案来组织代码。
国内实践大仓的团队少之又少,更别提 Android 的大仓了,几乎没有来自其它团队的可借鉴经验。在这条路上,我们可以算作先行者。本文粗陋,文中所列思路不可能适用所有团队,仅给同样想实践Android 大仓的人些许启发。
首先回顾一下 Android 项目的组织方式。自从13年开始官方逐渐迁移到 Android Studio 做为 IDE 后,Android 项目的开发和编译就绑在 Gradle 上了。
一个标准的 Gradle 项目
结构如下所示:
1 | MyApp/ |
通常,会有多个Gradle Module存在:
1 | MyApp/ |
其中 settings.gradle
会注册所有的 Module
1 | include ':app', ':lib1', ':lib2' |
随业务的扩张,Module 数量会越来越多。遵循多数人实践过的组件化的思路,按业务分仓库存放便理所当然:
1 | android group/ |
每个仓库都是一个标准 Gradle 项目
,通过 publishing
插件将module 都上传 aar(或者jar)到 maven私服(如nexus)上,再在 MyApp/build.gradle
中以 maven 组件的形式依赖它们,最终打包成apk:
1 | repositories { |
此时的代码组织方式便是上文中所述的多仓库形态(可能许多团队正处于当前阶段)。
那么,如何既能快速搭建出适用于 Android 的大仓,又能不影响当前的团队协作流程,还要尽量避免迁移带来的开发效率降低?
早在2012年,B 站 Android APP 便已上线。当时开发者不过一人,而如今,业务线众多、隶属不同团队的Android 端开发人员数以百计。从单兵作战到百花争鸣,代码库的组织管理也随之经过数次的改革、演进。
2014年底,Android 端的常驻开发人员一只手也数的过来。业务发展迅速,为追求效率,方便管理,所有代码都在一个仓库中,甚至包括第三方的、开源的代码(个别用 git submodule 管理)。Clone下来导入 Eclipse 就可以开干。
到大约15年中旬,开始使用 Android Studio,得益于 Gradle 的项目管理理念,分出了多个 library module。外部依赖使用 maven。也是这一期间开始搭建了内网 maven 服务。
这期间代码库组织结构是:单仓库 + 个别 git submodule 。
这种组织方式好处显而易见:
但是,约莫到16年中,业务发展,新团队纷纷成立,招聘要求降低人员迅速膨胀。这种小而美的代码库已经不适用了,主要有以下缺点:
博客目前用的是Hexo,没有后端,为静态博客,评论一般用的第三方系统,如常用的 Disqus。但众所周知的原因,在“火星”上无法正常访问Disqus。
隆重推荐一个通过api实现评论的项目:DisqusJS
这里说一下它的配置技巧。
这里以很多人用的 Next 主题为例,其它类似。
修改_config.yml 中的disqus配置为:
1 | # for DisqusJS, https://github.com/SukkaW/DisqusJS |
具体含义见DisqusJS
修改 layout/_partials/head/custom-head.swig:
1 | {% if theme.disqus.enable %} |