Android开发时常会遇到一些耗时的业务场景,比如后台批量处理数据、访问后端服务器接口等等,此时为了保证界面交互的及时响应,必须通过线程单独运行这些耗时任务。简单的线程可使用Thread类来启动,无论Java还是Kotlin都一样,该方式首先要声明一个自定义线程类,对应的Java代码如下所示:

    private class PlayThread extends Thread {
@Override
public void run() {
//此处省略具体的线程内部代码
}
}

自定义线程的Kotlin代码与Java大同小异,具体见下:

    private inner class PlayThread : Thread() {
override fun run() {
//此处省略具体的线程内部代码
}
}

线程类声明完毕,接着要启动线程处理任务,在Java中调用一行代码“new PlayThread().start();”即可,至于Kotlin则更简单了,只要“PlayThread().start()”就行。如此看来,Java的线程处理代码跟Kotlin差不了多少,没发觉Kotlin比Java有什么优势。倘使这样,真是小瞧了Kotlin,它身怀多项绝技,单单是匿名函数这招,之前在介绍任务Runnabe时便领教过了,线程Thread同样也能运用匿名函数化繁为简。注意到自定义线程类均需由Thread派生而来,然后必须且仅需重写run方法,所以像类继承、函数重载这些代码都是走过场,完全没必要每次都依样画葫芦,编译器真正关心的是run方法内部的具体代码。于是,借助于匿名函数,Kotlin的线程执行代码可以简写成下面这般:

    Thread {
//此处省略具体的线程内部代码
}.start()

以上代码段看似无理,实则有规,不但指明这是个线程,而且命令启动该线程,可谓是简洁明了。

线程代码在运行过程中,通常还要根据实际情况来更新界面,以达到动态刷新的效果。可是Android规定了只有主线程才能操作界面控件,分线程是无法直接调用控件对象的,只能通过Android提供的处理器Handler才能间接操纵控件。这意味着,要想让分线程持续刷新界面,仍需完成传统Android开发的下面几项工作:
1、声明一个自定义的处理器类Handler,并重写该类的handleMessage方法,根据不同的消息类型进行相应的控件操作;
2、线程内部针对各种运行状况,调用处理器对象的sendEmptyMessage或者sendMessage方法,发送事先约定好的消息类型;
举个具体的业务例子,现在有一个新闻版块,每隔两秒在界面上滚动播报新闻,其中便联合运用了线程和处理器,先由线程根据情况发出消息指令,再由处理器按照消息指令轮播新闻。详细的业务代码示例如下:

class MessageActivity : AppCompatActivity() {
private var bPlay = false
private val BEGIN = 0 //开始播放新闻
private val SCROLL = 1 //持续滚动新闻
private val END = 2 //结束播放新闻
private val news = arrayOf("北斗三号卫星发射成功,定位精度媲美GPS", "美国赌城拉斯维加斯发生重大枪击事件", "日本在越南承建的跨海大桥未建完已下沉", "南水北调功在当代,近亿人喝上长江水", "德国外长要求中国尊重“一个欧洲”政策") override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_message)
tv_message.gravity = Gravity.LEFT or Gravity.BOTTOM
tv_message.setLines(8)
tv_message.maxLines = 8
tv_message.movementMethod = ScrollingMovementMethod()
btn_start_message.setOnClickListener {
if (!bPlay) {
bPlay = true
//线程第一种写法的调用方式,通过具体的线程类进行构造。
//注意每个线程实例只能启动一次,不能重复启动。
//若要多次执行该线程的任务,则需每次都构造新的线程实例。
//PlayThread().start()
//线程的第二种写法,采用匿名类的形式。第二种写法无需显式构造
Thread {
//发送“开始播放新闻”的消息类型
handler.sendEmptyMessage(BEGIN)
while (bPlay) {
//休眠两秒,模拟获取突发新闻的网络延迟
Thread.sleep(2000)
val message = Message.obtain()
message.what = SCROLL
message.obj = news[(Math.random() * 30 % 5).toInt()]
//发送“持续滚动新闻”的消息类型
handler.sendMessage(message)
}
bPlay = true
Thread.sleep(2000)
//发送“结束播放新闻”的消息类型
handler.sendEmptyMessage(END)
bPlay = false
}.start()
}
}
btn_stop_message.setOnClickListener { bPlay = false }
} //自定义的处理器类,区分三种消息类型,给tv_message显示不同的文本内容
private val handler = object : Handler() {
override fun handleMessage(msg: Message) {
val desc = tv_message.text.toString()
tv_message.text = when (msg.what) {
BEGIN -> "$desc\n${DateUtil.nowTime} 下面开始播放新闻"
SCROLL -> "$desc\n${DateUtil.nowTime} ${msg.obj}"
else -> "$desc\n${DateUtil.nowTime} 新闻播放结束,谢谢观看"
}
}
} }

通过线程加上处理器固然可以实现滚动播放的功能,可是想必大家也看到了,这种交互方式依旧很突兀,还有好几个难以克服的缺点:

1、自定义的处理器仍然存在类继承和函数重载的冗余写法;
2、每次操作界面都得经过发送消息、接收消息两道工序,繁琐且拖沓;
3、线程和处理器均需在指定的Activity代码中声明,无法在别处重用;
有鉴于此,Android早已提供了异步任务AsyncTask这个模版类,专门用于耗时任务的分线程处理。然而AsyncTask的用法着实不简单,首先它是个模板类,初学者瞅着模板就发慌;其次它区分了好几种运行状态,包括未运行、正在运行、取消运行、运行结束等等,一堆的概念叫人头痛;再次为了各种状况都能与界面交互,又得定义事件监听器及其事件处理方法;末了还得在Activity代码中实现监听器的相应方法,才能正常调用定义好的AsyncTask类。
初步看了下自定义AsyncTask要做的事情,直让人倒吸一口冷气,看起来很高深的样子,确实每个Android开发者刚接触AsyncTask之时都费了不少脑细胞。为了说明AsyncTask是多么的与众不同,下面来个异步加载书籍任务的完整Java代码,温习一下那些年虐过开发者的AsyncTask:

//模板类的第一个参数表示外部调用execute方法的输入参数类型,第二个参数表示运行过程中与界面交互的数据类型,第三个参数表示运行结束后返回的输出参数类型
public class ProgressAsyncTask extends AsyncTask<String, Integer, String> {
private String mBook;
//构造函数,初始化数据
public ProgressAsyncTask(String title) {
super();
mBook = title;
} //在后台运行的任务代码,注意此处不可与界面交互
@Override
protected String doInBackground(String... params) {
int ratio = 0;
for (; ratio <= 100; ratio += 5) {
// 睡眠200毫秒模拟网络通信处理
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//刷新进度,该函数会触发调用onProgressUpdate方法
publishProgress(ratio);
}
return params[0];
} //在任务开始前调用,即先于doInBackground执行
@Override
protected void onPreExecute() {
mListener.onBegin(mBook);
} //刷新进度时调用,由publishProgress函数触发
@Override
protected void onProgressUpdate(Integer... values) {
mListener.onUpdate(mBook, values[0], 0);
} //在任务结束后调用,即后于doInBackground执行
@Override
protected void onPostExecute(String result) {
mListener.onFinish(result);
} //在任务取消时调用
@Override
protected void onCancelled(String result) {
mListener.onCancel(result);
} //声明监听器对象
private OnProgressListener mListener;
public void setOnProgressListener(OnProgressListener listener) {
mListener = listener;
} //定义该任务的事件监听器及其事件处理方法
public static interface OnProgressListener {
public abstract void onFinish(String result);
public abstract void onCancel(String result);
public abstract void onUpdate(String request, int progress, int sub_progress);
public abstract void onBegin(String request);
} }

见识过了AsyncTask的惊涛骇浪,不禁喟叹开发者的心灵有多么地强大。多线程是如此的令人望而却步,直到Kotlin与Anko的搭档出现,因为它俩在线程方面带来了革命性的思维,即编程理应是面向产品,而非面向机器。对于分线程与界面之间的交互问题,它俩给出了堪称完美的解决方案,所有的线程处理逻辑都被归结为两点:其一是如何标识这种牵涉界面交互的分线程,该点由关键字“doAsync”阐明;其二是如何在分线程中传递消息给主线程,该点由关键字“uiThread”界定。有了这两个关键字,分线程的编码异乎寻常地简单,即使加上Activity的响应代码也只有以下寥寥数行:

    //圆圈进度对话框
private fun dialogCircle(book: String) {
dialog = indeterminateProgressDialog("${book}页面加载中……", "稍等")
doAsync {
// 睡眠200毫秒模拟网络通信处理
for (ratio in 0..20) Thread.sleep(200)
//处理完成,回到主线程在界面上显示书籍加载结果
uiThread { finishLoad(book) }
}
} private fun finishLoad(book: String) {
tv_async.text = "您要阅读的《$book》已经加载完毕"
if (dialog.isShowing) dialog.dismiss()
}

以上代码被doAsync括号圈起来的代码段,就是分线程要执行的全部代码;至于uiThread括号圈起来的代码,则为通知主线程要完成的工作。倘若在分线程运行过程中,要不断刷新当前进度,也只需在待刷新的地方添加一行uiThread便成,下面是添加了进度刷新的代码例子:

    //长条进度对话框
private fun dialogBar(book: String) {
dialog = progressDialog("${book}页面加载中……", "稍等")
doAsync {
for (ratio in 0..20) {
Thread.sleep(200)
//处理过程中,实时通知主线程当前的处理进度
uiThread { dialog.progress = ratio*100/20 }
}
uiThread { finishLoad(book) }
}
}

  

Kotlin入门(30)多线程交互的更多相关文章

  1. Kotlin入门教程——目录索引

    Kotlin是谷歌官方认可的Android开发语言,Android Studio从3.0版本开始就内置了Kotlin,所以未来在App开发中Kotlin取代Java是大势所趋,就像当初Android ...

  2. 写给Android开发者的Kotlin入门

    写给Android开发者的Kotlin入门 转 https://www.jianshu.com/p/bb53cba6c8f4 Google在今年的IO大会上宣布,将Android开发的官方语言更换为K ...

  3. Kotlin入门(32)网络接口访问

    手机上的资源毕竟有限,为了获取更丰富的信息,就得到辽阔的互联网大海上冲浪.对于App自身,也要经常与服务器交互,以便获取最新的数据显示到界面上.这个客户端与服务端之间的信息交互,基本使用HTTP协议进 ...

  4. Kotlin入门第二课:集合操作

    测试项目Github地址: KotlinForJava 前文传送: Kotlin入门第一课:从对比Java开始 初次尝试用Kotlin实现Android项目 1. 介绍 作为Kotlin入门的第二课, ...

  5. Kotlin入门(28)Application单例化

    Application是Android的又一大组件,在App运行过程中,有且仅有一个Application对象贯穿应用的整个生命周期,所以适合在Application中保存应用运行时的全局变量.而开展 ...

  6. Kotlin入门(5)字符串及其格式化

    上一篇文章介绍了数组的声明和操作,包括字符串数组的用法.注意到Kotlin的字符串类也叫String,那么String在Java和Kotlin中的用法有哪些差异呢?这便是本文所要阐述的内容了. 首先要 ...

  7. Kotlin入门(9)函数的基本用法

    上一篇文章介绍了Kotlin新增的空安全机制,控制语句部分可算是讲完了,接下来将连续描述Kotlin如何定义和调用函数,本篇文章先介绍函数的基本用法. 前面几篇文章介绍控制语句之时,在setOnCli ...

  8. Kotlin入门(11)江湖绝技之特殊函数

    上一篇文章介绍了Kotlin对函数的输入参数所做的增强之处,其实函数这块Kotlin还有好些重大改进,集中体现在几类特殊函数,比如泛型函数.内联函数.扩展函数.尾递归函数.高阶函数等等,因此本篇文章就 ...

  9. Kotlin入门(13)类成员的众生相

    上一篇文章介绍了类的简单定义及其构造方式,当时为了方便观察演示结果,在示例代码的构造函数中直接调用toast提示方法,但实际开发是不能这么干的.合理的做法是外部访问类的成员属性或者成员方法,从而获得处 ...

随机推荐

  1. 大量示例彻底搞懂Linux查找,which,whereis,locate,find

    前言 Linux常用命令中,有些命令可以帮助我们查找二进制文件,帮助手册或源文件的位置,也有的命令可以帮助我们查找磁盘上的任意文件,今天我们就来看看这些命令如何使用. which which命令会在P ...

  2. 手把手教你在CSDN博客中插入图片之剑走偏锋系列

    1.在博客园注册账号.你没有看错,就是博客园,在图像上传方面博客园比CSDN这个垃圾强太多了. 2.在博客园进入随笔撰写编辑模块,点击上传图像按钮(点最黄的那个,别点错了). 3.弹出如下窗口 ,点击 ...

  3. [Swift]LeetCode434. 字符串中的单词数 | Number of Segments in a String

    Count the number of segments in a string, where a segment is defined to be a contiguous sequence of ...

  4. django中的跨表查询梳理

    1.前言 最近在写一个小项目,里面主要涉及的就是表与表之间复杂的关系.当真正开发起来的时候,才发现自己对复杂的表关系间的查询有点混乱,趁着这几天的时间,重新梳理了一下. 2.概念 在开始之前,先明确几 ...

  5. python glob的安装和使用

    基本概念 glob是python自己带的一个文件操作相关模块,用它可以查找符合自己目的的文件,类似于Windows下的文件搜索,支持通配符操作.*,?,[]这三个通配符,*代表0个或多个字符,?代表一 ...

  6. Data - Tools

    数据工具汇总 史上最全的大数据分析和制作工具 全球100款大数据工具汇总 SQL 数据分析常用语句 01 - NumPy HomePage:http://www.numpy.org/ NumPy(数值 ...

  7. js获取input上传文件名和后缀

    var file = $("#filedata").val(); var pos = file.lastIndexOf("\\");  var filename ...

  8. 开启 IPv6 新时代,升级后的 IPv6 厉害在哪?

    IPv6,Internet Protocol Version 6,从字面翻译 “互联网协议第 6 版”,它是IETF设计的用于替代现行版本 IP 协议-IPv4 协议,被称作“下一代互联网协议”.早在 ...

  9. Zookeeper知识点

    Zookeeper是一个分布式协调服务 Zookeeper服务器的角色 Leader: 所有的写操作必须要经过Leader完成,在广播给其它服务器.心跳检测.集群中只有一个Leader. Follow ...

  10. 不懂这些高并发分布式架构、分布式系统的数据一致性解决方案,你如何能找到高新互联网工作呢?强势解析eBay BASE模式、去哪儿及蘑菇街分布式架构

    互联网行业是大势所趋,从招聘工资水平即可看出,那么如何提升自我技能,满足互联网行业技能要求?需要以目标为导向,进行技能提升,本文主要针对高并发分布式系统设计.架构(数据一致性)做了分析,祝各位早日走上 ...