Android线程之异步处理技术/消息机制的应用/Thread的子类们

注意:本篇文章是本人阅读相关文章所写下的总结,方便以后查阅,所有内容非原创,侵权删。

本篇文章内容来自于:
Android开发艺术探索
Android第一行代码
Android高级进阶
Android中Handler的使用
Android异步处理技术

目录

  1. 异步处理技术有哪些?
  2. Thread(基础类)
    --2.1 创建线程(2种方法)
    --2.2 线程分类(主线程+Binder线程+后台线程)
  3. HandlerThread
  4. AsyncQueryHandler(待补)
  5. IntentService(待补)
  6. Executor Framework 线程池
  7. AsyncTask

1.异步处理技术有哪些?

异步处理技术继承图

2. Thread(基础类)

线程是Java语言的一种概念,是实际执行任务的基本单元。
Thread是Android中异步处理技术的基础。

2.1 创建线程(2种方法)

方法一:继承Thread类并重写run方法

public class Mythread extends Thread {
    @Override
    public void run() {
        //实现具体的逻辑,如文件读写,网络请求等
    }
    public void startThread(){
        Mythread mythread = new Mythread();
        mythread.start();//使用start启动线程
    }
}

方法二:实现Runnable接口并实现run方法

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        //实现具体的逻辑,如文件读写,网络请求等
    }
    public void startThread(){
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();//同样利用start启动线程
    }
}

2.2 线程分类

Android应用中各种类型的线程本质上都基于Linux系统的pthreads。
在应用层可以分为三种类型的线程:

1.主线程/UI线程
主线程随着应用启动而启动。
主线程用来运行Android组件,同时刷新屏幕上的UI元素。
非主线程更新UI组件,会抛出CallFromWrongThreadException异常。

为什么只有主线程才能操作UI?
因为Android的UI工具不是线程安全的,只能让他在同一个线程中操作UI来保证线程安全。

为什么主线程会出现阻塞?
主线程中创建的Handler会顺序执行接收到的消息,包括从其他线程发送的消息。
因此如果消息队列中前面的消息没有很快执行完,那么它可能会阻塞队列中的其他消息的及时处理。

2.Binder线程
Binder线程用于不同进程之间线程的通信。
每个进程都维护了一个线程池,用来处理其他进程中线程发送的消息。

其他进程有哪些?
其他进程包括系统服务、Intents、ContentProviders和Service等

大部分情况下,应用不需要关心Binder进程,因为系统会优先将请求转换为使用主线程。

一个典型的需要使用Binder进程场景是:
应用提供一个给其他进程通过AIDL接口绑定的Service。

3.后台线程
在应用中显式创建的线程都是后台线程。

2.3 Thread和Handler、Looper配合使用

(1) 主线程Thread中可直接使用Handler

public class MainActivity extends BaseActivity {
    //在执行new Handler()的时候,默认情况下Handler会绑定当前代码执行的线程
    //handler在主线程中创建,所以自动绑定主线程
    private Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MyThread myThread = new MyThread();
        myThread.start();
    }

    class MyThread extends Thread {
        @Override
        public void run() {

            //...

            //向另外一个线程发送消息
            //运行Runnable代码的线程与Handler所绑定的线程是一致的
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    //进行操作
                }
            };
            handler.post(runnable);
        }
    }
}

(2) 子线程使用Handler必须先创建Looper

        new Thread("thread1") {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler();
                Looper.loop();
            }
        }.start();

3. HandlerThread

HandlerThread是一个集成了Looper和MessageQueue的线程。
当启动HandlerThread时,会同时生成Looper和MessageQueue,然后等待消息进行处理。
使用HandlerThread的好处是开发者不需要自己去创建和维护Looper。

HandlerThread中只有一个消息队列,队列中的消息是顺序执行的,因此是线程安全的。队列中的人物可能会被前面没有执行完的任务阻塞。

3.1 使用HandlerThread

用法和普通线程一样。

        HandlerThread handlerThread = new HandlerThread("handlerThread");
        handlerThread.start();

        Handler handler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //处理 在handlerThread中
            }
        };

3.2 HandlerThread的进一步用法(开始接受消息前进行初始化)

可以重写HandlerThread的onLooperPrepared函数。
比如可以在这个函数中创建于HandlerThread关联的Handler实例,这同时也可以对外隐藏我们的Handler实例,提供公共方法来让外界来调用。

public class MyHandlerThread extends HandlerThread {

    private Handler handler;

    public MyHandlerThread(String name) {
        super(name);
    }

    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        handler = new Handler(getLooper()){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1:
                        break;
                }
            }
        };
    }
    
    public void publishedMethod1(){
        handler.sendEmptyMessage(1);
    }
    public void publishedMethod2(){
        handler.sendEmptyMessage(2);
    }
}

4. AsyncQueryHandler

AsyncQueryHandler是用于在ContentProvider上面执行异步的CRUD操作的工具类。
CRUD操作会被放在一个单独的子线程中执行,当操作结束获取到结果后,将通过消息的方式传递给调用AsyncQueryHandler的线程,通常是主线程。

5. IntentService

Service的每个生命周期函数都是运行在主线程的,因此它本身不是一个异步处理技术。
为了能够在Service中实现在子线程中处理耗时任务。Android引入了一个Service的子类:IntentService。

6. Executor Framework 线程池

创建和销毁对象(例如线程),是存在开销的
如果应用中频繁出现线程的创建和销毁,那么会影响到应用的性能。
使用Java Executor框架可以通过线程池等机制解决这个问题

Executor框架为开发者提供了如下能力:

  • 创建工作线程池,同时通过队列来控制能够在这些线程执行的任务的个数。
  • 检测导致线程意外终止的错误
  • 等待线程执行完成并获取执行结果
  • 批量执行线程,并通过固定的顺序获取执行结果。
  • 在合适的时机启动后台线程,从而保证线程执行结果可以很快反馈给用户

Executor框架的基础是Executor接口
Executor的主要目的是分离任务的创建和它的执行。

public interface Executor {
    void execute(Runnable command);
}

开发者可以通过实现Executor接口并重写execute方法从而实现自己的Executor类。但实际应用中需要增加类似队列,任务优先级的功能,最终实现一个线程池。

线程池是任务队列和工作线程的集合,这两者组合起来实现生产者消费者模式。

Executor框架为开发者提供了预定义的线程池实现

  • 固定大小的线程池:通过Executors.newFixedThreadPool(n)创建,其中n是线程池中线程的个数
  • 可变大小的线程池:通过Executors.newCachedThreadPool()创建。当有新的任务需要执行时,线程池会创建新的线程来处理它,空闲的线程会等待60s来执行新任务,当没有任务可执行时自动销毁,因此可变大小线程池会根据任务队列的大小而变化。
  • 单个线程的线程池:通过Executors.newSingleThreadExecutor()创建,这个线程池中永远只有一个线程来串行执行任务队列中的任务。

预定义的线程池都是基于ThreadPoolExecutor类之上构建的,
而通过ThreadPoolExecutor可以自定义线程的一些行为。

ThreadPoolExecutor自定义线程池

 ThreadPoolExecutor executor = new ThreadPoolExecutor(
                              int corePoolSize, //核心线程数,核心线程会一直存在于线程池中,即使当前没有任务需要处理;当线程数小于核心线程数时,即使当前有空闲的线程,线程池也会优先创建新的线程来处理任务。
                              int maximumPoolSize,//最大线程数,当线程数大于核心线程数,且任务队列已经满了,这时线程池就会创建新的线程,知道线程数量达到最大线程数为止。
                              long keepAliveTime,//线程的空闲存活时间,当线程的空闲时间超过这个时间,线程会被销毁,直到线程数等于核心线程数。
                              TimeUnit unit,//keepAliveTime的单位,可选的有TimeUnit类中的单位
                              BlockingQueue workQueue);//线程池所使用的任务缓冲队列。

7. AsyncTask

AsyncTask是在Executor框架基础上进行的封装,它将耗时任务移动到工作线程中执行,同时提供方便的接口实现工作线程和主线程的通信。

import android.os.AsyncTask;

public class FullTask extends AsyncTask{
//Params 执行AsyncTask时需要传入的参数,可用于在后台任务中使用
//Progress 后台任务执行时,如果需要在界面上显示当前的进度,则使用这里的泛型当进度单位。
//Result 当任务执行完成后,需要对结果进行返回,则这里泛型作为返回值单位。

    @Override
    //会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作。比如显示一个进度条对话框
    protected void onPreExecute() {
        super.onPreExecute();
    }
    
    @Override
    //在子线程运行,用于处理耗时操作
    //任务一旦执行完,就通过return语句返回任务的执行结果
    //不可UI操作,如果反馈当前任务的执行速度,调用publishProgress(Progress ...)方法
    protected Result doInBackground(Params... params) {
        return null;
    }

    @Override
     //当后台任务中调用publishProgress,onProgressUpdate会很快被调用
     //可进行UI操作
    protected void onProgressUpdate(Progress... values) {
        super.onProgressUpdate(values);
    }

    @Override
    //当后台任务执行完毕并通过return语句返回时,调用该方法。
    //可UI操作,可用于提醒任务执行的结果,以及关闭掉进度条对话框
    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

使用

new AsyncTask().execute();
//不同系统版本 AsyncTask的execute和executeOnExecutor方法的运行有差别(串行和并行的区别)。不同的版本不同。

一个应用中使用的所有AsyncTask实例会共享全局的属性,即所有AsyncTask实例会共享一个线程池。
如果AsyncTask中的任务是串行执行的,那么应用中所有的AsyncTask会进行排队,只有等前面的任务执行完成后才会执行下一个。
如果AsyncTask是异步执行的话,那么在四核CPU系统上,最多也只有五个任务可以同时进行,其他任务需要在队列中排队,等待空闲的线程。

AsyncTask内部实现
AsyncTask内部封装了Thread和Handler,通过AsyncTask可以更加方便地执行后台任务以及主线程中访问UI。
但是AsyncTask并不适合特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池

你可能感兴趣的:(Android线程之异步处理技术/消息机制的应用/Thread的子类们)