Android线程和线程池(1)——线程的介绍和HandlerThread

1.Android线程的基本介绍

线程在Android中是一个很重要的概念,从用途上说,线程分为主线程和子线程。主线程主要处理和界面相关的事情,而子线程则往往用于执行耗时操作。
 
除了Thread本身除外,在Android中可以扮演线程角色的的还有很多,比如AsyncTask和IntentService,同时HandlerThread也是一种特殊的线程。尽管他们的表现形式有别于传统的线程,但是他们的本质仍然是传统的线程。不同形式的线程虽然都是线程,但是它们仍然有不同的特性和使用场景。

2.AsyncTask

长久以来,AsyncTask在Android开发中一直扮演着一个重要的角色,它主要用于执行一些不太长的异步任务。作为用来替代Thread + Handler的辅助类,AsyncTask可以很轻松地执行异步任务并更新ui,但由于context泄露,回调遗漏,configuration变化导致崩溃,平台差异性等原因,在api 30(Android 11)中AsyncTask被正式废弃:
 
官方文档说明:

AsyncTask was intended to enable proper and easy use of the UI thread. However, the most common use case was for integrating into UI, and that would cause Context leaks, missed callbacks, or crashes on configuration changes. It also has inconsistent behavior on different versions of the platform, swallows exceptions from doInBackground, and does not provide much utility over using Executors directly.
AysncTask意图提供简单且合适的UI线程使用,然而,它最主要的使用实例是作为UI的组成部分,会导致内存泄漏,回调遗失或改变配置时崩溃。且它在不同版本的平台上有不一致的行为,吞下来自doInBackground的异常,并且不能提供比直接使用Executors更多的功能。

被弃用后,Android给出了两个替代的建议:

  1. java.util.concurrent包下的相关类,如Executor,ThreadPoolExecutor,FutureTask。
  2. kotlin并发工具,那就是协程 - Coroutines了。

 
具体的可以参考如下博客:
 
android多线程-AsyncTask之工作原理深入解析(上)

android多线程-AsyncTask之工作原理深入解析(下)

震惊!AsyncTask将被弃用?

3.HandlerThread

HandlerThread是Thread的一个子类,类似于Handler+Thread。HandlerThread自带一个Looper使得他可以通过消息队列来重复使用当前线程,节省资源开销。但是他也存在的问题就是,每一个任务都以队列的形式执行,导致如果有某一个任务执行时间过长的时候,后面的任务就只能等他执行完毕再执行。
 
他和普通线程的区别就是他在内部维护了一个looper(也正是这个原因所以他可以创建对应的Handler),然后就会通过Handler的方式来通过Looper执行下一个任务。

由于内部的Looper是一个无限循环,所以当我们不再使用HandlerThread的时候就需要调用quit方法来终止他。

优势:

  1. 将loop运行在子线程中处理,减轻了主线程的压力,使主线程更流畅
  2. 串行执行,开启一个线程起到多个线程的作用
  3. 有自己的消息队列,不会干扰UI线程

劣势:

  1. 由于每一个任务队列逐步执行,一旦队列耗时过长,消息延时
  2. 对于IO等操作,线程等待,不能并发

3.1工作原理

内部原理 = Thread类 + Handler类机制,即:

  • 通过继承Thread类,快速地创建1个带有Looper对象的新工作线程
  • 通过封装Handler类,快速创建Handler & 与其他线程进行通信

3.2使用步骤

// 步骤1:创建HandlerThread实例对象
// 传入参数 = 线程名字,作用 = 标记该线程
   HandlerThread mHandlerThread = new HandlerThread("handlerThread");

// 步骤2:启动线程
   mHandlerThread.start();

// 步骤3:创建工作线程Handler & 复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
  Handler workHandler = new Handler( handlerThread.getLooper() ) {
            @Override
            public boolean handleMessage(Message msg) {
                ...//消息处理
                return true;
            }
        });

// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
  // a. 定义要发送的消息
  Message msg = Message.obtain();
  msg.what = 2; //消息的标识
  msg.obj = "B"; // 消息的存放
  // b. 通过Handler发送消息到其绑定的消息队列
  workHandler.sendMessage(msg);

// 步骤5:结束线程,即停止线程的消息循环
  mHandlerThread.quit();

完整实例:


public class MainActivity extends AppCompatActivity {
    
    
    
    Handler mainHandler,workHandler;
    HandlerThread mHandlerThread;

    private final String[] url ={
    
    
            "https://img-blog.csdn.net/20160903083245762",
            "https://img-blog.csdn.net/20160903083252184",
            "https://img-blog.csdn.net/20160903083257871",
            "https://img-blog.csdn.net/20160903083257871",
            "https://img-blog.csdn.net/20160903083311972",
            "https://img-blog.csdn.net/20160903083319668",
            "https://img-blog.csdn.net/20160903083326871"
    };
    private ImageView imageView;

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

        initView();
        initData();

    }
    private void initView() {
    
    
        imageView= (ImageView) findViewById(R.id.image);
        mainHandler = new Handler(getMainLooper()){
    
    
            @Override
            public void handleMessage(@NonNull Message msg) {
    
    
                ImageModel model = (ImageModel) msg.obj;
                if(model.getBitmap()!=null){
    
    
                    imageView.setImageBitmap(model.getBitmap());
                }
            }
        };
    }
    private void initData() {
    
    
        HT_DownloadThePictures();
    }

    private void HT_DownloadThePictures() {
    
    
        /*
         * 步骤1:创建Handler实例对象
         * 传入参数 = 线程名称,作用 = 标记改线程
         */
        mHandlerThread = new HandlerThread("handlerThread");
        /*
         * 步骤2:启动线程
         */
        mHandlerThread.start();
        /*
         * 步骤3:
         * 创建工作线程Handler & 复写handleMessage()
         * 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
         * 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
         */
        workHandler = new Handler(mHandlerThread.getLooper(),new childCallback());
        /*
         * 步骤4:
         * 使用工作线程Handler向工作线程的消息队列发送消息
         * 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
         */
        for(int i = 0; i < 7; i++){
    
    
            /*
             * 使用工作线程Handler向工作线程的消息队列发送消息
             * 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
             */
            workHandler.sendEmptyMessageDelayed(i,1000*i);
        }

    }

    class childCallback implements Handler.Callback{
    
    

        @Override
        public boolean handleMessage(@NonNull Message msg) {
    
    
            //在子线程进行网络请求
            Bitmap bitmap = downloadUrlBitmap(url[msg.what]);
            ImageModel imageModel = new ImageModel();
            imageModel.setBitmap(bitmap);
            imageModel.setUrl(url[msg.what]);

            Message msg1 = new Message();
            msg1.what = msg.what;
            msg1.obj = imageModel;

            mainHandler.sendMessage(msg1);
            return false;
        }
    }

    private Bitmap downloadUrlBitmap(String urlString) {
    
    
        /*
        1. HttpURLConnection网络请求加载
         */
        /*
        Bitmap bitmap = null;
        HttpURLConnection httpURLConnection = null;
        InputStream in = null;
        try {
            URL url = new URL(urlString);
            httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setRequestMethod("GET");
            httpURLConnection.setReadTimeout(8000);     //设置读取超时
            httpURLConnection.setConnectTimeout(8000);  //读取连接超时
            httpURLConnection.setDoOutput(true);
            httpURLConnection.setDoInput(true);

            in = httpURLConnection.getInputStream();
            bitmap = BitmapFactory.decodeStream(in);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (httpURLConnection != null) {
                httpURLConnection.disconnect();
            }
            try {
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }
        return bitmap;

         */
        /*
        2.Glide加载
         */
        FutureTarget<Bitmap> futureTarget =
                Glide.with(getApplicationContext())
                        .asBitmap()
                        .load(urlString).submit(100,100);

        Bitmap bitmap = null;
        try {
    
    
            bitmap = futureTarget.get();
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        // Do something with the Bitmap and then when you're done with it:
        Glide.with(getApplicationContext()).clear(futureTarget);
        return bitmap;
    }
}
//ImageModel.java
public class ImageModel {
    
    
    private Bitmap bitmap;
    private String url;

    public Bitmap getBitmap() {
    
    
        return bitmap;
    }

    public void setBitmap(Bitmap bitmap) {
    
    
        this.bitmap = bitmap;
    }

    public String getUrl() {
    
    
        return url;
    }

    public void setUrl(String url) {
    
    
        this.url = url;
    }
}

3.3 HandlerThread的特点

  • HandlerThread将loop转到子线程中处理,说白了就是将分担MainLooper的工作量,降低了主线程的压力,使主界面更流畅。
  • 开启一个线程起到多个线程的作用。处理任务是串行执行,按消息发送顺序进行处理。HandlerThread本质是一个线程,在线程内部,代码是串行处理的。
  • 但是由于每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
  • HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。
  • 对于网络IO操作,HandlerThread并不适合,因为它只有一个线程,还得排队一个一个等着。

3.4 源码分析

先来看构造函数

public class HandlerThread extends Thread {
    int mPriority;			//线程优先级
    int mTid = -1;
    Looper mLooper;			//当前线程持有的Looper对象
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    protected void onLooperPrepared() {
    }

从源码可以看出HandlerThread继承自Thread,构造函数的传递参数有两个,一个是name指的是线程的名称,一个是priority指的是线程优先级,我们根据需要调用即可。其中成员变量mLooper就是HandlerThread自己持有的Looper对象。onLooperPrepared()该方法是一个空实现,是留给我们必要时可以去重写的,但是注意重写时机是在Looper循环启动前,再看看run方法:

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

前面我们在HandlerThread的常规使用中分析过,在创建HandlerThread对象后必须调用其start()方法才能进行其他操作,而调用start()方法后相当于启动了线程,也就是run方法将会被调用,而我们从run源码中可以看出其执行了Looper.prepare()代码,这时Looper对象将被创建,当Looper对象被创建后将绑定在当前线程(也就是当前异步线程),这样我们才可以把Looper对象赋值给Handler对象,进而确保Handler对象中的handleMessage方法是在异步线程执行的。接着将执行代码:

synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }

这里在Looper对象创建后将其赋值给HandlerThread的内部变量mLooper,并通过notifyAll()方法去唤醒等待线程,最后执行Looper.loop();代码,开启looper循环语句。那这里为什么要唤醒等待线程呢?我们来看看,getLooper方法

public Looper getLooper() {
//先判断当前线程是否启动了
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();		//等待唤醒
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

事实上可以看出外部在通过getLooper方法获取looper对象时会先先判断当前线程是否启动了,如果线程已经启动,那么将会进入同步语句并判断Looper是否为null,为null则代表Looper对象还没有被赋值,也就是还没被创建,此时当前调用线程进入等待阶段,直到Looper对象被创建并通过 notifyAll()方法唤醒等待线程,最后才返回Looper对象,之所以需要等待唤醒机制,是因为Looper的创建是在子线程中执行的,而调用getLooper方法则是在主线程进行的,这样我们就无法保障我们在调用getLooper方法时Looper已经被创建,到这里我们也就明白了在获取mLooper对象时会存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值,HandlerThread内部则通过等待唤醒机制解决了同步问题。

 public boolean quit() {
    
    
        Looper looper = getLooper();
        if (looper != null) {
    
    
            looper.quit();
            return true;
        }
        return false;
    }
 public boolean quitSafely() {
    
    
        Looper looper = getLooper();
        if (looper != null) {
    
    
            looper.quitSafely();
            return true;
        }
        return false;
    }

从源码可以看出当我们调用quit方法时,其内部实际上是调用Looper的quit方法而最终执行的则是MessageQueue中的removeAllMessagesLocked方法(Handler消息机制知识点),该方法主要是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送)还是非延迟消息。
  当调用quitSafely方法时,其内部调用的是Looper的quitSafely方法而最终执行的是MessageQueue中的removeAllFutureMessagesLocked方法,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理完成后才停止Looper循环,quitSafely相比于quit方法安全的原因在于清空消息之前会派发所有的非延迟消息。
 
参考博客
Android 多线程之HandlerThread 完全详解

猜你喜欢

转载自blog.csdn.net/haazzz/article/details/115053153