Android多线程编程(二)

不管是安卓还是其他的不放呢,同步与异步都是必不可少的知识,我们在学习工作的时候多多少少都会接触到这一部分,今天我就来详细的记录学习一下安卓学习的这一部分,首先我们来了解一下同步与异步的总体知识:


同步与异步

在我们编写的程序项目中,同步与异步都是并存的,不存在优劣之分,他们在不同的地方执行着自己独有的功能,我们根据不同的情况也会采取不同的通讯方式。比如说,我们这个程序,目前有很多任务,比如用户点击刷新,就会执行触碰判断请求,网络请求,界面刷新请求等很多种,那么我们采用同步和异步会如何执行?


同步

同步通讯的思想就是,用户操作之后,就等待,等待这部分程序依次全都跑完,然后再告诉你我们这里结束了,也就是这个时候返回数据给用户,用户会因此等待一阵子。

对应线程就是,这几个请求我们已经放在几个线程中了,不过呢,需要一个一个执行,其他线程等一等吧。


异步

异步通讯的思想就是,用户在进行操作后,我们将用户的操作请求放在请求队列中,并反馈给用户说这个操作已经在执行了,你想干什么干什么吧,我们会在后面执行你这个操作,你不用等了。

对应线程就是,这几个请求我们已经在多个线程中进行同时处理了,这几个线程给我一起冲。


这样看起来,异步是不是比同步好很多,但是存在就是有原因的,有一些问题是异步无法解决的,类似于:

张三李四王五三个人同时在不同的火车站售票口买票,买的还都是同一趟列车的票,如果我们这个时候使用异步通讯,三个售票口分别在后台进行处理,那么结果就是三个人买的票都是同一张,那么问题就大了,这个位置是谁的?所以对于这种多个线程需要同时访问共享资源的时候,我们就需要用到线程的同步知识了,一个一个售票口进行访问火车票资源,这样才可以避免卖了同一张票。


那么接下来,我们就开始正式学习安卓线程的同步和异步知识:

安卓线程异步通讯

在安卓中,只会存在两种线程,UI线程(主线程)以及工作线程(子线程),UI线程中主要负责处理用户的点击事件,绘图事件等UI界面相关的工作,而工作线程就负责处理耗时逻辑,等任务完成后通知UI线程更新UI界面。那么这就涉及到了线程之间的异步通讯了,我们接下来就来了解一下:


线程中进行异步消息处理的,有AsyncTaskHandler四兄弟Activity的runOnUIThread方法以及View的post以及postDelay方法,但是现在不妙的是,AsyncTask已经被弃用了,泪目,那就只能不学这部分了,我们依次来弄一下这几个通讯方式:


Handler异步通讯机制

事先说一下哈,这里知识会进行简要介绍,不会有什么源码解析什么的,源码解析应该会在后面学学吧,现在先掌握用法。


四兄弟介绍

异步消息处理主要是由四部分组成,Message,Handler,MessageQueue和Looper,对于Message以及Handler应该大多都有接触过,接下来我们依次学习一下:

Message

Message,顾名思义,信息,他是在线程中传递的信息,Message内部可以携带少量的信息,用于在不同的线程之间进行数据交换,Message不仅可以使用what字段携带信息,也可以用arg1和arg2来携带整形数据,还有obj字段来携带一个Object对象,在后面我们会写一写代码来进行实际应用;

Handler

Handler,顾名思义,处理者,那他处理什么呢,处理发送消息(sendMessage())方法,处理接收的消息(handlerMessage())方法,也就是这么个流程:使用Handler的sendMessage()方法发送出去了一条消息,传递传递,处理处理,最终传递到了需要这条消息的位置,这个位置的Handler的handlerMessage()方法对消息及逆行了接收处理;

MessageQueue

MessageQueue,顾名思义,消息队列,这就很好理解了,Handler发了几条信息,这部分消息还没被处理的时候,就会被放置在消息队列MessageQueue中,直到被处理,需要注意的是,每个线程只会有一个MessageQueue对象;

Looper

Looper,顾名思义,经过我的有道翻译之后,他被翻译成了电影,因此无法顾名思义,可惜。不过这个Looper,就是相当于管家,用来管理MessageQueue消息队列,在我们调用了Looper的loop()方法后,就会进入到一个无限的循环之中,每当MessageQueue中存在一条消息,我们就会把他取出,并且传递到之前说过的Handler的handlerMessage()方法中,进行消息处理。每个线程中只有一个MessageQueue消息队列,那么相应的也就只有一个管家,Looper。


异步通讯处理流程

1.子线程在进行UI操作的时候,会创建Message对象存储消息,并且通过Handler的sendMessage()方法来发送这条消息;

2.这条消息会进入到MessageQueue消息队列中,Looper会从MessageQueue中取出这条待处理消息,并且准备传递给主线程的handlerMessage()方法进行处理;

3.主线程创建Handler对象,并且重写实现handlerMessage()方法;

4.消息最终在主线程中的Handler的handlerMessage()中进行处理。

这个UI操作消息通过一系列的传递处理后,从子线程传递到了主线程进行处理,这样就做到了可以安全更新UI的操作,我们来画一张流程图来更清晰的看看这个处理流程:

android 间通讯 安卓通讯_UI


就这样,接下来我们尝试代码简单实现一下:


Handler异步消息机制代码实现

之前说了那么多,不过我们在实际应用的时候基本上是不会全都写的,这部分看起来很多,但是应用的时候代码却很简洁,基本上就是用到了说的Message还有Handler:


准备接收消息以及对UI进行更新处理的Handler:

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            String timeClock = (msg.obj).toString();
            
            time.setText(timeClock);

        }
    };

发送消息的Handler:

private void timeClock() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (count >= 0) {
                    Message msg = new Message();
                    msg.obj = count;
                    mHandler.sendMessage(msg);
                    count--;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }).start();
    }

这就是一个简单的倒计时,看一下效果:

android 间通讯 安卓通讯_数据_02


好了,下一个:


Activity.runOnUiThread(Runnable)

这盒相对来说就简单多了,从函数名我们就可以看出,是在UI线程上进行运行,直接看代码吧,和上一个效果一样:

private void timeClock() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (count >= 0) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            String timeclock = String.valueOf(count);
                            time.setText(timeclock);
                            count--;
                        }
                    });

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }).start();
    }

是不是和之前的很像,没错,就是按照上面的改了一小部分,把handler传递改成了runOnUiThread()这个方法,这样就可以在这个工作线程中进行UI线程的工作;


View.post(Runnable)

不想看了,下次再看,芜湖,自由的气息!


安卓线程同步通讯

在安卓中,其实同步编程使用的不算多,远没有异步编程使用广泛,但是我们仍突然要熟悉,说不定什么时候就用到了,我们就直接开始吧。


使用synchronized关键字

每一个Java对象都有一个内置锁,我们在使用synchronized关键字修饰方法或者代码块的时候,内治所会保护整个方法或者代码快,想执行这个方法活代码块时,必须要获得内置锁,在运行时会加上内置锁,运行结束时,内置锁就会打开了。类似加锁操作如下:

private synchronized void startProgressAnimation() {
        if (singleTapThread == null) {
            singleTapThread = new SingleTapThread();
            getHandler().postDelayed(singleTapThread, 100);
        }
    }

类似这种,在void之前加上synchronized关键字,这样的话,就算有多个线程访问这个方法,这个方法也只会依次执行。不过需要注意的是,synchronized关键字只能锁住同一个对象的线程,也就是说,锁住的是这个方法所属的对象自身。这种是修饰方法,也可以修饰代码块:

synchronized (this){
	getHandler().postDelayed(singleTapThread, 100);
}

这种就不多说了,和上面的一样。


volatile关键字

刚才说的synchronized有一个弊端,那就是不能只对某个变量加锁,这样很容易就会造成资源浪费,属于一种重量锁,而我们接下来要说的volatile则是一种轻量锁,锁住的是变量。


我们用volatile关键字修饰变量,可以保证不同的线程对共享数据的可见性,因为我们在多个线程同时对某个共享数据进行处理时,会有这种流程:

android 间通讯 安卓通讯_android_03


我也不知道为什么一直都是歪的,挺离谱的。不过也就是这个意思,线程从主存中取出数据进行修改,这个时候另一个线程可能同时对其进行修改,这个时候可能就会出现数据错乱的现象,这个问题我们采用synchronized关键字;


但是看图,我们所有的线程都有一个工作缓存区,这个缓存区在各个线程的处理下各不相同,那么这个时候我们就要用到volatile关键字了,用volatile关键字修饰的变量,在修改时,可以保证工作缓存区内的数据也会同样发生修改。

volatile int value=0;

大概的代码就是如从,具体的还是要看应用。


基本就到这吧,其他的我用的也不多,等有需要的时候再记笔记。