文档翻译-Kotlin协程 基本用法

该文档由作者自己的理解翻译,若有出入,敬请谅解。

Kotlin协程的基本用法

这一篇我们学习协同程序的基本概念。查看原文

你的第一个协同程序

复制下面的代码到你的程序中并运行:

1
2
3
4
5
6
7
8
9
10
import kotlinx.coroutines.*

fun main() {
GlobalScope.launch { // 启动一个新的后台协同程序,并继续
delay(1000L) // 非阻塞的延时1秒(默认时间单位是毫秒)
println("World!") // 延时结束打印
}
println("Hello,") // 协同程序结束延时后,主线程继续执行
Thread.sleep(2000L) // 阻塞主线程2秒用来保持虚拟机运行中
}

查看全部代码

你将看到如下结果:

1
2
Hello,
World!

本质上,协同程序是轻量级线程。它们被启动在一些协同程序范围的上下文。现在我们启动一个新的协同程序在全局范围,这意味着这个新的协同程序的生命周期被整个应用的生命周期所限制。

替换成 GlobalScope.launch { ... }thread { ... }delay(...)Thread.sleep(...), 你将能够得到同样的结果。试试吧(别忘记引入kotlin.concurrent.thread)。

如果你用thread替换GlobalScope.launch,编译器将出现以下错误:

1
Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function

那是因为delay是一个特殊的挂起方法不会阻塞线程,但是挂起协同程序和它都只能在协同程序中使用。

桥接阻塞和非阻塞世界

第一个例子将非阻塞的delay{} 和阻塞的Thread.sleep()混合在同一段代码中。这将很容易的忘记哪个是阻塞的哪个是非阻塞的。让我们使用runBlocking协程创建者明确关于阻塞:

1
2
3
4
5
6
7
8
9
10
11
12
import kotlinx.coroutines.*

fun main() {
GlobalScope.launch { // 启动一个新的后台协同程序,并继续
delay(1000L)
println("World!")
}
println("Hello,") // 主线程将立即执行
runBlocking { // 但是这个代码块阻塞在主线程
delay(2000L) // ... 我们延时2秒保持虚拟机的运行
}
}

查看全部代码

结果是一样的,但是这次的代码仅仅使用的是非阻塞的delay。在主线程唤起 runBlocking 阻塞直到runBlocking内的协程执行完成。

这个举例也能够使用更加通用的方式来重写,使用runBlocking去包裹主方法的执行:

1
2
3
4
5
6
7
8
9
10
import kotlinx.coroutines.*

fun main() = runBlocking<Unit> { // 开始主协程
GlobalScope.launch { // 启动一个后台的协程
delay(1000L)
println("World!")
}
println("Hello,") // 主协程立即执行
delay(2000L) // 延时2秒保持虚拟机的运行
}

查看全部代码

这里的runBlocking { … }作为一个被用于开始最高级的主协程(我们可以使用runBlocking开启一个最高等级的主协程)。我们明确指定它的返回类型Unit,因为一个好的主方法结构必须要返回Unit
这也是写挂起方法的单元测试的一种方式。

1
2
3
4
5
6
class MyTest {
@Test
fun testMySuspendingFunction() = runBlocking<Unit> {
// 在这里我们可以使用我们喜欢的任何风格的挂起方法
}
}

等待任务

当其他协程正在执行时,我们等待一段时间并不是一个好的方式。让我们明确的等待直到我们开启的后台工作完成。

1
2
3
4
5
6
7
8
9
10
11
12
import kotlinx.coroutines.*

fun main() = runBlocking {
//sampleStart
val job = GlobalScope.launch { // launch a new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // wait until child coroutine completes
//sampleEnd
}

查看完整代码
现在的结果仍然是一样的,但是主协程的代码不依赖于任何后台工作的时长。更加好。

结构并发

我们仍然有一些期望更加实用的协程。当我们使用GlobalScope.launch,我们创建 一个顶级的协程。尽管这是轻量级的,它运行时仍然会消耗一些内存资源。如果我们忘了给它指向新的引用,那么它会一直运行。如果代码在协程中挂起(例如,我们错误的延时很长时间),如果我们开启了太多的协程导致超过内存限制会怎么样?不得不手动的保持所有开启的协程引用,链接他们是错误的想发(倾向)。

有一个更好的解决办法。我们可以使用并发结构的代码。为了如我们使用线程(线程都是全局的)一样的在全局启动协程,我们可以在一个我们能够控制的一个域中启动协程。

回到我们的例子,我们由main方法已经被转变成使用runBlocking创建的协程。所有的协程创建方式,包括runBlocking,在代码块中添加了一个协程域的实例。我们能够在这个域中开启一个协程并且明确的没有join到域中,因为外层协程不会执行完成知道它的域开启的所有协程都完成。所以,我也可以类似的改造我们的例子:

1
2
3
4
5
6
7
8
9
import kotlinx.coroutines.*

fun main() = runBlocking { // 当前的协程域
launch { //在runVloacking下开启了一个新的协程
delay(1000L)
println("World!")
}
println("Hello,")
}

查看所有代码

域的创建

除了不同创建者提供的协程域,你也可以使用coroutineScope创建声明自己的域。它创建的一个协程域不会完成直到所有开启的子协程完成。
runBlockingcoroutineScope可能看起来比较相似,因为他们都是等待他们内部和所有子协程完成。这两个的主要不同是,runBlocking方法阻塞了当前线程去等待,而coroutineScope仅仅是挂起,释放下面的线程给其他用。由于上述的不同,runBlocking是一个普通的方法而coroutineScope是一个挂起的方法。
下面的例子演示一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking")
}

coroutineScope { // 创建一个协程域
launch {
delay(500L)
println("Task from nested launch")
}

delay(100L)
println("Task from coroutine scope") // 这一行将在延时之前执行
}

println("Coroutine scope is over") // 这一行不会执行直到延时的代码执行完毕
}

查看所有代码
从结果注意到当延时任务执行等待时,”Task from coroutine scope”就已经执行了, “Task from runBlocking”也会执行和打印,尽管coroutineScope还没有执行完成。

提取方法重构

让我们提取代码块中的launch{}成一个独立的方法。当你把代码提取出来成一个新的方法,需要加上suspend修饰。这就是你的第一个挂起的方法。挂起方法和普通方法一样能够在协程中被使用,但是额外的特点是他们能够有序、使用其他挂起的方法,就像这个例子中的delay{},在协程中挂起执行。

1
2
3
4
5
6
7
8
9
10
11
12
import kotlinx.coroutines.*

fun main() = runBlocking {
launch { doWorld() }
println("Hello,")
}

// this is your first suspending function
suspend fun doWorld() {
delay(1000L)
println("World!")
}

查看完整代码
但是如果提取的方法中包含协程的被执行的创建者在当前的域中会怎么样?这种情况下仅仅在提取的方法上添加suspend修饰符是不够的。在CoroutineScope写一个扩展的方法是一个解决办法,但是它可能不总是合适的由于它没有清理的API。管用的方法是在一个包含目标方法的类中显式的有一个CoroutineScope域或者外部实现CoroutineScope的类有隐式的域。作为最后的手段,使用CoroutineScope(coroutineContext),但是这种方法在结构上是不安全的,因为你在这个域中没有了执行方法的控制权。仅私有API能够使用这个创建者。

协程是轻量级

执行下面的代码:

1
2
3
4
5
6
7
8
9
10
import kotlinx.coroutines.*

fun main() = runBlocking {
repeat(100_000) { // 开启很多协程
launch {
delay(1000L)
print(".")
}
}
}

查看完整代码
开启十万个协程,一秒后分别打印一个点。然后尝试使用线程做这件事。会发生什么?(很有可能会产生内存不足的错误)。

全局的协程就像是后台守护线程

下面的代码开启了一个长时间运行的协程在全局域,它每一秒打印I'm sleeping然后等待一些时间后从主方法中退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
import kotlinx.coroutines.*

fun main() = runBlocking {
//sampleStart
GlobalScope.launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // 延时后退出
//sampleEnd
}

查看完整代码
你运行后可以看到以下三行打印然后结束:

1
2
3
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...

我们在全局域启动的活跃协程不会保持进程的存活。他们就像是守护线程。

分享到

文档翻译-Kotlin协程 简介

该文档由作者自己的理解翻译,若有出入,敬请谅解。

Kotlin协程简介

Kotlin作为一种语言,仅仅需要提供最小等级API的标准库用于其他不同库去使用的协同程序。类似的功能(协程)不同于其他的一些语言,async 和 await不是Kotlin的关键字,也不是标准库的一部分。而且,Kotlin的挂起方法的概念是一种对于异步的操作比期望的更加安全和发生更少的错误。

Kotlin的协同程序是由JetBrains为协同程序而开发的功能丰富的库。它包括一些高水平可协同的操作符,例如,launch,async 等等。

本文档主要包含核心工功能的一系列kotlin协程的举例,被分成若干个部分进行阐述。

为了更好的使用本文档中协程的举例,你需要先添加kotlin协程核心库依赖,可参考项目中的README。

内容列表

  • 基本用法
  • 可取消和超时
  • 多个挂起的协程
  • 协程的上下文和调度
  • 异步流(发起很多异步的操作)
  • 通道
  • 异常的处理和管理
  • 共享可变的状态和并发操作
  • 选择表示(测试中)
分享到

iphone6的切图如何适配Android?

Android 屏幕适配是Android工程师最最头疼的一件事情了。最近公司的项目中就需要引入屏幕适配的工作,但是,UI及尺寸是基于iPhone6的,那么如何做到显示效果上能够更加的接近设计图呢?

下面我将在最小限度的不更改源代码的情况下,使用屏幕分辨率限定符的形式进行Android屏幕的适配方式的介绍

一、 将iPhone6的尺寸与Android sw360dp 进行换算

通常我们需要UI设计基于1920*1080的设计稿,宽度为360dp,将切图的三倍图放置xxhdpi,如果标注是px,那么我们自动除以3作为dp,如果直接是dp那我们直接拿来使用。

手机 屏幕分辨率 宽度DP(heightPixels/density)
iPhone6 1334*750 375pt
小米6 1920*1080 360dp
华为荣耀8X 2045*1080 360dp
锤子坚果Pro2 2070*1080 432dp

假设:UI设计师以iPhone6为基准,标注中有一个为375pt宽的控件,实际像素为750px,如果我们直接用375dp拿到我们Android设备上,则 小米6、华为荣耀8X这两款手机会超出屏幕,而锤子坚果Pro2会小于屏幕,因此如果直接拿到标注进行设置,那么在锤子坚果Pro2手机上所有的控件都会显得比较小,而小米6略显一点大。
实验表明:小米6的控件比设计图大 1-375/360=0.041667,而坚果Pro2比设计图小1-375/432 = 0.131944。
因此我们换算iphone6与我们基准的360dp进行换算,得出更加近似的dp值:

手机 标注(换算) 换算公式
iPhone6 1pt 375/375=1
小米6 0.96dp 360/375=0.96
华为荣耀8X 0.96dp 360/375=0.96
锤子坚果Pro2 1.152dp 432/375=1.152

一、我们先使用Python对1~600常用的数值进行生成

1
2
3
4
5
6
7
8
from xml.etree import ElementTree as ET
if __name__ == '__main__':
resources = ET.Element("resources")
for i in range(1, 601):
dimen = ET.SubElement(resources, "dimen")
dimen.attrib = {"name": "pt_" + str(i)}
dimen.text = str(round(i * 0.96, 4)) + "dp"
ET.ElementTree(resources).write("dimens.xml")

dimens.xml

1
2
3
4
5
6
7
8
<resources>
<dimen name="pt_1">0.96dp</dimen>
<dimen name="pt_2">1.92dp</dimen>
<dimen name="pt_3">2.88dp</dimen>
<dimen name="pt_4">3.84dp</dimen>
<dimen name="pt_5">4.8dp</dimen>
....
</resources>

二、将dimens.xml或者里面的dimens复制到Android工程values下

我们可以在此目录下编写我们其他自定义的一些dimens,例如:

1
2
3
4
5
6
7
8
<resources>
....
<item name="main_taotao_scale" format="float" type="dimen">0.244</item>
<item name="home_play_scale" format="float" type="dimen">0.25</item>
<item name="main_taotao_left_back" format="integer" type="dimen">16</item>
<item name="new_main_taotao_left_back" format="integer" type="dimen">24</item>
....
</resources>

三、正常的使用

在xml布局文件中:

1
2
3
4
5
6
7
<ImageView
android:id="@+id/main_taotao_tell_bac"
android:layout_width="@dimen/pt_124"
android:layout_height="@dimen/pt_129"
android:scaleType="fitXY"
android:src="@drawable/main_taotao_tell_bac_phone"
app:layout_constraintDimensionRatio="372:387" />

在kotlin文件中:

1
context.resources.getDimension(R.dimen.pt_124)

四、屏幕分辨率限定符文件的生产

如果项目已经开发基本完成,不再添加新的dimens值,此时可以生成不同分辨率限定符文件,Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from xml.etree import ElementTree as ET
import os
if __name__ == '__main__':
array = [384, 392, 400, 410, 432, 480, 533, 592]
for a in array:
resources = ET.Element("resources")
path = 'values-sw' + str(a) + 'dp'
if not os.path.exists(path):
os.makedirs(path)
for e in ET.parse("dimens.xml").getroot():
dimen = ET.SubElement(resources, "dimen")
t = e.text
if t[-1] == 'p':
dimen.attrib = {"name": e.attrib['name']}
dimen.text = str(round(float(t[0:-2]) * a / 360, 4)) + "dp"
else:
dimen.attrib = {'name': e.attrib['name'], 'format': e.attrib['format'], 'type': e.attrib['type']}
dimen.text = str(round(float(t) * a / 360, 4))
ET.ElementTree(resources).write(path + "/dimens.xml")

代码中,array数组中的值选取大于360dp,常用的手机就是代码中数组中的值,运行代码后会生成多个values-sw***dp的文件夹:

将这些文件直接复制到Android工程res文件夹下。

适配结束。

五、适配前后对比

设计稿:

适配后(锤子坚果Pro2):

适配前(锤子坚果Pro2):

分享到

My 2018 And Coming 2019

今天是2019年1月9号,新的一年已经到来,今天才腾出时间来总结已经过去的2018。

2018.2.23 结婚啦。
2018.9.17 换新工作啦,搜狗大佬们的创业公司。
2018.10.28 期待的猪宝宝。

2018
在美灿,完成IOS的独立开发及上传AppStore实践,完成Python的初步学习,基于python独立开发后台小项目,Android方面全面转向Kotlin语言的开发,So fast,I like it.
在葡萄智学,火力全开投入到新项目的开发上。经历了三个星期的开发,第一版项目内部上线,在经过一个月的维护后,官网各大应用商店上线,又一个月重构了一部分java代码专向kotlin,保持代码的稳定统一,终于在年末项目稳定下来,各大应用商店上线。接下来的2019年,工作中心偏向程序内部功能的编写及调整。

2019
今年应该是一个平淡而又幸福的一年。
我将持续在Android领域接触更多的知识,把新技术运用到公司的项目中来。
迎接小baby。
完成北大的答辩和课程。

分享到

Android 安装界面启动APP后台切前台重复创建

在安装界面点击打开,讲app切到后台再切回前台后,重新进入欢迎页。

在欢迎页或启动页onCreate后添加判断

1
2
3
4
5

if (intent.flags and Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT != 0) {
finish()
return
}

李利锋@limxing
2018-11-27

分享到

Kotlin的一些扩展函数

Kotlin的一些扩展函数

标签(空格分隔): Android Kotlin


StandardKt.class 通用函数

repeat(Int){}

代码块执行多少次

1
2
3
repeat(2){
println("This is $it time")
}

with(obj){}

用法1:接收一个对象,在代码块中直接调用属性值赋值
用法2:接收一个对象后,可使用其属性转换成另外一个对象返回

1
2
3
4
5
6
7
8
9
10
11
12
with(TextView(context)) {
text = "Hello Kotlin"
textSize = 16f
setTextColor(android.R.color.white)
}

val imageView = with(TextView(context)) {
text = "Hello Kotlin"
textSize = 16f
setTextColor(android.R.color.white)
ImageView(context)
}

.run{}

与with类似,只不过是错用在对象上

1
2
3
4
5
TextView(context).run {
text = "Hello Kotlin"
textSize = 16f
setTextColor(android.R.color.white)
}

.let{}

多用于执行依据代码块,用在可能为空的对象上

1
2
3
4
val data = 1
data?.let {
println("data is not null")
}

.apply{}

对象的扩展方法,类似with

1
2
3
4
5
val textView = TextView(context).apply {
text = "Hello Kotlin"
textSize = 16
setTextColor(android.R.color.white)
}

.takeIf{}

.takeUnless{}

synchronized(Any){}

CloseableKt.class

.use{}

用于流操作,能够自动关闭流资源

1
2
3
assets.open("data.json").reader().use {
val text = it.readText()
}

closeFinally()

CollectionsKt.class

.groupBy{}

用于对集合进行分组,返回的是Map集合,key是{条件},value是集合中的对象

1
2
val list = listOf("abc", "fd", "edd", "zdc","pkj")
list.groupBy { it.first() }.forEach(::println)

.forEach{}

对集合进行遍历,array,list,map…

1
2
3
val list = listOf("abc", "fd", "edd", "zdc","pkj")
list.forEach (::println)
list.forEach{ println(it) }

.forEachIndexed{ index, T -> }

对数组进行遍历,提供角标和对象

1
2
3
list.forEachIndexed { index, s ->
println("index:$index obj:$s")
}

.filter{}

筛选指定条件的对象返回List

1
2
3
list.filter {
it.first() == 'a'
}

.reversed()

返回集合的倒序

1
list.reversed()

List.zip(List):List<Pair<T, R>>

两个集合合并,返回Pair对象

.average()

返回集合内数的平均值

.count()

返回集合的个数

…more collect

分享到

Android 三个SDKVersion的含义

1
2
3
4
5
6
7
8
9
10
11
android {
compileSdkVersion 27
defaultConfig {
applicationId "online.shuzhi.app"
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}

compileSdkVersion

表示整个项目编译时所使用的sdk版本,用于一些检查,一般使用最新版本,此值,影响了dependencies中Android Support的一些依赖包,需要同步到此版本的最新版本的依赖包,否则会提示报错,但不会影响编译

minSdkVersion

表示打包后的apk支持最低Android系统为多少,此值会记录在apk安装包中

targetSdkVersion

关系到是否能够使用到什么版本的新特性。举例:
android 23 ,加入了运行时权限,那么如果设置为23及之后的值,则需要在代码中适配运行时权限的操作
android 26,加入了通知栏渠道,那么在26之后的值需要使用最新的代码编写通知

此值关系到能否使用最新版本的android中的新效果,新特性,同时在某些功能上需要重新编写代码,因此,如果不想适配新的功能,则不要设置此值为最新,同时也无法使用到最新版本所优化的一些功能。

结论:如果没有使用到最新SDK中的技术,某一版本就能够达到开发APP的要求,那么也就没有必要升级到最新版本的targetSdkVersion,不过如果你已经了解并熟悉了最新特性的代码的编写,升级到响应的sdk版本,能够得到更好的效果。

附android版本图:

分享到

IOS移动开发:数据库orm增删改查

基于SQLite.swift的数据库orm增删改查 github:XMDBModel

1
2
3
4
5
6
7
8
9
/*
XMDBModel Type SQLite.swift Type SQLite Type
NSNumber Int64(Int,Bool) INTEGER
NSNumber Double REAL
String String TEXT
nil nil NULL
Data SQLite.Blob BLOB
Date Int64 (Date) INTEGER
*/

How To Use

1、copy Source to your project

2、add depend library:pod ‘SQLite.swift’, ‘~> 0.11.4’

3、creat Model imp:XMDBModel

4、API :demo中XMBean中有属性方法介绍,ViewController中有增删改查使用方法

参考:

ActiveSQLite

License

XMDBModel is available under the MIT license.

分享到

Mysql数据库的迁移

备份:mysqldump -uroot -p1234 [databases name] >bck.sql
还原:mysql -uroot -p1234 <bck.sql

分享到

Centos+python3+nginx+uwsgi+flask

1、下载Python3

wget https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tgz

2、安装openssl

$ yum install openssl -y (-y 是遇到选择yes/no的时候默认提前yes了)
$ yum install openssl-devel -y

3、解压Python3

$ tar -zxf Python-3.6.3.tgz
$ cd Python-3.6.3

4、编译Python

$ ./configure
$ make & make install

5、安装虚拟环境

$ pip3 install virtualenv
$ virtualenv --no-site-packages -p python3 **_env (不关联系统库,使用python3)

$ source **_env/bin/activate(激活)
$ deactivate (退出虚拟环境)

6、安装nginx

$ yum install nginx
$ service nginx start

6.1、谁占用了80端口?kill掉

$ netstat -tln | grep 80
$ kill -9 'id'

7、安装uwsgi

$ pip install uwsgi

8、配置nginx与uwsgi之间

$ vim /etc/nginx/nginx.conf
    server {
        listen 80;
        server_name localhost;
        charset utf-8;
        client_max_body_size 75M;
        location / {
            include uwsgi_params;
            uwsgi_pass 127.0.0.1:8000;
            uwsgi_param UWSGI_PYHOME /var/www/leefeng_env;
            uwsgi_param UWSGI_CHDIR /var/www/leefeng;
            uwsgi_param UWSGI_SCRIPT core:app;
        }
    }
$ vim /var/www/leefeng/uwsgi.ini
    [uwsgi]
    socket = 127.0.0.1:8000
    plugins = python
    chidir = /var/www/leefeng
    virtualenv = /var/www/leefeng_env
    wsgi-file = core.py
    callable = app

    threads = 2
    processes = 4

9、编写代码

$ pip install flask
$ vim /var/www/leefeng/core.py
1
2
3
4
5
6
7
8
9
from flask import Flask
app=Flask(__name__)

@app.route('/')
def index():
return '<h2>Hello Word!</h2>'

if __name__ == '__main__':
app.run()

10、开启

$ uwsgi uwsgi.ini
$ nohup uwsgi uwsgi.ini & (断开终端依旧运行)
$ ps -ef|grep uwsgi (查询正在运行的uwsgi的进程,然后kill -9 id 后退出uwsgi)

错误解决

make时报错zipimport.ZipImportError: can't decompress data; zlib not available:#yum install zlib-devel
启动nginx报错:nginx: [emerg] socket() [::]:80 failed=需要在nginx.conf 注释掉#listen       [::]:80 default_server;
pip报错:ssl module in Python is not available=(需要重新python :./configure make & make install )
yum install openssl -y (-y 是遇到选择yes/no的时候默认提前yes了)
yum install openssl-devel -y
安装python错误:Prior to installing Python in CentOS 7, let’s make sure our system has all the necessary development dependencies:

# yum -y groupinstall development
# yum -y install zlib-devel


1.查看数据库编码格式
mysql> show variables like 'character_set_database';
2.查看数据表的编码格式
mysql> show create table <表名>;
5.修改数据库的编码格式
mysql>alter database <数据库名> character set utf8;
6.修改数据表格编码格式
mysql>alter table <表名> character set utf8;
分享到