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<Runnable> workQueue);//线程池所使用的任务缓冲队列。

7. AsyncTask

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

import android.os.AsyncTask;

public class FullTask extends AsyncTask<Params,Progress,Result>{
//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并不适合特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容