AsyncTask,Handler,Looper

来源:互联网 发布:python dictionary 编辑:程序博客网 时间:2024/06/04 18:31

http://www.jb51.net/article/35816.htm                         http://www.cnblogs.com/jackhuclan/archive/2013/07/10/3182084.html

AsyncTask的隐蔽陷阱,先来看一个实例,展示了AsyncTask的一种极端用法。

public class AsyncTaskTrapActivity extends Activity {  
    private SimpleAsyncTask asynctask;  
    private Looper myLooper;  
    private TextView status;       
    @Override  
    public void onCreate(Bundle icicle) {  
        super.onCreate(icicle);  
        asynctask = null;  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                Looper.prepare();  
                myLooper = Looper.myLooper();  
                status = new TextView(getApplication());  
                asynctask = new SimpleAsyncTask(status);  
                Looper.loop();  
            }  
        }).start();  
        try {  
            Thread.sleep(1000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);  
        setContentView((TextView) status, params);  
        asynctask.execute();  
    }  
    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        myLooper.quit();  
    }  
    private class SimpleAsyncTask extends AsyncTask<Void, Integer, Void> {  
        private TextView mStatusPanel;  
        public SimpleAsyncTask(TextView text) {  
            mStatusPanel = text;  
        }  
        @Override  
        protected Void doInBackground(Void... params) {  
            int prog = 1;  
            while (prog < 101) {  
                SystemClock.sleep(1000);  
                publishProgress(prog);  
                prog++;  
            }  
            return null;  
        }          
        // Not Okay, will crash, said it cannot touch TextView  
        @Override  
        protected void onPostExecute(Void result) {  
            mStatusPanel.setText("Welcome back.");  
        }          
        // Okay, because it is called in #execute() which is called in Main thread, so it runs in Main Thread.  
        @Override  
        protected void onPreExecute() {  
            mStatusPanel.setText("Before we go, let me tell you something buried in my heart for years...");  
        }           
        // Not okay, will crash, said it cannot touch TextView  
        @Override  
        protected void onProgressUpdate(Integer... values) {  
            mStatusPanel.setText("On our way..." + values[0].toString());  
        }  
    }  
}  
这个例子在Android2.3中无法正常运行,在执行onProgressUpdate()和onPostExecute()时会报出异常但在Android4.0及以上的版本中运行就正常(3.0版本未测试)。
从2.3运行时的Stacktrace来看原因是在非UI线程中操作了UI组件。不对呀,神奇啊,AsyncTask#onProgressUpdate()和AsyncTask#onPostExecute()的文档明明写着这二个回调是在UI线程里面的嘛,怎么还会报出这样的异常呢!
原因分析
AsyncTask设计出来执行异步任务却又能与主线程通讯,它的内部有一个InternalHandler是继承自Handler的静态成员sHandler,这个sHandler就是用来与主线程通讯的。看下这个对象的声明:private static final InternalHandler sHandler = new InternalHandler();而InternalHandler又是继承自Handler的。所以本质上讲sHandler就是一个Handler对象。Handler是用来与线程通讯用的,它必须与Looper和线程绑定一起使用,创建Handler时必须指定Looper,如果不指定Looper对象则使用调用栈所在的线程,如果调用栈线程没有Looper会报出异常。看来这个sHandler是与调用new InternalHandler()的线程所绑定,它又是静态私有的,也就是与第一次创建AsyncTask对象的线程绑定。所以,如果是在主线程中创建的AsyncTask对象,那么其sHandler就与主线程绑定,这是正常的情况。在此例子中AsyncTask是在衍生线程里创建的,所以其sHandler就与衍生线程绑定,因此,它自然不能操作UI元素,会在onProgressUpdate()和onPostExecute()中抛出异常。
以上例子有异常的原因就是在衍生线程中创建了SimpleAsyncTask对象。至于为什么在4.0版本上没有问题,是因为4.0中在ActivityThread.main()方法中,会进行BindApplication的动作,这时会用AsyncTask对象,也会创建sHandler对象,这是主线程所以sHandler是与主线程绑定的。后面再创建AsyncTask对象时,因为sHandler已经初始化完了,不会再次初始化。至于什么是BindApplication,为什么会进行BindApplication的动作不影响这个问题的讨论。
AsyncTask的缺陷及修改方法
这其实是AsyncTask的隐藏的Bug,它不应该这么依赖开发者,应该强加条件限制,以保证第一次AsyncTask对象是在主线程中创建:
1. 在InternalHandler的构造中检查当前线程是否为主线程,然后抛出异常,显然这并不是最佳实践。
new InternalHandler() {  
if (Looper.myLooper() != Looper.getMainLooper()) {  
ow new RuntimeException("AsyncTask must be initialized in main thread");  
}  
2. 更好的做法是在InternalHandler构造时把主线程的MainLooper传给
new IntentHandler() {  
    super(Looper.getMainLooper());  
}  
会有人这样写吗,你会问?通常情况是不会的,没有人会故意在衍生线程中创建AsyncTask。但是假如有一个叫Worker的类,用来完成异步任务从网络上下载图片,然后显示,还有一个WorkerScheduler来分配任务,WorkerScheduler也是运行在单独线程中,Worker用AsyncTask来实现,WorkScheduler会在接收到请求时创建Worker去完成请求,这时就会出现在WorkerScheduler线程中---衍生线程---创建AsyncTask对象。这种Bug极其隐蔽,很难发现。
如何限制调用者的线程
正常情况下一个Java应用一个进程,且有一个线程,入口即是main方法。安卓应用程序本质上也是Java应用程序,它的主入口在ActivityThread.main(),在main()方法中会调用Looper.prepareMainLooper(),这就初始化了主线程的Looper,且Looper中保存有主线程的Looper对象mMainLooper,它也提供了方法来获取主线程的Looper,getMainLooper()。所以如果需要创建一个与主线程绑定的Handler,就可以用new Handler(Looper.getMainLooper())来保证它确实与主线程绑定。
如果想要保证某些方法仅能在主线程中调用就可以检查调用者的Looper对象:
if (Looper.myLooper() != Looper.getMainLooper()) {  
   throw new RuntimeException("This method can only be called in main thread");  
}  
Handler,Looper,MessageQueue机制
线程与线程间的交互协作
线程与线程之间虽然共享内存空间,也即可以访问进程的堆空间,但是线程有自己的栈,运行在一个线程中的方法调用全部都是在线程自己的调用栈中。通俗来讲西线程就是一个run()方法及其内部所调用的方法。这里面的所有方法调用都是独立于其他线程的,由于方法调用的关系,一个方法调用另外的方法,那么另外的方法也发生在调用者的线程里。所以,线程是时序上的概念,本质上是一列方法调用。
那么线程之间要想协作,或者想改变某个方法所在的线程(为了不阻塞自己线程),就只能是向另外一个线程发送一个消息,然后return;另外线程收到消息后就去执行某些操作。如果是简单的操作可以用一个变量来标识,比如A线程主需要B线程做某些事时,可以把某个对象obj设置值,B则当看到obj != null时就去做事,这种线程交互协作在《Java编程思想》中有大量示例。
Android中的ITC-Inter Thread Communication
注意:当然Handler也可以用做一个线程内部的消息循环,不必非与另外的线程通信,但这里重点讨论的是线程与线程之间的事情。
Android当中做了一个特别的限制就是非主线程不能操作UI元素,而一个应用程序是不可能不创衍生线程的,这样一来主线程与衍生线程之间就必须进行通信。由于这种通信很频繁,所以不可能全用变量来标识,程序将变得十分混乱。这个时候消息队列就变得有十分有必要,也就是在每个线程中建立一个消息队列。当A需要B时,A向B发一个消息,此过程实质为把消息加入到B的消息队列中,A就此return,B并不专门等待某个消息,而是循环的查看其消息队列,看到有消息后就去执行。
整套ITC的基本思想是:定义一个消息对象,把需要的数据放入其中,把消息的处理的方法也定义好作为回调放到消息中,然后把这个消息发送另一个线程上;另外的线程在循环处理其队列里的消息,看到消息时就对消息调用附在其上的回调来处理消息。这样一来可以看出,这仅仅是改变了处理消息的执行时序:正常是当场处理,这种则是封装成一个消息丢给另外的线程,在某个不确定的时间被执行;另外的线程也仅提供CPU时序,对于消息是什么和消息如何处理它完全不干预。简言之就是把一个方法放到另外一个线程里去调用,进而这个方法的调用者的调用栈(call stack)结束,这个方法的调用栈转移到了另外的线程中。
那么这个机制改变的到底是什么呢?从上面看它仅是让一个方法(消息的处理)安排到了另外一个线程里去做(异步处理),不是立刻马上同步的做,它改变的是CPU的执行时序(execution sequence)。
那么消息队列存放在哪里呢?不能放在堆空间里(直接new MessageQueue()),这样的话对象的引用容易丢失,针对线程来讲也不易维护。Java支持线程的本地存储ThreadLocal,通过ThreadLocal对象可以把对象放到线程的空间上,每个线程都有了属于自己的对象。因此,可以为每个需要通信的线程创建一个消息队列并放到其本地存储中。基于这个模型还可以扩展,比如给消息定义优先级等。
MessageQueue
以队列的方式来存储消息,主要是二个操作一个是入列enqueueMessage,一个是出列next(),需要保证的是线程安全,因为入列通常是另外的线程在调用。
MessageQueue是一个十分接近底层的机制,所以不方便开发者直接使用,要想使用此MessageQueue必须做二个方面工作,一个是目标线程端:创建,与线程关联,运转起来;另一个就是队列线程的客户端:创建消息,定义回调处理,发送消息到队列。Looper和Handler就是对MessageQueue的封装:Looper是给目标线程用的:用途是创建MessageQueue,将MessageQueue与线程关联起来,并让MessageQueue运转起来,且Looper有保护机制,让一个线程仅能创建一个MessageQueue对象;而Handler则是给队列客户端用的:用来创建消息,定义回调和发送消息。
因为Looper对象封装了目标队列线程及其队列,所以对队列线程的客户端来讲,Looper对象就代表着一个拥有MessageQueue的线程,和这个线程的MessageQueue。也即当你构建Handler对象时用的是Looper对象,而当你检验某个线程是否是预期线程时也用Looper对象。
Looper内幕
Looper的任务是创建消息队列MessageQueue,放到线程的ThreadLocal中(与线程关联),并且让MessageQueue运转起来,处于Ready的状态,并要提供供接口以停止消息循环。每个线程只有一个Looper,负责管理MessageQueue,会不断地从MessageQueue中取出消息,并将消息分给对应的Handler处理我的理解也就这了
它主要有四个接口:
public static void Looper.prepare()
这个方法是为线程创建一个Looper对象和MessageQueue对象,并把Looper对象通过ThreadLocal放到线程空间里去。需要注意的是这个方法每个线程只能调用一次,通常的做法是在线程run()方法的第一句,但只要保证在loop()前面即可。
public static void Looper.loop()
这个方法要在prepare()这后调用,是让线程的MessageQueue运转起来,一旦调用此方法,线程便会无限循环下去(while (true){...}),无Message时休眠,有Message入队时唤醒处理,直到quit()调用为止。它的简化实现就是:
loop() {  
   while (true) {  
      Message msg = mQueue.next();  
      if msg is a quit message, then  
         return;  
      msg.processMessage(msg)  
   }  
}  
public void Looper.quit()
让线程结束MessageQueue的循环,终止循环,run()方法会结束,线程也会停止,因此它是对象的方法,意即终止某个Looper对象。一定要记得在不需要线程的时候调用此方法,否则线程是不会终止退出的,进程也就会一直运行,占用着资源。如果有大量的线程未退出,进程最终会崩掉。
public static Looper Looper.myLooper()
这个是获得调用者所在线程所拥有的Looper对象的方法。
还有二个接口是与主线程有关的:
一个是专门为主线程准备的
public static void Looper.prepareMainLooper();
这个方法只给主线程初始化Looper用的,它仅在ActivityThread.main()方法中调用,其他地方或其他线程不可以调用,如果在主线程中调用会有异常抛出,因为一个线程只能创建一个Looper对象。但是如在其他线程中调用此方法,会改变mainLooper,接下来的getMainLooper就会返回它而非真正的主线程的Looper对象,这不会有异常抛出,也不会有明显的错误,但是程序将不能正常工作,因为原本设计在主线程中运行的方法将转到这个线程里面,会产生很诡异的Bug。这里Looper.prepareMainThread()的方法中应该加上判断:
[java] view plaincopyprint?
public void prepareMainLooper() {  
    if (getMainLooper() != null) {  
         throw new RuntimeException("Looper.prepareMainthread() can ONLY be called by Frameworks");  
     }  
}  
以防止其他线程非法调用,光靠文档约束力远不够。
另外一个就是获取主线程Looper的接口:
public static Looper Looper.getMainLooper()
这个主要用在检查线程合法性,也即保证某些方法只能在主线程里面调用。但这并不保险,如上面所说,如果一个衍生线程调用了prepareMainLooper()就会把真正的mMainLooper改变,此衍生线程就可以通过上述检测,导致getMainLooper() != myLooper()的检测变得不靠谱了。所以ViewRoot的方法是用Thread来检测:mThread != Thread.currentThread();其mThread是在系统创建ViewRoot时通过Thread.currentThread()获得的,这样的方法来检测是否是主线程更加靠谱一些,因为它没有依赖外部而是相信自己保存的Thread的引用。
Message对象
消息Message是仅是一个数据结构,是信息的载体,它与队列机制是无关的,封装着要执行的动作和执行动作的必要信息,what, arg1, arg2, obj可以用来传送数据;而Message的回调则必须通过Handler来定义,为什么呢?因为Message仅是一个载体,它不能自己跑到目标MessageQueue上面去,它必须由Handler来操作,把Message放到目标队列上去,既然它需要Handler来统一的放到MessageQueue上,也可以让Handler来统一定义处理消息的回调。需要注意的是同一个Message对象只能使用一次,因为在处理完消息后会把消息回收掉,所以Message对象仅能使用一次,尝试再次使用时MessageQueue会抛出异常。
Handler对象
它被设计出来目的就是方便队列线程客户端的操作,隐藏直接操作MessageQueue的复杂性。Handler最主要的作用是把消息发送到与此Handler绑定的线程的MessageQueue上,因此在构建Handler的时候必须指定一个Looper对象,如果不指定则通过Looper获取调用者线程的Looper对象。它有很多重载的send*Message和post方法,可以以多种方式来向目标队列发送消息,廷时发送,或者放到队列的头部等等;
它还有二个作用,一个是创建Message对象通过obtain*系统方法,另一个就是定义处理Message的回调mCallback和handleMessage,由于一个Handler可能不止发送一个消息,而这些消息通常共享此Handler的回调方法,所以在handleMessage或者mCallback中就要区分这些不同的消息,通常是以Message.what来区分,当然也可以用其他字段,只要能区别出不同的Message即可。需要指明的是,消息队列中的消息本身是独立的,互不相干的,消息的命名空间是在Handler对象之中的,因为Message是由Handler发送和处理的,所以只有同一个Handler对象需要区别不同的Message对象。广义上讲,如果一个消息自己定义有处理方法,那么所有的消息都是互不相干的,当从队列取出消息时就调用其上的回调方法,不会有命名上的冲突,但由Handler发出的消息的回调处理方法都是Handler.handleMessage或Handler.mCallback,所以就会有影响了,但影响的范围也令局限在同一个Handler对象。

因为Handler的作用是向目标队列发送消息和定义处理消息的回调(处理消息),它仅是依赖于线程的MessageQueue,所以Handler可以有任意多个,都绑定到某个MessageQueue上,它并没有个数限制。而MessageQueue是有个数限制的,每个线程只能有一个,MessageQueue通过Looper创建,Looper存储在线程的ThreadLocal中,Looper里作了限制,每个线程只能创建一个。但是Handler无此限制,Handler的创建通过其构造函数,只需要提供一个Looper对象即可,所以它没有个数限制。

protected Handler mHandler = new Handler(Looper.myLooper()) { 

//Looper.myLooper();获得当前的Looper       // Looper.getMainLooper () 获得UI线程的Lopper

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            onMessage(msg);
        }

  };

private void connectTimeout() {
    TimerTask task = new TimerTask() {
            @Override
            public void run() {
                connectHandler.sendEmptyMessage(CONNECT_TV_TIMEOUT);
            }
        };
        Timer timer = new Timer(true);
        timer.schedule(task, CONNECT_TIMEOUT);
    }
    Handler connectHandler = new Handler() {
          public void handleMessage(Message msg) {
               switch (msg.what) {
               case CONNECT_TV_TIMEOUT:
                break;
              default:
              break;
         }
      }
  };

Looper:每个线程只有一个Looper,负责管理MessageQueue,会不断地从MessageQueue中取出消息,并将消息分给对应的Handler处理

ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
ThreadLocal()
       创建一个线程本地变量。
T get()
      返回此线程局部变量的当前线程副本中的值,如果这是线程第一次调用该方法,则创建并初始化此副本。
protected  T initialValue()
      返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。
   若该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal 创建子类,并重写此方法。通常,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。
void remove()
      移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。
void set(T value)
          将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于 initialValue() 方法来设置线程局部变量的值。
在程序中一般都重写initialValue方法,以给定一个特定的初始值。

执行get()时首先获取当前的Thread,再获取Thread中的ThreadLocalMap - t.threadLocals,并以自身为key取出实际的value。ThreadLocal的变量实际还是保存在Thread中的,容器是一个Map,Thread用到多少ThreadLocal变量,就会有多少以其为key的Entry。

原创粉丝点击