Android 开发艺术探索笔记(十五) 之 Android 的线程和线程池

一、概述

Android 的线程分主线程和子线程,主线程主要处理界面相关的事情,而子线程往往用于执行耗时操作。Android 中扮演子线程角色的有:AsyncTaskIntentServiceHandlerThreadAsyncTask 封装了线程池和 Handler,主要用于在子线程中更新UI。 HandlerThread 是一种具有消息循环的线程,在它的内部可以使用 Handler。IntentService 是一个 Service,系统对其进行了封装使其可以方便地执行后台任务,执行完任务后自动调用 onDestroy() 退出。它是一个 Service,因而在执行后台任务的时候不容易被系统杀死。

二、AsyncTask

AsyncTask 是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后在主线程中回调执行的进度以及最终结果,从而更新 UI。其实,AsyncTask 内部封装了 Thread 和 Handler。

ASyncTask 是一个抽象类。

public abstract class AsyncTask<Params,Progress,Result>

主要有 4 个核心的方法:

  • onPreExecute():在主线程执行,在异步任务执行之前的准备工作,比如:提示 Loading

  • doInBackground(Params... params):在线程池中执行,此方法用于执行异步任务,params 表示异步任务的输入参数。

  • onProgressUpdated(Progress... value):在主线层中执行,当后台任务的进度发生改变时此方法被调用。

  • onPostExecute(Result result):在主线程执行,在异步任务执行完成后回调此方法。

  • onCancelled():在主线程执行,当异步任务被取消时会回调此方法。

举个栗子:

class DownloadTask extends AsyncTask<URL,Integer,Long>{

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

        @Override
        protected Long doInBackground(URL... urls) {
            return null;
        }

        @Override
        protected void onPostExecute(Long aLong) {
            super.onPostExecute(aLong);
        }

        @Override
        protected void onCancelled() {
            super.onCancelled();
        }
    }
    
    
    new DownloadTask().execute(url1,url2,url3);

划重点来啦 !!!

  • 1. AsyncTask 的类必须在主线程中加载,不过这个在 Android 4.1 及以上版本已经在 ActivityThread 中初始化了。

  • 2. AsyncTask 对象必须在主线程中创建。

  • 3. execute() 必须在 UI 线程中调用。

  • 4. 不要在程序中直接调用 onPreExcute()doInBackground()onPostExecute()onProgressUpdate()

  • 5. 一个 AsyncTask 对象只能执行一次,即只能调用一次 execute(),否则会报运行异常。

  • 6. 可以通过 executeOnExecutor() 来并行执行任务。

三、HandlerThread

HandlerThread 继承 Thread,它封装有 Looper 和 MessageQueue,可以绑定 Handler,从而实现线程间通信/异步消息处理机制。

举个栗子

/**
 * 使用 HandlerThread
 */
public class HandlerThreadActivity extends AppCompatActivity {

    private TextView mTvServiceInfo;

    private HandlerThread mCheckMsgThread;
    //HandlerThread 的 handler
    private Handler mCheckMsgHandler;
    private boolean isUpdateInfo;

    private static final int MSG_UPDATE_INFO = 0x110;

    //与UI线程管理的handler
    private Handler mHandler = new Handler();


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

        //创建后台线程
        initBackThread();

        mTvServiceInfo = findViewById(R.id.id_textview);
    }

    @Override
    protected void onResume() {
        super.onResume();
        //开始查询
        isUpdateInfo = true;
        mCheckMsgHandler.sendEmptyMessage(MSG_UPDATE_INFO);
    }

    @Override
    protected void onPause() {
        super.onPause();
        //停止查询
        isUpdateInfo = false;
        mCheckMsgHandler.removeMessages(MSG_UPDATE_INFO);

    }

    private void initBackThread() {
        mCheckMsgThread = new HandlerThread("check-message-coming");
        mCheckMsgThread.start();

        //绑定 HandlerThread 的 Looper
        mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {

                checkForUpdate();
                if (isUpdateInfo) {
                    //循环执行 HandlerThread Handler 的 handleMessage()
                    mCheckMsgHandler.sendEmptyMessageDelayed(MSG_UPDATE_INFO, 1000);
                }
            }
        };


    }

    /**
     * 模拟从服务器解析数据
     */
    private void checkForUpdate() {
        try {
            //模拟耗时
            Thread.sleep(1000);
            //切换回主线程
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    String result = "实时更新中,当前大盘指数:<font color='red'>%d</font>";
                    result = String.format(result, (int) (Math.random() * 3000 + 1000));
                    mTvServiceInfo.setText(Html.fromHtml(result));
                }
            });

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        //释放资源
        mCheckMsgThread.quit();
    }

}

这个栗子里面通过 HandlerThread 线程的 Handler 来通知 UI 线程的 Handler 来更新 UI,同时在 handleMessage() 方法中循环执行 HandlerThread 的方法,这个也是一种定时循环执行任务的方法。

源码分析:

/**
 * Handy class for starting a new thread that has a looper. The looper can then be 
 * used to create handler classes. Note that start() must still be called.
 */
public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    
    //内部有一个 Handler 成员变量
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    /**
     * Constructs a HandlerThread.
     * @param name
     * @param priority The priority to run the thread at. The value supplied must be from 
     * {@link android.os.Process} and not from java.lang.Thread.
     */
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }


    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
}

    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

我们仔细看看 run(),这里面调用了 Looper.prepare()Loop.loop()Looper.prepare() 中创建了一个 Looper 对象,并且把该对象放到了该线程范围内的变量中(sThreadLocal),在 Looper 对象的构造过程中,初始化了一个 MessageQueue ,作为该 Looper 对象成员变量。Looper.loop() 就不断地循环从 MessageQueue 中取消息处理了,当没有消息的时候会阻塞,有消息的到来的时候会唤醒。

如果我们不想自己初始化 HandlerThread 的 Handler 对象,可以直接使用 HandlerThread 里面的 Handler,使用的时候就只能用 post(Runnable r),因为 HandlerThread 的 Handler 没有派生子类,只能使用 Message.callback 来执行任务。

源码

四、IntentService

IntentService 是一种特殊的 Service,它继承了 Service,并且是一个抽象类,子类必须实现 onHandleIntent() 才可以使用。IntentService 可用于执行后台任务,我们可以通过 startService(Intent) 来提交请求,该Service会在需要的时候创建,当完成所有的任务以后自己关闭,且请求是在工作线程处理的。而且由于它是 Service,比单纯的后台线程优先级高,不容易被系统杀死。

优点:

  • 1.不需要自己去 new Thread

  • 2.不需要考虑在什么时候关闭该 Service

举个栗子

使用 IntentService 上传照片

public class UploadImgService extends IntentService {

    private static final String ACTION_UPLOAD_IMG = "com.zhy.blogcodes.intentservice.action.UPLOAD_IMAGE";
    public static final String EXTRA_IMG_PATH = "com.zhy.blogcodes.intentservice.extra.IMG_PATH";

    /**
     * 外部调用开启任务
     * @param context
     * @param path
     */
    public static void startUploadImg(Context context, String path) {

        Intent intent = new Intent(context, UploadImgService.class);
        intent.setAction(ACTION_UPLOAD_IMG);
        intent.putExtra(EXTRA_IMG_PATH, path);
        context.startService(intent);
    }


    public UploadImgService() {
        super("UploadImgService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {

        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_UPLOAD_IMG.equals(action)) {
                final String path = intent.getStringExtra(EXTRA_IMG_PATH);
                handleUploadImg(path);
            }
        }
    }

    private void handleUploadImg(String path) {
        try {
            //模拟上传耗时
            Thread.sleep(3000);

            Intent intent = new Intent(MainActivity.UPLOAD_RESULT);
            intent.putExtra(EXTRA_IMG_PATH, path);
            //通过广播通知 UI 线程
            sendBroadcast(intent);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e("TAG","onCreate");
    }


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e("TAG","onBind");
        return null;
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.e("TAG","onSatrtCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Log.e("TAG","onSatrt");
        super.onStart(intent, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e("TAG","onDestroy");
    }

}

上面的代码主要就是继承IntentService,然后复写onHandleIntent方法,根据传入的intent来选择具体的操作。

运行 Log:
04-16 07:56:59.894 19936-19936/com.innovator.intentservicetest E/TAG: onCreate
onSatrtCommand
onSatrt
04-16 07:57:00.500 19936-19936/com.innovator.intentservicetest E/TAG: onSatrtCommand
onSatrt
04-16 07:57:00.774 19936-19936/com.innovator.intentservicetest E/TAG: onSatrtCommand
onSatrt
04-16 07:57:01.788 19936-19936/com.innovator.intentservicetest E/TAG: onSatrtCommand
onSatrt
04-16 07:57:01.965 19936-19936/com.innovator.intentservicetest E/TAG: onSatrtCommand
onSatrt

通过 startUploadImg(Context context, String path) 来创建多个任务,可以看到 onCreate()只调用了一次,onStartCommand()onStart() 会回调多次。

划重点啦 !!!

源码解析:

package android.app;

import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;


public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }


    public IntentService(String name) {
        super();
        mName = name;
    }


    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
                super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }


    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    protected abstract void onHandleIntent(Intent intent);
}

可以看到它在 onCreate() 里面初始化了一个 HandlerThread,同时实例化了一个绑定该 HandlerThread 的 Handler。当任务开始的时候,会回调 onStartCommand(),即回调 onStart(),在 onStart() 中可以看到这个 Handler 发送了一条消息给自己,于是就会调用自己的处理方法,最后调用 onHandleIntent((Intent)msg.obj) 方法,所以我们在使用 IntentService 的时候需要重写 onHandleIntent((Intent)msg.obj)

而且,由于 HandlerThread 是串行执行任务的,所以 IntentService 也是串行执行任务的,执行完成后会调用 stopSelf(msg.arg1); 判断是否销毁该 Service(stopSelf(msg.arg1);会等待所有消息都处理完毕才会终止服务)。所以当任务完成,销毁 Service 回调 onDestory()时,源码里面会释放了我们的 Looper: mServiceLooper.quit()

源码

五、线程池

使用线程池有几个好处:

  • 1.重用线程池的线程,避免为线程的创建和销毁所带来的性能开销

  • 2.能有效控制线程池的最大并发数,避免大量线程之间因互相抢占资源而导致的阻塞现象

  • 3.能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。

5.1 ThreadPoolExecutor

ThreadPoolExecutor 是线程池的真正实现,它的构造方法为:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
                          
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
  • corePoolSize:线程池的核心线程数,默认情况下,核心线程会在线程池一直存活,即使它们处于闲置的状态。如果将 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性为 true,那么闲置的核心线程在等待新任务到来的时候会有超时策略,这个时间间隔超过 keepAliveTime 后,核心线程也会被终止。

  • maximumPoolSize:线程池所能容纳的最大线程数。当活动线程数达到这个数值后,后续的新任务会被阻塞。

  • keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。

  • unit:超时时长单位:毫秒,秒,分

  • workQueue:线程池的任务队列,通过线程池的 execute() 提交的 Runnable 对象就存储在这个队列中。

  • threadFactory:线程工厂,为线程池提供创造新线程的功能。

ThreadPoolExecutor 执行任务时的规则:

  • 1.如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务

  • 2.如果线程池中的线程数量已经达到或者超过核心线程数量,那么任务就会被插入到任务队列中排队等待执行

  • 3.如果步骤2中的任务无法插到任务队列,这往往是由于任务队列已满,这时如果线程数量未达到线程池的最大值,那么就会立刻启动一个非核心线程来执行任务。

  • 4.如果步骤3中线程数量已经达到线程池规定的最大值,那么就会拒绝执行这项任务,ThreadPoolExecutor 就会调用 RejectedExecutionHandler 的 rejectExecution() 来通知调用方。

5.2 线程池分类

  • 1.FixedThreadPool:一种线程数量固定的线程池,核心线程数就是线程池能容纳的最大线程数。所以当线程处于空闲状态时,它们并不会回收,除非线程池被关闭了。当所有线程都处于活动状态时,新任务都会处于等待状态,直到有空闲的线程。因为 FixedThreadPool 只有核心线程并且这些核心线程不会被回收,这就意味着它能更加快速地响应外界的请求。FixedThreadPool 的任务队列没有大小限制。

    构造方法:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    

使用方法:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
fixedThreadPool.execute(Runnable r);
  • CachedThreadPool:一种线程数量不定的线程池,只有非核心线程,并且最大线程数为 Interger.MAX_VALUE。当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理任务,否则就会利用空闲线程来处理新任务。线程池中的空闲线程都有超时机制,超时时长为 60s,超过 60s 就会被回收。它比较适合执行大量的耗时较少的任务。

    构造方法:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    

使用方法:

ExecutorService cacheThreadPool = Executors.newCachedThreadPool();
cacheThreadPool.execute(Runnable r);
  • ScheduledThreadPool:核心线程数量固定,线程池能容纳的最大线程数没有限制,并且当非核心线程闲置时会被立即回收。主要用于执行定时任务和具有固定周期的重复任务。

    构造方法:

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }
    

使用方法:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
      //延迟2秒后执行该任务
      scheduledThreadPool.schedule(new Runnable() {
          @Override
          public void run() {

          }
      }, 2, TimeUnit.SECONDS);
      
      //延迟1秒后,每隔2秒执行一次该任务
      scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
          @Override
          public void run() {

          }
      }, 1, 2, TimeUnit.SECONDS);
  • SingleThreadExecutor:这类线程池内部只有一个核心线程,它确保所有任务都在同一个线程中按顺序执行。意义就是统一所有外界的任务到同一线程中,使得这些任务之间不需要处理线程同步的问题。

    构造方法:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    

使用方法:

ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();

      //延迟1秒后,每隔2秒执行一次该任务
      singleThreadScheduledPool.scheduleAtFixedRate(new Runnable() {
          @Override
          public void run() {
              String threadName = Thread.currentThread().getName();
              Log.v("zxy", "线程:" + threadName + ",正在执行");
          }
      },1,2,TimeUnit.SECONDS);
      
   singleThreadScheduledPool.execute(Runnable r);   

六、参考资料

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

推荐阅读更多精彩内容