Android的同步对话框(AlertDialog模态对话框返回值实现原理)

最近做毕业设计,在抽象层次上需要做一些统一的可复用界面交互方法,比如对话框。具体需求是通过调用一个方法,这个方法体中生成一个对话框与用户交互,等与用户交互完毕后将用户输入的信息返回,用伪代码来体现,结构大致如下:
[mw_shl_code=java,true]public Object getXXXByDialog(){
  Object result;
  result = showDialog();//显示一个对话框与用户交互,并返回用户输入的信息
  return result;//返回用户输入的信息
}[/mw_shl_code]
很容易理解的结果,但是实现起来很麻烦,因为遇到一个同步和异步机制的问题。
在Android中,启动一个activity,serivice,对话框等等这些组件都是采用异步的机制(通过消息循环和消息队列)。也就是在上面的代码中,执行showDialog方法显示一个对话框后,不等对话框将用户输入的信息返回,showDialog下一行的return就会马上执行。所以在上面的伪代码结构中,return返回的结果永远都是null。整个过程用图形来表示大致如下,如图:

当使用showDialog方法后,实际上就是向消息队列中发送消息,要求启动对话框。消息发送完了之后就继续执行showDialog后面的代码,对话框什么时候出现取决于对话框的消息处理的时候。因为处理的很快,所以就好像是showDialog调用后就直接显示出了对话框,但实际上showDialog后面的代码已经执行了。
解决这个问题的思路就是想办法让对话框显示,并且用户输入信息后把对话框结束了再执行return方法,也就是让它们同步。因此,先简单了解一下线程和消息循环。
在Android中,当启动一个程序的时候,系统会先为这个程序启动一个主线程并且为这个线程创建一个Looper,这个Looper就是管理主线程消息循环消息队列的一个对象,它包含了一个消息队列。实际上,Activity、Serivice这些组件的启动也是通过异步机制来实现的,同样是通过向主线程的这个消息队列中发送消息,等到消息被处理程序处理解析后才会创建Activity、Service这些组件。异步机制的好处大概就是可以提高程序的并发性和响应性等等。
与消息队列相关的还有怎么向消息队列发送消息的问题。向消息队列中发送消息还涉及到另一个大名鼎鼎的类Handler,这个类可以看做是消息队列的工具类,用于向消息队列中添加消息(sendMessage等方法),以及为消息处理环节提供具体处理代码(重写Handler类的handleMessage方法)。一个Handler实例会绑定到一个线程的Looper,绑定后就可以通过Handler向Looper的消息队列中发送消息和提供处理代码。使用默认的Handler构造方法构造的Handler实例会自动绑定到当前线程拥有的Looper(一般常说Handler要在主线程中创建的原因就是因为主线程拥有一个Looper,不是每个线程拥有Looper,在没有Looper的线程中创建Handler将会抛出异常)。
简单了解了异步的一些基础东西后,回到真题。对话框是一个UI,所以运行在主线程上面,showDialog方法也就是向主线程的Looper消息队列中发送消息。Looper类提供了getMainLooper()静态方法用于获取主线程的Looper。另外Looper还提供了另一个静态方法loop(),这个方法内是一个死循环,用于立即从消息队列中获取消息进行处理,当没有消息可以处理时,这个循环就是挂起,等到有新的消息出现时再继续循环。官方文档上说使用loop()方法后,loop方法后面的代码将不会执行,直到调用了Looper的另一个方法quit()才会结束死循环继续后续代码。所以,根据这个信息来编写同步对话框。
先来看一下我根据这个思路抽象出来的一个同步对话框抽象类:
[mw_shl_code=java,true]import android.app.Dialog;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;

public abstract class SynDialog extends Dialog {

        private Handler mHandler;
        protected Object result;
       
        public SynDialog(Context context){
                super(context);
                onCreate();
        }

        public abstract void onCreate();
       
        /**
         * 结束对话框,将触发返回result对象
         */
        public void finishDialog(){
                dismiss();
               mHandler.sendEmptyMessage(0);
        }
       
        static class SynHandler extendsHandler{
                @Override
                public voidhandleMessage(Message msg) {
                        throw newRuntimeException();
                }
        }
       
        /**
         * 显示同步对话框
         * @return 返回result对象
         */
        public Object showDialog() {
                super.show();
                try {
                       Looper.getMainLooper();
                        mHandler = newSynHandler();
                        Looper.loop();
                } catch (Exception e) {
                }
                return result;
        }
}
[/mw_shl_code]
这个抽象类继承了Dialog,继承这个抽象类实现自己具体的同步对话框类,然后构造出实例调用showDialog就可以显示同步对话框(在对话框结束之前不会return result)。继承的时候需要实现这个抽象类的onCreate方法,在onCreate方法中设置你自己的对话框界面。finishDialog方法用于结束这个同步对话框,也就是关闭结束对话框的时候一定要调用这个方法才会让result返回。
大致解释下原理:继承后在onCreate中实现自定义对话框界面,显示对话框要调用showDialog方法。进入showDialog方法,首先执行super.show()方法发送消息要求显示对话框,此时还是因为异步机制,消息发送出去后继续运行下面的代码,进入try-catch模块。先用Looper.getMainLooper()方法获取主线程的Looper对象,再创建Handler对象,也就是将创建的Handler对象和主线程的Looper绑定。再往下执行Looper.loop()方法,关键就是在这里了,此时是主线程,loop方法后进入消息死循环处理,后续代码暂停不再继续执行下去,return自然没有执行。等到用户在对话框中输入信息后调用finishDialog方法,finishDialog先用dismiss关闭对话框,在用mHandler发送一条空消息进入消息队列。消息队列接受到消息用,从mHandler的handleMessage方法中获取处理代码。在这里,处理代码仅仅是抛出一个运行时异常。这个异常一抛出就会被loop()所在的try-catch捕捉,然后进入catch,loop()方法也就退出了。异常catch之后,后续代码也就继续执行下去,return也就被执行将result返回。这样就实现了调用showDialog后,对话框没有结束,就不会返回结果的同步假象。
至于为什么要采用抛异常的方式,Looper有提供一个quit退出loop的方法,为什么不直接调用这个quit方法?遗憾的是,主线程的Looper很特殊,不能quit,一quit也会抛出异常。我试过另外创建一个线程并赋给一个新的Looper,但由于对话框运行在UI线程,实行起来也很麻烦,反而这种抛异常的方法最简洁,虽然心里上不太能接受这种奇怪的做法。
这里有一个问题,在调用showDialog后,后续的代码没有继续执行,相当于是阻塞的样子。按照android的机制,超时会抛出ANR超时异常,但是在我的测试中并没有抛出ANR。找了很多ANR的资料,我自己猜测的结论是loop方法虽然是个死循环,但是它在消息队列中消息空的时候会挂起睡眠,没有一直占用CPU等资源,也就没抛出ANR。
好了,同步对话框就介绍到这里,折腾了一天,查了很多资料,结论多少有些个人的理解和猜想,如果有不对的地方还请指正。
主要参考了以下文章:
找到一个在Android上创建阻塞式模态对话框的方法http://blog.csdn.net/winux/article/details/6269687
Android应用程序消息处理机制(Looper、Handler)分析http://blog.csdn.net/luoshengyang/article/details/6817933?reload
强烈跟大家推荐下面这个老罗的博客,讨论的东西深入到很多源代码,值得膜拜的大神。

 

你可能感兴趣的:(Android的同步对话框(AlertDialog模态对话框返回值实现原理))