天天看点

Android多线程实现-Handler详解Android常用的多线程实现方法总结

文章目录

  • Android常用的多线程实现方法
    • 1.Java原生的实现
      • 继承Thread类与实现Runnable接口的区别
      • 实现Runnable接口与实现Callable接口的区别
    • 2.AsyncTask
    • 3.Handler
      • Handler的基本使用
      • Looper、MessageQueue、Message与Handler
  • 总结

Android常用的多线程实现方法

说起多线程编程大家都不陌生,所谓线程就是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。在Android开发中,当我们执行一些耗时操作,比如发起一条网络请求时,我们不知道服务器何时会响应请求,这类操作需要放在新的线程执行,以免阻塞主线程(UI线程)。在Android开发中常用的实现多线程的方式有以下几种:

1.Java原生的实现

作为Android开发常用的语言,Java已经为我们提供了多线程的实现,主要有以下三种方式:

1.继承Thread类,重写run方法;

2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target;

3.通过Callable和FutureTask创建线程;

继承Thread类与实现Runnable接口的区别

那么这三种方法也是各有特点,实现Runnable接口方法相比于继承Thread类就有这几个优势:

1)由于Java不支持多继承,因此继承Thread类之后就不可以再继承别的类了,而实现Runnable接口方法可以解决单继承而带来的局限性;

2)增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的

3)适合多个相同程序代码的线程去处理同一资源的情况

4)线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类

针对2)和3)这两点,我们以卖票系统举个例子,通过继承Thread类方式实现多线程如下:

public class MutliThreadDemo {  
    public static void main(String[] args) {  
        MutliThread m1=new MutliThread("Window 1");   
        MutliThread m2=new MutliThread("Window 2");   
        MutliThread m3=new MutliThread("Window 3");   
        m1.start();  
        m2.start();  
        m3.start();  
    }  
}  
           
public class MutliThread extends Thread {  
    private int ticket=100;//每个线程都拥有100张票   
  
    public MutliThread (){}  
    public MutliThread (String name){  
        super(name);  
    }  
      
    @Override  
    public void run() {  
        while(ticket>0){   
            System.out.println(ticket--+" is saled by "+Thread.currentThread().getName());   
        }   
    }  
}  
           

从结果可以看到,每个线程分别对应100张电影票,之间并无任何关系,这就说明每个线程之间是平等的,系统会分别为其分配内存资源。而如果我们要实现资源共享,也就是说这些线程卖同一份票池,使用这种方法就无法实现了,而使用Runnable接口方式就可以解决这个问题:

public class MutliThreadDemo {  
    public static void main(String[] args) {  
        MutliThread m=new MutliThread();   
        Thread t1=new Thread(m);   
        Thread t2=new Thread(m);   
        Thread t3=new Thread(m);   
        t1.start();   
        t2.start();   
        t3.start();   
    }  
} 
           
public class MutliThread implements Runnable{   
    private int ticket=100;//每个线程都拥有100张票   
    public void run(){   
        while(ticket>0){   
            System.out.println(ticket--+" is saled by "+Thread.currentThread());   
        }   
    }   
}  
           

如上所示,程序在内存中仅创建了一份资源,而新建的三个线程都是访问同一资源的。

在第一种方法中中,要想开多个线程,就要先new多个自定义类MutliThread对象,每一个自定义类MyThread的对象的成员变量都相同,这样需要在栈中开辟很多内存;

在第二种方法中中,要想开多个线程,只需要new一个自定义类MutliThread的对象,再new多个Thread类的对象即可,这样就大大节约了内存。

因此,我们说Runnable接口方式相比于继承Thread类方式,适合多个相同程序的代码去处理同一个资源的情况,能够把线程同程序的代码和数据有效分离(即耦合性降低),较好的体现了Java面向对象的设计思想。

实现Runnable接口与实现Callable接口的区别

实现Runnable接口和实现Callable接口的区别主要有以下几点:

1)Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的

2)实现Callable接口的任务线程能返回执行结果(需要调用FutureTask.get()方法实现,但会阻塞主线程直到获取返回结果),而实现Runnable接口的任务线程不能返回结果

3)Callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

4)加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法

我们看到相比于Runnable接口方法,Callable接口可以返回执行结果。因此如果你希望任务在完成的能返回一个值,那么可以通过Callable接口实现。Callable接口使用泛型去定义它的返回类型,它的参数类型表示的是从方法call()(不是run())中返回的值,下面简单说一下用用法。

首先定义一个类实现java.util.concurrent包下的Callable接口,然后重写里面的call方法(相当于其他方法的run()函数),创建ExecutorService线程池,将自定义类的对象放入线程池里面,即可获取线程的返回结果,最后,关闭线程池,不再接收新的线程,未执行完的线程不会被关闭。简单的示例如下:

public static void main(String[] args) {
        
    //创建线程池对象
    ExecutorService pool = Executors.newFixedThreadPool(2) ;
        
    //提交任务
    pool.submit(new MyCallable()) ;
    pool.submit(new MyCallable()) ;
        
    //关闭线程池
    pool.shutdown();
}
           
public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        for(int x = 0 ; x < 100 ; x ++) {
            System.out.println(Thread.currentThread().getName()+":"+x);
        }        
        return null;
    }    
}
           

2.AsyncTask

AsyncTask是一个Android 已封装好的轻量级的异步类,可以用来实现多线程及多线程通信,通常用来实现一些较简单的耗时操作(如:倒计时,下载等)。它是一个抽象类,需要我们在使用的时候做具体实现。

public abstract class AsyncTask<Params, Progress, Result> { 
 ... 
 }
           

如上所示,AsyncTask拥有三个泛型参数:

  • Params 启动任务执行的输入参数,比如下载URL;
  • Progress 后台任务执行的百分比,比如下载进度;
  • Result 后台执行任务最终返回的结果,比如下载结果。

AsyncTask的常用方法有以下几个:

onPreExecute方法:

异步任务开始执行时,系统最先调用此方法。此方法运行在主线程中,可以对控件进行初始化等操作。

dolnBackground方法:

执行完onPreExecute方法后,系统执行此方法。此方法运行在子线程中,比较耗时的操作放在此方法中执行。

onProgressUpdate方法:

1.显示当前进度,适用于下载或扫描这类需要实时显示进度的需求。

2.此方法运行在主线程中,可以修改控件状态,例如: 显示百分比。

3.触发此方法,需要在dolnBackground中使用publishProgress方法。

publishProgress方法:

在doInBackground中使用。用于触发onProgressUpdate方法。

on PostExecute方法:

当异步任务执行完成后,系统会调用此方法。此方法运行在主线程中,可以修改控件状态,例如: 下载完成。

那么AsyncTask使用起来也非常简单,主要分为三步:

  1. 创建 AsyncTask 子类 & 根据需求实现核心方法
  2. 创建 AsyncTask子类的实例对象(即 任务实例)
  3. 手动调用execute(()从而执行异步线程任务

看起来还是很简单的,下面是一个网上找的简单例子。

/**
  * 步骤1:创建AsyncTask子类
  * 注: 
  *   a. 继承AsyncTask类
  *   b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替
  *   c. 根据需求,在AsyncTask子类内实现核心方法
  */

 public class MainActivity extends AppCompatActivity {

    // 线程变量
    MyTask mTask;

    // 主布局中的UI组件
    Button button,cancel; // 加载、取消按钮
    TextView text; // 更新的UI组件
    ProgressBar progressBar; // 进度条
    
    /**
     * 步骤1:创建AsyncTask子类
     * 注:
     *   a. 继承AsyncTask类
     *   b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替
     *      此处指定为:输入参数 = String类型、执行进度 = Integer类型、执行结果 = String类型
     *   c. 根据需求,在AsyncTask子类内实现核心方法
     */
    private class MyTask extends AsyncTask<String, Integer, String> {

        // 方法1:onPreExecute()
        // 作用:执行 线程任务前的操作
        @Override
        protected void onPreExecute() {
            text.setText("加载中");
            // 执行前显示提示
        }


        // 方法2:doInBackground()
        // 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
        // 此处通过计算从而模拟“加载进度”的情况
        @Override
        protected String doInBackground(String... params) {

            try {
                int count = 0;
                int length = 1;
                while (count<99) {

                    count += length;
                    // 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
                    publishProgress(count);
                    // 模拟耗时任务
                    Thread.sleep(50);
                }
            }catch (InterruptedException e) {
                e.printStackTrace();
            }

            return null;
        }

        // 方法3:onProgressUpdate()
        // 作用:在主线程 显示线程任务执行的进度
        @Override
        protected void onProgressUpdate(Integer... progresses) {

            progressBar.setProgress(progresses[0]);
            text.setText("loading..." + progresses[0] + "%");

        }

        // 方法4:onPostExecute()
        // 作用:接收线程任务执行结果、将执行结果显示到UI组件
        @Override
        protected void onPostExecute(String result) {
            // 执行完毕后,则更新UI
            text.setText("加载完毕");
        }

        // 方法5:onCancelled()
        // 作用:将异步任务设置为:取消状态
        @Override
        protected void onCancelled() {

            text.setText("已取消");
            progressBar.setProgress(0);

        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 绑定UI组件
        setContentView(R.layout.activity_main);

        button = (Button) findViewById(R.id.button);
        cancel = (Button) findViewById(R.id.cancel);
        text = (TextView) findViewById(R.id.text);
        progressBar = (ProgressBar) findViewById(R.id.progress_bar);

        /**
         * 步骤2:创建AsyncTask子类的实例对象(即 任务实例)
         * 注:AsyncTask子类的实例必须在UI线程中创建
         */
        mTask = new MyTask();

        // 加载按钮按按下时,则启动AsyncTask
        // 任务完成后更新TextView的文本
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                /**
                 * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务
                 * 注:
                 *    a. 必须在UI线程中调用
                 *    b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常
                 *    c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute()
                 *    d. 不能手动调用上述方法
                 */
                mTask.execute();
            }
        });

        cancel = (Button) findViewById(R.id.cancel);
        cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 取消一个正在执行的任务,onCancelled方法将会被调用
                mTask.cancel(true);
            }
        });
    }

}
           

布局文件我没有贴,主要包括两个button和一个进度条,这个例子实现了一个进度条的实时更新,点击一个按钮开始,调用AsyncTask的execute()方法开始异步请求,然后再doInBackground方法中增加进度,然后通过publishProgress方法将进度发送,这样onProgressUpdate(progresses)中可以通过progresses获取当前紧毒并对UI进行更新,最后,点击停止按钮时调用cancel方法取消这个任务。

AsyncTask很容易引起内存泄漏,如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。

为了避免内存泄漏,我们在使用时要特别注意:第一,在Activity生命周期结束前,去cancel AsyncTask。第二,如果一定要写成内部类的形式,对context采用WeakRefrence,在使用之前判断是否为空。

3.Handler

AsyncTask使用的优点是简单,快捷,过程可控。使用的缺点是在使用多个异步操作和并需要进行UI变更时,就会变得复杂起来,而接下来要说的Handler,结构清晰,对于多个后台任务时简单清晰。所以一般进行数据量多且复杂的操作就使用Handler,Handler也是Android面试经常考查的知识点。

Handler是Android 提供的一种异步通信机制,我们经常用它将工作线程中需更新UI的操作信息 传递到 UI主线程(只有主线程能更新UI),实现工作线程对UI的更新处理,例如:在UI界面上进行某项操作后要执行一段很耗时的代码,需要根据执行结果来更新UI,由于这是一个耗时操作,我们不知道什么时候才能完成,这个时候Handler就派上用场了。

Handler的基本使用

Handler有两种实现方式,一种是调用sendMessage方式,一种是通过post方式。基本实现如下:

sendMessage方式:

android.os.Handler handler = new Handler(){
  @Override
  public void handleMessage(final Message msg) {
    //这里接受并处理消息,如更新UI等操作
  }
};
//发送消息
handler.sendMessage(message);
           

post方式:

// 在主线程中创建Handler实例
    private Handler mhandler = new mHandler();

    // 在工作线程中发送消息到消息队列中并进行更新UI的操作
    mHandler.post(new Runnable() {
            @Override
            public void run() {
                // 需执行的UI操作 
            }
    });
           

Handler使用起来非常简单方便,但实现原理我们也是要知道的,这里就涉及到Looper、MessageQueue、Message这几个Handler中的重要概念,下面来详细介绍:

Looper、MessageQueue、Message与Handler

Looper、MessageQueue、Message和Handler是handler中重要的四个类,他们分工合作实现消息的发送和处理,下面是各自的作用:

Looper:消息循环器,循环取出消息(Message),将取出的消息分发给对应的处理者(Handler)。

Message:消息,线程间通讯的数据单元。

MessageQueue:消息队列,存储Handler发送过来的消息(Message),如其名字,是一个先进先出的队列。

Handler:消息处理器,将消息传递给消息队列,然后再由对应线程从消息队列中逐个取出数据并执行。

这里有一张图比较直观的说明了它们的关系:

Android多线程实现-Handler详解Android常用的多线程实现方法总结

那么这里就有一个问题了,在上面handler的基本使用中并没有看到Looper相关调用,那Looper是如何工作的呢。其实是在主线程使用Handler系统在启动App时已经把这些提前为我们做好了,因此我们平常在主线程不需要手动调用,在ActivityThread.main方法中会调用到Looper.prepareMainLooper()和Looper.loop(),必须执行这两步使Looper完成启动才可以正常的使用,如果我们在子线程里使用Handler,必须手动执行这两个方法,否则会报错。

按照这个流程,我们先来看首先调用的Looper.prepare()里面都做了什么:

public static void prepare() {
        prepare(true);
    }
    private static void prepare(boolean quitAllowed) {
        // 规定了一个线程只有一个Looper,也就是一个线程只能调用一次Looper.prepare()
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 如果当前线程没有Looper,那么就创建一个,存到sThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }
           

从上面的代码可以看出,一个线程只能有一个Looper对象。当没有Looper对象时,去创建一个Looper,并存放到sThreadLocal中,sThreadLocal是一个ThreadLocal对象,ThreadLocal是一个线程内部的存储类,它提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。在Handler里它用来存储Looper对象的副本,并且可以通过它取得当前线程在之前存储的Looper的副本。

在Looper.prepare()中新建了一个Looper对象,下面我们来看Looper的构造方法:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
           

这里的实现很简单,主要就是创建了消息队列MessageQueue,并让它供Looper持有,因为一个线程最大只有一个Looper对象,所以一个线程最多也只有一个消息队列。然后再把当前线程赋值给mThread。而MessageQueue的构造方法就是新建了一个消息队列,用于存放Message。

Looper就绪后就可以新建Handler了,Handler最基本的构造方法如下:

public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
      // 不相关代码
       ......
        //得到当前线程的Looper,其实就是调用的sThreadLocal.get
        mLooper = Looper.myLooper();
        // 如果当前线程没有Looper就报运行时异常
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        // 把得到的Looper的MessagQueue让Handler持有
        mQueue = mLooper.mQueue;
        // 初始化Handler的Callback
        mCallback = callback;
        mAsynchronous = async;
    }
           

首先,调用了Looper.myLooper,其实就是调用sThreadLocal.get方法,来得到当前线程调用保存的Looper对象,并让Handler持有它。接下来对该Looper对象进行非空判断,避免如没有调用Looper.prepare()新建Looper对象。然后,让Handler持有Looper对象的MessageQueue,最后设置处理回调的Callback对象。这些都准备好后,我们来看loop()方法:

public static void loop() {
        // 得到当前线程的Looper对象
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // 得到当前线程的MessageQueue对象
        final MessageQueue queue = me.mQueue;
        
        // 无关代码
        ......
        
        // 死循环
        for (;;) {
            // 不断从当前线程的MessageQueue中取出Message,当MessageQueue没有元素时,方法阻塞
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            // Message.target是Handler,其实就是发送消息的Handler,这里就是调用它的dispatchMessage方法
            msg.target.dispatchMessage(msg);
            // 回收Message
            msg.recycleUnchecked();
        }
    }
           

首先还是判断了当前线程是否有Looper,然后得到当前线程的MessageQueue。接下来,就是最关键的代码了,写了一个死循环,不断调用MessageQueue的next方法取出MessageQueue中的Message,拿到Message以后,会调用它的target的dispatchMessage方法,这个target其实就是发送消息时用到的Handler。所以就是调用Handler的dispatchMessage方法,这个方法最终绘调用到handleMessage方法,也就是我们使用时重写的那个方法。

最后就是发送消息了,上面说过使用Handler发送消息主要有两种,不过两种方式最后都会调用到sendMessageDelayed方法,所以我们就以最简单的sendMessage方法来分析。

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        // 这里拿到的MessageQueue其实就是创建时的MessageQueue,默认情况是当前线程的Looper对象的MessageQueue
        // 也可以指定
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        // 调用enqueueMessage,把消息加入到MessageQueue中
        return enqueueMessage(queue, msg, uptimeMillis);
    }
           

可见其主要实现是调用enqueueMessage来实现的,这里面会调用MessageQueue的enqueueMessage方法,我们来看一下它的实现:

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        // 一个Message,只能发送一次
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                msg.recycle();
                return false;
            }
            // 标记Message已经使用了
            msg.markInUse();
            msg.when = when;
            // 得到当前消息队列的头部
            Message p = mMessages;
            boolean needWake;
            // 我们这里when为0,表示立即处理的消息
            if (p == null || when == 0 || when < p.when) {
                // 把消息插入到消息队列的头部
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // 根据需要把消息插入到消息队列的合适位置,通常是调用xxxDelay方法,延时发送消息
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 把消息插入到合适位置
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // 如果队列阻塞了,则唤醒
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
           

内容很多,总结一下就是把Message加入到MessageQueue并按照当前的情况放到合适的位置。

总结

以上就是对三种常用的多线程实现的一个学习总结,相信这样总结过后对它们都有了更深刻的理解,由于Handler在我平常项目使用上频率较高,而且据说是面试经常考察的点,本篇文章也是对其着墨较多,并特别的从源码的角度进行了一定理解,但是使用时也要注意,由于Handler一般定义为Activity的内部类,会持有外部类的引用,因此很容易引起内存泄漏,这里官方给出了避免引起内存泄漏的建议是:

将Handler声明为静态类; 在外部类中,实例化WeakReference到外部类并在实例化Handler时将此对象传递给Handler,使用WeakReference对象创建对外部类成员的所有引用。