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

    private class PlayThread extends Thread {@Overridepublic 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 = falseprivate 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.BOTTOMtv_message.setLines(8)tv_message.maxLines = 8tv_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 = SCROLLmessage.obj = news[(Math.random() * 30 % 5).toInt()]//发送“持续滚动新闻”的消息类型handler.sendMessage(message)}bPlay = trueThread.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;}//在后台运行的任务代码,注意此处不可与界面交互@Overrideprotected 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执行@Overrideprotected void onPreExecute() {mListener.onBegin(mBook);}//刷新进度时调用,由publishProgress函数触发@Overrideprotected void onProgressUpdate(Integer... values) {mListener.onUpdate(mBook, values[0], 0);}//在任务结束后调用,即后于doInBackground执行@Overrideprotected void onPostExecute(String result) {mListener.onFinish(result);}//在任务取消时调用@Overrideprotected 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()}

以上代码被doAsyc括号圈起来的代码段,就是分线程要执行的全部代码;至于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入门教程的完整目录

__________________________________________________________________________
打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。

Kotlin入门(30)多线程交互相关推荐

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

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

  2. kotlin入门学习文档

    kotlin入门学习文档 前言:本文会着重对比java和kotlin,方便Java选手理解 提前总结:kotlin在服务端应用本质上是基于Java进行的改进,底层都是由JVM翻译成底层语言,我们只需要 ...

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

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

  4. Kotlin入门(31)JSON字符串的解析

    json是App进行网络通信最常见的数据交互格式,Android也自带了json格式的处理工具包org.json,该工具包主要提供了JSONObject(json对象)与JSONArray(json数 ...

  5. Kotlin入门(20)几种常见的对话框

    提醒对话框 手机上的App极大地方便了人们的生活,很多业务只需用户拇指一点即可轻松办理,然而这也带来了一定的风险,因为有时候用户并非真的想这么做,只是不小心点了一下而已,如果App不做任何提示的话,继 ...

  6. Kotlin入门(18)利用单例对象获取时间

    前面介绍了,使用扩展函数可以很方便地扩充数组Array的处理功能,例如交换两个数组元素.求数组的最大元素等等.那么除了数组之外,日期和时间的相关操作,也是很常见的,比如获取当前日期,获取当前时间.获取 ...

  7. 线程间定制化调用通信—— 1 高内聚低耦合的前提下,线程操作资源类 2 判断/干活/通知 3 多线程交互中,必须要防止多线程的虚假唤醒,也即(判断只用while,不能用if)

    生产者与消费者模式 一个生产者与一个消费者 题目:现在有两个线程,可以操作初始值为0的一个变量,实现一个线程对该变量加1,另一个线程对该变量减1,这两个线程的操作加一.减一交替,进行10轮,变量的初始 ...

  8. JAVA入门_多线程_邮局派发信件

    JAVA入门_多线程_邮局派发信件 Postman package cn.campsg.java.experiment.entity;public class Postman {private Str ...

  9. Kotlin入门(14)继承的那些事儿

    上一篇文章介绍了类对成员的声明方式与使用过程,从而初步了解了类的成员及其运用.不过早在<Kotlin入门(12)类的概貌与构造>中,提到MainActivity继承自AppCompatAc ...

最新文章

  1. SqlMapConfig.xml
  2. ActiveX(五)更好的“ActiveX”?
  3. l2的最优回归_【机器学习】逻辑回归(非常详细)
  4. 小汤学编程之JDBC番外篇——DBUtil工具类
  5. UsageLog4j
  6. 谈谈我对-大学-游戏-追名逐利-等10个话题的一些简要看法
  7. lesson1 ODE的几何解法:方向场,积分曲线
  8. mysql -f --force_mysqldump备份MYSQL数据库的参数详细说明
  9. 规则引擎 drools_Drools的入门初探
  10. 自然语言处理——分词系统(正向最大匹配法)
  11. 计算机一级wpsoffice知识,全国计算机一级WPSOffice考试试题
  12. 云服务器出现502错误的原因与解决方案
  13. 斯坦福、康奈尔都推荐的量子计算课程教材:《量子计算》
  14. ipad如何与计算机连接网络连接不上,苹果平板ipad的无法连接无线网络WiFi如何解决...
  15. 什么是动态编程Python示例
  16. 16秋南开计算机应用答案,南开16秋学期《计算机应用基础》在线作业.doc
  17. 认识和使用热插拔的正确姿势-续
  18. 医院计算机人员考试试题,医院信息科考试试题及答案-
  19. appJSON[tabBar][borderStyle] 字段需为 black 或 white console.error @ VM1402:1 (anonymous) @ VM1415:2
  20. 对数正态分布均值和方差控制着正态分布什么

热门文章

  1. Leetcode每日一题:493.reverse-pairs(翻转对)
  2. 西瓜书+实战+吴恩达机器学习(八)监督学习之朴素贝叶斯 Naive Bayes
  3. Focal Loss for Dense Object Detection(RetinaNet)(代码解析)
  4. 283EEZOJ #89 Cow Tennis Tournament
  5. mysql 递归_mysql5.7递归使用
  6. 实战中的Agile开发
  7. Chrome无法打开文件的错误 [Not allowed to load local resource: file://XXXX]
  8. Java中的SOAP技术
  9. ShardingSphere JDBC 语句执行初探
  10. JS保证输入框里面的数值是数字