ARTS1
A
调整数组顺序使奇数位于偶数前面
双指针解法。头尾各一个指针,头指针遇到的偶数和尾指针遇到的奇数,两个数字的位置互换。
题解思路比较简单,但是判断奇数和偶数可以使用与 1 按位与 结果是否为 1 来判断,作为计算的一种优化。这个做题时没想到。然后搜了一下运算优化的文章(像 【算法】位运算与优化这种 ),看了下其他的运算优化方式。
R
Kotlin Clean Architecture,又是一个代码架构方案,能有多好用,只能试试才能知道。
T
发现 LeakCannery 发现内存泄漏后,给引用链的时候,给不出 JNI 层导致的内存泄漏的引用链,但是它能发现 JNI 导致的泄漏。
S
最近股市挺热,分享个文章,大家还是要理性一点。https://mp.weixin.qq.com/s/OhuNId4NoaiD-kZpzVJ1Mw
ARTS2(补一周)
A
206. 反转链表 也是双指针,循环一遍就 ok。
21. 合并两个有序链表。简单题,思路也很简单,主要的时间是把代码写完善和优雅一点,
R
看了两个周的 sentry 使用文档,就是个用户手册,没啥好讲的。但是有一个东西比较有意思,关于 ELF 格式的 debug info files 说明,有些干货在里面。
T
- 一直苦于 Linux 命令行光标移动和内容删改效率低,从大佬那里学习了一下,还是有很多快捷键的。类似这种 Linux 命令行快捷键,之前居然没有留意过。
- 使用文档协作工具还是要留意是否支持 markdown 语法(现在想想一般应该都会支持)。傻乎乎的用了石墨好久,才发现它可以用 markdown 语法直接写内容。
- 接上回说 leakcannery 的泄露引用链少了 native 的部分。但是用 haha 库试了一下,发现它其实能够找到 JNI 层的 GC root,leakannery 其实就是做了个所有根节点到泄露 activity reference 的广度优先遍历,给出引用链最短的一个链路,为什么 leakcannery 没有 native 的 refe,还没细看。
后来使用最新版本的 leakcannery 试了下,它能给出是 globle reference 引用了 activity,但 globle reference 的名称信息没有。
S
空白,最近没看到想分享的东西。
ARTS3
A
二叉搜索树的后序遍历序列。
难度中等。判断一个数组是否是二叉搜索树的后序遍历序列。需要先找到二叉搜索树后序遍历结果的特点:按最右侧的值可以把数组划分成两部分,前一部分数字都小于最右侧值,后一部分数字都大于最右侧的值。划分出的两部分依然符合这个特点。然后是码代码实现递归判断数组是否符合上述规律。代码写的没有答案简洁。
R
7 Quick Kotlin Tips for Android Developers
kotlin 相对 java 的简单写法。有两个之前没用过的 tips。
交换两个数的值:
a = b.also { b = a }
check 条件 返回 IllegalArgumentException 异常
require(n>=0){"error message"}
list map
val nameList = persionList.map{persion -> persion,name}
=> val nameList = persionList.map(Persion::name)
T
consumerProguard,aar 提供自己的混淆规则,集成进项目打包的时候会对整个项目应用这个规则。
S
根据 Probe:Android线上OOM问题定位组件、matrix、haha 来学习内存 dump 、剪裁与分析。
从代码结合文章,看内存 hprof 的格式:
// package com.tencent.matrix.resource.hproflib.HprofReader
public void accept(HprofVisitor hv) throws IOException {
acceptHeader(hv);
acceptRecord(hv);
hv.visitEnd();
}
private void acceptHeader(HprofVisitor hv) throws IOException {
final String text = IOUtil.readNullTerminatedString(mStreamIn);
final int idSize = IOUtil.readBEInt(mStreamIn);
if (idSize <= 0 || idSize >= (Integer.MAX_VALUE >> 1)) {
throw new IOException("bad idSize: " + idSize);
}
final long timestamp = IOUtil.readBELong(mStreamIn);
mIdSize = idSize;
hv.visitHeader(text, idSize, timestamp);
}
private void acceptRecord(HprofVisitor hv) throws IOException {
try {
while (true) {
final int tag = mStreamIn.read();
final int timestamp = IOUtil.readBEInt(mStreamIn);
final long length = IOUtil.readBEInt(mStreamIn) & 0x00000000FFFFFFFFL;
switch (tag) {
case HprofConstants.RECORD_TAG_STRING:
acceptStringRecord(timestamp, length, hv);
break;
case HprofConstants.RECORD_TAG_LOAD_CLASS:
acceptLoadClassRecord(timestamp, length, hv);
break;
case HprofConstants.RECORD_TAG_STACK_FRAME:
acceptStackFrameRecord(timestamp, length, hv);
break;
case HprofConstants.RECORD_TAG_STACK_TRACE:
acceptStackTraceRecord(timestamp, length, hv);
break;
case HprofConstants.RECORD_TAG_HEAP_DUMP:
case HprofConstants.RECORD_TAG_HEAP_DUMP_SEGMENT:
acceptHeapDumpRecord(tag, timestamp, length, hv);
break;
case HprofConstants.RECORD_TAG_ALLOC_SITES:
case HprofConstants.RECORD_TAG_HEAP_SUMMARY:
case HprofConstants.RECORD_TAG_START_THREAD:
case HprofConstants.RECORD_TAG_END_THREAD:
case HprofConstants.RECORD_TAG_HEAP_DUMP_END:
case HprofConstants.RECORD_TAG_CPU_SAMPLES:
case HprofConstants.RECORD_TAG_CONTROL_SETTINGS:
case HprofConstants.RECORD_TAG_UNLOAD_CLASS:
case HprofConstants.RECORD_TAG_UNKNOWN:
default:
acceptUnconcernedRecord(tag, timestamp, length, hv);
break;
}
}
} catch (EOFException ignored) {
// Ignored.
}
}
代码来自 matrix 一个名为 HprofReader 的类。是一个典型的访问者模式,在访问和处理比较复杂数据结构的存储文件,经常会见到。类用来解析 hprof 文件,解析出来的内容,交给传进来的 HprofVisitor 进行操作。
可以看到它先访问了文件的头信息。取的内容很简单:一个 String(这里没有这个String 的长度信息,但是这个 String 以 0 结尾)、一个 int 值 idSize、一个时间戳。
然后就是内容信息,每部分内容信息的开头是一个 tag 用来标识内容的类型,后面跟着的是时间戳和这部分内容的长度。根据不同的内容,进行不同的解析。
具体内容类型的解析就不说了,看这个类的代码就能明白了。
HAHA 库(2.0.3 版本)的解析类 HprofParser 与上述过程类似。
内存剪裁、内存分析:留坑。需要考虑内存dump剪裁和分析的内存占用、时间消耗怎么优化。如果线上要 dump 内存上报,要剪去什么内容。剪掉内容后,内存占用大小、引用关系的信息是否还会保留,用于线上分析。看美团的操作更骚气一点,hook 了 dump 过程,生成过程中过滤掉之前想剪裁的内容。
ARTS4
A
剑指 Offer 36. 二叉搜索树与双向链表
只能说,好题。把二叉搜索树转换成排序的双向链表,原地转,不要申请额外的空间。
二叉搜索树的中序遍历就是从小到大排序的。做中序遍历,并且让当前遍历到的节点,left 指向 pre 节点,pre 节点的 right 指向当前节点,pre 指针移动到当前节点。这样操作会影响之后的遍历吗?答案是不会,只会改变已经遍历到的节点。
R
Handling Android runtime permissions in UI tests
这周在看测试,在做集成测试的时候,需求需要将单测跑在运行环境里面,并且能操作权限。上文是查找过程中看的文章。是在 ui 测试中怎么点掉权限申请弹窗。
AndroidTest 在 beta 版中已经提供了 PermissionRequester api 做这件事情,申请权限不再有弹窗。
T
做单测时有在真机上跑的需求时,可以用 AndroidTest。
S
本周没有内容分享。但分享一个还未处理好的问题:单测中的代码重复该如何对待?
前提:一个大佬的描述:写测试用例的目标是,即使没有技术背景也应该可以明白在做什么,沟通在测试中是第一位的。
问题:一个大佬的描述:当只有一个case的时候都很好,但是当有多个case时,就会出现很多重复的内容。
网上搜的问题:在单元测试中,重复代码是否更容易容忍
答案:不可以。重复在单测中也会导致难维护。并且如果重复还有一点不同,那么这点不同就会被忽略,让人变得难懂这些测试有什么不同。
解决:下周接着看。
ARTS5
A
剑指 Offer 38. 字符串的排列 求一个字符串里的字符能组成多少种不同的字符串,列举他们。
是回溯的典型题目。
R
无
T
IDEA 的 LiveTemplate。学习一下,有助于开发效率的提高。
速成文章链接 https://www.cnblogs.com/chenfangzhi/p/liveTemplate.html
S
这周分享一下 BDD 和 BDD 工具。
看了个文章感觉写的挺清楚的:探索 Android BDD 开发方法。
BDD 工具:Cucumber
Cucumber 的使用分两部分:
- 使用中文
- 验证什么
- 测试用例
- 与上张图里面的步骤说明一致
- 与上张图里面的步骤关键字一致。
Cucumber 规定了这两者的语法,并将这两者关联起来,转化需求和用例描述为可以执行的AndroidTest 形式的单测。执行 ./gradlew connectedCheck 就可以验证研发的业务实现了。
关于上周提到的单测代码重复和单测代码框架的问题,继续留坑。