Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >小知识 安卓线程和ui

小知识 安卓线程和ui

作者头像
用户1172465
发布于 2018-01-05 09:21:19
发布于 2018-01-05 09:21:19
1.2K00
代码可运行
举报
文章被收录于专栏:everhadeverhad
运行总次数:0
代码可运行

多线程环境下的ui修改

  开发过程中,经常需要开启新的线程,并且在其它线程中改变ui线程的ui对象的状态。Android设计出于性能考虑,ui对象为非线程安全的,然后让ui对象仅能在主线程——也就是ui线程中被修改,以此来保证ui对象的线程安全。以下引出一些跨线程修改ui对象的情形,以及可能的实现方式。

1. 定时更新ui

一些类似定时更新ui的代码,如动画控制。

1.1 多线程定时更改ui

具体就是新启动(不让ui线程sleep而卡住)一个线程去计时,之后定时来通知ui修改。

1.1.1 新启动线程定时执行任务

  • Timer + TimerTask
  • 新启动线程:run方法中:while(true) + Thread.Sleep/SystemClock.Sleep

  本质上都是一个新线程在背后计时。由于使用一个新的非ui线程执行计时,需要在时间到达后去通知ui修改。出于性能考虑,安卓的ui控件不是线程安全的,然后谷歌设计只让ui线程(主线程)能够直接修改ui控件,其它非ui线程不能来达到ui的线程安全。

1.1.2 非ui线程更新ui控件的方式

  • runOnUiThread
  • Handler
  • View.postDelay

  runOnUiThread从名字上可以看出就是专门供其它线程更改ui使用的。而handler用于不同线程之间的消息传递,可以让线程T1在希望的时刻去通知T2执行某些特定操作。这当然也完全能满足[非ui线程定时通知ui线程更改ui控件状态] 的目的。

1.2 Handler定时更新ui

如果只是为了完成“定时执行”,那么不开启一个新线程,使用handler.sendEmptyMessageDelayed(1,2000)也可以。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
在开始定时任务执行的地方调用:
handler.sendEmptyMessage();

主线程中的handler重写handleMessage:
final int UPDATE_ANIM = 1001;
boolean isAnimRun;
Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case UPDATE_ANIM:
            if(isAnimRun){
                //更新ui的代码,代码中根据情况改变isAnimRun标志。
                handler.sendEmptyMessageDelayed(UPDATE_ANIM, 2000);
            }
            break;
        }
    }
};

  可见,开启新的线程一般是执行一些非ui但耗时的操作,比如网络获取新闻数据。而定时任务这样的事情Handler也可以搞定。

2.Handler的跨线程通信

2.1原理简述

为了让其它线程发消息通知当前线程执行一些任务,当前线程线程可以这样做:

  • 当前线程执行Looper.prepare方法,这会产生一个MessageQueue和一个Looper实例。两个实例会被保存到ThreadLocal中,prepare只能执行一次,以保证每个线程仅有唯一的Looper和MessageQueue。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
>> MessageQueue用来接收其它线程丢进来的Message,使用先进先出的时间顺序维护所有消息。
>> Looper管理MessageQueue,主要就是不断的(loop)从中取走Message,然后发送给把此Message的target去处理,
   target即为发送此Message的handler。 
  • 当前线程可以创建一或多个Handler对象。Handler对象创建时会记录当前线程的MessageQueue和Looper实例作为自己的成员变量。。所以,handler在哪个线程中创建,就和哪个线程的Looper、MessageQueue对应起来。
  • 其它线程可以根据需要创建Message对象,调用handler的sendMessage,此实例方法会设置Message的target为方法的handler实例。 然后Message被发送到handler记录的MessageQueue中。
  • 当前线程执行Looper.loop方法,进入一个死循环。执行方式就是一个生产者消费者模式。消费者就是Looper,loop方法一旦执行,就去MessageQueue中取消息,没有消息时会阻塞,取到消息就把Message交给Message.target对应的handler去处理。生产者就是其它线程,其它线程创建Message并使用当前线程的handler执行sendMessage方法(会设置方法的实例对象handler为Message的target)来发送消息到handler字段记录的队列中。

  通过以上Looper、MessageQueue、Handler的合作,每一个线程都通过Handler来让其它线程根据需要通知自己执行一些操作。 Handler可以实现不同线程之间的通信,默认主线程已经提供好了Looper和MessageQueue,所以按需要自己写个handler对象,就可以在新开启的其它线程中使用handler来让主线程执行操作,常见的就是更新ui控件。 每个Message对象同时设置了when属性,这样可以做到消息通知的立即执行和延迟执行。

2.2 让自己的线程开始接收消息

其它线程默认Looper和MessageQueue是没有准备好的,可以在run方法里通过以下几步配置好:

  • 调用Looper.prepare()方法,这会建立Looper实例和对应的MessageQueue。
  • 设计好自己的Handler对象,主要是处理消息,之后供外界用它来向MessageQueue发送消息。
  • 最后调用Looper.loop()方法开启Looper对消息队列的监测。表示可以让其它线程来发送通知了。

2.3 Toast.show的调用

本质上对ui控件的修改,最终都是ui线程执行的。比如我们的线程里需要设置某个TextView的Text属性,那么只能是使用ui线程的handler去发送消息给ui线程去执行。或者使用runOnUiThread这样的简便方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
一个特殊的例子就是Toast.show,它仅要求当前线程Looper和MessageQueue准备好即可:
void run(){
    Looper.prepare();
    Toast.makeText(DemoActivity.this,"Wow......",0).show();
    Looper.loop();
}

3.多线程更新ListView

另一个常见“跨线程改变ui”的例子就是网络数据加载,比如加载新闻列表到ListView,启动新的线程是为了避免主线程阻塞而卡ui。相比启动一个线程去达到计时器的目的,使用非ui线程去执行耗时操作等就划算得多了。一般的套路是:

  • 界面上需要新的数据时,启动一个线程去从网络或本地获取一批数据,通常是分页获得一个合理的数据集合。界面上显示进度条,并且使得一部分界面不可交互。
  • 获取数据完毕后,调用adapter的notifyDataSetChanged()它是一个ui操作,需要使用“非ui线程执行ui操作”的技巧去完成。

  ListAdapter的要点就是:复用屏幕不可见的View对象,并且使用ViewHolder来避免findViewById的开销。

4.AsyncTask

AsyncTask是围绕Thread和Handler构建的一个简单包裹类,可以完成一些后台执行任务后更新UI的操作,api中指出操作不宜过长——a few seconds at most,如果是更为耗时的任务,就需要自己使用java.util.concurrent 包下的诸如Executor, ThreadPoolExecutor 和 FutureTask等类。

4.1 基本要点

  • new AsyncTask<Prams,Progress,Result>
  • onPreExecute中执行一些耗时操作的预备动作,可以是ui操作,如显示进度条。
  • doInBackground中执行耗时任务,调用publishProgress来更新进度。
  • onPostExecute中使用结果数据,更新ui,如dismiss掉进度条。
  • 应该在ui线程中创建AsyncTask的实例,并调用其execute方法。

4.2 原理简述

AsyncTask拥有一个静态的Handler成员:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static final InternalHandler sHandler = new InternalHandler();

  InternalHandler继承自Handler,它接收处理两个消息:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void handleMessage(Message msg) {
        AsyncTaskResult result = (AsyncTaskResult) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_RESULT:
                // There is only one result
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }

  可以知道,更新进度结束2个回调方法的执行是涉及到ui操作的,因此必须确保sHandler对象是属于ui线程的: The AsyncTask class must be loaded on the UI thread. This is done automatically as of JELLY_BEAN.

AsyncTask的构造方法使用创建好了一个Callable和一个FutureTask来实现线程的创建。

api要求AsyncTask的创建和execute方法的调用必须在ui线程中执行,实际上重点是execute方法,它里面调用了onPreExecute()方法,此方法会涉及ui操作,而且没有使用handler机制,就必须被在ui线程中执行。execute只能执行一次,我们通常会写new MyAsyncTask().execute() 这样的代码,所以为了确保在ui线程中执行execute,我们最好是在ui线程中执行AsyncTask的创建——当然了,在非ui线程中创建AsyncTask实例通常也没多大意义。

  • 第1个关键点就是InternalHandler,它保证updateProgress和postExecute在ui线程中执行。sHandler是类级别的,它结合AsyncTaskResult完成了在UI线程中调用指定AsyncTask的updateProgress和postExecute:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
AsyncTaskResult result = (AsyncTaskResult) msg.obj; 
result.mTask.finish(result.mData[0]);
result.mTask.onProgressUpdate(result.mData);
  • 第2个关键点就线程创建部分:Executor,最终是使用ThreadPoolExecutor完成对Future的启动执行:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//在executeOnExecutor中
exec.execute(mFuture);
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015-09-16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
AsyncTask源码解析
导语 AsyncTask对Android开发者来说并不陌生,当有耗时任务并涉及UI交互,AsyncTask可是处理异步任务的利器。AsyncTask使用也很方便,开发的时候多留意不要内存泄漏,一般处理方法就是把AsyncTask写成静态内部类的形式。 使用方法 new AsyncTask<Integer, Integer, Boolean>() { private int NUM = 0; @Override protected void
用户1269200
2018/02/01
8270
AsyncTask实现机制
execute先调用onPreExecute()(可见,onPreExecute是自动调用的)然后调用exec.execute(mFuture)
提莫队长
2019/02/21
4790
深入解析AsyncTask的原理
在初学 Android 的时候,AsyncTask 应该是大家都比较熟悉的。我们都知道 AsyncTask 可以在后台开启一个异步的任务,当任务完成后可以更新在 UI 上。而在 AsyncTask 中,比较常用的方法有: onPreExecute 、 doInBackground 、 onPostExecute 和 onProgressUpdate 等。而上述的方法中除了 doInBackground 运行在子线程中,其他的都是运行在主线程的,相信大家对这几个方法也了如指掌了。
俞其荣
2022/07/28
7750
Android多线程:深入AsyncTask原理及源码分析
前言 AsyncTask在Android开发中是十分常见的 今天,我将全面讲解AsyncTask的源码,希望你们会喜欢 Carson带你学多线程系列 基础汇总 Android多线程:基础知识汇总 基础使用 Android多线程:继承Thread类使用(含实例教程) Android多线程:实现Runnable接口使用(含实例教程) 复合使用 Android多线程:AsyncTask使用教程(含实例讲解) Android多线程:AsyncTask的原理及源码分析 Android多线程:Han
Carson.Ho
2022/03/25
3250
Android多线程:深入AsyncTask原理及源码分析
Android查缺补漏(线程篇)-- AsyncTask的使用及原理详细分析
本文作者:CodingBlock 文章链接:http://www.cnblogs.com/codingblock/p/8515304.html 一、AsyncTask的使用 AsyncTask是一种轻量级的异步任务类,可以很方便的在线程池中执行异步任务,并且将进度和结果传递给主线程。其底层由Thread+handler实现。 AsyncTask是一个抽象的泛型类,其类的声明如下: public abstract class AsyncTask<Params, Progress, Result> 其中三个泛型
codingblock
2018/03/30
1.1K0
AsyncTask 源码解析
后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
Yif
2019/12/26
4920
Asynctask那些事
耗时操作?更新UI?这些都是我们经常听到的词汇了,最常用的方法就是Thread+Handler的方法,今天就来聊聊另一个熟悉的Asynctask,或许你没有听过,别着急,通过本次(API23)的源码进
用户1337002
2018/03/09
7610
Asynctask那些事
AsyncTask使用和源码解析
在Android中提起异步通信我们都会想到Handler,之前写过一篇文章又一年对Android消息机制(Handler&Looper)的思考 。Android除过使用Handler进行异步任务外,AnsyncTask也是Android原生就有的执行异步任务的关键类。 首先来看一下AsyncTask的基本用法,由于AsyncTask是一个抽象类,所以如果我们想使用它,就必须要创建一个子类去继承它。在继承时我们可以为AsyncTask类指定三个泛型参数,这三个参数的用途如下:
静默加载
2020/05/29
7080
Android 多线程:AsyncTask的原理 及其源码分析
在进行源码分析前,先介绍`AsyncTask`中的类 & 核心方法
Carson.Ho
2019/02/22
7360
【Android开发进阶系列】多线程专题
        在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制地产生,并且线程的创建和销毁都会有相应的开销。当系统中存在大量的线程时,系统会通过会时间片轮转的方式调度每个线程,因此线程不可能做到绝对的并行。
江中散人_Jun
2023/10/16
6320
【Android开发进阶系列】多线程专题
源码分析Android中的线程和线程池
线程分为主线程和子线程,主线程主要处理和界面相关的事情,而子线程则往往用于执行耗时的操作。在操作系统中,线程是操作系统调度的最小单元。 - AsyncTask封装了线程池和Handler,它主要方便开发者在子线程中更新UI。 - HandlerThread是一个具有消息循环的线程,在它的内部可以使用Handler。 - IntentService是一个服务,系统对它进行了封装使其可以更方便地使用后台任务,IntentService内部使用了HandlerThread来执行任务,当任务执行完毕之后IntentService会自动退出。
见得乐
2022/07/20
4050
AsyncTask解析
需要进度更新: onPreExecute() --> doInBackground() --> publishProgress() --> onProgressUpdate() --> onPostExecute()
用户1205080
2019/04/25
6400
锦囊篇|一文摸懂AsyncTask
一般我们在代码中只用执行excute()的函数,在各个函数流程中给出相对应的操作。
ClericYi
2020/07/24
4370
锦囊篇|一文摸懂AsyncTask
Android Handler机制13之AsyncTask源码解析
AsyncTask是一个抽象类,我们需要创建子类去继承它,并且重写一些方法。AsyncTask接受三个泛型的参数:
隔壁老李头
2018/08/30
8450
Android Handler机制13之AsyncTask源码解析
Android基础总结(7)——异步消息处理
服务(Service)是Android中实现程序后台运行的解决方案,它非常适合用于去执行哪些不需要和用户交互而且还要长期运行的任务。服务的运行不依赖任何用户界面,即使当程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。不过需要注意的是:服务并不是运行在一个独立的进程当中,而是依赖于创建服务的应用程序进程,当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。   此外,我们也不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程中。也就
mukekeheart
2018/02/27
8310
Android基础总结(7)——异步消息处理
android异步任务asyntask详解
在Android中实现异步任务机制有两种方式,Handler和AsyncTask。 Handler模式需要为每一个任务创建一个新的线程,任务完成后通过Handler实例向UI线程发送消息,完成界面的更新,这种方式对于整个过程的控制比较精细,但也是有缺点的,例如代码相对臃肿,在多个任务同时执行时,不易对线程进行精确的控制。关于Handler的相关知识,前面也有所介绍,不清楚的朋友们可以参照一下。 为了简化操作,Android1.5提供了工具类android.os.AsyncTask,它使创建异步任务变得
xiangzhihong
2018/01/30
1.1K0
android异步任务asyntask详解
Android AsyncTask完全解析,带你从源码的角度彻底理解
根据文章内容为社区阅读者提供摘要总结。
用户1158055
2018/01/05
9090
AsyncTask源码分析
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/57416380
程序员徐公
2018/09/18
3770
AsyncTask源码分析
android多线程-AsyncTask(二)
上篇分析AsyncTask的一些基本用法以及不同android版本下的区别,接着本篇我们就来全面剖析一下AsyncTask的工作原理。在开始之前我们先来了解一个多线程的知识点——Callable<V> 、Future<V>和FutureTask类
用户9253515
2021/12/09
3350
AsyncTask源码分析
AsyncTask是android为我们提供执行异步任务的一个轻量的类,可以用来处理耗时操作,并且能够很方便的将执行结果返回给主线程。本篇文章将会通过源码分析来介绍AsyncTask的内部实现原理。
蜻蜓队长
2019/03/18
8640
AsyncTask源码分析
相关推荐
AsyncTask源码解析
更多 >
LV.0
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验