以下是来源:
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();
}
}
编辑#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();
}
}
答案 0 :(得分:1)
崩溃的原因是你试图关闭主 looper。总是需要至少为主(也称为UI)线程的looper。
所以,永远不要打电话
getMainLooper().quit();
您可能希望拨打Looper.myLooper()
而不是Looper.getMainLooper()
?但是,我不完全确定你的程序正在尝试做什么。
您可以want to have a read of this Android threading tutorial。
AsyncTask也可能会让你更容易使用,虽然我对你的应用程序的功能有点不清楚,也许不是。
此外,至少看起来你的boolean running
标志不是线程安全的。它是从多个线程访问的,没有任何保护。这不会导致你发布的消息崩溃,我只是指出来了。
编辑:实际上,现在我看一下,即使你的running
变量中存在潜在的不安全感,但在创建后台线程之前,它看起来只是设置了一次。所以,如果那是你唯一的用法,那就不是不安全......但是,价值也永远不会改变。所以,它是无用的,或者你在其他地方调用setLoopFlag()
,这可能是不安全的(?)。
答案 1 :(得分:1)
此答案适用于您尝试使用AsyncTask
的问题更新。您拥有的代码实际上与AsyncTask
的使用方式相反。 AsyncTask
有多个方法,旨在从不同的线程运行。您实现的方法doInBackground()
旨在从后台线程调用。因此,您不应该在该方法中直接更新UI。
在AsyncTask
的结束处运行的方法是onPostExecute()
。这是在UI线程上运行的,可以安全地进行UI调用,例如显示Dialog
。如果您需要在>>运行任务期间更新UI ,那么您可以实施第三种方法onProgressUpdate()
。它对UI操作也很安全。如果您的后台处理(在doInBackground()
中)需要将信息传递给onProgressUpdate()
方法,那么它可以通过调用publishProgress()
方法,传入{{}中需要的任何数据来实现。 1}}。这些调用的参数是泛型,所以你几乎可以做任何事情。典型的实现将%complete作为整数传递。
See the very start of the API docs for AsyncTask这是一个非常简单的例子。
但是,听起来更简单的东西对你有用。如果您更改onProgressUpdate()
课程的构造函数以取Render
而不是Activity
:
Context
然后,您可以在Activity:
中使用超级有用的 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()
的{{1}}方法中。试试吧。