您的位置:首页 > 移动开发 > Android开发

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

2016-11-13 12:09 288 查看
最近做毕业设计,在抽象层次上需要做一些统一的可复用界面交互方法,比如对话框。具体需求是通过调用一个方法,这个方法体中生成一个对话框与用户交互,等与用户交互完毕后将用户输入的信息返回,用伪代码来体现,结构大致如下:

[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

强烈跟大家推荐下面这个老罗的博客,讨论的东西深入到很多源代码,值得膜拜的大神。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: