Android:如何获得modal dialog或类似的模态行为?

这几天我正在模拟Android中的modal dialog。 我search了很多,有很多的讨论,但可悲的是没有太多的选项来获得它的模式。 这里有一些背景,
对话框,modal dialog和Blockin
Dialogs / AlertDialogs:如何在对话框启动时阻止执行(.NET风格)

有没有直接的方式来获得模态行为,然后我想出了3个可能的解决scheme,
1.使用一个对话主题的活动,就像这个线程所说的那样,但是我仍然不能使主要活动真正地等待对话活动的返回。 主要活动转到停止状态,然后重新启动。
2.构build一个工作线程,并使用线程同步。 然而,这是我的应用程序的一个巨大的重构工作,现在我有一个主要的UI线程中的主要活动和服务。
3.当模式对话框出现时,接pipe循环内的事件处理,并在对话框closures时退出循环。 实际上,这是build立一个真正的modal dialog的方式,就像在Windows中一样。 我还没有这样的原型。

我仍然想用对话主题的活动来模拟它,
1.通过startActivityForResult()启动dialog-activity
2.从onActivityResult()获取结果
这里有一些来源

public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyView v = new MyView(this); setContentView(v); } private final int RESULT_CODE_ALERT = 1; private boolean mAlertResult = false; public boolean startAlertDialog() { Intent it = new Intent(this, DialogActivity.class); it.putExtra("AlertInfo", "This is an alert"); startActivityForResult(it, RESULT_CODE_ALERT); // I want to wait right here return mAlertResult; } @Override protected void onActivityResult (int requestCode, int resultCode, Intent data) { switch (requestCode) { case RESULT_CODE_ALERT: Bundle ret = data.getExtras(); mAlertResult = ret.getBoolean("AlertResult"); break; } } } 

startAlertDialog的调用者将阻止执行并期望返回的结果。 但是当然,startAlertDialog立即返回,当DialogActivity启动时,主要活动进入STOP状态。

那么问题是,如何使主要活动真的等待结果呢?
谢谢。

我使用时有一个modal dialog:

 setCancelable(false); 

在DialogFragment上(不在DialogBu​​ilder上)。

这是不可能的,你计划的方式。 首先,您不允许阻止UI线程。 你的申请将被终止。 其次,需要处理使用startActivity启动另一个活动时调用的生命周期方法(当另一个活动正在运行时,原始活动将暂停)。 第三,你可能通过使用startAlertDialog()而不是UI线程,线程同步(比如Object.wait() )和一些AlertDialog 。 不过,我强烈build议你不要这样做。 这是丑陋的,肯定会破坏,这不是事情的目的。

重新devise您的方法来捕获这些事件的asynchronous性质。 如果你想要例如一些对话,要求用户做出决定(如接受ToS或不),并根据该决定做出特殊的行动,创build一个这样的对话框:

 AlertDialog dialog = new AlertDialog.Builder(context).setMessage(R.string.someText) .setPositiveButton(android.R.string.ok, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); // Do stuff if user accepts } }).setNegativeButton(android.R.string.cancel, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); // Do stuff when user neglects. } }).setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { dialog.dismiss(); // Do stuff when cancelled } }).create(); dialog.show(); 

然后有两种方法相应地处理积极或消极的反馈(即进行一些操作或完成活动或任何有意义的)。

Android和iOS的开发人员决定,他们非常强大而且足够聪明,可以拒绝Modal Dialog的概念(这已经在市场上已经有很多年了,之前没有人打扰过),这对我们来说是不幸的。

这是我的解决scheme,它很好用:

  int pressedButtonID; private final Semaphore dialogSemaphore = new Semaphore(0, true); final Runnable mMyDialog = new Runnable() { public void run() { AlertDialog errorDialog = new AlertDialog.Builder( [your activity object here] ).create(); errorDialog.setMessage("My dialog!"); errorDialog.setButton("My Button1", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { pressedButtonID = MY_BUTTON_ID1; dialogSemaphore.release(); } }); errorDialog.setButton2("My Button2", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { pressedButtonID = MY_BUTTON_ID2; dialogSemaphore.release(); } }); errorDialog.setCancelable(false); errorDialog.show(); } }; public int ShowMyModalDialog() //should be called from non-UI thread { pressedButtonID = MY_BUTTON_INVALID_ID; runOnUiThread(mMyDialog); try { dialogSemaphore.acquire(); } catch (InterruptedException e) { } return pressedButtonID; } 

这适用于我:创build一个活动作为你的对话框。 然后,

  1. 将此添加到您的清单中以进行活动:

    机器人:主题= “@安卓风格/ Theme.Dialog”

  2. 将此添加到您的活动的创build

    setFinishOnTouchOutside(false);

  3. 在您的活动中覆盖onBackPressed:

    @Override public void onBackPressed(){//防止“后退”离开这个活动}

第一个给对话框外观的活动。 后两个使它performance得像一个modal dialog。

最后,我最终得出了一个非常简单直接的解决scheme。

熟悉Win32编程的人可能知道如何实现modal dialog。 一般来说,当有一个模式对话框时,它会运行一个嵌套的消息循环(通过GetMessage / PostMessage)。 所以,我试图用这种传统的方式来实现我自己的modal dialog。

首先,android没有提供接口注入到ui线程的消息循环,或者我没有find一个。 当我看到源代码Looper.loop()时,我发现它正是我想要的。 但MessageQueue / Message仍然没有提供公共接口。 幸运的是,我们在java中进行了反思。 基本上,我只是复制了Looper.loop()所做的,它阻止了工作stream,并且仍然正确地处理了事件。 我没有testing嵌套模式对话框,但理论上它会工作。

这是我的源代码,

 public class ModalDialog { private boolean mChoice = false; private boolean mQuitModal = false; private Method mMsgQueueNextMethod = null; private Field mMsgTargetFiled = null; public ModalDialog() { } public void showAlertDialog(Context context, String info) { if (!prepareModal()) { return; } // build alert dialog AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setMessage(info); builder.setCancelable(false); builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { ModalDialog.this.mQuitModal = true; dialog.dismiss(); } }); AlertDialog alert = builder.create(); alert.show(); // run in modal mode doModal(); } public boolean showConfirmDialog(Context context, String info) { if (!prepareModal()) { return false; } // reset choice mChoice = false; AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setMessage(info); builder.setCancelable(false); builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { ModalDialog.this.mQuitModal = true; ModalDialog.this.mChoice = true; dialog.dismiss(); } }); builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { ModalDialog.this.mQuitModal = true; ModalDialog.this.mChoice = false; dialog.cancel(); } }); AlertDialog alert = builder.create(); alert.show(); doModal(); return mChoice; } private boolean prepareModal() { Class<?> clsMsgQueue = null; Class<?> clsMessage = null; try { clsMsgQueue = Class.forName("android.os.MessageQueue"); } catch (ClassNotFoundException e) { e.printStackTrace(); return false; } try { clsMessage = Class.forName("android.os.Message"); } catch (ClassNotFoundException e) { e.printStackTrace(); return false; } try { mMsgQueueNextMethod = clsMsgQueue.getDeclaredMethod("next", new Class[]{}); } catch (SecurityException e) { e.printStackTrace(); return false; } catch (NoSuchMethodException e) { e.printStackTrace(); return false; } mMsgQueueNextMethod.setAccessible(true); try { mMsgTargetFiled = clsMessage.getDeclaredField("target"); } catch (SecurityException e) { e.printStackTrace(); return false; } catch (NoSuchFieldException e) { e.printStackTrace(); return false; } mMsgTargetFiled.setAccessible(true); return true; } private void doModal() { mQuitModal = false; // get message queue associated with main UI thread MessageQueue queue = Looper.myQueue(); while (!mQuitModal) { // call queue.next(), might block Message msg = null; try { msg = (Message)mMsgQueueNextMethod.invoke(queue, new Object[]{}); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } if (null != msg) { Handler target = null; try { target = (Handler)mMsgTargetFiled.get(msg); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } if (target == null) { // No target is a magic identifier for the quit message. mQuitModal = true; } target.dispatchMessage(msg); msg.recycle(); } } } } 

希望这会有所帮助。

这并不困难。

假设您的所有者活动(名为waiting_for_result )有一个标志,每当您的活动恢复:

 public void onResume(){ if (waiting_for_result) { // Start the dialog Activity } } 

这保证了所有者的活动,除非modal dialog被解散,只要它试图获得焦点将传递到modal dialog的活动。

一个解决scheme是:

  1. 将每个选定button的所有代码放入每个button的侦听器中。
  2. alert.show(); 必须是调用Alert的函数中的最后一个代码行。 此行之后的任何代码都不会等待closures警报,而是立即执行。

希望帮助!

正如hackbod和其他人所指出的,Android有意不提供一个嵌套事件循环的方法。 我明白这个原因,但是有些情况需要他们。 在我们的例子中,我们有自己的虚拟机在不同的平台上运行,我们想把它移植到Android上。 在内部有很多需要嵌套事件循环的地方,重写Android的整个事情是不可行的。 无论如何,这里是一个解决scheme(基本上来自我怎样才能做Android上的非阻塞事件处理? ,但我已经增加了超时):

 private class IdleHandler implements MessageQueue.IdleHandler { private Looper _looper; private int _timeout; protected IdleHandler(Looper looper, int timeout) { _looper = looper; _timeout = timeout; } public boolean queueIdle() { _uiEventsHandler = new Handler(_looper); if (_timeout > 0) { _uiEventsHandler.postDelayed(_uiEventsTask, _timeout); } else { _uiEventsHandler.post(_uiEventsTask); } return(false); } }; private boolean _processingEventsf = false; private Handler _uiEventsHandler = null; private Runnable _uiEventsTask = new Runnable() { public void run() { Looper looper = Looper.myLooper(); looper.quit(); _uiEventsHandler.removeCallbacks(this); _uiEventsHandler = null; } }; public void processEvents(int timeout) { if (!_processingEventsf) { Looper looper = Looper.myLooper(); looper.myQueue().addIdleHandler(new IdleHandler(looper, timeout)); _processingEventsf = true; try { looper.loop(); } catch (RuntimeException re) { // We get an exception when we try to quit the loop. } _processingEventsf = false; } } 

我有一个像第五个类似的解决scheme,但它有点简单,不需要reflection。 我的想法是,为什么不使用例外退出活套。 所以我的习惯looper读取如下:

1)抛出的exception:

 final class KillException extends RuntimeException { } 

2)自定义活套:

 public final class KillLooper implements Runnable { private final static KillLooper DEFAULT = new KillLooper(); private KillLooper() { } public static void loop() { try { Looper.loop(); } catch (KillException x) { /* */ } } public static void quit(View v) { v.post(KillLooper.DEFAULT); } public void run() { throw new KillException(); } } 

自定义活套的使用非常简单。 假设你有一个对话框foo,那么只需要在你想以模态方式调用foo对话框的地方进行下面的操作:

a)当调用foo时:

 foo.show(); KillLooper.loop(); 

在foo对话框中,当你想退出时,你只需调用自定义循环的quit方法即可。 这看起来如下:

b)从foo退出时:

 dismiss(); KillLooper.quit(getContentView()); 

我最近看到5.1.1 Android的一些问题,不要从主菜单调用modal dialog,而是发布一个调用modal dialog的事件。 没有发布主菜单将失速,我已经看到Looper :: pollInner()SIGSEGVs在我的应用程序。

我不知道这是否是100%模式,因为你可以点击其他组件来closures对话框,但我对循环结构感到困惑,所以我提供这个作为另一种可能性。 它对我很好,所以我想分享这个想法。 您可以在一个方法中创build并打开对话框,然后在callback方法中closures它,程序将在执行callback方法之前等待对话框回复。 如果在新线程中运行其余的callback方法,则在执行其余代码之前,对话框也将首先closures。 你需要做的唯一事情就是拥有一个全局对话框variables,以便不同的方法可以使用它。 所以像下面的东西可以工作:

 public class MyActivity extends ... { /** Global dialog reference */ private AlertDialog okDialog; /** Show the dialog box */ public void showDialog(View view) { // prepare the alert box AlertDialog.Builder alertBox = new AlertDialog.Builder(...); ... // set a negative/no button and create a listener alertBox.setNegativeButton("No", new DialogInterface.OnClickListener() { // do something when the button is clicked public void onClick(DialogInterface arg0, int arg1) { //no reply or do nothing; } }); // set a positive/yes button and create a listener alertBox.setPositiveButton("Yes", new DialogInterface.OnClickListener() { // do something when the button is clicked public void onClick(DialogInterface arg0, int arg1) { callbackMethod(params); } }); //show the dialog okDialog = alertBox.create(); okDialog.show(); } /** The yes reply method */ private void callbackMethod(params) { //first statement closes the dialog box okDialog.dismiss(); //the other statements run in a new thread new Thread() { public void run() { try { //statements or even a runOnUiThread } catch (Exception ex) { ... } } }.start(); } }