Ⅰ android线程之HandlerThread
HandlerThread是Thread的一个子类,是Android中提供的另一种线程形态。
我擦,有线程、有looper这不正是我们当初声称在子线程中构建handler消息系统的所需要的吗?
那很明显,这个 HandlerThread 的目的就是让我们创建一个 Handler ,然后所有的任务操作成功的转给了 Handler 来处理完成。
但注意的是这个 Handler 的消息处理是运行在子线程中的。
在主线程中创建 handler ,简单的模拟一下 HandlerThread 工作原理
由于这个handler回调是运行在子线程中的,因此如果你想要更新UI可以借助主线程的默认的looper来实现,这个问题又愉快的转化到了子线程更新UI的问题。
HandlerThread其本质就是一个线程,只不过这个线程加入了Handler消息异步处理的机制。
那这与普通的创建线程的好处是什么呢?
Ⅱ android中有一个按钮,状态改变时一直运行某个函数,怎么实现。我写的都是只运行一次
状态改变时,在回调方法里,开个子线程里面用个无限循环去调用那个函数,在用个变量去控制线程的终止. 开子线程是为了不阻塞UI线程. 要是你那个函数里要改变UI的话,那就要用到Handler了, 因为函数的调用是在子线程. 或者在那个循环里用Handler来发送消息去调用你那个函数. 这样你的函数也是在主线程里执行了. 一直执行某个函数? 最好是用服务来代替函数吧!.
Ⅲ android之Handler处理机制
在深入理解Android的Handler处理机制之前,让我们先解决几个关键问题:
1. Handler的必要性: Android设计Handler的主要目的是解决多线程并发更新UI时的复杂性。如果多个线程直接修改UI,可能导致界面混乱,而加锁又会降低性能。Handler提供了一种机制,确保所有UI更新在主线程的消息队列中处理,避免并发问题。
2. Handler的定义: Handler是Android消息处理的核心组件,它负责在主线程与子线程之间传递和处理消息,类似于异步通信中的回调机制,允许子线程通知主线程执行特定任务。
3. 工作原理: Handler的工作涉及Looper(消息通道)和Message(消息封装)。子线程通过Handler发送消息,消息进入主线程的消息队列,Looper按顺序取出并分发给对应的Handler,由其handleMessage方法处理。
4. Message和MessageQueue: Message类用于封装任务信息,而MessageQueue存储消息。Looper负责循环处理消息队列中的任务,Handler通过sendMessage方法将任务发送至队列。
5. 创建和使用: Handler创建时,如果不指定,会绑定当前线程的Looper。在主线程创建Handler简单明了,但在非主线程则需要手动开启Looper并绑定。Handler的handleMessage方法需要重写,接收并处理消息。
总结,Handler机制确保了UI的线程安全,通过Looper和MessageQueue的协作,实现了子线程与主线程之间高效且有序的消息传递。
Ⅳ Framework事件机制——手撕Android事件处理的三种方法
Android的事件处理的三种方法:
setOnClickListener,setOnLongClickListener、setOnTouchListener
注意:如果onTouchEvent方法return true,则单击事件和长摁事件不再执行;若onLongClick方法返回true,则单击事件不再处理。
需要定义继承组件的类,重写回调方法Touch方法执行时,先被Activity捕获,DispatchTouchEvent方法处理。return false,交给上层的onTouchEvent方法处理;return super.dispatchTouchEvent(ev),则传递给最外层的View。
View用Dispatch方法处理,return false,由上层的onTouchEvent方法处理。如果返回super.dispatchTouchEvent(ev),则本层的onInterceptTouchEvent拦截,如果拦截true,则拦截,false不拦截,传递给子View的DispatchTouchEvent处理。
常用的回调方法:onKeyDown,onKeyLongPress,onKeyUp,onTouchEvent,onTrackballEvent(轨迹球事件)监听和回调同时存在时,先调用监听。
流程模型图:
Event source 事件源
Event 事件
Event Listener 事件监听器
下面我们来看一下点击事件和触摸事件的监听三要素具体是那部分:
由于点击事件比较简单,系统已经帮我们处理了,并没有找到具体事件是哪个。
View.OnClickListener 单击事件监听器必须实现的接⼝
View.OnCreateContextMenuListener 创建上下⽂菜单事件
View.OnFocusChangeListener 焦点改变事件
View.OnKeyListener 按键事件监听器
View.OnLongClickListener 长按事件监听器
View.OnTouchListener 触摸屏事件监听器
⾸先,事件监听机制中由事件源,事件,事件监听器三类对象组成。
事件监听器处理流程:
在此以OnClickListener单击事件为例使用intent来实现页面的跳转
监听事件处理是事件源与事件监听器分开的而基于回调的事件处理UI组件不但是事件源,而且还是事件监听器,通过组件的相关回调方法处理对应的事件。
Ⅰ. 自定义View类,继承自需要的View UI类。ex :自定义 MyButton按钮类 extends 基础Button类
Ⅱ. 复写回调函数。ex:public boolean onTouchEvent(MotionEvent event)
每一个事件回调方法都会返回一个boolean值,①.如果返回true:表示该事件已被处理,不再继续向外扩散,②.如果返回false:表示事件继续向外扩散
而说到基于回调就离不开监听机制 。
几乎所有基于回调的事件处理方法都有一个boolean类型的返回值,该返回值用于表示该处理方法是否能完全处理该事件。
如果处理事件的回调方法返回true,表明该处理方法已经完全处理改事件,该事件不会传播出去。
如果处理事件的回调方法返回false,表明该处理方法并未完全处理该事件,该事件会传播出去。
对于基于回调的时间传播而言,某组件上所发生的事件不仅会激发该组件上的回调方法,也会触发该组件所在Activity的回调方法——只要事件能传播到该Activity。
这里是在模拟器里进行的测试,这里按下键盘(而不是点击),会看到 logcat 中的输出,如下:
View类实现了KeyEvent.Callback接口中的一系列回调函数,因此,基于回调的事件处理机制通过自定义View来实现,自定义View时重写这些事件处理方法即可。
Handler是一个消息分发对象。
Handler是Android系统提供的一套用来更新UI的机制,也是一套消息处理机制,可以通过Handler发消息,也可以通过Handler处理消息。
在下面介绍Handler机制前,首先得了解以下几个概念:
在子线程执行完耗时操作,当Handler发送消息时,将会调用 MessageQueue.enqueueMessage ,向消息队列中添加消息。 当通过 Looper.loop 开启循环后,会不断地从消息池中读取消息,即调用 MessageQueue.next , 然后调用目标Handler(即发送该消息的Handler)的 dispatchMessage 方法传递消息, 然后返回到Handler所在线程,目标Handler收到消息,调用 handleMessage 方法,接收消息,处理消息。
从上面可以看出,在子线程中创建Handler之前,要调用 Looper.prepare() 方法,Handler创建后,还要调用 Looper.loop() 方法。而前面我们在主线程创建Handler却不要这两个步骤,因为系统帮我们做了。
初始化Looper :
从上可以看出,不能重复创建Looper,每个线程只能创建一个。创建Looper,并保存在 ThreadLocal 。其中ThreadLocal是线程本地存储区(Thread Local Storage,简称TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。
开启Looper
发送消息 :
post方法:
send方法:
在子线程中,进行耗时操作,执行完操作后,发送消息,通知主线程更新UI。
本文讲解了三个方面;Android事件机制;基于监听、基于回调以及Handler消息处理。还有许多没有讲解到的知识点,我总结在了整理的一套Android进阶笔记里面;需要学习进阶的同学可以前往获取: Frame Work源码解析手册 、 Android核心技术进阶手册、实战笔记、面试题纲资料
Ⅳ android AsyncTask的方法在哪几个线程中调用
在开发Android移动客户端的时候往往要使用多线程来进行操作,我们通常会将耗时的操作放在单独的线程执行,避免其占用主线程而给用户带来不好的用户体验。但是在子线程中无法去操作主线程(UI 线程),在子线程中操作UI线程会出现错误。因此android提供了一个类Handler来在子线程中来更新UI线程,用发消息的机制更新UI界面,呈现给用户。这样就解决了子线程更新UI的问题。但是费时的任务操作总会启动一些匿名的子线程,太多的子线程给系统带来巨大的负担,随之带来一些性能问题。因此android提供了一个工具类AsyncTask,顾名思义异步执行任务。这个AsyncTask生来就是处理一些后台的比较耗时的任务,给用户带来良好用户体验的,从编程的语法上显得优雅了许多,不再需要子线程和Handler就可以完成异步操作并且刷新用户界面。
先大概认识下Android.os.AsyncTask类:
* android的类AsyncTask对线程间通讯进行了包装,提供了简易的编程方式来使后台线程和UI线程进行通讯:后台线程执行异步任务,并把操作结果通知UI线程。
* AsyncTask是抽象类.AsyncTask定义了三种泛型类型 Params,Progress和Result。
* Params 启动任务执行的输入参数,比如HTTP请求的URL。
* Progress 后台任务执行的百分比。
* Result 后台执行任务最终返回的结果,比如String,Integer等。
* AsyncTask的执行分为四个步骤,每一步都对应一个回调方法,开发者需要实现这些方法。
* 1) 继承AsyncTask
* 2) 实现AsyncTask中定义的下面一个或几个方法
* onPreExecute(), 该方法将在执行实际的后台操作前被UI 线程调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条,或者一些控件的实例化,这个方法可以不用实现。
* doInBackground(Params...), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台处理工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
* onProgressUpdate(Progress...),在publishProgress方法被调用后,UI 线程将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
* onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI 线程调用,后台的计算结果将通过该方法传递到UI 线程,并且在界面上展示给用户.
* onCancelled(),在用户取消线程操作的时候调用。在主线程中调用onCancelled()的时候调用。
为了正确的使用AsyncTask类,以下是几条必须遵守的准则:
1) Task的实例必须在UI 线程中创建
2) execute方法必须在UI 线程中调用
3) 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法,需要在UI线程中实例化这个task来调用。
4) 该task只能被执行一次,否则多次调用时将会出现异常
doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为doInBackground接受的参数,第二个为显示进度的参数,第第三个为doInBackground返回和onPostExecute传入的参数。
Ⅵ Android中的线程状态 - AsyncTask详解
在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制地产生,并且 线程的创建和销毁都会有相应的开销。 当系统中存在大量的线程时,系统会通过会时间片轮转的方式调度每个线程,因此线程不可能做到绝对的并行。
如果在一个进程中频繁地创建和销毁线程,显然不是高效的做法。正确的做法是采用线程池,一个线程池中会缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁线程所带来的系统开销。
AsyncTask是一个抽象类,它是由Android封装的一个轻量级异步类(轻量体现在使用方便、代码简洁),它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。
AsyncTask的内部封装了 两个线程池 (SerialExecutor和THREAD_POOL_EXECUTOR)和 一个Handler (InternalHandler)。
其中 SerialExecutor线程池用于任务的排队,让需要执行的多个耗时任务,按顺序排列 , THREAD_POOL_EXECUTOR线程池才真正地执行任务 , InternalHandler用于从工作线程切换到主线程 。
1.AsyncTask的泛型参数
AsyncTask是一个抽象泛型类。
其中,三个泛型类型参数的含义如下:
Params: 开始异步任务执行时传入的参数类型;
Progress: 异步任务执行过程中,返回下载进度值的类型;
Result: 异步任务执行完成后,返回的结果类型;
如果AsyncTask确定不需要传递具体参数,那么这三个泛型参数可以用Void来代替。
有了这三个参数类型之后,也就控制了这个AsyncTask子类各个阶段的返回类型,如果有不同业务,我们就需要再另写一个AsyncTask的子类进行处理。
2.AsyncTask的核心方法
onPreExecute()
这个方法会在 后台任务开始执行之间调用,在主线程执行。 用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
doInBackground(Params...)
这个方法中的所有代码都会 在子线程中运行,我们应该在这里去处理所有的耗时任务。
任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。 注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。
onProgressUpdate(Progress...)
当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。 在这个方法中可以对UI进行操作,在主线程中进行,利用参数中的数值就可以对界面元素进行相应的更新。
onPostExecute(Result)
当doInBackground(Params...)执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中, 可以利用返回的数据来进行一些UI操作,在主线程中进行,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
上面几个方法的调用顺序:
onPreExecute() --> doInBackground() --> publishProgress() --> onProgressUpdate() --> onPostExecute()
如果不需要执行更新进度则为onPreExecute() --> doInBackground() --> onPostExecute(),
除了上面四个方法,AsyncTask还提供了onCancelled()方法, 它同样在主线程中执行,当异步任务取消时,onCancelled()会被调用,这个时候onPostExecute()则不会被调用 ,但是要注意的是, AsyncTask中的cancel()方法并不是真正去取消任务,只是设置这个任务为取消状态,我们需要在doInBackground()判断终止任务。就好比想要终止一个线程,调用interrupt()方法,只是进行标记为中断,需要在线程内部进行标记判断然后中断线程。
3.AsyncTask的简单使用
这里我们模拟了一个下载任务,在doInBackground()方法中去执行具体的下载逻辑,在onProgressUpdate()方法中显示当前的下载进度,在onPostExecute()方法中来提示任务的执行结果。如果想要启动这个任务,只需要简单地调用以下代码即可:
4.使用AsyncTask的注意事项
①异步任务的实例必须在UI线程中创建,即AsyncTask对象必须在UI线程中创建。
②execute(Params... params)方法必须在UI线程中调用。
③不要手动调用onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法。
④不能在doInBackground(Params... params)中更改UI组件的信息。
⑤一个任务实例只能执行一次,如果执行第二次将会抛出异常。
先从初始化一个AsyncTask时,调用的构造函数开始分析。
这段代码虽然看起来有点长,但实际上并没有任何具体的逻辑会得到执行,只是初始化了两个变量,mWorker和mFuture,并在初始化mFuture的时候将mWorker作为参数传入。mWorker是一个Callable对象,mFuture是一个FutureTask对象,这两个变量会暂时保存在内存中,稍后才会用到它们。 FutureTask实现了Runnable接口,关于这部分内容可以看这篇文章。
mWorker中的call()方法执行了耗时操作,即result = doInBackground(mParams);,然后把执行得到的结果通过postResult(result);,传递给内部的Handler跳转到主线程中。在这里这是实例化了两个变量,并没有开启执行任务。
那么mFuture对象是怎么加载到线程池中,进行执行的呢?
接着如果想要启动某一个任务,就需要调用该任务的execute()方法,因此现在我们来看一看execute()方法的源码,如下所示:
调用了executeOnExecutor()方法,具体执行逻辑在这个方法里面:
可以 看出,先执行了onPreExecute()方法,然后具体执行耗时任务是在exec.execute(mFuture),把构造函数中实例化的mFuture传递进去了。
exec具体是什么?
从上面可以看出具体是sDefaultExecutor,再追溯看到是SerialExecutor类,具体源码如下:
终于追溯到了调用了SerialExecutor 类的execute方法。SerialExecutor 是个静态内部类,是所有实例化的AsyncTask对象公有的,SerialExecutor 内部维持了一个队列,通过锁使得该队列保证AsyncTask中的任务是串行执行的,即多个任务需要一个个加到该队列中,然后执行完队列头部的再执行下一个,以此类推。
在这个方法中,有两个主要步骤。
①向队列中加入一个新的任务,即之前实例化后的mFuture对象。
②调用 scheleNext()方法,调用THREAD_POOL_EXECUTOR执行队列头部的任务。
由此可见SerialExecutor 类仅仅为了保持任务执行是串行的,实际执行交给了THREAD_POOL_EXECUTOR。
THREAD_POOL_EXECUTOR又是什么?
实际是个线程池,开启了一定数量的核心线程和工作线程。然后调用线程池的execute()方法。执行具体的耗时任务,即开头构造函数中mWorker中call()方法的内容。先执行完doInBackground()方法,又执行postResult()方法,下面看该方法的具体内容:
该方法向Handler对象发送了一个消息,下面具体看AsyncTask中实例化的Hanlder对象的源码:
在InternalHandler 中,如果收到的消息是MESSAGE_POST_RESULT,即执行完了doInBackground()方法并传递结果,那么就调用finish()方法。
如果任务已经取消了,回调onCancelled()方法,否则回调 onPostExecute()方法。
如果收到的消息是MESSAGE_POST_PROGRESS,回调onProgressUpdate()方法,更新进度。
InternalHandler是一个静态类,为了能够将执行环境切换到主线程,因此这个类必须在主线程中进行加载。所以变相要求AsyncTask的类必须在主线程中进行加载。
到此为止,从任务执行的开始到结束都从源码分析完了。
AsyncTask的串行和并行
从上述源码分析中分析得到,默认情况下AsyncTask的执行效果是串行的,因为有了SerialExecutor类来维持保证队列的串行。如果想使用并行执行任务,那么可以直接跳过SerialExecutor类,使用executeOnExecutor()来执行任务。
四、AsyncTask使用不当的后果
1.)生命周期
AsyncTask不与任何组件绑定生命周期,所以在Activity/或者Fragment中创建执行AsyncTask时,最好在Activity/Fragment的onDestory()调用 cancel(boolean);
2.)内存泄漏
3.) 结果丢失
屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask(非静态的内部类)会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。
自己是从事了七年开发的Android工程师,不少人私下问我,2019年Android进阶该怎么学,方法有没有?
没错,年初我花了一个多月的时间整理出来的学习资料,希望能帮助那些想进阶提升Android开发,却又不知道怎么进阶学习的朋友。【 包括高级UI、性能优化、架构师课程、NDK、Kotlin、混合式开发(ReactNative+Weex)、Flutter等架构技术资料 】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。