Behind the Cloud

Part 1: The Start-Up Playbook: How to Turn a Simple Idea into a High-Growth Company

  • Allow Yourself Time to Recharge
  • Have a Big Dream
  • Believe in Yourself
  • Trust a Select Few with Your Idea and Listen to Their Advice
  • Pursue Top Talent as If Your Success Depended on It
  • Sell Your Idea to Skeptics and Respond Calmly to Critics
  • Define Your Values and Culture Up Front
  • Work Only on What Is Important
  • Listen to Your Prospective Customers
  • Defy Convention
  • Have–and Listen to–a Trusted Mentor
  • Hire the Best Players You Know
  • Be willing to Take a Risk–No Hedging
  • Think Bigger

Part 2: The Marketing Playbook: How to Cut Through the Noise and Pitch the Bigger Picture

Part 3: The Events Playbook: How to Use Events to Build Buzz and Drive Business

Part 4: The Sales Playbook: How to Energize Your Customers into a Million-Member Sales Team

Part 5: The Technology Playbook: How to Develop Products Users Love

Part 6: The Corporate Philanthropy Playbook: How to Make Your Company About More Than Just the Bottom Line

Part 7: The Global Playbook: How to Launch Your Product and Introduce Your Model to New Markets

Part 8: The Finnace Playbook: How to Raise Captical, Create a Return, and Never Sell Your Soul

  • Don’t Underestimate Your Financial Needs
  • Consider Fundraising Strategies Other Than Venture Capital
  • User Internet Models to Reduce Start-Up Costs
  • Set Yourself Up Properly from the Beginning, Then Allow Your Financial Model to Evolve
  • Measure a Fast-Growing Company on Revenue, Not Profitability
  • Build a First-Class Financial Team
  • Be Innovative and Edgy in Everything You Do–Except When It Comes to Your Finances
  • When It Comes to Compliance, Always Play by the Rules
  • Focus on the Future
  • Allow for Change as Your Company Grows

Part 9: The Leadership Playbook: How to Create Alignment–the Key to Organizational Success

  • Use V2MOM to Focus Your Goals and Align Your Organization
  • Use a Top-Down and Bottom-Up Approach
  • Build a Recruiting Culture
  • Recruiting Is Sales
  • Keep Your Standards High as You Grow
  • How to Retain Top Talent
  • The Importance of Mahalo
  • Foster Loyalty by Doing the Right Thing
  • Challenge Your Best People with New Opportunities
  • Solicit Employee Feedback – and Act On It
  • Leverage Everything

The Final Play

  • Make Everyone Successful

English

Listening

TED

Open Courses

Reading

General

  • The Non-Designer’s Design Book

Business

  • Behind the Cloud
  • Rework
  • It doesn’t have to be crazy at work
  • The hard thing about hard things
  • Antifragile: Things That Gain from Disorder
  • The Mom Test: how to talk to customers and learn if your business is a good idea when everybody is lying to you

Marketing

  • Hacking Growth: How Today’s Fastest-Growing Companies Drive Breakout Success

Sales

  • The Greatest Salesman in the World
  • Sales 101: A Crash Course in How to Sell Anything

Technology

  • DDD
    • Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans
    • Implementing Domain-Driven Design by Vaughn Vernon
  • Frontend
    • CSS
      • CSS3: the missing manual
    • JavaScript
      • JavaScript The Definitive Guide: Master the World’s Most-Used Programming Language
  • Test
    • Unit Testing: Principles, Practices, and Patterns

Leadership

  • Objectives and Key Results
  • Head First Agile

Writing

TODO

Speaking

TODO

Product Ideas

App

  • Color Wheel
    • Material Design
  • National Flag
    • DK
    • Learn English/Japanse/Chinese
  • Animals
    • Hangzhou Wild Animals Party
    • Learn English/Japanse/Chinese
  • Quotation Card
    • Display curated quotations coming out of books
    • Learn English/Japanese/Chinese
  • Vocabulary
    • Works on Desktop/Phone/Watch

2022 春节计划

Business & Front End

My Spring Festival vocation begins at 28 Jan, ends at 6 Feb, 10 days in total.

From the fist day on, I will focus on the goals that I want to achive during this period.

Before doing the task listed below, Let me clarify the accomplishment:

  • Top level offer as a senior developer
  • Write one article about ‘Micro Frontend’
  • Write one article about ‘Business’
  • Complete the web design of ‘senluoshe.com’

Design

Layout & Composition

Color

Typography

Images

Frontend Development

  • 📚 Objectives and Key Results-Driving Focus, Aglignment, and Engagement with OKRs
  • 📚 Head First Agile
  • 📚 Continuous Integration
  • 📚 Unit Test
  • 📚 Head First Object-Oriented Analysis & Design
  • 📚 Docker in Action

Micro Frontends

Web Components

Low Code Platform

  • Nothing

Business

  • 📚 The Business Book (DK)
    • 🎯 Build the business framework from my viewpoint

Data Driven

  • Google Analytics (Apply it to senluoshe.com)
  • 📚 Lean Analytics
  • 📚 Hacking Growth

2022读书目标

Design

  • 🧨 The Non-Designer’s Design Book
  • 🧨 Color Works
  • 🧨 The Designer’s Dictionary of Color
  • 🧨 About Face
  • 🧨 Prototyping for Designers
  • The Design of Everyday Things

Frontend

  • 🧨 Micro Frontends in Action
  • 🧨 Docker in Action
  • 🧨 CSS3 the missing manual
  • 🧨 Responsive Web Design with HTML5 and CSS3
  • 🧨 JavaScript: The Good Parts
  • JavaScript Data Structures and Algorithms
  • High Performance Browser Networking

Business

  • Rework
  • It doesn’t Have to be Crazy at Work
  • The Hard Thing about Hard Things
  • What I Wish I Knew When I Was 20
  • The greatest salesman in the world
  • Behind the Cloud
  • 🧨 From Impossible to Inevitable
  • Lean Analytics
  • EVERYBODY WRITES
  • The Lean Startup
  • Getting Real
  • Traction
  • How Google Works
  • Hacking Growth
  • Good to Great
  • Marketing/Sales
    • The SALES ACCELERATION FORMULA
    • The SaaS SALES method for customer success & account manager
    • The SaaS sales method fundamentals
    • Social Selling
    • The art and skill of sales psychology
    • The SaaS Email Marketing Playbook
    • The One Thing
    • Crossing The Chasm
  • The Mom Test
  • OBVIOUSLY AWESOME
  • THE SCIENCE OF SELLING
  • Don’t Just Roll the Dice
  • The innovator’s dilemma
  • The Four Steps to the Epiphany
  • Anything you want
  • How to win friends & influence people
  • The 80/20 principle and 92 other powerful laws of nature
  • Pitch anything
  • Play bigger
  • SaaS 100 Success Secrets
  • 90 days to profit
  • Radical Focus
  • Mindset
  • Flash Foresight
  • Customer Success
  • Zero to one
  • THE PERSONAL MBA

CS & SE

Basics

  • Coding Theory

Testing

  • Unit Testing

DevOps

  • Pro Git

Programming Language

  • Learn You a Haskell for Great Good
  • Monads for functional programming
  • Linux Command Line and Shell Scripting Bible
  • Structure and Interpretation of Computer Programs
  • Programming Languages and Lambda Calculi
  • Types and Programming Languages
  • Learning Python

Architecture

  • Code Complete 2
  • THE DESIGN OF THE UNIX® OPERATING SYSTEM
  • Operating System Concepts
  • Continuous Integration
  • Domain Driven Design
  • Implementing Domain Driven Design
  • OAuth 2.0 Simplified
  • OAuth2.0

Management

  • 🧨 Objectives and Key Results
  • High Output Management
  • The One Minute Manager

DK - Big Ideas Simple Explained

  • 🧨 The Business Book
  • The Economics Book
  • The Psychology Book
  • The Politics Book
  • The Science Book

Head First

  • Agile
  • Statistics
  • Networking
  • WordPress
  • Algebra

如何成为一个很diao的前端开发

DESIGN

Books

  • The Non-Designer’s Design Book
  • Color Works - Best Practices for Graphic Designers
  • The Designer’s Dictionary of Color
  • The Design of Everyday Things
  • About Face
  • Prototyping for Designers

Websites

Tools

Practice

  • Copy

HTML/CSS/JAVASCRIPT

Books

  • Responsive Web Design with HTML5 and CSS3
  • JavaScript: The Good Parts
  • CSS3 the missing manual
  • JavaScript Data Structures and Algorithms

Software Engineering

Git

* Read "Pro Git"
* Conventioinal Commits
* Git Flow

Web History

Concepts

掌握一些技能

  • JavaScript
    • Promise
      • Javascript Eventloop
  • Html5
  • CSS3
    • SCSS
  • Webpack
  • Babel

读懂几本书

  • High Performance Browser Networking

懂得架构

  • 组件化
    • Element 是如何实现的
  • 模块化
  • 工程化
  • 微前端

懂设计模式

  • prototype 模式
    • clone,Java 天生支持

了解一些后端知识

  • CloudNative
  • Serveless
  • DDD
  • Garbadge Collector

全栈工程师的自我修养

Write blog on the topics listed below:

  • Design
    • Axure
    • Figma
  • Backend
    • DDD
    • Django/Python
    • MySQL
    • MongoDB
    • Redis
  • Frontend
    • React
    • Rust
    • GraphQL
    • Vue3
    • MiniProgram
    • Flutter
  • DevOps
    • CI/CD
    • Linux
    • Docker & Docker Hub
    • K8S
    • Github & Github Actions

Anko的设计之道

Anko 是一个完全基于 Kotlin 设计的 Android 三方库,名字来自于 Android Kotlin 这两个单词的前两个字母。Anko 试图建立一套新的 Android 开发范式, 虽然不会成为主流,但是它的设计思想值得我们借鉴。

新的 UI 体系

先看一下 Anko 用于构建 UI 的几个关键类:

+--------------+
| ViewManaager |
+-------.------+
       /|\
        |
+---------------+
|  AnkoContext  |<--------------+
+-------.-------+               |
       /|\                      |
        |                       |
+-----------------+   +-----------------------+
| AnkoContextImpl |   | DelegatingAnkoContext |
+-------.---------+   +-----------------------+
       /|\
        |
+---------------------+
| ReusableContextImpl |
+---------------------+

这里需要强调的是,AnkoContext 继承自 ViewManager,而不是 android.content.Context。刚开始读源码时,总会觉得 AnkoContext 的命名有点反直觉,但是,就像文章一开始所说的——“Anko 试图建立一套新的 Android 开发范式” ——其实 AnkoContextandroid.content.Context 之间是并列关系。

就像 Android 需要基于 Context 来创建一个 View 一样,Anko 创建 UI 组件也需要基于 AnkoContext

既然 AnkoContextandroid.content.Context 是并列关系,那么大部分为 android.content.Context 定义扩展的地方也定义了 AnkoContext 的扩展。例如 Dimensions.kt 文件:

1
2
3
4
fun Context.dip(value: Int): Int = (value * resources.displayMetrics.density).toInt()
inline fun AnkoContext<*>.dip(value: Int): Int = ctx.dip(value)
inline fun View.dip(value: Int): Int = context.dip(value)
inline fun Fragment.dip(value: Int): Int = activity.dip(value)

Anko 的 UI 组件用一个接口 AnkoComponent 来表示,接口内提供了一个模板方法 createView

1
2
3
interface AnkoComponent<in T> {
fun createView(ui: AnkoContext<T>): View
}

可以看出,AnkoComponent 的类型参数(type parameter)是一个逆变的声明处变型(declaration-site variance),模板方法 createView 的作用是基于 AnkoContext<T> 实例创建一个 View

AnkoContext 继承自 ViewManager,也是一个接口,它“内藏”了 Android 的 android.content.Context 实例,同时还定义了一个类型为 T 的属性 owner 用于表示 AnkoContext 的拥有者。AnkoContext 没有对类型参数 T 加以限定,任何类型都可以。

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
interface AnkoContext<out T> : ViewManager {
val ctx: Context
val owner: T
val view: View

override fun updateViewLayout(view: View, params: ViewGroup.LayoutParams) {
throw UnsupportedOperationException()
}

override fun removeView(view: View) {
throw UnsupportedOperationException()
}

companion object {
fun create(ctx: Context, setContentView: Boolean = false): AnkoContext<Context>
= AnkoContextImpl(ctx, ctx, setContentView)

fun createReusable(ctx: Context, setContentView: Boolean = false): AnkoContext<Context>
= ReusableAnkoContext(ctx, ctx, setContentView)

fun <T> create(ctx: Context, owner: T, setContentView: Boolean = false): AnkoContext<T>
= AnkoContextImpl(ctx, owner, setContentView)

fun <T> createReusable(ctx: Context, owner: T, setContentView: Boolean = false): AnkoContext<T>
= ReusableAnkoContext(ctx, owner, setContentView)

fun <T: ViewGroup> createDelegate(owner: T): AnkoContext<T> = DelegatingAnkoContext(owner)
}
}

AnkoContext 还通过伴生对象(companion object)提供了五个工厂方法,它们生产出来的对象都是 AnkoContext 的子类,上面的类关系图已经展示出了它们之间的关系。

AnkoContextImplAnkoContext 接口的具体实现,覆写了 ViewManager#addView 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
override fun addView(view: View?, params: ViewGroup.LayoutParams?) {
if (view == null) return

if (myView != null) {
alreadyHasView()
}

this.myView = view

if (setContentView) {
doAddView(ctx, view)
}
}

我们来分析一下 addView 的具体实现:

参数 view: View? 是一个可空类型,但是 AnkoContext 的属性 view: View 是一个非可空类型,所以 AnkoContextImpl 重新定义了一个属性 var myView: View? 用于存储参数 view: View?

1
2
3
4
private var myView: View? = null

override val view: View
get() = myView ?: throw IllegalStateException("View was not set previously")

AnkoContextImpl 的构造方法多了一个属性参数 setContentView: Boolean,它表示是否要把 addView 的参数 view 设置为属性 ctx: Context 的 content view:

1
2
3
4
5
6
7
private fun doAddView(context: Context, view: View) {
when (context) {
is Activity -> context.setContentView(view)
is ContextWrapper -> doAddView(context.baseContext, view)
else -> throw IllegalStateException("Context is not an Activity, can't set content view")
}
}

ReusableAnkoContext 继承自 AnkoContextImpl,两者唯一的不同点在于 ReusableAnkoContextalreadHasView 是一个空实现,而 AnkoContextImpl 会抛出一个异常,它不支持 view 复用。

DelegatingAnkoContextAnkoContext 的另一个实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
internal class DelegatingAnkoContext<T: ViewGroup>(override val owner: T): AnkoContext<T> {
override val ctx: Context = owner.context
override val view: View = owner

override fun addView(view: View?, params: ViewGroup.LayoutParams?) {
if (view == null) return

if (params == null) {
owner.addView(view)
} else {
owner.addView(view, params)
}
}
}

DelegatingAnkoContext 的类型参数指定了 upper bound —— <T: ViewGroup>,也就是说 DelegatingAnkoContextowner 必须是 ViewGroup 或者它的子类。

关于 DelegatingAnkoContext 名字中的 Delegating 应该是为了表达属性 view: View 代理了(delegating)owner: T

1
override val view: View = owner

再来回看一下这几个类的继承关系:

  • AnkoContext 继承自 ViewManager
  • AnkoContextImplDelegatingAnkoContext 实现了 AnkoContext
  • ReusableContextImpl 继承自 AnkoContextImpl
+--------------+
| ViewManaager |
+-------.------+
       /|\
        |
+---------------+
|  AnkoContext  |<--------------+
+-------.-------+               |
       /|\                      |
        |                       |
+-----------------+   +-----------------------+
| AnkoContextImpl |   | DelegatingAnkoContext |
+-------.---------+   +-----------------------+
       /|\
        |
+---------------------+
| ReusableContextImpl |
+---------------------+

基于 DSL 实现 UI 布局

DSL 可以用于取代手写 XML 布局,其优势如下:

  • 类型安全:编译阶段就能够发现类型错误,而且不会出现 NPE
  • 代码复用:可封装 View 的创建逻辑
  • 性能提升:节省了 inflate 的运行开销

先来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
verticalLayout {
padding = dip(32)

imageView(android.R.drawable.ic_menu_manage).lparams {
margin = dip(16)
gravity = Gravity.CENTER
}

val name = editText {
hintResource = R.string.name
}
val password = editText {
hintResource = R.string.password
inputType = TYPE_CLASS_TEXT or TYPE_TEXT_VARIATION_PASSWORD
}

button("Log in") {
onClick {
ui.owner.tryLogin(ui, name.text, password.text)
}
}

myRichView()
}.applyRecursively(customStyle)

在 Anko 新的 UI 体系中,AnkoComponent 表示一个 UI 组件,模板方法 createView 返回 View 实例,上面的代码即可作为 createView 的函数内容,返回值是一个 vertical 的 LinearLayout

加上 AnkoComponent 把上面的代码补全:

1
2
3
4
5
6
7
8
9
10
11
12
13
class MainActivityUi : AnkoComponent<MainActivity> {
private val customStyle = { v: Any ->
when (v) {
is Button -> v.textSize = 26f
is EditText -> v.textSize = 24f
}
}
override fun createView(ui: AnkoContext<MainActivity>) = with(ui) {
relativeLayout {
// ...
}.applyRecursively(customStyle)
}
}

我们来分析一下 relativeLayout 如何成为 ui: AnkoContext<MainActivity> 的 content view。

首先,relativeLayoutViewManager 的扩展函数,返回值是一个 LinearLayout

1
2
3
inline fun ViewManager.verticalLayout(theme: Int = 0, init: (@AnkoViewDslMarker _LinearLayout).() -> Unit): LinearLayout {
return ankoView(`$$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, theme, init)
}

然后,它会调用 ViewManager 的另一个扩展函数——ankoView

1
2
3
4
5
6
7
inline fun <T : View> ViewManager.ankoView(factory: (ctx: Context) -> T, theme: Int, init: T.() -> Unit): T {
val ctx = AnkoInternals.wrapContextIfNeeded(AnkoInternals.getContext(this), theme)
val view = factory(ctx)
view.init()
AnkoInternals.addView(this, view)
return view
}

ankoView 会调用 AnkoInternals#addView

1
2
3
4
5
fun <T : View> addView(manager: ViewManager, view: T) = when (manager) {
is ViewGroup -> manager.addView(view)
is AnkoContext<*> -> manager.addView(view, null)
else -> throw AnkoException("$manager is the wrong parent")
}

也就是说,verticalLayout 函数会把它所创建的 LinearLayout 作为 child view 添加到接收者(receiver)中,这里是 ViewManager

再回到 AnkoComponentcreateView

1
2
3
4
5
override fun createView(ui: AnkoContext<MainActivity>) = with(ui) {
verticalLayout {
// ...
}
}

with(ui) 会把 ui: AnkoContext<MainActivity> 作为第二个参数(用大括号表示的高阶函数)的 receiver,也就是说 高阶函数内的 this 会指向变量 ui。这样的话,AnkoInternals.addView(this, view) 会调用到 AnkoContextaddView 方法,具体如何处理取决于接口 AnkoContext 的具体实现。

下面举例来展示它们的用法。

自定义 View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class RichView : LinearLayout {
private lateinit var image: ImageView
private lateinit var text: TextView

private fun init() = AnkoContext.createDelegate(this).apply {
gravity = CENTER
padding = dip(24)

image = imageView(imageResource = R.drawable.kotlin) {
onClick { startAnimation() }

padding = dip(8)
layoutParams = LinearLayout.LayoutParams(dip(48), dip(48))
}

text = textView("Anko Rich view") {
textSize = 19f
}

startAnimation()
}

// ...
}

AnkoContext.createDelegate 创建了一个 DelegatingAnkoContext,它的 owner 和 view 都是 this,也就说扩展函数 imagetext 所创建的 view 都会作为 child view 添加给 this

Activity 的 content view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val adapter = ProverbAdapter(this, proverbs)
MainActivityUI(adapter).setContentView(this)
}
}

class MainActivityUI(private val adapter: ProverbAdapter) : AnkoComponent<MainActivity> {
override fun createView(ui: AnkoContext<MainActivity>) = with(ui) {
relativeLayout {
recyclerView {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false).apply {
adapter = this@MainActivityUI.adapter
}
}.lparams(width = matchParent, height = matchParent)
}.apply {
layoutParams = FrameLayout.LayoutParams(matchParent, matchParent).apply {
padding = dip(16)
}
}
}
}

MainActivityUIAnkoComponent<MainActivity> 的子类,类型实参 <MainActivity> 表示 AnkoContextowner 的类型。

创建完 MainActivityUI 的实例之后,用法就变得非常简单,直接调用它的扩展函数 setContentView

1
MainActivityUI(adapter).setContentView(this)

setContentView 会创建一个 AnkoContextImpl 类型的实例,然后把这个实例作为参数调用 createView

1
2
fun <T : Activity> AnkoComponent<T>.setContentView(activity: T): View =
createView(AnkoContextImpl(activity, activity, true))

RecyclerView.ViewHolder

首先用 AnkoComponent 定义 item 的 UI 组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ProverbComponent(val ui: AnkoContext<ProverbAdapter>) : AnkoComponent<ProverbAdapter> {
lateinit var category: TextView
lateinit var title: TextView

fun createView() = createView(ui)

override fun createView(ui: AnkoContext<ProverbAdapter>) = with (ui) {
linearLayout {
category = textView {
textColor = Color.RED
}

textView {
text = ": "
}

title = textView {
textColor = Color.BLUE
}
}
}
}

然后用 ViewHolder 来 hold 这个组件。

1
class ViewHolder(val ankoComponent: ProverbComponent) : RecyclerView.ViewHolder(ankoComponent.createView())

创建组件所使用的 AnkoContextReusableAnkoContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ProverbAdapter(context: Context, private val items: List<Proverb>) : RecyclerView.Adapter<ViewHolder>() {
private val ankoContext: AnkoContext<ProverbAdapter> = AnkoContext.createReusable(context, this)

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.ankoComponent.category.text = items[position].category
holder.ankoComponent.title.text = items[position].phrase
}

override fun getItemCount(): Int {
return items.size
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ProverbComponent(ankoContext))
}
}

总结

Anko 这种基于 DSL 的布局方式可以算得上是一股清流,虽然在执行效率上面有所提升,但是从开发效率和分工协作的角度来看,DSL 算不上是一种高效的方式。

我们都知道,用 Sketch 画 UI 肯定比手写代码的方式效率高,无论是 Android 还是 iOS,UI 的布局方式都在朝着“拖拽”的方向演进,比如 ConstraintLayoutAutoLayout,设计稿直接转换为 XML 布局文件也已经指日可待。除此之外,逻辑代码和布局代码混在一起的方式也不便于代码维护。

UI 布局只是 Anko 的功能之一,有时间了再分析一下 Anko Coroutines 的实现。

Google I/O 2018 看点

Google I/0 2018 马上就要开始了,美国加利福尼亚州山景城时间 5 月 8 日上午 10 点,对应北京时间是 5 月 9 日凌晨 1 点。

本来今年打算报名现场的,因其它事情花了一笔钱,经费紧张,遂放弃,只能看直播了。

Google I/O Extended 2018 Hangzhou 报名链接: https://www.meetup.com/Hangzhou-GDG/events/249858297/

我根据 topics 整理了一个 session 的脑图,格式是 svg 格式,可以无限放大,新 Tab 中打开也可以查看大图。

内容脑图

兴趣主题

Machine Learning & AI

毫无疑问,今年的主题依然是 AI。RISC 发明人、图领奖得主 John Hennsessy 会做主题为《The future of computing》的 Keynote。Fei-Fei Li 也会露面,她的 Keynote 是《Building the future of artificial intelligence for everyone》,值得期待。

TensorFlow 大大降低了 Machine Learning 的入门门槛,基于它,没有读过 PhD 的人也能够做一些 AI 相关的工作。

物联网 IoT

IoT 方面,除了一如至往的 Android Things,又多了一个技术栈—— OpenThread,它为低功耗(low-power)的 IoT 设备带来了 Internet。

IoT 产品也可以基于 Google Cloud Platform 来开发,叫 Cloud IoT。

终端设备用于收集数据,再加上机器学习的运用,一定会创造出意想不到的产品体验,这是我们的机会。

Machine learning models + IoT data = a smarted world

突然想起了以前在 fujitsu 工作时墙上的 slogon:Shaping tomorrow with you。

Firebase

Firebase 为 Mobile、Web 提供了全套解决方案,基于它我们可以快速高效地部署应用,它是 Google 云计算的产品之一,背后依托于强大的 Google Cloud Platform。

Google Cloud Platform

云产品的基石是 Google Cloud Platform ,Firebase、Cloud TPU、Cloud IoT 等都是基于 GCP 为开发者提供服务。

Android

Android 内容不少,Googler 会传授很多开发姿势给我们,还有一些 OS 层的原理,比如 ART、Rendering。当然也少不了 Architecture Components、RecycerView、ConstraintLayout、Support Library 这些能够着实提高开发效率的 SDK。

Kotlin 的内容不多,只有两个 session,一个是 Kotlin 语言的设计者教你怎么写代码,另一个是 Jake 大神加盟 Google 之后开源的 Android KTX

Web

相比于 Android 来说,Web 出现了很多新技术,旨在打造一个摩登时代,叫做 Modern Web。

例如,注册登录相关的 WebAuthnOne-tap Sign-upreCAPCHA V3,第一个 secure-only 的顶级域名 .app,无 UI 的 Headless Chrome,PWA、AMP 等等,很有意思,值得学习。

Flutter

Flutter 可以快速构建一个跨平台、响应式的 App。本次 IO 大会上,与 Material Design 结合得比较紧密。

总结

对于一般人来说,知易行难,仅仅了解这些技术是远远不够的,只有大量的工程实践才能慢慢转化为自己的内功,这种内功才能为未来的技术决策提供强大的能量。

Kotlin网络库Fuel的设计之道

使用场景

一个“朴素”的 url 完全可以用一个字符串来表示(例如 "https://www.youzan.com"),我们可以利用 Kotlin 语言本身的特性为 String 类型添加一个扩展函数 httpGet(),然后借此发起 http 请求:

1
"https://www.youzan.com".httpGet()

但是,对于不是朴素字符串的对象来说,我们可以让其实现一个接口:

1
2
3
interface PathStringConvertible {
val path: String
}

然后,将“计算”过后的 path 通过一个 String 类型提供出来,例如:

1
2
3
4
5
6
7
8
9
enum class HttpsBin(relativePath: String) : Fuel.PathStringConvertible {
USER_AGENT("user-agent"),
POST("post"),
PUT("put"),
PATCH("patch"),
DELETE("delete");

override val path = "https://httpbin.org/$relativePath"
}

但是,也会存在一种情况,所有的 url 可能会共享一个 base url,或者是其他公用参数,那么还需有一个地方来存储这些通用配置,这个地方的幕后老大就叫 FuelManager

StringPathStringConvertible 最终也会调用到 FuelManager

+----------+
|  String  |------------->----+
+----------+                  |    +------+    +-------------+
                              |--->| Fuel |--->| FuelManager |
+-------------------------+   |    +------+    +-------------+
|  PathStringConvertible  |->-+
+-------------------------+

除了通过 String 或者 PathStringConvertiable 来发起请求,我们还可以直接用一个 Request,因此 Fuel 还提供了转换 Request 的接口:

1
2
3
interface RequestConvertible {
val request: Request
}

综上来看,发起一个 http 请求可以有如下四种方式:
0. 一个字符串
0. PathStringConvertible 变量
0. RequestConvertible 变量
0. 直接使用 Fuel 伴生对象提供的方法

代码实现

对外提供服务的 Fuel

首先 Fuel 作为对外的接口提供方(类似 Facade 模式),通过一个伴生对象(companion object)提供服务(以 get 方法为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
companion object {
@JvmStatic @JvmOverloads
fun get(path: String, parameters: List<Pair<String, Any?>>? = null): Request =
request(Method.GET, path, parameters)

@JvmStatic @JvmOverloads
fun get(convertible: PathStringConvertible, parameters: List<Pair<String, Any?>>? = null): Request =
request(Method.GET, convertible, parameters)

private fun request(method: Method, path: String, parameters: List<Pair<String, Any?>>? = null): Request =
FuelManager.instance.request(method, path, parameters)

private fun request(method: Method, convertible: PathStringConvertible, parameters: List<Pair<String, Any?>>? = null): Request =
request(method, convertible.path, parameters)
}

Fuel 类通过伴生对象提供的 http 方法有 get/post/put/patch/delete/download/upload/head,这些方法最终会路由到 FuleManager 的实例(instance)。

同时,Fule.kt 源文件为 StringPathStringConvertible 定义了扩展,以支持这些 http 方法(以 get 方法为例):

1
2
3
4
5
@JvmOverloads
fun String.httpGet(parameters: List<Pair<String, Any?>>? = null): Request = Fuel.get(this, parameters)

@JvmOverloads
fun Fuel.PathStringConvertible.httpGet(parameter: List<Pair<String, Any?>>? = null): Request = Fuel.get(this, parameter)

幕后老大 FuleManager

FuleManager 利用伴生对象实现了单例模式:

1
2
3
4
companion object {
//manager
var instance by readWriteLazy { FuelManager() }
}

同时利用代理属性实现了单例的懒加载。

readWriteLazy 是一个函数,它的返回值是一个 ReadWriteProperty,代码比较容易,具体可见 Delegates.kt

也就是说,当我们第一次访问 FuelManager 时,一个具体的实例会被创建出来,这个实例担负了存储公用配置和发起请求的重任,首先来看它的属性:

1
2
3
4
5
6
7
8
9
10
11
var client: Client
var proxy: Proxy?
var basePath: String?

var baseHeaders: Map<String, String>?
var baseParams: List<Pair<String, Any?>>

var keystore: KeyStore?
var socketFactory: SSLSocketFactory

var hostnameVerifier: HostnameVerifier

Client 是一个接口,通过它我们可以自定义 http 引擎。

1
2
3
interface Client {
fun executeRequest(request: Request): Response
}
+---------+     +--------+     +----------+
| Request | ==> | Client | ==> | Response |
+---------+     +--------+     +----------+
                     |
                    \|/                   +--------------------+
              +------------+              | HttpURLConnection  |
              | HttpClient | --based on-- +--------------------+
              +------------+              | HttpsURLConnection |
                                          +--------------------+

Fuel 默认提供的 Http 引擎是 HttpClient,它是基于 HttpURLConnection 的实现。

basePathbaseHeadersbaseParams 存储了请求的公用配置,我们可以通过 FuleManager.instance 为其赋值:

1
2
3
4
5
FuelManager.instance.apply {
basePath = "http://httpbin.org"
baseHeaders = mapOf("Device" to "Android")
baseParams = listOf("key" to "value")
}

keystore 用于构建 socketFactory,再加上 hostnameVerifier,它们用于 https 请求,在 HttpClient 中有用到:

1
2
3
4
5
6
7
8
9
10
11
12
private fun establishConnection(request: Request): URLConnection {
val urlConnection = if (proxy != null) request.url.openConnection(proxy) else request.url.openConnection()
return if (request.url.protocol == "https") {
val conn = urlConnection as HttpsURLConnection
conn.apply {
sslSocketFactory = request.socketFactory // socketFactory
hostnameVerifier = request.hostnameVerifier // hostnameVerifier
}
} else {
urlConnection as HttpURLConnection
}
}

如果要深入了解 HTTPS 证书,可参考 「HTTPS 精读之 TLS 证书校验」。

FuelManager 在发起请求时会用这些参数构建一个 Request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun request(method: Method, path: String, param: List<Pair<String, Any?>>? = null): Request {
val request = request(Encoding(
httpMethod = method,
urlString = path,
baseUrlString = basePath,
parameters = if (param == null) baseParams else baseParams + param
).request)

request.client = client
request.headers += baseHeaders.orEmpty()
request.socketFactory = socketFactory
request.hostnameVerifier = hostnameVerifier
request.executor = createExecutor()
request.callbackExecutor = callbackExecutor
request.requestInterceptor = requestInterceptors.foldRight({ r: Request -> r }) { f, acc -> f(acc) }
request.responseInterceptor = responseInterceptors.foldRight({ _: Request, res: Response -> res }) { f, acc -> f(acc) }
return request
}

关于 requestInterceptorresponseInterceptor,原理与 OkHttp 实现的拦截器一致,只不过这里利用了 Kotlin 的高阶函数,代码实现非常简单,具体细节可参考 「Kotlin实战之Fuel的高阶函数」。

跟其他网络库一样,一次完整的请求,必然包含两个实体—— Request & Response,先来看 Request

请求实体 Request

1
2
3
4
5
6
7
8
9
10
11
12
class Request(
val method: Method,
val path: String,
val url: URL,
var type: Type = Type.REQUEST,
val headers: MutableMap<String, String> = mutableMapOf(),
val parameters: List<Pair<String, Any?>> = listOf(),
var name: String = "",
val names: MutableList<String> = mutableListOf(),
val mediaTypes: MutableList<String> = mutableListOf(),
var timeoutInMillisecond: Int = 15000,
var timeoutReadInMillisecond: Int = timeoutInMillisecond) : Fuel.RequestConvertible

它支持三种类型的请求:

1
2
3
4
5
enum class Type {
REQUEST,
DOWNLOAD,
UPLOAD
}

针对每个类型都有对应的任务(task):

1
2
3
4
5
6
7
8
//underlying task request
internal val taskRequest: TaskRequest by lazy {
when (type) {
Type.DOWNLOAD -> DownloadTaskRequest(this)
Type.UPLOAD -> UploadTaskRequest(this)
else -> TaskRequest(this)
}
}

涉及到上传下载的 DownloadTaskRequestUploadTaskRequest 都继承自 TaskRequest,它们会处理文件和流相关的东西,关于此可参考 IO 哥写的 一些「流与管道」的小事 以及 OK, IO

FuelManager 在构造 Request 时用到了一个类——Encoding

1
2
3
4
5
6
class Encoding(
val httpMethod: Method,
val urlString: String,
val requestType: Request.Type = Request.Type.REQUEST,
val baseUrlString: String? = null,
val parameters: List<Pair<String, Any?>>? = null) : Fuel.RequestConvertible

Encoding 也是继承自 Fuel.RequestConvertible,它完成了对 Request 参数的组装编码,并产生了一个 Request

Encoding 组装 query parameter 的方式可以说赏心悦目,贴出来欣赏一下:

1
2
3
4
private fun queryFromParameters(params: List<Pair<String, Any?>>?): String = params.orEmpty()
.filterNot { it.second == null }
.map { (key, value) -> URLEncoder.encode(key, "UTF-8") to URLEncoder.encode("$value", "UTF-8") }
.joinToString("&") { (key, value) -> "$key=$value" }

请求返回结果 Response

1
2
3
4
5
6
7
class Response(
val url: URL,
val statusCode: Int = -1,
val responseMessage: String = "",
val headers: Map<String, List<String>> = emptyMap(),
val contentLength: Long = 0L,
val dataStream: InputStream = ByteArrayInputStream(ByteArray(0))

Response 的属性可以看出,它所携带的仍然是一个流(Stream),我们先看 Response 是如何与 Request 串联起来的。

Deserializable.kt 文件为 Request 定了名称为 response 的扩展函数:

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
private fun <T : Any, U : Deserializable<T>> Request.response(
deserializable: U,
success: (Request, Response, T) -> Unit,
failure: (Request, Response, FuelError) -> Unit): Request {

val asyncRequest = AsyncTaskRequest(taskRequest)

asyncRequest.successCallback = { response ->
val deliverable = Result.of { deserializable.deserialize(response) }
callback {
deliverable.fold({
success(this, response, it)
}, {
failure(this, response, FuelError(it))
})
}
}

asyncRequest.failureCallback = { error, response ->
callback {
failure(this, response, error)
}
}

submit(asyncRequest)
return this
}

扩展函数 response 的参数中,deserializable 负责反序列化操作,successfailure 用于处理请求结果。

Fuel 提供了两个 Deserializable 的实现:StringDeserializer 以及 ByteArrayDeserializer,它们用于反序列化 response 的 stream。

异步请求

Deserializable.ktRequest 定义的扩展函数 response 在执行异步操作时用到了一个 AsnycTaskRequest,其实它本身并不提供异步实现,而是交由一个 ExecutorService 去执行,而这个 ExecutorService 恰由 FuelManager 定义,并在构造 Request 时传入给它。

FuleManager.kt

1
2
3
4
5
6
7
8
9
//background executor
var executor: ExecutorService by readWriteLazy {
Executors.newCachedThreadPool { command ->
Thread(command).also { thread ->
thread.priority = Thread.NORM_PRIORITY
thread.isDaemon = true
}
}
}

AsyncTaskRequestUploadTaskRequestDownloadTaskRequest 一样,都是继承自 TaskRequest,只不过它多了两个异步调用的回调:

1
2
var successCallback: ((Response) -> Unit)? = null
var failureCallback: ((FuelError, Response) -> Unit)? = null

请求图例

至此,请求、回复,异步调用,对外接口都了解过了,一个基本的网络库框架已经成型。

         +------------------------+
         | https://www.youzan.com |
         +------------------------+
                     |
                     |
                    \|/
                  +------+
                  | Fuel |
                  +------+
                     |
                     |
                    \|/
              +-------------+
              | FuelManager |
              +-------------+
                     |
                     |
                    \|/
+---------+      +--------+      +----------+
| Request | ===> | Client | ===> | Response |
+---------+      +--------+      +----------+

虽然Fuel 的复杂度不可与 OkHttp 相提并论,但是依赖 Kotlin 语言本身的灵活性,它的代码却比 OkHttp 要简洁的多,特别是关于高阶函数和扩展函数的运用,极大地提升了代码的可读性。

参考资料