Java 如何使AlertDialog在后台渲染时工作,而不使应用程序崩溃?

Java 如何使AlertDialog在后台渲染时工作,而不使应用程序崩溃?,java,android,android-alertdialog,looper,Java,Android,Android Alertdialog,Looper,资料来源如下: 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 an

资料来源如下:

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,而是一个简单的“背景颜色变化时显示对话框”工作怎么可能变得越来越难解决

日志:

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)

编辑#3(我想这是今天的最后一次编辑)

这是到目前为止我得到的“解决办法”。所有的功劳都归于内特的帮助

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();
    }
}

此崩溃的原因是您试图关闭main循环器。主线程(也称为UI)始终需要至少一个循环器

所以,永远不要打电话

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

也许吧

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

另外,至少看起来您的
布尔运行
标志不是线程安全的。它是从多个线程访问的,没有任何保护。这并不是导致崩溃的原因,我只是指出它


Edit:事实上,现在我看到了它,尽管运行的
变量中存在潜在的不安全性,但在创建后台线程之前,它看起来只设置了一次。因此,如果这是您唯一的用法,它并不不安全……但是,该值也永远不会更改。因此,它要么是无用的,要么是不安全的在其他地方调用
setLoopFlag()
,这可能不安全(?).

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

AsyncTask
端运行的方法是
onPostExecute()
。该方法在UI线程上运行,可以安全地进行UI调用,例如显示
对话框
。如果在任务运行期间需要更新UI,那么还有第三种方法,
onProgressUpdate()
,您可以实现。它对于UI操作也是安全的。如果您的后台处理(在
doInBackground()
)中)需要将信息传递给
onProgressUpdate()
方法,那么它可以通过调用
publishProgress()
方法,在
onProgressUpdate()中传递所需的任何数据来实现
。这些调用的参数是泛型的,因此您几乎可以对其进行任何设置。典型的实现将%complete作为整数传递

举一个非常简单的例子

但是,这听起来似乎更简单的方法也适用于您。如果您将
呈现
类的构造函数更改为采用
活动
,而不是
上下文

    private Activity parent;

    public Render(Activity activity) {
       super(activity);
       parent = 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()
方法或
run()中
在后台线程中运行
的方法。试试看。

提供logcat会很有用。在你的情况下,我认为这是一个解决方案。@HaiBison我确实提供了logcat。灰色的注释就是logcat。很抱歉,我搞错了。我认为你应该将对话框生成器移到class
Basic
,使用关于线程(prepare(),loop()…),我不确定。Looper.myLooper()与Looper.getMainLooper()相同,因为只有一个活套实例。我要做的是尝试弹出一个对话框,而背景一直在渲染背景色。我专门设计了一个无限循环的程序,所以我可以测试我的程序是否可以渲染背景色(从黑色到白色)同时看到前面出现的对话框。我添加了一些更多信息。请检查。@tom_mai78101,如果
myLooper()
返回与
getMainLooper()
相同的值,那么您不应该调用
myLooper().quit()
。关键是您不应该尝试调用
quit()
在主活套上。首先调用
quit()
有什么意义?如果我不调用活套,你想用该调用做什么?(我现在检查你的AsyncTask代码…),那么我从一开始就不能使用AlertDialog。根据Logcat,它说要使用AlertDialog,我需要调用Looper.prepare()。我试图用这个调用,在按下对话框的OK按钮后停止获取用户输入。我只是不想
    private Activity parent;

    public Render(Activity activity) {
       super(activity);
       parent = activity;
    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();
       }
    });