以下是来源:
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(运行OnUIThread()和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(我认为这是当天的最后一次编辑)
这是我迄今为止得到的"变通方法"。所有的功劳都归于内特的帮助。
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();
}
}
这个答案与问题的更新有关,您正在尝试使用AsyncTask
。您所拥有的代码实际上与AsyncTask
的使用方式相反。AsyncTask
有多个方法,这些方法旨在从不同的线程运行。您实现的方法doInBackground()
是从后台线程调用的。因此,您不应该(直接)在该方法中更新UI。
在AsyncTask
的端运行的方法是onPostExecute()
。它在UI线程上运行,在中进行UI调用是安全的,例如显示Dialog
。如果在任务运行期间需要更新UI,则可以实现第三种方法onProgressUpdate()
。它对UI操作也是安全的。如果您的后台处理(在doInBackground()
中)需要向onProgressUpdate()
方法传递信息,那么它可以通过调用publishProgress()
方法来实现,并在onProgressUpdate()
中传递所需的任何数据。这些调用的参数是泛型,所以您几乎可以使它们成为任何东西。一个典型的实现将%complete作为整数传递。
请参阅AsyncTask的API文档的开头部分,以获取一个非常简单的示例。
但是,这听起来更简单的东西会对你有用。如果将Render
类的构造函数更改为采用Activity
,而不是Context
:
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()
方法中,或者放在后台线程中Runnable
的run()
方法中。试试看。
崩溃的原因是您正试图关闭mainlooper。总需要至少有一个用于主线程(又称UI)的活套。
所以,千万不要打
getMainLooper().quit();
可能,您想调用Looper.myLooper()
而不是Looper.getMainLooper()
?但是,我不完全确定你的程序要做什么
你可能想读一读这个Android线程教程。
AsyncTask最终可能也会让你更容易使用,尽管我对你的应用程序的功能有点不清楚,也许不是。
此外,至少看起来您的boolean running
标志不是线程安全的。它是在没有保护的情况下从多个线程访问的。这并不是你发布的崩溃消息的原因,我只是指出了这一点。
编辑:事实上,现在我来看一下它,即使running
变量中存在潜在的不安全因素,但在创建后台线程之前,它看起来只设置了一次。所以,如果这是你唯一的用法,它并不安全。。。但是,价值也永远不会改变。所以,它要么没用,要么你在其他地方调用setLoopFlag()
,这可能不安全(?)。