Android事件的处理模型和多线程处理

来源:互联网

Android平台的事件处理机制有两种:一种是基于回调机制的, 一种是基于监听接口的。

1.1 事件的回调机制

Android平台中,每个View都有自己的处理事件的回调方法,开发人员可以通过重写View中的这些回调方法来实现需要的响应事件。当某个事件没有被任何一个View处理时,便会调用Activity中相应的回调方法。Android提供了以下回调方法供用户使用:

1.1.1 onKeyDown

功能:该方法是接口KeyEvent.Callback中的抽象方法,所有的View全部实现了该接口并重写了该方法,该方法用来捕捉手机键盘被按下的事件。
声明:public boolean onKeyDown (int keyCode, KeyEvent event)
参数说明:
参数keyCode,该参数为被按下的键值即键盘码,手机键盘中每个按钮都会有其单独的键盘码,在应用程序都是通过键盘码才知道用户按下的是哪个键。
参数event,该参数为按键事件的对象,其中包含了触发事件的详细信息,例如事件的状态、事件的类型、事件发生的时间等。当用户按下按键时,系统会自动将事件封 装成KeyEvent对象供应用程序使用。
返回值,该方法的返回值为一个boolean类型的变量,当返回true时,表示已经完整地处理了这个事件,并不希望其他的回调方法再次进行处理,而当返回false时,表示并没有完全处理完该事件,更希望其他回调方法继续对其进行处理,例如Activity中的回调方法。

1.1.2 onKeyUp

功能:该方法同样是接口KeyEvent.Callback中的一个抽象方法,并且所有的View同样全部实现了该接口并重写了该方法,onKeyUp方法用来捕捉手机键盘按键抬起的事件。
声明:public boolean onKeyUp (int keyCode, KeyEvent event)
参数说明: 同onKeyDown

1.1.3 onTouchEvent

功能:该方法在View类中的定义,并且所有的View子类全部重写了该方法,应用程序可以通过该方法处理手机屏幕的触摸事件。

声明:public boolean onTouchEvent (MotionEvent event)

参数说明:

参数event:参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。

返回值:该方法的返回值机理与键盘响应事件的相同,同样是当已经完整地处理了该事件且不希望其他回调方法再次处理时返回true,否则返回 false。

详细说明:

该方法并不像之前介绍过的方法只处理一种事件,一般情况下以下三种情况的事件全部由onTouchEvent方法处理,只是三种情况中的动作值不同。

屏幕被按下:当屏幕被按下时,会自动调用该方法来处理事件,此时MotionEvent.getAction()的值为 MotionEvent.ACTION_DOWN,如果在应用程序中需要处理屏幕被按下的事件,只需重新该回调方法,然后在方法中进行动作的判断即可。
屏幕被抬起:当触控笔离开屏幕时触发的事件,该事件同样需要onTouchEvent方法来捕捉,然后在方法中进行动作判断。当 MotionEvent.getAction()的值为MotionEvent.ACTION_UP时,表示是屏幕被抬起的事件。
在屏幕中拖动:该方法还负责处理触控笔在屏幕上滑动的事件,同样是调用MotionEvent.getAction()方法来判断动作值是否为 MotionEvent.ACTION_MOVE再进行处理。

注意:ACTION_DOWN事件作为起始事件,他的重要性是超过ACTION_MOVE和ACTION_UP的,如果发生这两个事件,那么一定曾经发生了ACTION_DOWN事件。

1.1.4 onTrackBallEven

功能: 接下来将介绍的是手机中轨迹球的处理方法onTrackBallEvent。所有的View同样全部实现了该方法。
声明: public boolean onTrackballEvent (MotionEvent event)
详细说明:该方法的使用方法与前面介绍过的各个回调方法基本相同,可以在Activity中重写该方法,也可以在各个View的实现类中重写。
参数event:参数event为手机轨迹球事件封装类的对象,其中封装了触发事件的详细信息,同样包括事件的类型、触发时间等,一般情况下,该对象会在用户操控轨迹球时被创建。
返回值:该方法的返回值与前面介绍的各个回调方法的返回值机制完全相同,因本书篇幅有限,不再赘述。
轨迹球与手机键盘的区别如下所示:
1) 某些型号的手机设计出的轨迹球会比只有手机键盘时更美观,可增添用户对手机的整体印象。
2) 轨迹球使用更为简单,例如在某些游戏中使用轨迹球控制会更为合理。
3)使用轨迹球会比键盘更为细化,即滚动轨迹球时,后台的表示状态的数值会变化得更细微、更精准。
提示:在模拟器运行状态下,可以通过F6键打开模拟器的轨迹球,然后便可以通过鼠标的移动来模拟轨迹球事件。

1.1.5 onFocusChanged

功能: 前面介绍的各个方法都可以在View及Activity中重写,接下来介绍的onFocusChanged却只能在View中重写。该方法是焦点改变的回调方法,当某个控件重写了该方法后,当焦点发生变化时,会自动调用该方法来处理焦点改变的事件。
声明:protected void onFocusChanged (boolean gainFocus, int direction, Rect previously FocusedRect)
详细说明:
参数gainFocus:参数gainFocus表示触发该事件的View是否获得了焦点,当该控件获得焦点时,gainFocus等于true,否则等于false。
参数direction:参数direction表示焦点移动的方向,用数值表示,有兴趣的读者可以重写View中的该方法打印该参数进行观察。
参数previouslyFocusedRect:表示在触发事件的View的坐标系中,前一个获得焦点的矩形区域,即表示焦点是从哪里来的。如果不可用则为null。
提示:
焦点:焦点描述了按键事件(或者是屏幕事件等)的承受者,每次按键事件都发生在拥有焦点的View上。在应用程序中,我们可以对焦点进行控制,例如从一个 View移动另一个View。下面列出一些与焦点有关的常用方法:
setFocusable方法:设置View是否可以拥有焦点。
isFocusable方法:监测此View是否可以拥有焦点。
setNextFocusDownId方法:设置View的焦点向下移动后获得焦点View的ID。
hasFocus方法:返回了View的父控件是否获得了焦点。
requestFocus方法:尝试让此View获得焦点。
isFocusableTouchMode方法:设置View是否可以在触摸模式下获得焦点,在默认情况下是不可用获得的。

1.2 事件的监听机制

1.2.1 OnClickListener接口

功能:该接口处理的是点击事件。在触控模式下,是在某个View上按下并抬起的组合动作,而在键盘模式下,是某个View获得焦点后点击确定键或者按下轨迹球事件。
对应的回调方法:public void onClick(View v)
说明:需要实现onClick方法,参数v便为事件发生的事件源。

1.2.2 OnLongClickListener接口

功能:OnLongClickListener接口与之前介绍的OnClickListener接口原理基本相同,只是该接口为View长按事件的捕捉接口,即当长时间按下某个View时触发的事件。
对应的回调方法:public boolean onLongClick(View v)
说明:需要实现onLongClick方法。
参数v:参数v为事件源控件,当长时间按下此控件时才会触发该方法。
返回值:该方法的返回值为一个boolean类型的变量,当返回true时,表示已经完整地处理了这个事件,并不希望其他的回调方法再次进行处理;当返回 false时,表示并没有完全处理完该事件,更希望其他方法继续对其进行处理。

1.2.3 OnFocusChangeListener接口

功能:OnFocusChangeListener接口用来处理控件焦点发生改变的事件。如果注册了该接口,当某个控件失去焦点或者获得焦点时都会触发该接口中的回调方法。
对应的回调方法:public void onFocusChange(View v, Boolean hasFocus)
说明:需要实现onFocusChange方法。
参数v:参数v便为触发该事件的事件源。
参数hasFocus:参数hasFocus表示v的新状态,即v是否是获得焦点。

1.2.4 OnKeyListener接口

功能:OnKeyListener是对手机键盘进行监听的接口,通过对某个View注册该监听,当View获得焦点并有键盘事件时,便会触发该接口中的回调方法。
对应的回调方法:public boolean onKey(View v, int keyCode, KeyEvent event)
说明:需要实现onKey方法。
参数v:参数v为事件的事件源控件。
参数keyCode:参数keyCode为手机键盘的键盘码。
参数event:参数event便为键盘事件封装类的对象,其中包含了事件的详细信息,例如发生的事件、事件的类型等。

1.2.5 OnTouchListener接口

功能:OnTouchListener接口是用来处理手机屏幕事件的监听接口,当为View的范围内触摸按下、抬起或滑动等动作时都会触发该事件。
对应的回调方法:public boolean onTouch(View v, MotionEvent event)
说明:需要实现onTouch方法。
参数v:参数v同样为事件源对象。
参数event:参数event为事件封装类的对象,其中封装了触发事件的详细信息,同样包括事件的类型、触发时间等信息。

1.2.6 OnCreateContextMenuListener接口

功能:OnCreateContextMenuListener接口是用来处理上下文菜单显示事件的监听接口。该方法是定义和注册上下文菜单的另一种方式。
对应的回调方法:public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo info)
说明:需要实现onCreateContextMenu方法。
参数menu:参数menu为事件的上下文菜单。
参数v:参数v为事件源View,当该View获得焦点时才可能接收该方法的事件响应。
参数info:info对象中封装了有关上下文菜单额外的信息,这些信息取决于事件源View。
该方法会在某个View中显示上下文菜单时被调用,开发人员可以通过实现该方法来处理上下文菜单显示时的一些操作。其使用方法与前面介绍的各个监听接口没有任何区别。

1.3 View与ViewGroup触摸事件传递机制

触摸事件有:ACTION_DOWN, ACTION_MOVE, ACTION_UP 。一个简单的触摸动作触发了一系列Touch事件,如:

(1) ACTION_DOWN -> ACTION_MOVE->.....-> ACTION_MOVE-> ACTION_UP

(2) ACTION_DOWN -> ACTION_UP

当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View,TouchEvent最先到达最顶层View的dispatchTouchEvent,然后由dispatchTouchEvent方法进行分发,如果dispatchTouchEvent返回TRUE,则交给这个View的onTouchEvent处理;如果dispatchTouchEvent返回false,则交给这个View的OninterceptTouchEvent方法来决定是否要拦截这个事件,如果OninterceptTouchEvent返回true,也就拦截了,则交给它的onTouchEvent来处理;如果OninterceptTouchEvent返回false,那就传递给子View,由子View的dispatchTouchEvent再来开始这个事件的分发,如果事件传递到某一层View的onTouchEvent了,这个方法返回false,那么这个事件从这个View往上传递,都是onTouchEvent来接收,而如果传递到onTouchEvent返回是false,这个事件“消失”,而且接收不到下一次事件。

其分发和传递的过程如下:

wps_clip_image-29187

1.4 Handler消息传递机制

解决的问题:

每一个应用程序都是一个单独的进程,运行于单独的Dalvik虚拟机实例中,在运行与单独Linux进程中。每一个进程默认只有一个线程即UI主线程,因为他是以UI界面更新为主要任务的主线程。

同样继承于context的Activity和Serivce都是跑在同一个线程里的,即UI主线程。这样他们之间是相互阻塞的,当Serivce运行较为费时的工作,而Activity上的UI 界面需要更新导致程序卡住时,就会导致程序被系统Kill掉。解决办法是Serivce需要用更多的线程来运行费时的动作。Google为了解决此问题提供了Handler类。

UI主线程里初始化时自带一个消息队列(MessageQueen),需要用Looper进行管理。Looper的主要作用是循环迭代MessageQueen,加入新的Message,轮到这条消息时在发送出去,而Handler就可以直接操作Looper了。比如:

Handler mHandler = new Handler(Looper. getMainLooper);用mHandler对象控制UI主线程的Looper对象。

Handler mHandler = new Handler(Looper. myLooper);或 Handler mHandler = new Handler(); 就是控制当前线程的MessageQueen。

1.4.1 Looper类、Message类和Handler类之间的关系

Looper中有一个Message队列,里面存储的是一个个待处理的Message;Message中有一个Handler,这个Handler是用来处理Message的,其中,Handler类封装了很多琐碎的工作。

一个简单例子如下:

1. class LooperThread extends Thread {

2.     public Handler mHandler;

3.     public void run() {

4.      //① 调用prepare。

5.      Looper.prepare();

6.      ......

7.      //② 进入消息循环。

8.      Looper.loop();

9.    }

10. }

11. //应用程序使用LooperThreadActivity。

12. {

13.

14.  LooperThread  mTread = new LooperThread();

15.

16. ......

17.  mTread.start();//启动新线程,线程函数是run

18. }

1.4.2 Looper类

Android中的Looper类用来封装消息循环和消息队列的一个类,用于在android线程中进行消息处理,handler其实可以看做是一个工具类,用来向消息队列中插入消息的。

(1) Looper类用来为一个线程开启一个消息循环,在默认情况下android中新诞生的线程是没有开启消息循环的,主线程除外。Looper对象通过MessageQueue来存放消息和事件,一个线程只能有一个Looper和对应的一个MessageQueue。

(2) 通常通过Handler对象来与Looper进行交互的,Handler可看作是Looper的一个接口,用来指定的Looper发送消息及定义处理方法。

(3) 在非主线程下直接new Handler()会报错,原因是该线程还没有创建Looper以及消息队列。

synchronized static Looper getMainLooper():获取当前主线程的Looper实例

Thread getThread():获取当前调用处所在的线程

static void loop():进入消息循环

static Looper myLooper():获取当前线程的Looper

static MessageQueue myQueue():获取当前线程的消息队列句柄

static void prepare():创建当前线程的消息队列

static void prepareMainLooper():创建主线程的消息队列

void quit():退出当前线程

在Android中基本上每一个线程都有一个Looper,但是我们自己通过Thread建立的线程是没有Looper的,此时Android为我们提供一个集成自Tread的HanderTread类来处理。

Looper主要是为Handler服务的,Looper是一种循环,这种循环用来监听线程的消息队列,将对应的消息交给Handler处理。

1.   public static final void prepare() {

2.    //一个Looper只能调用一次prepare。

3.   if (sThreadLocal.get() != null) {

4.      throw new RuntimeException("Only one
Looper may be created per thread");

5.   }

6.    //构造一个Looper对象,设置到调用线程的局部变量中。

7.    sThreadLocal.set(new Looper());

8. }

9. //sThreadLocal定义

10. private static final ThreadLocal
sThreadLocal = new ThreadLocal();

1. public static final void loop() {

2.         Looper me = myLooper();//myLooper
返回保存在调用线程TLV中的Looper对象。

3.         //取出这个Looper的消息队列。

4.         MessageQueue queue = me.mQueue;

5.         while (true) {

6.             Message msg = queue.next();

7.            //处理消息,Message对象中有一个target,它是Handler类型。

8.             //如果target为空,则表示需要退出消息循环。

9.             if (msg != null) {

10.                 if (msg.target == null) {

11.                      return;

12.                 }

13.                //调用该消息的Handler,交给它的dispatchMessage函数处理。

14.                msg.target.dispatchMessage(msg);

15.                msg.recycle();

16.             }

17.         }

18. }

19. //myLooper函数返回调用线程的线程局部变量,也就是存储在其中的Looper对象。

20. public static final Looper myLooper() {

21.         return (Looper)sThreadLocal.get();

22. }

通过上面的分析会发现,Looper的作用是:封装了一个消息队列。Looper的prepare函数把这个Looper和调用prepare的线程(也就是最终的处理线程)绑定在一起了

1.4.3 Handler类

Handler类构造函数分析:

1. public Handler() {

2.          //获得调用线程的Looper。

3.  mLooper = Looper.myLooper();

4.         if (mLooper == null) {

5.             throw new RuntimeException(......);

6.         }

7.         //得到Looper的消息队列。

8.  mQueue = mLooper.mQueue;

9.        //无callback设置。

10.  mCallback = null;

11.     }

12.

13.  //构造函数2

14.    public Handler(Callback callback) {

15.  mLooper = Looper.myLooper();

16.         if (mLooper == null) {

17.           throw new RuntimeException(......);

18.         }

19.         //和构造函数1类似,只不过多了一个设置callback。

20.  mQueue = mLooper.mQueue;

21.  mCallback = callback;

22.     }

23. //构造函数3

24.    public Handler(Looper looper) {

25.  mLooper = looper; //looper由外部传入,是哪个线程的Looper不确定。

26.  mQueue = looper.mQueue;

27.  mCallback = null;

28.     }

29. //构造函数4,和构造函数3类似,只不过多了callback设置。

30.    public Handler(Looper looper, Callback callback) {

31.  mLooper = looper;

32.  mQueue = looper.mQueue;

33.  mCallback = callback;

34. }

在上述构造函数中,Handler中的消息队列变量最终都会指向Looper的消息队列

Handler是如何向消息队列中添加消息的呢?Handler提供了一系列函数,帮助我们完成创建消息和插入消息队列的工作。这里只列举其中一二。要掌握详细的API,则需要查看相关的文档。

1. //查看消息队列中是否有消息码是what的消息。

2. final boolean    hasMessages(int what)

3. //从Handler中创建一个消息码是what的消息。

4. final Message    obtainMessage(int what)

5. //从消息队列中移除消息码是what的消息。

6. final void       removeMessages(int what)

7. //发送一个只填充了消息码的消息。

8. final boolean    sendEmptyMessage(int what)

9. //发送一个消息,该消息添加到队列尾。

10. final boolean    sendMessage(Message msg)

11. //发送一个消息,该消息添加到队列头,所以优先级很高。

12. final boolean    sendMessageAtFrontOfQueue(Message msg)

只需对上面这些函数稍作分析,就能明白其他的函数。现以sendMessage为例,其代码如下所示:

1. public final boolean sendMessage(Message msg)

2. {

3.     return sendMessageDelayed(msg, 0); //调用sendMessageDelayed

4.  }

5. [-->Handler.java]

6. // delayMillis是以当前调用时间为基础的相对时间

7. public final boolean sendMessageDelayed(Message
msg, long delayMillis)

8. {

9.    if (delayMillis < 0) {

10.  delayMillis = 0;

11.   }

12.    //调用sendMessageAtTime,把当前时间算上

13.    return sendMessageAtTime(msg,SystemClock.
uptimeMillis() + delayMillis);

14. }

15. [-->Handler.java]

16. //uptimeMillis 是绝对时间,即sendMessageAtTime函数处理的是绝对时间

17. public boolean sendMessageAtTime(Message msg, long uptimeMillis){

18.     boolean sent = false;

19.     MessageQueue queue = mQueue;

20.     if (queue != null) {

21.           //把Message的target设置为自己,然后加入到消息队列中

22.  msg.target = this;

23.  sent = queue.enqueueMessage(msg, uptimeMillis);

24.     }

25.      return sent;

26. }

看到上面这些函数我们可以预见,如果没有Handler的辅助,当我们自己操作MessageQueue的enqueueMessage时,得花费多大工夫!Handler把Message的target设为自己,是因为Handler除了封装消息添加等功能外还封装了消息处理的接口。

Handler的消息处理消息处理方法:

1. public void dispatchMessage(Message msg) {

2.   //如果Message本身有callback,则直接交给Message的callback处理

3.           if (msg.callback != null) {

4.             handleCallback(msg);

5.         } else {

6.     //如果本Handler设置了mCallback,则交给mCallback处理

7.             if (mCallback != null) {

8.                 if (mCallback.handleMessage(msg)) {

9.                     return;

10.                 }

11.             }

12.             //最后才是交给子类处理

13.             handleMessage(msg);

14.         }

15.     }

dispatchMessage定义了一套消息处理的优先级机制,它们分别是:

Message如果自带了callback处理,则交给callback处理。

Handler如果设置了全局的mCallback,则交给mCallback处理。

如果上述都没有,该消息则会被交给Handler子类实现的handleMessage来处理。当然,这需要从Handler派生并重载handleMessage函数。

在通常情况下,我们一般都是采用第三种方法,即在子类中通过重载handleMessage来完成处理工作的。

1.4.4 Looper和Handler的同步关系

例子:

1. //先定义一个LooperThread类

2. class LooperThread extends Thread {

3.     public Looper myLooper = null;//
定义一个public的成员myLooper,初值为空。

4. public void run() { //假设run在线程2中执行

5.           Looper.prepare();

6.          // myLooper必须在这个线程中赋值

7.  myLooper = Looper.myLooper();

8.           Looper.loop();

9.    }

10. }

11. //下面这段代码在线程1中执行,并且会创建线程2

12. {

13.   LooperThread lpThread= new LooperThread;

14.   lpThread.start();//start后会创建线程2

15.   Looper looper = lpThread.myLooper;//<======注意

16.  // thread2Handler和线程2的Looper挂上钩

17.   Handler thread2Handler = new Handler(looper);

18.  //sendMessage发送的消息将由线程2处理

19.   threadHandler.sendMessage(...)

20. }

线程1中创建线程2,并且线程2通过Looper处理消息。线程1中得到线程2的Looper,并且根据这个Looper创建一个Handler,这样发送给该Handler的消息将由线程2处理。但很可惜,上面的代码是有问题的。如果我们熟悉多线程,就会发现标有“注意”的那行代码存在着严重问题。myLooper的创建是在线程2中,而looper的赋值在线程1中,很有可能此时线程2的run函数还没来得及给myLooper赋值,这样线程1中的looper将取到myLooper的初值,也就是looper等于null。

上面问题采用同步的方式来解决,其代码如下:

1. public class HandlerThread extends Thread{

2. //线程1调用getLooper来获得新线程的Looper

3.  public Looper getLooper() {

4.        ......

5.         synchronized (this) {

6.             while (isAlive() && mLooper == null) {

7.                 try {

8.                     wait();//如果新线程还未创建Looper,则等待

9.                 } catch (InterruptedException e) {

10.                 }

11.             }

12.         }

13.         return mLooper;

14.     }

15.

16. //线程2运行它的run函数,looper就是在run线程里创建的。

17.   public void run() {

18.  mTid = Process.myTid();

19.         Looper.prepare();  //创建这个线程上的Looper

20.         synchronized (this) {

21.  mLooper = Looper.myLooper();

22.             notifyAll();//通知取Looper的线程1,此时Looper已经创建好了。

23.         }

24.         Process.setThreadPriority(mPriority);

25.         onLooperPrepared();

26.         Looper.loop();

27.  mTid = -1;

28.     }

29. }

1.4.5 Message类

消息对象,记录消息信息的类,这个类有几个比较重要的字段:

public int arg1; 和public int arg2:可以使用两个字段用来存放我们需要传递的整型值,在service中,我们可以用来存放service的ID。

public Object obj:该字段Object类型,可以让该字段传递某个多项到消息的接受者中。

public int what: 可以说是消息的标志,在消息处理中,可以根据这个字段的不同的值进行不同的处理。在使用Message时,可以通过new Message()创建一个实例,但是Android更推荐我们通过Message.obtain或者Handler.obtainMessage()获取Message对象,这并不一定直接创建一个新的实例,而是从消息池中没有可用的Message实例,则根据给定的参数new一个新Message对象,通过分析源码可得知,Android系统默认情况下在消息池中实例化10个Message对象。

获取一个新的Message对象方法有:

static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)

static Message obtain(Handler h, int what, Object obj)

static Message obtain(Handler h, int what)

static Message obtain(Handler h)

static Message obtain(Handler h, Runnable callback)

static Message obtain()

static Message obtain(Handler h, int what, int arg1, int arg2)

static Message obtain(Message orig)

Handler target: 处理当前Message的Handler

private static Message sPool和private static int sPoolSize = 0:

private static final int MAX_POOL_SIZE = 10; 内存池的大小

private static final Object sPoolSync = new Object():同步操作Message的对象

Message内存池,sPool下一个分配的对象, sPoolSize表示当前内存池可分配Message对象的个数。

Bundle data; 绑定在线程中传递的数据。

long when: 发送该消息的时间。

Message next:标记下一个空闲的Message

int flags:标记当前Message所处的状态

1.4.6 MessageQueue类

Message Queue消息队列,用来存放通过Handler发布的消息,按照先进先出执行。每个message queue都会有一个对应的Handler。Handler会向message queue通过两种方法发送消息:sendMessage或post。这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同:通过sendMessage发送的是一个message对象,会被Handler的handleMessage()函数处理;而通过post方法发送的是一个runnable对象,则会自己执行。

每一个线程最多只可以拥有一个MessageQueue数据结构,创建一个线程时并不会自动创建其MessageQueue,通常使用一个Looper对象对线程的MessageQueue进行管理,主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue,其他非主线程不会自动创建Looper,需要的时候,通过调用prepare函数来实现。

Message mMessages: 采用线性链表的方式来维护的。

Final addIdleHandler(MessageQueue.IdleHandler handler)

Final Void removeIdleHandler(MessageQueue.IdleHandler handler)

Void finalize()

1.5 AsyncTask异步任务

AsyncTask是android提供的轻量级的异步类,可以直接继承该类来实现异步操作,并提供接口反馈当前异步执行的程度,其优点是简单、快捷,过程可控;但使用多个异步操作和需要进行UI更新时就比较复杂。

1.5.1 原理介绍

AsyncTask实际上主要对Thread+Handler的封装,封装了一个多线程,向开发者屏蔽了考虑多线程的问题。开发人员只需要去重写AsynTask中的doInBackground、onProgressUpdate、onPreExecute、onPostExecute等方法,然后生成对象并执行execute方法即可以实现异步操作。

AsyncTask的线程启动其实是采用了一个线程池来进行管理,以便能有效利用资源,下面是创建线程池的初始化代码:

关键点:这里使用了原子计数器mCount,来区分线程。

wps_clip_image-9118

1.5.1.1 内部类

private static final InternalHandler sHandler = new InternalHandler();

在第一调用new AsyncTask()会初始化,且这个调用一定是在UI的主线程下,因此sHandler的处理将运行在UI的主线程下。由此可知,这样目的在于将处理的结果传递到UI主线程中来处理。

AsyncTaskResult类:将数据传递到AsyncTask实体当中,从函数可以看出:

private Result postResult(Result result)

WorkerRunnable类:表示一个运行实体,携带输入参数、处理回调函数等信息,一旦该任务启动后,将调用该类中call函数来处理函数,并返回处理后的结果。

protected abstract Result doInBackground(Params... params)

巧妙之处一: 通过在UI线程创建一个处理句柄,这个句柄能获取到UI主线程消息队列,且发送该句柄的消息处理都运行在UI主线程中。

巧妙之处二:WorkerRunnable类封装了传递的参数、处理过程和提供执行器调用。

wps_clip_image-29669

1.5.1.2 AsyncTask函数

FutureTask将分配给该任务一个线程实体,当调用mWorker中的call后处理子类doInBackground函数后将调用mFutrueTask中done函数表示处理已经结束。

wps_clip_image-19259

1.5.1.3 Execute函数

当创建完以后,调用execute就开始执行,这个过程最终是如何调用到doInBackground呢?实际上执行Executor中的execute,而Executor的默认处理由静态变量传递过来的,wps_clip_image-18024

默认的执行器实现如下, 在执行execute时,会新建一个Runnable,并插入到mTasks队列中,通过ScheduleNext函数启动线程,调用run来处理传递过来的run。最后追溯到FeatureTask中的call函数,执行子类的接口。

wps_clip_image-1088

1.5.2 线程池TreadPoolExecutor
1.5.2.1 TreadPoolExecutor函数

wps_clip_image-25545

corePoolSize:线程池维护线程的最少数量

maximumPoolSize: 线程池维护线程的最大数量

keepAliveTime和unit:决定线程池维护线程所允许的空闲时间的单位

workQueue:线程池所使用的缓冲队列

handler:线程池对拒绝任务的处理策略,有四种处理情况:ThreadPoolExecutor.AbortPolicy()            抛出java.util.concurrent.RejectedExecutionException异常ThreadPoolExecutor.CallerRunsPolicy()       重试添加当前的任务,他会自动重复调用execute()方法ThreadPoolExecutor.DiscardOldestPolicy()    抛弃旧的任务ThreadPoolExecutor.DiscardPolicy()          抛弃当前的任务

1.5.2.2 execute函数

一个任务通过execute(Runnable)方法被添加到线程池,任务就是一个Runnable类型对象,任务执行了Runnable类型对象的run()方法。该函数的处理机制:

l 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

l 如果此时线程池中的数量不小于corePoolSize时,当缓冲队列workQueue未满,则将任务放入缓冲队列;当缓冲队列已经满了,并且线程池中的数量小于maximumPoolSize,新建线程来处理被添加的任务;当缓冲队列已满,且线程池数量不小于maximumPoolSize时,则通过handler所指定策略来处理此任务。

wps_clip_image-17521

1.5.2.3 addWorker函数

execute函数会启动一个线程,并在线程内执行command中run函数,这是如何实现的呢?其实在addWorker函数中通过Worker类中的runWorker函数封装对command的调用。

wps_clip_image-1383

getThreadFactory().newThread(this):将创建一个线程,且线程入口函数是Worker类下run函数,而run函数回调runWorker函数,这个函数将处理实际的任务实体。

addWorker将Worker加入到队列中,并且启动了线程。

wps_clip_image-14247

1.5.2.4 runWorker函数

runWorker是真正将执行任务实体函数,从下面的分析可看出,提供两个函数便于子类来处理逻辑:

//执行实体之前调用该函数

protected void beforeExecute(Thread t, Runnable r) protected

//执行实体结束后调用该函数

void afterExecute(Runnable r, Throwable t)

wps_clip_image-10155

1.5.2.5 总结

一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。 当一个任务通过execute(Runnable)方法欲添加到线程池时: 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。 如果此时线程池中的数量等corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

也就是:处理任务的优先级为:

核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

示意图如下:

wps_clip_image-20674

1.5.3 使用方法

AsyncTask定义了三种泛型类型 Params,Progress和Result。

· Params 启动任务执行的输入参数,比如HTTP请求的URL。

· Progress 后台任务执行的百分比。

· Result 后台执行任务最终返回的结果,比如String。

使用过AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法:

· doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用public Progress(Progress…)来更新任务的进度。

· onPostExecute(Result)  相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回

有必要的话你还得重写以下这三个方法,但不是必须的:

· onProgressUpdate(Progress…)   可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。

· onPreExecute()        这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。

· onCancelled()             用户调用取消时,要做的操作

使用AsyncTask类,以下是几条必须遵守的准则:

· Task的实例必须在UI thread中创建;

· execute方法必须在UI thread中调用;

· 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;

· 该task只能被执行一次,否则多次调用时将会出现异常;

发表评论