0%

作者:Oz

版权声明:本文图文为博主原创,转载请注明出处。

Compose 作为一个新兴高效的 UI 框架,它使用声明式的语言来构建 UI,同时,它还使用惰性计算来优化 UI 的性能。目前在线上的业务中已经有了落地实践,例如实感骑容器、KS 等项目。正所谓知其然也要知其所以然,为了探究 Compose 的实现原理,对部分源码进行了阅读整理,这里尝试回答以下几个问题:

  • 作为一种新的 UI 框架它是如何将 Compose UI 渲染到屏幕上的?★★★★★

  • 平常在写代码时,经常用到 LaunchedEffectSideEffect 是怎么样生效的? ★

  • CompositionLocal 通过 @Composable 函数隐式向下传递数据,它又是怎么样发挥作用的?★

  • @Composable AndroidView 是怎么和 Compose 进行组合渲染的?★★

Pre-Start

进入正题前,我们先看看官方的介绍:

https://developer.android.com/jetpack/compose/phases?hl=zh-cn

与大多数其他界面工具包一样,Compose 会通过几个不同的“阶段”来渲染帧。如果我们观察一下 Android View 系统,就会发现它有 3 个主要阶段:测量、布局和绘制。Compose 和它非常相似,但开头多了一个叫做“组合”的重要阶段。

帧的 3 个阶段

Compose 有 3 个主要阶段:

  1. 组合:要显示什么样的界面。Compose 运行可组合函数并创建界面说明。

  2. 布局:要放置界面的位置。该阶段包含两个步骤:测量和放置。对于布局树中的每个节点,布局元素都会根据 2D 坐标来测量并放置自己及其所有子元素。

  3. 绘制:渲染的方式。界面元素会绘制到画布(通常是设备屏幕)中。

Compose Phases

以上,官方基本将渲染的关键流程讲出来了,但是还比较抽象。为了了解得更具体深入一些,我们有必要钻研一下 Compose 的源码。我们先来看一个实际使用的例子:

1
2
3
4
5
6
7
8
9
class TestComposeActivity: ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text(text = "Hello World!")
}
}
}

以上实际画的内容是什么并不重要,重要的是我们从 setContent 这个入口追踪一下流程中发生了什么。

Compose 环境初始化

首先,为了更快的让大家了解 setContent 这个过程发生了什么,我们可以先看整理好的流程图:

compose-create-composition.png

下面我们来通过代码流程,看下这里面都做了哪些事。

Read more »

作者:Oz

v 信号: Mojitok8275

版权声明:本文图文为博主原创,转载请注明出处。

吸顶效果是各家 App 或多或少都会用的一个交互,这种交互也常见于 PC、H5,可以说是一种通用性很强的前端交互体验,相比较来说京东首页的嵌套滑动吸顶效果是各个类似效果中体验比较好的一个,因为在嵌套布局中滑动连贯性处理得非常好,今天我们就来实现同样的交互效果。

这篇文章我会讲些什么

  • 对于吸附效果实现的思路
  • 3 个版本的 NestedScrollingParent、NestedScrollingChild 简单介绍
  • 嵌套组件滑动连贯性(一致性)的处理(事件分发、Fling 等)
  • 交互的优化问题

首先,先看一下我们实现的效果图:

这里只介绍我们实现过程中的思路,以及部分代码,源码请查看 NestedCeilingEffect,欢迎建议、Issues、Star

实现滑动吸顶效果的简单分析

对于吸顶效果,首先我们要先创造出 “顶” 来,那么如何创造 “顶” 呢?常见的实现方式有很多,比如:

  • 通过两个相同的顶部控件显示或隐藏来实现
  • 通过动态 layout 顶部控件来实现
  • 通过重写 ItemDecoration 来实现
  • 通过 CoordinatorLayout 协调子布局来实现

这些方式或多或少在某些方面有一些场景上的限制,这次我们采用另外一种方式来实现 “顶” 的效果,这里先卖一个关子。

Read more »

作者:Oz

v 信号: Mojitok8275

版权声明:本文为博主原创,转载请注明出处。

题目要求

211:添加与搜索单词 - 数据结构设计

设计一个支持以下两种操作的数据结构:

1
2
void addWord(word)
bool search(word)

search(word) 可以搜索文字或正则表达式字符串,字符串只包含字母 .a-z. 可以表示任何一个字母。

示例:

1
2
3
4
5
6
7
addWord("bad")
addWord("dad")
addWord("mad")
search("pad") -> false
search("bad") -> true
search(".ad") -> true
search("b..") -> true

说明:你可以假设所有单词都是由小写字母 a-z 组成的。

解题思路

题解同时发表在 leetcode 前缀树 + 队列处理通配符查找

如果不考虑通配符,这道题就是一个典型的前缀树(字典树)问题,我们只需要构造一个前缀树,实现对应的插入搜索功能即可。

由于题目要求匹配了“.”,因此,在前缀树的搜索时,我们就要对 “.” 进行特殊处理,那我们接下来思考,如何处理通配符?

根据题目要求 . 可以表示任何一个字母,如果一个输入的字符串中包含 . 就意味着,这个查找结果可能会有多个。那么我们来思考带有通配符 . 的字符串的查找结果有几种情况:

  • 没有找到对应的单词

    这种情况代表着虽然包含通配符 . 但由于其他位置的字符与字典中的单词字符不能匹配

  • 找到一个或多个相应的单词

    这种情况下代表着字典中有一个或多个单词与之相匹配

根据以上两种情况的分析,综合不含通配符单词的查找,我们需要使用集合来保存查找到的 TrieNode 结果。

我们再分析,如果输入的字符串本身不是一个单词,这时我们就要对我们查找到的节点进行过滤,必须存在一个或多个单词字符位数与输入的字符串相匹配,才能视为查找到对应的结果。

实现前缀树

通过上面的分析,我们来构建一个合理的前缀树,这其中的关键就在于设计 Trie 节点结构,所以为了解决我们上述分析的问题,我们需要引入一个布尔变量来表示当前字符串是否是单词,另外为了方便与输入字符串比较长度我们这里通过保存单词字符串的形式处理。

1
2
3
4
5
6
7
8
9
class TrieNode {
boolean isWord = false;
String word = "";
HashMap<Character, TrieNode> children;

TrieNode() {
children = new HashMap<>();
}
}

设计好了节点结构以后,我们就来实现前缀树了:

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
class Trie {
private TrieNode root;

Trie() {
root = new TrieNode();
}

void insert(String word) {
TrieNode cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
TrieNode child = cur.children.get(c);
if (child != null) {
cur = child;
} else {
TrieNode next = new TrieNode();
cur.children.put(c, next);
cur = next;
}
}
cur.isWord = true;
cur.word = word;
}

boolean search(String word) {
// ??
}

}

通过队列来处理多节点遍历

那么接下来如何对搜索进行实现呢?根据我们开头的分析,由于通配符 . 的存在,在遍历节点时可能需要一次遍历多个节点(例如:一个字符. 匹配出 a b c 三个前缀节点),此时我们自然想到用队列的数据结构比较适合处理这个问题。

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
boolean search(String word) {
// 引入队列
LinkedList<TrieNode> queue = new LinkedList<>();
queue.add(root);
// 遍历字符串的索引;
int index = 0;
List<TrieNode> res = new ArrayList<>();
while (!queue.isEmpty() && index < word.length()) {
int size = queue.size();
char c = word.charAt(index++);
for (int j = 0; j < size; j++) {
TrieNode cur = queue.poll();
if (c == '.') { // 处理通配符 “.”
for (Map.Entry<Character, TrieNode> e : cur.children.entrySet()) {
TrieNode child = e.getValue();
queue.add(child);
if(index == word.length()){
res.add(child);
}
}
} else {
TrieNode child = cur.children.get(c);
if (child != null) {
queue.add(child);
if(index == word.length()){
res.add(child);
}
} else {
// nothing to do
// 如果节点不匹配,则不需要添加到结果集合
}
}
}
}
return checkStatus(res, word.length());
}

通过搜索找到临时的结果,此时我们还需要对结果进行筛选处理,保证我们找到的 TrieNode 节点是满足条件的结果。

### 完整代码

image.png

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
public class WordDictionary {
private Trie mDict;

public WordDictionary() {
mDict = new Trie();
}

public void addWord(String word) {
mDict.insert(word);
}

public boolean search(String word) {
return mDict.search(word);
}

private static class Trie {
private TrieNode root;

Trie() {
root = new TrieNode();
}

void insert(String word) {
TrieNode cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
TrieNode child = cur.children.get(c);
if (child != null) {
cur = child;
} else {
TrieNode next = new TrieNode();
cur.children.put(c, next);
cur = next;
}
}
cur.isWord = true;
cur.word = word;
}

boolean search(String word) {
LinkedList<TrieNode> queue = new LinkedList<>();
queue.add(root);

int index = 0;
List<TrieNode> res = new ArrayList<>();
while (!queue.isEmpty() && index < word.length()) {
int size = queue.size();
char c = word.charAt(index++);
for (int j = 0; j < size; j++) {
TrieNode cur = queue.poll();
if (c == '.') {
for (Map.Entry<Character, TrieNode> e : cur.children.entrySet()) {
TrieNode child = e.getValue();
queue.add(child);
if(index == word.length()){
res.add(child);
}
}
} else {
TrieNode child = cur.children.get(c);
if (child != null) {
queue.add(child);
if(index == word.length()){
res.add(child);
}
} else {
// nothing to do
// 如果节点不匹配,则不需要添加到结果集合
}
}
}
}
return checkStatus(res, word.length());
}

boolean checkStatus(List<TrieNode> res, int len) {
if(res.isEmpty()) {
return false;
}
int f = 0;
for (TrieNode n : res) {
if (n.isWord && n.word.length() == len)
f++;
}
return f != 0;
}
}

private static class TrieNode {
boolean isWord = false;
String word = "";
HashMap<Character, TrieNode> children;

TrieNode() {
children = new HashMap<>();
}
}
}

作者:Oz

v 信号: Mojitok8275

版权声明:本文图文为博主原创,转载请注明出处。

文章似乎有些标题党的嫌疑,但是我相信根据我的理解画出两幅图可以让大家理解 RxJava2 的核心原理,稍后不要吝啬,请叫我灵魂画手😄!相信 RxJava 是大家业务中用到比较多的一个依赖库,RxJava 的强大之处在于它改变了程序员的编程习惯,相比较其他的开源项目,Rxjava 是最弯弯绕的一个。对于 RxJava 种类繁多的操作符,大多数同学都表示很是头疼,也有不少同学陷入了学习操作符不能停的怪圈。操作符要不要学,当然要,但是如果能理解 RxJava 的核心,操作符的使用就像是学会九阳神功的张无忌学招数,必定是手到擒来。所谓器欲尽其用,必先得其法。

这篇文章我会讲些什么

  • RxJava2 基本的运行流程
  • RxJava2 线程切换的原理(涉及到为什么 subscribeOn() 只有第一次调用时有效)
  • 为什么一订阅就回调了 onSubscribe
  • 为什么 subscribeOn() 对上面的代码生效,observerOn() 对下面代码生效

以下内容如果涉及到自己写的代码我会采用 Kotlin 进行示例展示,涉及到 RxJava2 会展示部分源码。

简单的链式调用(无线程切换)

Read more »

作者:Oz

版权声明:转载请注明出处。

一直以来,Android 项目在构建速度是一大槽点,随着Android Studio 3.0 的大版本的升级使得多Module工程的构建速度加快很多。这主要依赖于 Android Plugin for Gradle 插件版本的升级,因此部分 API 发生了较大变化。本文主要是记录整个迁移过程以及聊一些常用的优化构建速度的建议,以供参考。

android studio 3.1.3; gradle-4.4; gradle plugin 3.1.3

Android SDK 构建系统

在正式讲述升级迁移之前,大家应该熟悉一下 Android SDK 的构建系统,Android 构建系统编译应用资源和源代码,然后将它们打包成可供开发人员测试、部署、签署和分发的 APK。Android Studio 使用 Gradle 这一高级构建工具包来自动化执行和管理构建流程,同时也允许开发人员定义灵活的自定义构建配置。每个构建配置均可自行定义一组代码和资源,同时对所有应用版本共有的部分加以重复利用。Android Plugin for Gradle 与这个构建工具包协作,共同提供专用于构建和测试 Android 应用的流程和可配置设置。

Read more »

作者:Oz

版权声明:本文图文为博主原创,转载请注明出处。

随着跨平台技术的发展演进,React Native 被越来越多的公司所接受,因此在这里分享一下 React Native 渲染流程,希望能帮助大家对 React Native 进行更深入的理解。

跨平台框架

跨平台一直以来是一个工程实践上的真实需求,用来节约项目开发的人力成本、时间成本等,尤其是在当下移动互联网的时代。在 React Native 诞生之前,已经存在了很多跨平台的方案,例如:Phone GapXamarinCorona 等。

跨平台框架都是伪命题?

  • PhoneGap 利用 open-web 技术,即 HTML 5、CSS3 以及 JavaScript 构建移动 Web 应用。
  • Xamarin 利用 C# 语言开发原生移动应用,打包时将 C# 转化为对应平台的原生代码。
  • Corona 2D 游戏与应用开发平台,利用 lua 进行开发,主要面向游戏开发。

在以上我们提到这个几个跨平台框架,相对来说是被采用较多或比较知名的,但也缺点十分明显。其实在接触 React Native 之前我一直都认为所谓跨平台框架都是伪命题,但是在接触之后,确实改变了我的认识。

基于 JS 的跨平台框架

FacebookReact.js Conf 2015 大会上推出了基于 JavaScript 的开源框架 React Native

React Native 结合了 Web 应用和 Native 应用的优势,可以使用 JavaScript 来开发 iOSAndroid 原生应用。在 JavaScript 中用 React 抽象操作系统原生的 UI 组件,代替 DOM 元素来渲染。这种方案与类似 PhoneGap 这种依赖 open-web 技术方案最大的不同就是 React Native 会将标签元素渲染成原生 UI 组件,从而提升性能及交互体验,使得应用本身更加接近原生应用的体验,也因此有越来越多的公司开始考虑 React Native 的跨平台方案。

Read more »

作者:Oz

版权声明:本文为博主原创,转载请注明出处。

今天主要安利一个 JavaScript 代码规范 standard js ,不论你一开始喜不喜欢这个标准,相信我开使用它,你会爱上这个标准!

序言

是的最近团队一直在 React Native 上进行探索性业务开发,在团队项目的合作中代码规范对于我们是一个相当重要的素质要求,对于注重代码质量注重代码可读性的团队尤其重要。由于缺乏代码规范,不同的人有不同的偏好,代码可读性会随着团队成员的更迭逐渐降低,也因为这样有可能带来线上 bug,而这些完全是可以通过代码检查避免的。

是时候强制要求代码规范了

强制两个字是好说不好听,谁也不愿意被强制要求这样那样,但这个事情我觉得懂的人自然懂,没什么好说的,团队合作就应该采用相同的规范,不同的人写的代码应该看起来是出自同一个人之手。

Read more »

作者:Oz

版权声明:本文图文为博主原创,转载请注明出处。

随着 React Native 的不断发展完善,越来越多的公司选择使用 React Native 替代 iOS/Android 进行部分业务线的开发,也有不少使用 Hybrid 技术的公司转向了 React Native 。要说 React Native 最能吸引开发者的地方那就是其拥有前端的开发速度以及原生的体验。

序言

今天要跟大家探讨的是 React Native 的拆包及热更新方案,官方并没有很好的支持这一企业十分看中的热更新能力,因此也催生了第三方的热更新方案,如 CodePushreact-native-pushy 。由于公司内部有不同的业务线,所以在采用第三方的热更新方案灵活度不够,在调研的初期,我们参考了携程的提到的 jsbundle 拆分和加载优化方案,但这个方案需要改变 React Native 的打包代码及 Runtime 代码,实施难度上非常大,暂无精力深入研究,但这个方案对加载速度提升也是显而易见的。我们暂时放弃了携程的方案,我们前期需要一套相对简单稳定且可行度高的方案,在经过调研及讨论后定下了这样一套热更方案,今天我们就来聊聊这个方案。

Read more »

作者:Oz

版权声明:本文图文为博主原创,转载请注明出处。

在 2016 年移动端跨平台开发是几个最热的技术之一,相信在 2017 年这股热潮将持续发酵。为什么这么说呢,因为随着业务的爆发式增长,传统的原生开发模式有点显得跟不上节奏了,这也促使各个公司希望寻找到一个更加高效的开发方案,当下可以被选择的方案中,React NativeWeex 都是不错的技术方案。在年前团队内部的一场 React Native vs Weex 的技术对垒中本来我选择的是 Weex 的阵营,但当时在多维的技术指标中新生的 Weex 还是不敌 React Native ,团队内部最终敲定了采用 React Native 跨平台方案。

概述

闲话不多说,这里的主要目的是跟大家聊聊 React NativeAndroid 平台使用原生自定义 View ,这里默认大家对 React Native 已经有一定的了解,React Native 中的组件都是基于 iOS/Android 的官方组件进行封装,所以在一些特别的场景下并不能很好的满足需求。正如标题中的下拉刷新组件,React NativeAndroid 平台采用的是 android.support.v4.widget.SwipeRefreshLayout ,一些 iOS 设计优先的团队(譬如我司)而言对于 Android 开发人员简直就是灾难。在众多开源的 React Native 项目中大家也不会再这些细节上较真,但是公司的 UED 这关可不好过。

Read more »

作者:Oz

版权声明:本文图文为博主原创,转载请注明出处。

这是一篇 KPI 考核背景下产出的文章,这一切都起源于我司要求提升 App 推送送达率,以节省在短信推广上花费的开销。这里记录了在整个技术调研的关键点。

概述

iOSAndroid 均在系统级集成了推送服务,来说说原生 Android 的推送服务,最在 Android 2.2 时,C2DM 作为系统级服务集成进了 Android 系统,而 GCM(Google Clould Messaging) 在 2013 Google IO 大会发布后就正式取代了 C2DM ,然后 Google 并没有止步,在 2014 年收购了 Firebase ,经过近两年的整合,在 2016 年 Google IO 大会上隆重发布了 Firebase 服务,一个全新的移动和 Web 开发的完整后端解决方案,其中就包括了FCM(Firebase Cloud Messaging)。如果就这么简单,我们就可以在 Android 平台上像 iOS 平台一样使用系统级共享的推送服务了,然而一股神秘的东方力量打破了原本简单的事情…

Read more »