【问题标题】:How to get an AlertDialog to work, while background is rendering, and not crash the app?如何在背景渲染时让 AlertDialog 工作,而不会使应用程序崩溃?
【发布时间】:2012-07-08 21:57:53
【问题描述】:

这里是来源:

package ff.ff;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Surface.OutOfResourcesException;

public class Basic extends Activity {
    private Render view;

    public class Render extends SurfaceView implements Runnable {


        //TODO: Test if AlertDialog can be able to work while another
        //thread is running continuously.
        //
        // Failed miserably.

        //ERROR Received:
        /*
         * 07-08 17:34:51.035: E/AndroidRuntime(7356): FATAL EXCEPTION: Thread-12
         * 07-08 17:34:51.035: E/AndroidRuntime(7356): java.lang.RuntimeException: Main thread not allowed to quit
         * 07-08 17:34:51.035: E/AndroidRuntime(7356):  at android.os.MessageQueue.enqueueMessage(MessageQueue.java:191)
         * 07-08 17:34:51.035: E/AndroidRuntime(7356):  at android.os.Looper.quit(Looper.java:231)
         * 07-08 17:34:51.035: E/AndroidRuntime(7356):  at ff.ff.Basic$Render$1$1.run(Basic.java:45)
         * 07-08 17:34:51.035: E/AndroidRuntime(7356):  at java.lang.Thread.run(Thread.java:1027) 
         * 
         */


        private int r, g, b;

        private boolean running;
        private SurfaceHolder holder;
        private AlertDialog.Builder builder;
        private AlertDialog dialog;

        public Render(Context context) {
            super(context);
            holder = this.getHolder();
            r = g = b = 0;
            builder = new AlertDialog.Builder(context);
            builder.setTitle("Enter");
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    Log.d("Render Dialog", "Working...");
                    Log.d("Render Dialog", "Exiting the Looper loop...");
                    new Thread(new Runnable(){
                        public void run(){
                            Looper.getMainLooper().quit();
                        }
                    }).start();
                }
            });
            dialog = builder.create();
        }

        public void setLoopFlag(boolean value) {
            running = value;
        }

        public void run() {
            boolean flag = false;
            while(running) {
                if (holder.getSurface().isValid()) {
                    Canvas c = null;
                    try {
                        c = holder.getSurface().lockCanvas(null);
                    }
                    catch(IllegalArgumentException e) {
                        e.printStackTrace();
                    }
                    catch(OutOfResourcesException e) {
                        e.printStackTrace();
                    }
                    c.drawARGB(255, r, g, b);
                    r++;
                    g++;
                    b++;
                    if (r > 250 || g > 250 || b > 250) {
                        r = 0;
                        g = 0;
                        b = 0;
                    }
                    if (!flag){
                        flag = true;
                        Looper.prepare();
                        dialog.show();
                        Looper.loop();
                    }
                    holder.getSurface().unlockCanvasAndPost(c);
                }
            }
        }
    }



    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        view = new Render(this);
        view.setLoopFlag(true);
        setContentView(view);
        Thread thread = new Thread(view);
        thread.setName("Render Thread");
        thread.start();
    }
}

您是否知道当游戏结束时,游戏会要求玩家输入姓名,以便在记分板上显示姓名和分数?通常,就是这样。我有一个将所有 3 个对象都渲染到屏幕上的游戏。当满足某个条件时,游戏会出现一个对话框,询问玩家的名字并祝贺玩家完成它。

这个简单的任务就是弹出一个玩家名字的对话框,这让很多人头疼。上面给出了源代码。

当线程处于紧密循环中(例如游戏循环)时,当程序想要向用户显示对话框时,通常推荐的执行方式是什么?为什么 Looper.prepare() 在这种情况下有用?

我无法理解这一点。 :(


编辑(更多信息):

我尝试使用 AsyncTask,它真的让我更加困惑。并不是我不想使用 AsyncTask,而是一个简单的“在背景更改颜色时显示对话框”作业怎么会变得越来越难以修复??

Logcat:

07-08 20:20:02.445: E/AndroidRuntime(11085): FATAL EXCEPTION: AsyncTask #1
07-08 20:20:02.445: E/AndroidRuntime(11085): java.lang.RuntimeException: An error occured while executing doInBackground()
07-08 20:20:02.445: E/AndroidRuntime(11085):    at android.os.AsyncTask$3.done(AsyncTask.java:200)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:274)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at java.util.concurrent.FutureTask.setException(FutureTask.java:125)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:308)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at java.lang.Thread.run(Thread.java:1027)
07-08 20:20:02.445: E/AndroidRuntime(11085): Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
07-08 20:20:02.445: E/AndroidRuntime(11085):    at android.os.Handler.<init>(Handler.java:121)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at android.app.Dialog.<init>(Dialog.java:122)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at android.app.AlertDialog.<init>(AlertDialog.java:63)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at android.app.AlertDialog.<init>(AlertDialog.java:59)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at android.app.AlertDialog$Builder.create(AlertDialog.java:786)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at ff.ff.Basic$DialogTask.doInBackground(Basic.java:112)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at ff.ff.Basic$DialogTask.doInBackground(Basic.java:1)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at android.os.AsyncTask$2.call(AsyncTask.java:185)
07-08 20:20:02.445: E/AndroidRuntime(11085):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:306)
07-08 20:20:02.445: E/AndroidRuntime(11085):    ... 4 more
07-08 20:20:03.276: E/msm8660.gralloc(11085): [unregister] handle 0x341330 still locked (state=c0000001)

来源:

package ff.ff;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class Basic extends Activity {
    private Render view;

    public class Render extends SurfaceView implements Runnable {


        //TODO: Test if AlertDialog can be able to work while another
        //thread is running continuously.
        //
        // Failed miserably.

        //ERROR Received:
        /*
         * 07-08 17:34:51.035: E/AndroidRuntime(7356): FATAL EXCEPTION: Thread-12
         * 07-08 17:34:51.035: E/AndroidRuntime(7356): java.lang.RuntimeException: Main thread not allowed to quit
         * 07-08 17:34:51.035: E/AndroidRuntime(7356):  at android.os.MessageQueue.enqueueMessage(MessageQueue.java:191)
         * 07-08 17:34:51.035: E/AndroidRuntime(7356):  at android.os.Looper.quit(Looper.java:231)
         * 07-08 17:34:51.035: E/AndroidRuntime(7356):  at ff.ff.Basic$Render$1$1.run(Basic.java:45)
         * 07-08 17:34:51.035: E/AndroidRuntime(7356):  at java.lang.Thread.run(Thread.java:1027) 
         * 
         */


        private int r, g, b;

        private boolean running;
        private SurfaceHolder holder;
        private DialogTask task;

        public Render(Context context) {
            super(context);
            holder = this.getHolder();
            task = new DialogTask(context);
            r = g = b = 0;
        }

        public void setLoopFlag(boolean value) {
            running = value;
        }

        public void run() {
            boolean flag = false;
            while(running) {
                if (holder.getSurface().isValid()) {
                    Canvas c = null;
                    try {
                        c = holder.getSurface().lockCanvas(null);
                    }
                    catch(IllegalArgumentException e) {
                        e.printStackTrace();
                    }
                    catch(OutOfResourcesException e) {
                        e.printStackTrace();
                    }
                    c.drawARGB(255, r, g, b);
                    r++;
                    g++;
                    b++;
                    if (r > 250 || g > 250 || b > 250) {
                        r = 0;
                        g = 0;
                        b = 0;
                    }
                    if (!flag){
                        flag = true;
                        Void[] v = new Void[1];
                        v[0] = null;
                        task.execute(v);
                    }
                    holder.getSurface().unlockCanvasAndPost(c);
                }
            }
        }
    }

    public class DialogTask extends AsyncTask<Void, Void, Void>{

        private Context context;
        private boolean exit;

        public DialogTask(Context c){
            context = c;
            exit = false;
        }

        @Override
        protected Void doInBackground(Void... params) {
            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    exit = true;
                }
            });
            builder.setTitle("Enter...");
            AlertDialog dialog = builder.create();
            dialog.show();
            return null;
        }

    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        view = new Render(this);
        view.setLoopFlag(true);
        setContentView(view);
        Thread thread = new Thread(view);
        thread.setName("Render Thread");
        thread.start();
    }
}

EDIT #2 (runOnUIThread() 和 onTouchEvent(MotionEvent e) 锁定,源代码如下:

public class Basic extends Activity {
    Render view;
    public class Render extends SurfaceView implements Runnable {
        private Activity activity;
        private SurfaceHolder holder;
        private boolean running;
        public Render(Activity a){
            super(a);
            activity = a;
            holder = this.getHolder();
            running = true;
        }

        public void run(){
            int r = 0;
            while (running){
                if (holder.getSurface().isValid()){
                    Canvas canvas = holder.lockCanvas();
                    canvas.drawARGB(255, r, 255, 255);
                    r++;
                    if (r > 255)
                        r = 0;
                    holder.unlockCanvasAndPost(canvas);
                }
            }   
        }

        public void start(){
            new Thread(this).start();
        }

        public boolean onTouchEvent(MotionEvent event){
            activity.runOnUiThread(new Runnable(){
                public void run(){
                    AlertDialog.Builder builder = new AlertDialog.Builder(activity);
                    builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Log.d("Activity", "It worked also......");  
                        }
                    });
                    AlertDialog dialog = builder.create();
                    dialog.show();
                }
            });
            return false;
        }

        public void stop(){
            running = false;
            boolean r = true;
            while(r){
                try {
                    Thread.currentThread().join();
                    r = false;
                }
                catch(InterruptedException e) {
                    r = true;
                }
            }
        }
    }


    public void onCreate(Bundle b){
        super.onCreate(b);
        view = new Render(this);
        this.setContentView(view);
    }

    public void onPause(){
        super.onPause();
        view.stop();
    }

    public void onResume(){
        super.onResume();
        view.start();
    }
}

编辑 #3(我认为这是当天的最后一次编辑)

这是迄今为止我得到的“解决方法”。所有功劳都归功于 Nate 的帮助。

package ff.ff;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;


public class Basic extends Activity {
    private AlertDialog dialog;
    private AlertDialog.Builder builder;
    private BackgroundColors view;

    public class BackgroundColors extends SurfaceView implements Runnable {
        private Thread thread;
        private boolean running;
        private SurfaceHolder holder;

        public BackgroundColors(Context context) {
            super(context);
        }

        public void run() {
            int r = 0;
            while (running){
                if (holder.getSurface().isValid()){
                    Canvas canvas = holder.lockCanvas();
                    if (r > 250)
                        r = 0;
                    r += 10;
                    canvas.drawARGB(255, r, 255, 255);
                    holder.unlockCanvasAndPost(canvas);
                }
            }
        }

        public void start() {
            running = true;
            thread = new Thread(this);
            holder = this.getHolder();
            thread.start();
        }

        public void stop() {
            running = false;
            boolean retry = true;
            while (retry){
                try {
                    thread.join();
                    retry = false;
                }
                catch(InterruptedException e) {
                    retry = true;
                }
            }
        }

        public boolean onTouchEvent(MotionEvent e){
            dialog.show();
            return false;
        }
    }

    public void onCreate(Bundle b) {
        super.onCreate(b);
        view = new BackgroundColors(this);
        this.setContentView(view);
        builder = new AlertDialog.Builder(this);
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.d("Basic", "It worked");
            }
        });
        dialog = builder.create();
    }

    public void onPause(){
        super.onPause();
        view.stop();
    }

    public void onResume(){
        super.onResume();
        view.start();
    }
}

【问题讨论】:

  • 提供 logcat 会很有用。在你的情况下,我认为AsyncTask 可以是一个解决方案。
  • @HaiBison 我确实提供了 logcat。 logcat 是灰色的 cmets。
  • 我明白了,对不起,我的错误。我认为您应该将对话框构建器移至Basic 类,使用Handler 与之通信。关于线程(prepare()、loop()…),我不确定。

标签: java android android-alertdialog looper


【解决方案1】:

此答案与您尝试使用AsyncTask 的问题更新有关。您拥有的代码实际上与 AsyncTask 的使用方式相反。 AsyncTask 有多个方法,旨在从不同的线程运行。您实现的方法doInBackground() 旨在从后台线程调用。因此,您不应该在该方法中(直接)更新 UI。

在AsyncTask 的end 运行的方法是onPostExecute()。这是在 UI 线程上运行的,并且可以安全地进行 UI 调用,例如显示Dialog。如果您需要在任务运行期间更新 UI,那么您可以实施第三种方法 onProgressUpdate()。对于 UI 操作也是安全的。如果您的后台处理(在doInBackground() 中)需要将信息传递给onProgressUpdate() 方法,那么它可以通过调用publishProgress() 方法来实现,在onProgressUpdate() 中传递所需的任何数据。这些调用的参数是通用,因此您几乎可以将它们设为任何参数。典型的实现将完成百分比作为整数传递。

See the very start of the API docs for AsyncTask 一个非常简单的例子。

但是,听起来更简单的东西对你也有用。如果您将Render 类的构造函数更改为采用Activity,而不是Context:

    private Activity parent;

    public Render(Activity activity) {
       super(activity);
       parent = activity;

那么,你就可以在Activity中使用超级好用的runOnUiThread()方法了:

    parent.runOnUiThread(new Runnable() {
       public void run() {
          AlertDialog.Builder builder = new AlertDialog.Builder(parent);
          builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int which) {
                 exit = true;
             }
          });
          builder.setTitle("Enter...");
          AlertDialog dialog = builder.create();
          dialog.show();
       }
    });

上面的代码块可以安全地放在任何地方。你可以把它放在你的 doInBackground() 方法中,或者在后台线程中 Runnable 的 run() 方法中。试试看。

【讨论】:

  • 我刚刚阅读了您的最后一条评论和这个答案。我觉得我在用 Looper.prepare() 和 AsyncTask 转圈圈。请问如果我不应该调用 Looper.myLooper().quit(),为什么 Logcat 告诉我使用 Looper.prepare()?是的,我正在调查你的答案。
  • 调用Looper.prepare() 是解决问题的一种方法。我认为使用AsyncTask(根本没有Looper)更容易。而且,我认为使用runOnUiThread() 更容易。无论如何,即使您确实坚持使用Looper,您也根本无法在主Looper 上调用quit()。
  • 你的意思是我只需要调用 Looper.prepare() 就可以了?圣摩西...
  • 所以,对于AsyncTask,我已经迷惑了很多次。对于 runOnUIThread() 方法,我发现它有效,但它很容易锁定我的手机。我决定调用 SurfaceView.onTouchEvent(MotionEvent) 中的 runOnUIThread(),它按预期锁定。现在,我正在研究这个问题。上面给出了 OnTouchEvent() 中的 runOnUIThread() 锁定的源代码。
  • 没有。我的意思是你根本不应该使用Looper。我在上面给了你两个使用 Looper 的替代方案。如果您查看我在第一个答案中发布的链接,本文讨论了这些不同的方法,并且在我看来,让读者了解为什么 AsyncTask 在大多数情况下是一个更可取的解决方案。但是,在那篇文章中,确实说您需要关注prepare() 和loop()。但是,如果您使用的是主 Looper,则不是 quit()。
【解决方案2】:

崩溃的原因是您试图关闭 ma​​in 循环器。主(又名 UI)线程总是需要至少有一个 looper。

所以,永远不要打电话

getMainLooper().quit();

您可能想调用Looper.myLooper() 而不是Looper.getMainLooper()?但是,我不完全确定您的程序要做什么。

你可以want to have a read of this Android threading tutorial。

AsyncTask 也可能会让你更容易使用,尽管我对你的应用程序的功能有点不清楚,也许不是。

此外,至少看起来您的 boolean running 标志不是线程安全的。它被多个线程访问,没有保护。这不是导致您发布的消息的崩溃,我只是指出来。

编辑:实际上,现在我查看它,即使您的 running 变量中存在潜在的不安全性,它看起来只是您在创建后台线程之前设置了一次。所以,如果这是你唯一的用法,它不是不安全的......但是,价值也永远不会改变。所以,它要么没用,要么你在其他地方调用setLoopFlag(),这可能是不安全的(?)。

【讨论】:

  • Looper.myLooper() 与 Looper.getMainLooper() 相同,因为 Looper 只有 1 个实例。我正在尝试做的是尝试弹出一个对话框,同时背景不断呈现背景颜色。
  • 我专门为我的程序设计了一个无限循环,所以我可以测试我的程序是否真的可以渲染背景颜色(从黑色到白色),同时看到对话框出现在前面。
  • 我添加了更多信息。请检查。
  • @tom_mai78101,如果myLooper() 返回的结果与getMainLooper() 相同,那么您不应该调用myLooper().quit()。关键是你不应该尝试在主循环器上调用quit()。首先打电话给quit() 有什么意义?你想用那个电话做什么? (我现在会检查你的 AsyncTask 代码......)
  • 如果我不调用 Looper.quit(),那么我一开始就无法使用 AlertDialog。根据 Logcat 的说法,要使用 AlertDialog,我需要调用 Looper.prepare()。我试图用那个调用做的是,一旦按下对话框的 OK 按钮,就停止获取用户输入。我只是想显示一个对话框,而背景将黑色渲染为灰色到白色。就是这样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多