当前位置:首页 » 安卓系统 » android开启线程方法

android开启线程方法

发布时间: 2023-04-01 12:58:05

‘壹’ 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等架构技术资料 】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

‘贰’ android创建子线程

创建后台线程的方法有多种,这里说三种,可以回去试试

1、使用Android系统工具类 AsyncTask(Params,Progress,Result)
AsyncTask是一个轻量级线程,三个泛型参数分别是 Params传入参数,int型Progress为进度条进度,Result为返回值
要使用AsyncTask,必须继承之并复写其中的几个重要的函数。
onPreExecute(), 该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。
doInBackground(Params...), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
onProgressUpdate(Progress...),在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread.

注:Task必须在UI线程中创建,并调用并且只能调用一次execute方法,该方法的参数为传入的泛型Params。
其余函数最好不要手动调用,容易造成线程崩溃。多次调用Task,容易造成线程池溢出。

2、使用Handler和HandlerThread
误区: Handler handler = new Handler ();
handler.post(r);
这种做法看似创建了一个线程,但实际上handler只是直接调用Runnable中的run() 方法,而不执行线程的start()函数,所以这两句代码执行后,程序仍然在UI线程中执行。所以我们引入HandlerThread,因为HandlerThread中有一个Looper对象,用以循环消息队列。
为了使用Looper,必须子类化Handler,并复写它的构造函数。
class MyHandler extends Handler{
public MyHandler() {}
public MyHandler(Looper looper){
super (looper);
}

public void handleMessage(Message msg){
//....这里运行耗时的过程
}
}
}
handleMessage(Message msg)函数用以接收消息,msg则是从UI线程中发出的消息,可以传递各种对象,根据这些对象和数值进行操作。
有了Handler子类,则可以在UI线程中进行创建和初始化
HandlerThread handlerThread = new HandlerThread( "backgroundThread" );
handlerThread.start();
MyHandler myHandler = new MyHandler(handlerThread.getLooper());
Message msg = myHandler.obtainMessage();
//....此处对msg进行赋值,可以创建一个Bundle进行承载
msg.sendToTarget();
之后如果需要调用线程,只要对handler传入msg,就可以执行相应的过程了
最后,很重要的一点,HandlerThread 不随Activity的生命周期结束而消亡,所以必须复写Ondestory(),调用HandlerThread .stop()

3、使用线程同步 synchronized、 wait()、 notify()
使用线程同步机制synchronized实现多线程操作,相对来说比较复杂,但也是灵活性最佳、效率最高的一种做法,在产品开发当中也使用得最广。本人水平相当有限,也只学得一点皮毛。
synchronized相当于一把锁,当线程运行的时候,会同时有几个线程访问一个对象,对其进行操作或者修改。这可能引起很不良的后果(例如改变判定条件,或者在别的线程还没使用完的时候对象已经被析构等等),所以必须对一些对象进行加锁,保证它在同一时间只允许被一个线程访问。
synchronized使用方法有两种:
<1> 同步方法在方法名前加入synchronized关键字,则该方法在同一时间内只允许一个线程访问它,其代码逻辑较为简单,但使用起来不太灵活,并且大大降低效率,耗时太长的同步方法甚至会使得多线程失去原本的意义成为单线程
<2>同步参数 对某一个代码块加锁,并且以synchronized(obj)的方式,表明该代码块内的obj对象是线程同步的。这个做法使得代码灵活性大大加强,缩小同步代码块的范围,并且可以在不同的函数体和类内进行同步,唯一遗憾的是实现起来比较复杂,容易产生一些问题

而wait()和notify(),必须在synchronized锁的代码块内执行,否则系统将会报错。
有了以上基础,就可以很容易的创建后台线程了

Private Runnable backgroundRunnable = new Runnable () {
@Override
public void run() {
if(isFinish){
//.....
break;
}
for(;;){
synchronized(this){
//....写耗时过程
wait();
}
}
}
}

UI线程中,就是一般的创建线程过程了
Thread backgroundThread = new Thread (backgroundRunnable);
backgroundThread.start();
这样,在后台线程中会不断的循环,当执行完一次过程以后就会wait()休眠。然后在OnTouchListener或者OnClickListener内相应的位置执行
synchronized(backgroundRunnable){
backgroundRunnable.notify();
}
当用户触摸屏幕产生一个event的时候,线程就会被唤醒,执行下一次循环。
最后,还是内存安全问题,必须复写Activity中的OnDestory()方法,将标志量isFinish设为false,并且backgroundThread .stop()

‘叁’ eclipse中做android游戏如何创建Thread(线程)

1、第一种方式:使用Thread不带参数的构造方法,并重写run()方法
new Thread(){
public void run(){
//在run方法中调用需要执行的代码,完成后发送消息与主线程交互
Message msg=new Message();
msg.what=110;
handler.sengMessage();
}
}.start();
2、第二种方式:使用Thread带参数的构造方法,并重写Runable()中的run方法
new Thread(new Runable(){
public void run(){
//在run方法中调用需要执行的代码,完成后发送消息与主线程交互
Message msg=new Message();
msg.what=110;
handler.sengMessage();

}).start();
3、在UI线程中接收Thread子线程发送的消息,刷新UI界面

‘肆’ Android线程启动start()和run()的区别

         在java中有两种启动线程的方法,一种是start()方法,而另外一种是run()方法,但是在安卓开发中,用run()方法可能会出现一些问题,所以本文做以下区别:

1,run()方法,开启线程,实际还是在当前线程运行,线程的执行的顺序,按照程序的顺序执行,实际上是没有意义的,比如在主线程中请求网络,如果用run()方法,会阻塞主线程,导致界面没有反应.

2,start()方法,只有执行了start()方法线程会执行,这个方法是真正意义上的多线程,不会阻塞主线程,是开启另一个线程执行操作.

‘伍’ android 创建多线程的几种方法

Java提供了线程类Thread来创建多线程的程序。其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象。每个Thread对象描述了一个单独的线程。要产生一个线程,有两种方法:
◆需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法;
◆实现Runnalbe接口,重载Runnalbe接口中的run()方法。
http://blog.csdn.net/lanhuzi9999/article/details/31531813

‘陆’ Android 怎么启动一个工作线程及线程如何与UI线程交互

启动线程就调用start方法,跟UI交互的话可以借助Handler

‘柒’ Android开发之路-多线程

多线程作为Android开发中相对而言较为高阶的知识,其中用到相关的知识点是非常的多,所以在我们需要进行设计或者写多线程的代码就必须要进行相对谨慎的处理,这样就由必要对其要有着比较系统化的认知

我们一般将Android应用分成为两种:主线程和工作线程;主线程主要是用来进行初始化UI,而工作线程主要是进行耗时操作,例如读取数据库,网络连接等

Android系统是以进程为单位来对应用程序资源进行限制,这个问题的可以解释为:一个进程最多能够开几个线程?最好能开几个?但实则这个是没有上限这一说,主要是因为资源的限制

Android中关于主线程的理解:Android的主线程是UI线程,在Android中,四大组件运行在主线程中,在主线程中做耗时操作会导致程序出现卡顿甚至出现ANR异常,一个.

在一个程序中,这些独立运行的程序片断叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。多线程处理一个常见的例子就是用户界面。

线程总的来就是进程的一个实体,是CPU进行分派和调度的基本单位,拥有着比进程更小且能够独立运行的基本单位,线程本身基本上是不拥有系统资源,仅拥有一点在运行过程中必须拥有的资源,但它可与同属一个进程中的其他进程进行共享其所拥有的所有资源

线程状态有些地方将之分为5中状态,而且在Java Jdk中线程被其定义为6中状态,我们可以对其进行类比

普遍定义的5中状态:新建,就绪,运行,阻塞, 死亡

Java Jdk 定义状态

线程阻塞是指在某一时刻的某一个线程在进行运行一段代码的情况下,突然另一个线程也要进行运行,但在运行过程中,那个线程执行完全运行之前,另一个线程是不可能获取到CPU的执行权,就会导致线路阻塞的出现

死锁也称之为抱死,意思就是说一个进程锁定了另外一个进程所需要的页或表是,但第二个进程同时又锁定了第一个进程所需的一页,这样就会出现死锁现象

简要介绍实现线程的三种方式:继承Thread,实现runnable,实现callable。这里有一点需要注意的是,实现callable是与线程池相关联的而callable很重要的一个特性是其带有返回值。当我们只需实现单线程时实现runnable更加利于线程程序的拓展

在线程开启之前进行调用 thread.setDaemon(true); 将thread设定成当前线程中的守护线程 使用案例

线程让步【yield方法】让当前线程释放CPU资源,让其他线程抢占

这种具体某个对象锁 wait & notify 方法与Condition 的 await以及signal方法类似; 全面这种方法的阻塞等待都可以是释放锁,而且在唤醒后,这种线程都是能够获取锁资源的,而这个门栓就跟阀门类似

‘捌’ Android多线程的四种方式:Handler、AsyncTask、ThreadPoolExector、IntentService

     异步通信机制,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理。Handler不仅仅能将子线程的数据传递给主线程,它能实现任意两个线程的数据传递。

(1)Message

    Message 可以在线程之间传递消息。可以在它的内部携带少量数据,用于在不同线程之间进行数据交换。除了 what 字段,还可以使用 arg1 和 arg2 来携带整型数据,使用 obj 来携带 Object 数据。

(2) Handler

    Handler 作为处理中心,用于发送(sendMessage 系列方法)与处理消息(handleMessage 方法)。

(3) MessageQueue

    MessageQueue 用于存放所有通过 Handler 发送的消息。这部分消息会一直存放在消息队列中,直到被处理。每个线程中只会有一个 MessageQueue 对象

(4) Looper

    Looper 用于管理 MessageQueue 队列,Looper对象通过loop()方法开启了一个死循环——for (;;){},不断地从looper内的MessageQueue中取出Message,并传递到 Handler 的 handleMessage() 方法中。每个线程中只会有一个 Looper 对象。

    AsyncTask 是一种轻量级的任务异步类,可以在后台子线程执行任务,且将执行进度及执行结果传递给 UI 线程。

(1)onPreExecute()

    在 UI 线程上工作,在任务执行 doInBackground() 之前调用。此步骤通常用于设置任务,例如在用户界面中显示进度条。

(2)doInBackground(Params... params)

    在子线程中工作,在 onPreExecute() 方法结束后执行,这一步被用于在后台执行长时间的任务,Params 参数通过 execute(Params) 方法被传递到此方法中。任务执行结束后,将结果传递给 onPostExecute(Result) 方法,同时我们可以通过 publishProgress(Progress) 方法,将执行进度发送给 onProgressUpdate(Progress) 方法。

(3)onProgressUpdate(Progress... values)

    在 UI 线程上工作,会在 doInBackground() 中调用 publishProgress(Progress) 方法后执行,此方法用于在后台计算仍在执行时(也就是 doInBackgound() 还在执行时)将计算执行进度通过 UI 显示出来。例如,可以通过动画进度条或显示文本字段中的日志,从而方便用户知道后台任务执行的进度。

(4)onPostExecute(Result result)

    在 UI 线程上工作,在任务执行完毕(即 doInBackground(Result) 执行完毕)并将执行结果传过来的时候工作。

使用规则:

(1)AsyncTask 是个抽象类,所以要创建它的子类实现抽象方法

(1)AsyncTask 类必须是在 UI 线程中被加载,但在Android 4.1(API 16)开始,就能被自动加载完成。

(2)AsyncTask 类的实例对象必须在 UI 线程中被创建。

(3)execute() 方法必须是在 UI 线程中被调用。

(4)不要手动调用方法 onPreExecute()、onPostExecute()、doInBackground()、onProgressUpdate()

(5)任务只能执行一次(如果尝试第二次执行,将抛出异常)。即一个AsyncTask对象只能调用一次execute()方法。

原理:

          其源码中原理还是 Thread 与 Handler 的实现,其包含 两个线程池,一个 Handler,如下所示:

名称类型作用

SERIAL_EXECUTOR线程池分发任务,串行分发,一次只分发一个任务

THREAD_POOL_EXECUTOR线程池执行任务,并行执行,执行的任务由 SERIAL_EXECUTOR 分发

InternalHandlerHandler负责子线程与主线程的沟通,通知主线程做 UI 工作

    一方面减少了每个并行任务独自建立线程的开销,另一方面可以管理多个并发线程的公共资源,从而提高了多线程的效率。所以ThreadPoolExecutor比较适合一组任务的执行。Executors利用工厂模式对ThreadPoolExecutor进行了封装。

Executors提供了四种创建ExecutorService的方法,他们的使用场景如下:

1. Executors.newFixedThreadPool()

    创建一个定长的线程池,每提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不再变化。

当线程处于空闲状态时,它们并不会被回收,除非线程池被关闭。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。

只有核心线程并且不会被回收,能够更加快速的响应外界的请求。

2. Executors.newCachedThreadPool()

    创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时,它可以灵活的添加新的线程,而不会对池的长度作任何限制

    线程数量不定的线程池,只有非核心线程,最大线程数为 Integer.MAX_VALUE。当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则利用空闲的线程来处理新任务。线程池中的空闲线程具有超时机制,为 60s。

    任务队列相当于一个空集合,导致任何任务都会立即被执行,适合执行大量耗时较少的任务。当整个线程池都处于限制状态时,线程池中的线程都会超时而被停止。

3. Executors.newScheledThreadPool()

    创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。

    非核心线程数没有限制,并且非核心线程闲置的时候立即回收,主要用于执行定时任务和具有固定周期的重复任务。

4. Executors.newSingleThreadExecutor()

    创建一个单线程化的executor,它只创建唯一的worker线程来执行任务

    只有一个核心线程,保证所有的任务都在一个线程中顺序执行,意义在于不需要处理线程同步的问题。

    一般用于执行后台耗时任务,当任务执行完成会自动停止;同时由于它是一个服务,优先级要远远高于线程,更不容易被系统杀死,因此比较适合执行一些高优先级的后台任务。

使用步骤:创建IntentService的子类,重写onHandleIntent方法,在onHandleIntent中执行耗时任务

    原理:在源码实现上,IntentService封装了HandlerThread和Handler。onHandleIntent方法结束后会调用IntentService的stopSelf(int startId)方法尝试停止服务。

    IntentService的内部是通过消息的方式请求HandlerThread执行任务,HandlerThread内部又是一种使用Handler的Thread,这就意味着IntentService和Looper一样是顺序执行后台任务的

(HandlerThread:封装了Handler + ThreadHandlerThread适合在有需要一个工作线程(非UI线程)+任务的等待队列的形式,优点是不会有堵塞,减少了对性能的消耗,缺点是不能同时进行多个任务的处理,需要等待进行处理。处理效率低,可以当成一个轻量级的线程池来用)

‘玖’ Android 中的“子线程”解析

Android 中线程可分为 主线程 和 子线程 两类,其中主线程也就是 UI线程 ,它的主要这作用就是运行四大组件、处理界面交互。子线程则主要是处理耗时任务,也是我们要重点分析的。

首先 Java 中的各种线程在 Android 里是通用的,Android 特有的线程形态也是基于 Java 的实现的,所以有必要先简单的了解下 Java 中的线程,本文主要包括以下内容:

在 Java 中要创建子线程可以直接继承 Thread 类,重写 run() 方法:

或者实现 Runnable 接口,然后用Thread执行Runnable,这种方式比较常用:

简单的总结下:

Callable 和 Runnable 类似,都可以用来处理具体的耗时任务逻辑的,但是但具体的差别在哪里呢?看一个小例子:

定义 MyCallable 实现了 Callable 接口,和之前 Runnable 的 run() 方法对比下, call() 方法是有返回值的哦,泛型就是返回值的类型:

一般会通过线程池来执行 Callable (线程池相关内容后边会讲到),执行结果就是一个 Future 对象:

可以看到,通过线程池执行 MyCallable 对象返回了一个 Future 对象,取出执行结果。

Future 是一个接口,从其内部的方法可以看出它提供了取消任务(有坑!!!)、判断任务是否完成、获取任务结果的功能:

Future 接口有一个 FutureTask 实现类,同时 FutureTask 也实现了 Runnable 接口,并提供了两个构造函数:

用 FutureTask 一个参数的构造函数来改造下上边的例子:

FutureTask 内部有一个 done() 方法,代表 Callable 中的任务已经结束,可以用来获取执行结果:

所以 Future + Callable 的组合可以更方便的获取子线程任务的执行结果,更好的控制任务的执行,主要的用法先说这么多了,其实 AsyncTask 内部也是类似的实现!

注意, Future 并不能取消掉运行中的任务,这点在后边的 AsyncTask 解析中有提到。

Java 中线程池的具体的实现类是 ThreadPoolExecutor ,继承了 Executor 接口,这些线程池在 Android 中也是通用的。使用线程池的好处:

常用的构造函数如下:

一个常规线程池可以按照如下方式来实现:

执行任务:

基于 ThreadPoolExecutor ,系统扩展了几类具有新特性的线程池:

线程池可以通过 execute() 、 submit() 方法开始执行任务,主要差别从方法的声明就可以看出,由于 submit() 有返回值,可以方便得到任务的执行结果:

要关闭线程池可以使用如下方法:

IntentService 是 Android 中一种特殊的 Service,可用于执行后台耗时任务,任务结束时会自动停止,由于属于系统的四大组件之一,相比一般线程具有较高的优先级,不容易被杀死。用法和普通 Service 基本一致,只需要在 onHandleIntent() 中处理耗时任务即可:

至于 HandlerThread,它是 IntentService 内部实现的重要部分,细节内容会在 IntentService 源码中说到。

IntentService 首次创建被启动的时候其生命周期方法 onCreate() 会先被调用,所以我们从这个方法开始分析:

这里出现了 HandlerThread 和 ServiceHandler 两个类,先搞明白它们的作用,以便后续的分析。

首先看 HandlerThread 的核心实现:

首先它继承了 Thread 类,可以当做子线程来使用,并在 run() 方法中创建了一个消息循环系统、开启消息循环。

ServiceHandler 是 IntentService 的内部类,继承了 Handler,具体内容后续分析:

现在回过头来看 onCreate() 方法主要是一些初始化的操作, 首先创建了一个 thread 对象,并启动线程,然后用其内部的 Looper 对象 创建一个 mServiceHandler 对象,将子线程的 Looper 和 ServiceHandler 建立了绑定关系,这样就可以使用 mServiceHandler 将消息发送到子线程去处理了。

生命周期方法 onStartCommand() 方法会在 IntentService 每次被启动时调用,一般会这里处理启动 IntentService 传递 Intent 解析携带的数据:

又调用了 start() 方法:

就是用 mServiceHandler 发送了一条包含 startId 和 intent 的消息,消息的发送还是在主线程进行的,接下来消息的接收、处理就是在子线程进行的:

当接收到消息时,通过 onHandleIntent() 方法在子线程处理 intent 对象, onHandleIntent() 方法执行结束后,通过 stopSelf(msg.arg1) 等待所有消息处理完毕后终止服务。

为什么消息的处理是在子线程呢?这里涉及到 Handler 的内部消息机制,简单的说,因为 ServiceHandler 使用的 Looper 对象就是在 HandlerThread 这个子线程类里创建的,并通过 Looper.loop() 开启消息循环,不断从消息队列(单链表)中取出消息,并执行,截取 loop() 的部分源码:

dispatchMessage() 方法间接会调用 handleMessage() 方法,所以最终 onHandleIntent() 就在子线程中划线执行了,即 HandlerThread 的 run() 方法。

这就是 IntentService 实现的核心,通过 HandlerThread + Hanlder 把启动 IntentService 的 Intent 从主线程切换到子线程,实现让 Service 可以处理耗时任务的功能!

AsyncTask 是 Android 中轻量级的异步任务抽象类,它的内部主要由线程池以及 Handler 实现,在线程池中执行耗时任务并把结果通过 Handler 机制中转到主线程以实现UI操作。典型的用法如下:

从 Android3.0 开始,AsyncTask 默认是串行执行的:

如果需要并行执行可以这么做:

AsyncTask 的源码不多,还是比较容易理解的。根据上边的用法,可以从 execute() 方法开始我们的分析:

看到 @MainThread 注解了吗?所以 execute() 方法需要在主线程执行哦!

进而又调用了 executeOnExecutor() :

可以看到,当任务正在执行或者已经完成,如果又被执行会抛出异常!回调方法 onPreExecute() 最先被执行了。

传入的 sDefaultExecutor 参数,是一个自定义的串行线程池对象,所有任务在该线程池中排队执行:

可以看到 SerialExecutor 线程池仅用于任务的排队, THREAD_POOL_EXECUTOR 线程池才是用于执行真正的任务,就是我们线程池部分讲到的 ThreadPoolExecutor :

再回到 executeOnExecutor() 方法中,那么 exec.execute(mFuture) 就是触发线程池开始执行任务的操作了。

那 executeOnExecutor() 方法中的 mWorker 是什么? mFuture 是什么?答案在 AsyncTask 的构造函数中:

原来 mWorker 是一个 Callable 对象, mFuture 是一个 FutureTask 对象,继承了 Runnable 接口。所以 mWorker 的 call() 方法会在 mFuture 的 run() 方法中执行,所以 mWorker 的 call() 方法在线程池得到执行!

同时 doInBackground() 方法就在 call() 中方法,所以我们自定义的耗时任务逻辑得到执行,不就是我们第二部分讲的那一套吗!

doInBackground() 的返回值会传递给 postResult() 方法:

就是通过 Handler 将最终的耗时任务结果从子线程发送到主线程,具体的过程是这样的, getHandler() 得到的就是 AsyncTask 构造函数中初始化的 mHandler , mHander 又是通过 getMainHandler() 赋值的:

可以在看到 sHandler 是一个 InternalHandler 类对象:

所以 getHandler() 就是在得到在主线程创建的 InternalHandler 对象,所以
就可以完成耗时任务结果从子线程到主线程的切换,进而可以进行相关UI操作了。
当消息是 MESSAGE_POST_RESULT 时,代表任务执行完成, finish() 方法被调用:

如果任务没有被取消的话执行 onPostExecute() ,否则执行 onCancelled() 。

如果消息是 MESSAGE_POST_PROGRESS , onProgressUpdate() 方法被执行,根据之前的用法可以 onProgressUpdate() 的执行需要我们手动调用 publishProgress() 方法,就是通过 Handler 来发送进度数据:

进行中的任务如何取消呢?AsyncTask 提供了一个 cancel(boolean mayInterruptIfRunning) ,参数代表是否中断正在执行的线程任务,但是呢并不靠谱, cancel() 的方法注释中有这么一段:

大致意思就是调用 cancel() 方法后, onCancelled(Object) 回调方法会在 doInBackground() 之后被执行而 onPostExecute() 将不会被执行,同时你应该 doInBackground() 回调方法中通过 isCancelled() 来检查任务是否已取消,进而去终止任务的执行!

所以只能自己动手了:

AsyncTask 整体的实现流程就这些了,源码是最好的老师,自己跟着源码走一遍有些问题可能就豁然开朗了!

‘拾’ android的按钮如何启动一个线程

Handler handler = new Handler();
Runnable updateThread = new Runnable() {
public void run() {
//里边写要实现的方法

}

};

写个按钮的监听,里边加 handler.post(updateThread);
移除线程则加handler.removeCallbacks(updateThread);

热点内容
java语义分析 发布:2024-05-05 05:32:39 浏览:754
我的世界服务器房型 发布:2024-05-05 05:31:16 浏览:701
pythonwhere 发布:2024-05-05 05:30:22 浏览:441
免费加密隐身侠 发布:2024-05-05 05:07:54 浏览:613
我的世界模组服务器推荐手机版 发布:2024-05-05 05:02:49 浏览:818
pr默认存储 发布:2024-05-05 04:29:31 浏览:553
roblox跑酷脚本怎么做 发布:2024-05-05 03:57:35 浏览:702
捷径清理缓存 发布:2024-05-05 03:57:35 浏览:479
ftputility哪里下载 发布:2024-05-05 03:47:13 浏览:1001
雷凌运动版如何连接安卓手机导航 发布:2024-05-05 03:42:48 浏览:268