文章中没有奇淫技巧,都是一些在实践开发中常用,但很简略被咱们疏忽的一些常见问题,源于平常的总结,这篇文章首要对这些常见问题进行剖析。

之前共享过一篇文章 为数不多的人知道的 Ko; 3 8 C { ( @tlin 技巧以及 原理解析 首要剖析了一些让人傻傻分不清楚的操作符的原理。

这篇文章首要剖析一些常见问题的处理方案,假如运用不当会对 功用内存 造成的那些影响以及怎么躲避这些问题,文章中涉及的事例来自 Kotlin 官方、Stackoverflow、Medium 等等网站,都是平常! = I J j 2 Q v看到,然后进行汇总和剖析。

经过这篇文章你将学习到以下内容:

  • 运用 toLowerCasetoUpperCase 等等办法会造成那些影响l – & B 2 h P
  • 怎么高雅的处理空字符串?
  • 为什么解构声明和数据类不能在一同运用?
  • Kotlin 供给的高效的f y 6 Y c q {文件处理办法,以及原理解析?
  • SequenL m &ceIterator 有那些不同之处?S : 3 B / @ O A
  • 快捷的 joinToString 办法的运用?
  • 怎么用一行代码完成移除字符串的前缀和后缀?

尽量少运用 toLowerCase 和 toUpperCase 办法

当咱们比较两个字符串,需求疏$ L T C B h 4 d忽大小写的时分,通常的写法是调用 toH I v # a r ( xLowerCase() 办法或许 toUpperCase() 办法转换成大写或许小写,然后在进行比较, ] 5 : @ y ]可是这样的话有一个欠好的当地,每次调用 toLowerCase() 办法或许 toUpperCase() 办法会创立一个新的字符串,然后在进行比较。

调用 toLowerCase() 办法

fun main(args2 I - { [ q j: Array<String>) {
//    use toLowerCase()
val oldName = "Hi dHL"
vaT p W b H 4 y Ul newName = R (  F 3 a @"hi Dhl"
val result = oldName.toLowerCase() == newName.toLowerCase()
//    or use toUpperCase()
//    val result = oldName.toUpperCase() == newNam7 ; K I q @ ? ce.toUpperCase()
}

toLowerCase() 编译之后的 Java 代码

为数不多的人知道的 Kotlin 技巧以及 原理解析(二)

如上图所示首要会生成一个新的字符串,然后A T 4 +在进行字符串比较,那么 toUpperW : . ECase() 办法也是相同的如下图所示。

toUpperCase() 编译之后的 Java 代码

为数不多的人知道的 Kotlin 技巧以及 原理解析(二)

这儿有一个更好的处理方案,运用 equals 办法来比较两个字符串,添加可选参数 ignoreCase 来疏忽大小写,这样就不需求分配任何新的字符串来进行比较了。

fun main(B | Nargs: Array<String>) {
val oldNaf * c 8 ( s N ^ yme = "hi DHL"
val newName = "hi dhl"S Y W s & 1 Y 6 $
val result = oldName.equals(newName, igno. / ! p 5reCase = trP { 1 # K S H mue)
}

equals 编译之后的 Java 代码

为数不多的人知道的 Kotlin 技巧以及 原理解析(二)

运用 e$ T ; g ; U H fqu@ t k ) ials 办法并没有r q 7 3 ^ ` q创立额定的目标,假如遇到需求G $ 比较字符串的时分,能够运用这种办法,削减额定的目标创立。g 5 * d C 0

怎么高雅的处理空字符串

当字符串为空字符串的% _ 9 # V o : &时分,回来一个默许值,[ b % ?常见的写法如下所示:

val target = ""X v T w
v_ z o 1 6 Mal nw P O * M tame = if (target.isEmpty()) "dhl" else target

其实有一个更简洁的办法,可读性更强,运用 ifEmpty 办法,当字符串为空字符串时,回来一个默许值,如下所示。

val name = target.ifEmptyJ B @ { "dhl" }

其原理跟咱们运用 if 表达式是相同的,来剖析一下源码。

public inline fun <h j o J K qC, R> C.ifEmpe x  M Gty(defaultValue:g v r z () -> R): R where C : CharSequence, C : R =
if (isEmpty()) defaultValue() else this

ifEmpty 办法是一个扩展办法,接受一个 lambda 表达式 defaultValue ,假如是空字符串,回来 defaultValue,不然不为空,回来调用者自身x @ ! o g )

除了 ifEmpty 办法,Kotlin 库中还封装许多其他十分有用的字符串,例如:将字符串转为数字。常见的写法如下所示:

val input = "123"
val number = inpu` O - b { - at.toInt()

其实这种写法存在必定问题,假定输入字符串并不是纯数字,例如 123ddd 等等,调用 input.toInt() 就会报错,那么有没有更好的写法呢?如下所示。

val input = "123"
//    val input = "123ddd"
//    vaU W z t = & R D Ll input =I d I R "R S u @ N N q D $"
val number = iJ @ 8 0 - D u #nput.toI4 B AntOrNull(u w F ! , @) ? S & f G :: 0

防止将解构声明和数据类一同运用

这是 Kotlin 团队一个主张:防止将解构声明和数据类一同运用,假如以后往数据类添加新的特点,很简略损坏代码的结构。咱们一同来( R思考一下,为什么 Kotl= H } 1 ! tin 官方会这么说,我先来看一f Y { 1 | g个比2 ) s –如:数据类和解构声明的运6 z z , c E M ~ s用。

// 数据类
data class People(
val name: String,
val city: StrG 3 F T Eing
)
fun mai! g Q w 9 ] q X On(args:! ( s $ } G X n l Array<String>) {
// 编译测验
printlnPeople(People("dhl", "beijing"))
}
fun printlnPeople(people: People) {
// 解构声a U V  y 2明,k Z X获取 name 和 city 并将其输出
val (name, city) = people
println("name: ${name}")
printlI f gn("city:j K k 1 B | * x ${city}" ) 9 | t L)
}

输出成果如下所示:

namei a 6: dhl
city: beijing

随着需求的变更,需求给数据类 People 添加一个新的特点 age。

// 数据类,添加了 age
data class Pe- O n I 2 e % ) _ople(
val name: String,
val age: Int,
val city: String
)
fun main(args: Array<String>) {
// 编译测验
printlnPeople(Peo. v Y dple("dhl", 80, $ 9 } - R"bN 7 O A I *eijing"))
}

此刻没有更改解构声明,也不会有任何错误,编译输出成果如下所示:

name:/ p c y dhl
city: 80

得到的成果并不是咱2 4 D x O | $们希R % ( Z H L r望的,此刻咱们不得不更改解构& E k & {声明的当地,假如代码中有多处用到V P y e W了解构声明,因为添加了新的特点,就要去更改一切运用解构声明的当地,这0 X p o {明显是不合理的,P I V c很简略损坏代码的结构,所以必定要防止Z p &将解构声明和数据类一同运用。当咱们运用不规范2 A 4 s – 3 Q f的时分,而且编译器也会给出警告,如下图所示。

为数不多的人知道的 Kotlin 技巧以及 原理解析(二)

文件的扩展办法

Kotlin 供给了许多文件扩展办法 Extensionz Y o J U # is for java.io.Reade+ E 0 v z Z h C nforEachLinereadLin9 Y I [ t ) 4 S 6esreadTextuseLin+ = B a Y ?esv C L 等等办法r k ` . T 4 L,协助咱们简化文件的操作,而且运用完8 } E c L q J成之后,它们会主动关闭,例如 useLines 办法:

File("dhlz @ 0 D.txt").useLines { line ->
println(line)b , & Z + & y K x
}

useLines 是 File 的扩展办法,调用 useLines 会回来一个文件中一切行的 Sequence,当文件内容读取完毕之后,它会主动关闭,其源码如下。

public inline fun <T> File.useLines(charse~ p $ o ?  vt: Charset = Charsets.Ur t rTF_8, block: (SequenceC o B O<String>) -> T): T =
bufferedReader(charset).N  ! j D r j 2use { block(it.lineSequence()) }
  • useLines 是 File 的一个扩展办法
  • useLin! 0 Z 3 Tes 接受一个 lambda 表达式 block
  • 调用了 BufferedReader 读取文件内容,之; + P P r c q后调用 block 回来文件中一切行的 Sequence 给调用者

那它是怎么在读取完毕主动关闭的呢,中心在 use 办法里边,在 useLines 办法内部0 ; – ? * p V S r调用了( 1 ? M use 办法,use 办法也是一个扩展办法,源码如下所示。

public inline fun <T : Clos& D X v 6eable?, R> T.use(block: (T) -> R): R {
var ex6  : I i & tcep u _ Z (tion: Throwable? = null
try {
return block(this)
} catch (e:D C _ j 4 . Throwable) {
exception = e
throw e
} finally {
when {
apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exceptJ K 3  6 sion)
this == null -> {}
exception == null -> close()
else ->
try {
clm a mose()
} catch (closeException: Throwable) {
// cause.addSuppressed(closeException) // ignored he: g ; A , q Vre
}
}
}
}

其实很简略,调用 try...catch...finally 最终在 finally 内部进行 close。其实咱们也能够依据源码完成一个通用的反常捕获办法。

inline fun <T, R> T.dowit* ` ~ v N J XhTry(block: (T) -u $ 3 # b> R) {
try {
block(this)
} c8 T a p . , 9 } watch (e: Throwable/ 9 + ` #) {
e.printStackTrace()
}
}
// 运用方法
dowithTry {
// 添加会出现反常的@ g K Y C 9代码, 例如
val result = 1 / 0
}

当然这仅仅一个十分简略的反) b 3 v常捕获办法,在实践项目中还有许多需求去处理的,比如说反常信息需不需求回来给调用者等等。

在上文中提到了调用 useLines 办法回来一个文件中一切行的 Sequence,为什么 Kolin 会回来 Sequence,而不回来 Iterator?

Sequence 和 Iterator 不同之处

为什么 Kolin 会回L i $ h ` z ~ y k来 Sequence,而不回来 Iterator?其实这个中心原因因为 Sequence 和 Iterator 完成不同导致 内存功用 有很大的差异。

接下来咱们围绕这两个方面来剖析它们的功用,Sequences(序列) 和 Iterator(迭代器) 都是一个比较大的概念,本文的目的不是去剖析它们,所以在这儿不会去详细剖析 Sequence 和 Iterator,只会围绕着 内存功用 两个方面去剖析它们的区别,让咱们有一个直观的印象。更多信息能够查看国外一位大神写的文章 Prefer Sequeh / Ance for big collections with more than one processing step。

Sequence 和 Itez m Grator 从代码结构上来看,它们十分的相似如下所示:

interface Iterable<out T> {
operator fun iterator(): Iterator<T>7 k ;
}
interface Sequence<out T> {
operator fun iterator(): Iterator<T>
}

除了代码结构之外,Sequences(序列) 和 Iterator(迭代器) 它们的完成Y ( l ` ! 9 { 5完全不相同。

Sequences(序列)

Sequences 是归于懒加载操作类型,在 Sequences 处理过程中,每一个中心操作不会进行任何核算,它们只会回来一个新的 Sl / b E ? f L Eequence,经过一系列中心操作之后,会在结尾操作 toListcount 等等办法; S 8 : 4 r T P G中进行最终的求职运算,如下图所示。

为数不多的人知道的 Kotlin 技巧以及 原理解析(二)

在 SequencesN T b 5 处理} M d C q S z过程中,会对单个元素进行一系列操作,然后在对下一个元素进行一系列操作,直到一切元素处理完毕。

val data = (1..3).asS$ V % L 8 G |equence()
.filter { print("F$it, "); it % 2 == 1 }
.map { print("M$itn _ : r 7 N  l, "); it * 2A Q } / d }
.forEach { print("E$it, ") }
println(daD _ w i Y  6 ^ta)
// 输4 ~ p出 F1, M1, E2, F2, F3, M3, E6
为数不多的人知道的 Kotlin 技巧以及 原理解析(二)

如上所示:在 Sequences 处理过程中,对 1 进行一系列操作输出 F1, M1| K n D I, E2, 然后_ = j 1对 2 进行一系列操作,顺次类推,直到一切元素处理完毕,输出成果为 F1, M1, E2, F2, F3, M3, E6

在 Sequences 处理* d v h G过程中,每一个中心操作( map、fiv e @ _ wlter 等等 )不进行任何核算,只有在结尾操作( toList、count、forEach 等等办法 ) 进行求值运算,怎么区分是中心操作仍是结尾操作,看办法的回来类型,中心操作回来的是 Sequence,结尾操作回来的是一个详细的类型( L/ g 6 `ist、int、Unit 等+ v & J { V 0 ~ }等 )源 } ~ | : V r码如下所示。

// 中心操作 map ,回来的是  Sequence
public fun <T, R> Seq} { P [ { quence&y h s Y 7 q  Blt;T>.map(transform: (T) -> R): SC S k ) v T m bequence<R> {
return TransformingSequence(this, transform)
}
// 结尾操作 toList 回来的是一个详细的类型(List)
public fun <T> Sequence<T>o 1 y x J x N R D.toList()@ m - d a y F: List<T&b [ g P d Kgt; {
return tX Q 7 Ghis.toMutableList().optimizeReadOnlyList()
}
// 结尾操作 forEachIndexed 回来的是一个详细的类型(Unit)
public inline fun <T> Sequence<T>.forEa=  ^ $chIndexed(a + Dactionc f _ n X 1 ; Z: (index: Int, T) -> Unit): Unit {
var index = 0
for (iteY X w sm in this) action(p r _checkIndexOverflow(index++), item)
}
  • 假如是中心操作 map、filte$ b z s G i zr 等等,它们回来的是一个 Sequence,不会进行任何核算
  • 假如是结尾操作 toList、count、forEachIndexed 等等,回来的是一个详细的类型( List、int、Unit 等等 ),会做求值运算

Iterator(迭代器)

在 Iterator 处理过程中,每一次的操作都是对整个数据进行操$ g A V作,需求拓荒新的内存来存储中心成果,将成果传递给下一个操作,代码如下所示:

vaj r 7 . H V e 1l daE # { 3 yta = (1..3).asIterable()
.filter { print("F$it, "); it % 2 == 1 }
.map { print("M$it, "); it * 2 }
.forEach { print("E$it, ") }
println(data)
// 输出 F1, F2, F3, M1, M3, E2, E6
为数不多的人知道的 Kotlin 技巧以及 原理解析(二)

如上所示:在 Ite( k M D z S 6rator 处理7 D V V s过程中,调用 filN ( C N d = K k :tei ] _r 办法对整个数据进行操作输出 F1, F2, F3,将成果存储到 List 中, 然后将成果传递给下一个操作 ( map ) 输出 M1, M3 将新的成果在存储的 Listv ^ . ^ M X 中, 直到一切操作处理完毕。

// 每次操作都会拓荒一块新的空间,存储核算的成果
public$ j O  inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
// 每次操作都会拓荒一块新的空间,存储核算的成果
public inline fun <* { V K 0 ]T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
return mapTo(ArrayList<R>(collectionSizeO2 v : b A - 9 irDefault(10)), transform)
}

关于每次操作都会拓] L w 2 g o z荒一块新的空间,存储核算的成果,这是对内存极大的糟蹋,咱们往往只关怀最终的成果,而不是中心的过程。

了解完 Sequences 和 Iterator 不同之处,接下里咱们从 功用内存 两个方面来剖析 Sequenx M A Nces 和 Iterator。

S& ! a /equences 和 Iterator 功用比照

别离运用 Sequences 和 Iterator 调用它们各自的 filter、map 办法,处理相同的数据的情况下,比较它们的执行时刻。

运用 Sequences :

val time = measureTimeMillis {
(1..10000000 * 10).asSequence()
.filter { it % 2 == 1 }
.map { it * 2 }
.cX U # r Qount()
}
println(time) // 1197

运用 It/ M W S M Lerator :

val time2 = measureTimeMillis {
(1..10000000 * 10).asIterable()
.filter { it % 2 == 1 }
.map { it * 2 }
.count()
}
println(time2) // 23641

Sequences 和 Iterator 处理时刻如下所示:

Sequences Iterator
1197 23641

这个成果是c 5 很让人吃惊的,Sequences 比3 2 5 K * P D Iterator 快 19 倍,假如数据量越大,它们的时刻距离会` d Q V ( S u n 7越来越大,当咱$ # _ j们在读取文件的时分,可能会进行一系列的数据操作 dropfilter 等等,所以 Kotlin 库函数 useLines 等等办法会回来 Seqj t * s { % T Yuences,因为它们愈加的高效。

Sequences 和 Iterator 内存比照

这儿运用了 Prefer Sequence for big collectih p 8 F o E Lons with more than one processing step 文章的一个比如。

有 1.53 GB 犯罪分子的数据存储在文件中,从文件中找出有多少犯罪分子携带大麻,别离运用 Sequences 和 Iterator,咱们先来看一下假如运用 Iterator 处理会怎么样(这儿调用 readLines 函回来 List<String>

File("ChicagoCrimes.csv").readLines()
.drop(1) // Drop descriptions of1 t i S 3 - P the columns
.mapNotNull { it.split(",").getOrNull(6) }
// Find description
.filter { "CANNABIS" in it }
.count(a 1 ? w)
.let(::println)

运转完之后,你将会得到一个意想不到的成果 Oo u 6 k ~ * R = cutOfMemo1 L ^ k $ryError

Excepe , 9 x V Gtion in thread "main" java.lang.OutOfMemoryError: Java heap space

调用 readLines 函回来一个集合,有 3 个中心操作,每一个中心操作都需求一块空间存储 1.53 GB 的数据,它们需求占用超过 4.59 GB 的空间,每次操作都拓荒了一块新的空间,这是对内存巨大糟蹋。假如咱们运用序[ ! ; 2 l 2列 Se; $ 2 O J G X quences 会怎么样呢?(调用 useLines 办法回来的是一个 Sequences)。

File(J D g"ChicagoCrim% = ~ 6 j 0es.csv"@ + ` /).useLines { l! i b   T 7inesg ^ c e 4 ->
// The typt O A te of `lines` is Sequence<String>
lines
.drop(1) // Drop descriptions of the} l t columns
.m2 F z f | ( !apNotNull { it.split(",").getOrNull(6) }
// Find description
.filter { "CANNABIS" in it }
.count()
.let { println(it) } // 318185

没有出现 OutOfMemoryError 反常,共耗时 8.3 s,由此可见关于文件操作运用序列不只能提高功用,还能削减内存的运用,从功用和内存这两面也解6 Y M X释了为什么 Kotlin 库的扩展办法 useLines 等等,读取文件的时分运用 Sequences 而不运用 Iterator。

快捷的 joinToString 办法的运用

joinToString 办法供给了一组丰厚的可选择项( 分隔符,前缀,后缀,数量限制等等 )可用于将可迭代目标转换为字符串。

val data = list! B n J q 0 5 COf("Java", "Kotlin", "C++", "Python")
.joinToStri5 O h  r 8 T B Wng(
separator = " | ",
prefix = "{",
postfix = "}"
) {
it.toUpperCase()
}
println(data) // {JAb L 3 Z [ .VA | KOTLIN | C++ | PYTHON}

这是很常见的用法,将集合转换成字符串,高效利用快捷的joinToString 办法,J ; v w W E开发的时分事半功倍,既然能够添加前缀,后缀,那么能够移除9 M W它们吗? 能够的,Kotlin 库函数供给了一些办法,协助咱们完成,如下` m :代码所示。

var data = "**hi dhl**"
// 移除前缀
println(data.removePrefix("**"))w x Q w . u U //  hi dhl**
// 移除后缀
println(data.removy / n J p keSuffix("**")) //  **hi dhl
// 移除前缀和后缀
println(data.removeSurrounding(h J ~ { o M"**")) // hi dhl
// 回来第一次出现分隔符后的字符串
prin[ ~ X 9 # | Stln(data.su: 7 M C + 8bstringAfter("**")) // hi dhl**
// 假如没有找1 } v i & W到,回来原始字符串
println(data.substrin Y ? Z % : I c LgAfter("--")) // **hi dhl**
// 假如没有找到,` 1 k ]回来默许字符串 "no mK c H } s g m Watch"3 7 a b r e d g
println(data.substringAfter("--","no match")2 V 0 7 / a m 4) //0 ; t M ( H N no match
data = "{JAVA | KOTLIN | C++ | PYTHON}"
// 移除前缀和后缀
println(data.removeSurrounding("{", "}")) // JAVA | KOTLIN | C++ | PYTHON

有了这些 Kotlin 库函数,咱们就不需求在做 startsWith()endsWith() 的查看了,假如让咱们自己来完成上面的功用,咱们需求花多少行代码去完成呢,一同来看一下 Kotlin 源码是怎么完成的,上面的操作符最终都会调用以下代码,进行字符串的查看和截取。

public String substring(in) c &t beginIndex, int endIndex) {
if (begB E f U X D _ zinIndex <T F J _ 0) {
throw new StringIndexOutOfBoundsException(begiY 5 xnIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsExcepti@ j P D pon(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfB8 P FoundsException(subLen);
}
return ((beginIndex == 0) &&@ n T $ ; / yamp;_ E 6 % I L (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}

参阅源码的完成,假如以后遇到相似的z ~ C b I ` m需求,可是 Kotlin 库函数有无法满足咱们,咱们能够以源码为基础进行扩展g D [ ~ 3 ^ n # 9

全文到这儿就完毕了,Kotlin 的强大不止于此,后边还会U g y共享更多的技巧,在 Kotlin 的道路上还有许多实用的Q a [ = Z y技巧等着咱们一同来探究。

正在建o ` x L [立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理剖析文章,1 r & Z ~ – 8 t现在已经包含了 App Start/ T @ I q hup、Paging3、Hilt 等等,正在逐渐添加其他 Jetpack 新成员,库房持续更新,能够前去查看:AndroidX-Jetpack-Practice, 假如这个库房对你有协助,请库房右上角帮我点个赞。

结语

致力于共享一系列 Android 体系源码、逆向剖析、算法、翻译、Jetpack 源码相关的文章s R 5 o | ,,正在努力写出更好的文章,假如这篇文章对你有协助M ^ V % D X给个 star,文章中有什么没有写理解的当地,或许有什么更好的主3 o N ? R N张欢迎留言,t k H { ( 2 R ( V一同来学习,等待K R M X J N与你一同生长。

算法

因为 LeetCode 的题库巨大,每个分类都能筛选出数百道题,因为每个人的精力有限,不可能刷完一切标题,因而我按照经典类型标题去分类、和标题的难易程度去排序。

  • 数据结构: 数组、栈、行列S d { 7 i P o、字符串、链表% x 6 w % (、树……
  • 算法: 查找算法、搜索算法、位运算、排序、数学、……

每道G ~ ] m 9 u 3标题都会用 Java 和 kotlin 去完成,而且每道标题都有解题思路、时刻复杂度和空间复杂度,假如你同我相同喜欢算法、LeetCode,能够重? ~ , N Z视我 GitHub 上的 LeetCode 题解:Leetcode-Solutions-with-Java-AndS $ H r A-Kotlin,一同来学习,等待与你一同生长。

An0 B l R G } Edroid 10 源码系列

正在写一系列的 Android 10 源码剖析的文章,了解体系源码,不只有助0 U A s e 于剖析问题,在面试过程中,对咱们也是十分有协助的,假如你同我相同喜欢研讨 Android 源码,能够重视我 GitHub 上的 Android10-Source-Analysis,文章都会同步到这个库房。

  • 0xA01 Android 10 源码剖析:APK 是怎么生成的
  • 0xA02 Android 1C o i N }0 源码剖析:APK 的装置流程
  • 0xA03 Android 10 源码剖析:APK 加载流程之资源加载
  • 0xA04 Android 10 源码剖析:APK 加载流程之资源加载(二)
  • 0xA05 Android 10 源码剖析:Dialog 加载绘制流程以及在a O ^ KotliZ M u = % ~ u s fn、DataBinding 中的运用
  • 0xA06 Android 10 源码剖析:Windj y m g B U dowManager 视图绑定以及体系结构
  • 0xA07 Android 10 源码剖析:Window 的类型 以及 三v a + y Z *维视^ 0 ) :图层级剖析
  • 更多……

Android 使用系列

  • 怎么在项目中封装 Kg 6 gotlin + Android Databinding
  • 再见吧 buildSrc, 拥抱 Composing builds 提高 Android 编译速度
  • 为数不多的人N d J知道的 Kotlin 技巧以D U J )及 原理解析
  • JetpacW f n y H zk 最新成员 AndroidX App Sta[ i M ~ G ( Wrtup 实践以及原理剖析
  • Jetpack 成员 Paging3 实践以及源码剖析(一)
  • Jetpack 新成员 PaX Z ` $ a } 0 * |ging3 网络实践及原理剖析(二)
  • Jetpack 新成员 Hilt 实践(一)启程过坑[ K E A E
  • Jetpack 新成员 Hilt 实践之 App Startup(二)进阶篇
  • Jetpack 新成员? 3 w R x Hilt 与 Dagger 大不同(三)落地篇
  • 全方面剖析 Hilt 和 Koin 功用

? ; Z ~ % t选译文

现在正在收拾和翻译一系列精选国外的技术文x W 6章,不只仅是翻译,许多优秀的英文技术文章J 3 3 V u ~供给了很好思路和办法,每篇文章都会有译者思考部分,对原文的愈加深化的解读,能够重视我 GitHub 上的 Technical-Art6 A qicle-Translation,文章都会V A * m q J : c同步到这个库房。

  • [译][Gob N e J _ $ h C Gogle工程师]4 l w . J l ( 9 刚刚发V m X E ] ! o !布了 Fragment 的新特性 “Fragment 间传递数据的新方法” 以及源码剖析
  • [译][Googt [ f X | J 3 u Zle工程5 U x W A [ j j M师] 详解 FragmentFactory 怎么高雅运用 Koin 以及部分源码剖析
  • [译][2.4K Start] 抛弃 DaggeS A cr 拥抱 Koin
  • [译][5k+] Ko% h d P N @ {tlin 的功用优化那些事
  • [译] 解– D V @ d % H密 RxJava 的反常处理机制
  • [译][1.4K+ Star] Kotlin 新秀 Coil VS Glide and Picasso
  • 更多……

东西系列

  • 为数不多的人知道的 AndroidStudio 快捷键(一)
  • 为数不多的人知道的 AndroidStudio 快捷键(二)
  • 关于 ag $ & P O F q 2 ?db 命令你所需求知道的
  • 10分钟入门 Shell 脚本编程
  • 根据 Smali 文件 An. p ! Gdroid Studio 动态调试 APP
  • 处理在 Android Studio 3.2 找不到 Android Device MonitA l Gor 东西