Kevin Liu's Blog

Keep Moving

0%

看到

入职新公司已有3个月,做的方向是之前接触过、做过但却从未深入过的性能优化方向。
刚接触时,一脸懵逼,不知道该做些什么。这不像是需求有直接告诉你标的在哪,你根据需求的脉络在脑子里构件好大概的构思后就可以下笔犹如神了。
性能,更像是迷雾。作为老兵,必然了解性能包含什么:卡顿、内存、CPU、帧率等等。这些常规的数字,如何得到是比较经典且易于实践的话题,而真正的问题在于:得到这些冰冷的数据之后呢?下一步是什么?

感受

数据是冰冷的,我较为推崇数据推动论,即:通过数据折射客观的现实,依据科学合理的数据与业务指标的关系,通过客观的数据推动我们做优化。
但大家尝鲜尝的太多了,如果现在职场内一水都是刚刚毕业的学生,一下子就进入到后半场中。可能这样子,这个群体的意识是会有共识的。这个共识可能就是:

  • 结果、进展不是那么好拿的,如此大的一个盘子,寻求突破是一个积沙成塔的过程,日行千里已不现实,需要沉下心来认真思索、探索、尝试、应用对某个或大或小的领域做持续的进步

    当然,如果是小盘子中,是0到1的过程中,这些应该并不适合

But,现实并不如此,当今互联网群体中绝大绝大部分人都是从飞速发展中走过来的,多多少少都经历过、或至少听闻过,从0到1的过程是怎么怎么的酸爽,而其中通过乘风拿到的各种结果,借助其得到的晋升是如何如何的爽。所以,群体意识是必然不同的。

曾经(很久),有同事直言不讳:“我现在就是想一下子发现一个业界大框架的BUG,把它改掉就一战成名!”。是啊,谁不曾这么想呢?只消花那么一天半天,只要坚持不看手机不大一会,稍稍认真那么一下下,好家伙,就可以发现一个无人发现、曾经很烂的问题,从此扬名立万。此举写入简历,谁人不佩服?

私以为

谁人不想吃快餐呢?耐心是比较难得的东西,就像是面试。有几人有真本事,又有多少是突击的八股文?说到底,短平快的东西,只要试过就回不去了。

我以为不只是性能,但确实是性能。怎么说?以性能为点,我看到了优化已经难做,更多的时候是你不知道该做什么,是在找目标。你说帧率,一定要60帧?不见得,强行60帧反倒会体感卡顿,参见电影帧的概念。所以,数据、耐心、客观有思考,找回头脑,有些东西是值得的,有些东西是不值得的,至于哪些是要靠事实来探索。如果你操作的是10万以内的流量,那且慢,直接去做就是了。如果你操作的已经上了千万,别相信自己的感觉,除非你真的是天选之人,否则数据才是王道。为什么说“确实是性能”,你不觉得以这一点看,凡是已经到瓶颈的业务再度寻求发展,都是一样的逻辑吗?想要短平快,我想说是不存在了(有人会反对,所以我又说这不是绝对的)。

阅读全文 »

20210909【译】深入Kotlin协程

原文链接:https://kt.academy/article/cc-under-the-hood

内容来自于Kotlin Coroutines书中的一个章节,可以在LeanPub中找到它

就有那么一类人,他们不会仅仅只是接受汽车是能开动的。他们需要去打开它的引擎盖,尝试理解一下在引擎盖之下它是如何运作的。而我就是这一类人,所以我需要搞清楚协程是如何运作的。如果你也和我一样,那么你会喜欢这一章节的内容,如果你不是的话,那么可以跳过它了。

本章不会想你介绍新的工具,仅仅只是一个解释。将要试图去达到让人满意的程度来解释协程是如何工作的。关键的课程是:

  • 挂起方法就像是状态机,在方法执行伊始以及每个挂起函数之后都带着一个可能的状态数据
  • 用来表征状态的数字和本地数据都会被保持在Continuation所代表的的后续执行过程对象中
  • 代表一个方法的Continuation又被另一个的所修饰起来,结果就是所有的Continuation代表着一个调用栈,会在恢复的时候被用到

如果你对一些内部原理感兴趣(简化过的),随我来。

后续传递风格

Continuation-passing style

挂起函数是有好几种声明的方式的,但是Kotlin团队决定了使用了:后续传递风格。这代表了后续过程会被作为参数在方法之间进行传递

阅读全文 »

20210806 Dagger2 Tips

  1. @Inject是javax中的annotation,可以用作一般的构造方法,field等。不能用在:
    1. 接口上(因为接口是不能被构造的)
    2. 第三方的类(你无法annotate他们)
    3. 需要进一步配置的类
  2. @Provides,针对上边的情况就可以用到Provides Annotation来定制化的去构造需要的实例对象
  3. @Binds注解在简单对象构造的时候会觉得和@Provides的功能有所类似。但实际上其使用更加简单,可以认为其是简化了@Provides的一种用法:

@Provides static Heater provideHeater(E1ectricHeater heater ) {  return heater;

这种case的意思是,当需要Heater这个接口类型的实体时,用ElectricHeater的实现去返回(一定的,ElectricHeater的constructor是标记了@Inject)。简单将,这种case变成一句话就是某个接口由哪个实现来提供,那么可以通过@Binds简化为:

@Binds Heater bindHeater (ElectricHeater impl) ;

综上,推荐这种case下优先使用@Binds

  1. 依赖图构建的起点对于普通Java程序来说是main方法,对于Android程序来说就是Application的onCreate生命周期
  2. 命名规则上,Provides的方法就应该以provide开头,bind就是bind开头,而module就是module结尾
  3. 依赖图的根(root)是一个包含若干方法的接口,这里的方法是不能有参数的,而且返回值应该是返回响应的类型(被其他模块需要的类型实例),这样接口文件标记上@Component注解就ok了,然后在其modules属性中传入所有module的类,Dagger2就能自动的生成全依赖图了
  4. 每个应用只可能有一个Component,可以有多个Module
  5. 开发者在调用Component(如Android的Application的onCreate时),要记得,Dagger2给自动生成的文件是带有Dagger开头的,所以直接调用相关类即可(如你的类叫做CoffeeShop,那么Dagger为你生成的就是DaggerCoffeeShop)。需要调用它的builder方法来构建。如果你的Component类并不是顶级类,那么Dagger生成类时会加入下划线
  6. Scope是说某依赖在那个范围内生效,如果不标记的话,在当前的Component中能生效,或者是标记的Scope也符合当前的Component的话也能够生效
  7. Component可以拥有子Component
  8. @Singleton注解所标记的@Provides方法、@Inject的类都会被用作单例。Dagger2使用singleton的方法是用类静态全局变量的方法,故而没有线程竞争的问题,可以放心使用
  9. 因为Dagger2把依赖图中声明了领域配置的实例与Component实现实例关联在一起,所以组件本身也需要声明其所代表的领域(Scope)。所谓Scope,其背后代表其中的实例生命周期不一样。声明Scope的方法是在Component的接口文件上做标记(@Component的modules参数,即代表有哪些Module在这个Component下)。Singleton也算是一个Scope修饰,表明这个实例全局就只有一个
  10. 可复用——@Reusable。首先,这不是单例(Singleton),不能保证在scope内始终是一个实例(否则就singleton就好了),但是还可以限制@Inject和@Provides的构造和调用次数。
  11. Lazy这个类是个泛型,其声明是Lazy<T>,如果你为这样的field标记了singleton,那么每个人都会拿到同一个Lazy实例,如果未标记Singleton而是默认的scope的话,那么每个人拿到的Lazy实例是不一样的。但是无论如何,他们最后拿到的潜在的T对象实例都会是一个,因为Lazy的意思就是只有在第一次get行为被触发的时候T实例才会被构建,从今往后任何的get都会返回同一个实例
  12. Provider<T>是可以保证每次其T的实例都会被重新构造,@Provides并不能保证。但是如果标记了@Singleton,那么就没有意义了,因为总会是同一个T实例
  13. multibinding,多绑定(@IntoSet、@IntoMap) https://dagger.dev/dev-guide/multibindings
  14. multibinding的@IntoMap比IntoSet会复杂一些,如果你想要把一些依赖加入一个Map,而且它的key在编译时是已知的,那么就可以使用@IntoMap了。使用方法是在@Provides所标注的方法中添加注解@IntoMap,然后再添加一个自定义的annotation,这个annotation是用来定义map的key的。自定义的annotation中要标记上dagger的@MapKey这个注解,表示这个annotation是个map key,此自定义annotation的value就可以用作后续从map中获取实例的key,只要符合annotation的定义就可以,什么值都行。也可以不用自定义的annotation,而用Dagger2中定义好的,有两个@StringKey(用来定义简单的字符串key),@ClassKey(以类为key)。
  15. 全局只有一个Component,但可以有多个sub-Component,sub-Component会被自动收到到这个全局主的Component上
  16. @BindsInstance vs @Bind。@Bind是用在@Module中,用来给抽象方法定义,说明接口和实现的关系。而@BindsInstance是在@Component中,用来表示此Component中某个实现是绑定到哪个实例上,需要由开发者使用时显式设置进去(通过builder)
  17. 注意,Lazy(interface)在kotlin的包中也有,需要引入dagger包中的,否则会提示失败
阅读全文 »

DartVM介绍

原文地址:https://mrale.ph/dartvm/

此文的书写目的

此文章用来作为DartVM团队新成员、潜在的外部贡献者或仅仅是对VM内部有兴趣的任何人提供参考。本文对DartVM进行了高度的概括,并对各种内部组件的细节进行了一些描述。

DartVM是用于在原生环境执行Dart代码的一系列组件的集合。其主要包含了一下内容:

  • 运行时系统
    • 对象模型
    • 垃圾回收
    • 快照
  • 核心库的native方法
  • 通过服务协议形成的众多开发体验组件,如调试、性能测量、热加载
  • JIT和AOT编译管线
  • 解释器
  • ARM模拟器

“Dart VM”是一个历史命名。Dart VM是一个为高级编程语言提供运行环境的虚拟机,然而Dart并不总是解释型或者说是JIT编译的。比如说,在使用通过AOT编译管线Dart代码将会被编译为机器码,然后运行在一个阉割版本的DartVM中,这个DartVM叫做预编译运行时,这个运行时中并不包含用来动态运行Dart源码的编译组件。

译者注

第一句话稍显诡异,我妄加揣测是作者想要说Dart的运行时环境与以往的VM有所不同,其在不同的环境中有着不一样的效率和目标,而不要一概而论的理解成为虚拟机的效能就会偏低,DartVM也是可以直接执行机器码的。

DartVM如何运行你的代码?

Dart VM有很多种方式运行代码,比如:

  • 用JIT模式运行源码或者是内核二进制文件(kernel binary)
  • 运行快照(AOT和AppJIT)
阅读全文 »

Jetpack之Startup快速掌握

功能

用于为App提供初始化回调能力。体系化得将各种App内的功能模块、所引入的SDK的初始化联合起来,不各自为战。一方面能够将所有的初始化方式进行统一,在代码学习层面便于进入。另一方面,在初始化方式收敛后也能够有效的做性能数据监控。

配置

1
implementation "androidx.startup:startup-runtime:1.0.0"

使用

实现子类

每一个需要初始化的组件实现一个Initializer<T>的子类

并实现两个方法:

阅读全文 »

20201217客户端版本代码分支管理

前言

在使用Git作为代码版本管理工具的背景下,为了适应较快的迭代速度,降低代码冲突、覆盖等错误出现,以及提高协作效率,特此给出以下代码分支管理规范。

最佳实践

这里简单列举一些Git操作中较好的最佳实践:

  1. 不要将巨量修改提交在一个commit中,不利于记录查询乃至回退。类似于“少食多餐”的思想,每一次功能修改、每一个问题修复、每一个独立的特性引入后都做一个独立的commit进行提交
  2. 使用命令行进行Git操作,工具会隐藏很多Git操作背后的细节。一方面不利于开发者理解Git,另一方面可能会夹带私货,携带预期外的参数或操作
  3. 对于没有稳定预期的内容做单独分支进行管理,以避免污染主要开发分支

几个Git操作准则(必须遵守)

合并 merge

做分支合并时,应使用非快进式合并(no fast forward)。如使用的是命令行,则应添加--no-ff参数,如:

1
git merge --no-ff [from_branch]
阅读全文 »

20201208 Android Gradle Plugin迎来7.X版本

前言

通过Android Weekly了解到,在12.1日Android Gradle Plugin(以下简称AGP)迎来了7.0版本。忽然一下子有些迷惑,之前不是还在3、4的版本吗?为什么一下子跳到了7,仔细看了下来大致了解到了。后续会将AGP和Android Studio的版本拆开来单独发展,之所以跳版本号是因为目前Gradle是7版本,乍以为以后都会跟随Gradle的主版本,但是官方又说此版本后续也会支持8。当前也是出于Alpha版本,想必日后的变数还会比较多吧。下边大概介绍下官方宣布的主要内容。

官宣内容

新版本控制

从7.0开始引入了 语义化版本 的概念,其内容可以大意概括为:

  • 版本号由【主版本】•【次版本】•【修订版本】构成
  • 只要主版本不动,那么API都是可以兼容的,动了就代表API产生了不兼容的情况

更新的频率会控制在每年一个主版本,会正好在Gradle的年度大版本更新之后。

会提前一年对要删除的API做废弃(@Deprecated)标识。

需要Java11

阅读全文 »

增量APT

背景

这是一个Gradle提供的特性,其在4.7版本后提供了对增量处理注解的支持。这个功能依赖Java的增量编译,否则也是不生效的。

正文

Gradle通过“类别”Category来识别可以进行增量工作的注解处理器,可以支持两种类型:

  1. isolating 隔离
  2. aggregating 聚合

从注册说起

正统的APT注册方式是在META-INF文件夹(src/main/resources/META-INF)下注册的:

services目录下,创建javax.annotation.processing.Processor文本文件,其中写入自己Processor类的全称

这是大家都知道的事情。Google的auto这个脚手架自动帮我们完成了这一点。

阅读全文 »

一、简介

Annotation Processing Tool(APT),即Java注解处理器,是JavaX包中提供的工具。Android中并不具备此包,所以在Gradle工程中的体现形式为java-library。大名鼎鼎的ButterKnife就基于此工具而成,常用于自动生成具备一定规律的机械化代码。善加利用,可以在大型工程协作中发挥巨大的作用,从没有灵魂的代码中解放我们的双手,把每一份可贵的精力都放在更富有创造力的地方。

在ButterKnife横空出世后,APT(Annotation Processing Tool)对于Android开发者来说早已不陌生。因其可以自动生成机械化代码的特性,各类基于此的工具也层出不穷。本文会详细拆解APT周边的众多API以及深入探讨此机制背后的设计思想与逻辑,让读者对APT的理解更深一层,从而更加活学活用。

二、基础夯实

说在前面

当我们面对APT时,需要将脑子换一换。

平日里,我们是在面向runtime编程。而在面对APT时,实际面对的是编译时compile-time。此时此刻,我们所书写的代码还在编译成Class的途中,对他们的反射用法在这个环节是没什么用武之地的,代码以另外的AST格式存在于这个环节。相对应的,如果要操作它们就需要新的一套API。现在,像张无忌忘掉乾坤大挪移,重新学习太极拳一样,请大家换个脑子,面对新的领域。

注解是什么

@Nullable这种带@符号的叫做注解。

定义注解很简单:

阅读全文 »