Android 应用程序在完成活动时冻结,runOnUiThread() 停止工作
Posted
技术标签:
【中文标题】Android 应用程序在完成活动时冻结,runOnUiThread() 停止工作【英文标题】:Android app freezes on finishing activity, runOnUiThread() stops working 【发布时间】:2021-03-25 16:41:06 【问题描述】:我正在编写我的第一个 android 应用程序,一个小行星克隆,我有一个我无法弄清楚的最后一个错误。当游戏结束并尝试进入游戏结束活动时,游戏活动偶尔会冻结。游戏活动有一些 UI 视图在冻结之前停止工作。有一个按钮可以暂停游戏,停止响应输入。还有一个 TextView 用于我正在使用 runOnUIThread() 更新的分数(因为我在单独的线程上运行游戏)停止更新。
有几件事让我感到困惑。首先,我没有看到任何错误消息。该错误仅在游戏活动开始的每 5 次中发生大约 1 次,并且在运行时随机发生。它发生在游戏活动大约一半的时间开始时,但似乎可能发生在任何时候,尽管它确实倾向于迟早发生,即使它没有在开始时发生。此外,当错误发生时,我制作的游戏线程和自定义游戏视图会不断更新。最后,我制作了两个虚拟操纵杆视图来控制玩家的动作和射击,它们也不断更新和响应触摸输入。最后两个真的让我很困惑,因为起初我认为主线程做的工作太多或陷入循环或其他什么,但如果我理解正确(并且根据我所做的一些测试)绘图和触摸输入发生在主线程,据我所知,所有视图都画得很好,触摸输入适用于操纵杆,但不适用于暂停按钮。另外,如果主线程没有响应,我不会得到一个应用程序没有响应的弹出窗口吗?但这永远不会发生。
我尝试删除 runOnUIThread() 的所有实例,只是为了查看是否修复了它,除了破坏 TextView 得分并使应用程序卡在游戏活动中,即使错误没有发生也是如此。我尝试在游戏循环、游戏视图的 onDraw() 方法、JoystickView 的 onTouch() 方法以及在游戏中更新分数 TextView 时打印 Thread.currentThread() 返回的线程的名称活动以确保我实际上正在运行 2 个单独的线程,并且所有内容都在它们应该运行的线程上运行(游戏循环的游戏线程,其他所有线程的主线程),并且已签出。我已经尝试在游戏循环期间减少创建新对象,但不知道这除了有助于提高性能外还能做什么,但我认为这不会造成伤害,也没有任何效果。
这一切让我很难知道要发布什么代码。以下是一些猜测:
我的游戏活动中的 onCreate、goToGameOverActivity 和 updateScoreTextView 方法:
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
setContentView(R.layout.activity_play);
gameView = findViewById(R.id.gameView);
game = new Game(this, gameView);
float joystickRadius = 150;
CartesianPoint2D motionJoystickCenter = new CartesianPoint2D(
10 + joystickRadius,
Constants.getScreenInformation().getHeight() - 10 - joystickRadius);
motionJoyStickView = new MotionJoystickView(this, joystickRadius, motionJoystickCenter, game);
CartesianPoint2D shootJoystickCenter = new CartesianPoint2D(
Constants.getScreenInformation().getWidth() - 10 - joystickRadius,
Constants.getScreenInformation().getHeight() - 10 - joystickRadius);
shootJoystickView = new ShootJoystickView(this, joystickRadius, shootJoystickCenter, game);
scoreTextView = findViewById(R.id.playActivityScoreTextView);
pauseButton = findViewById(R.id.playActivityPauseButton);
soundEffects = new SoundEffects(this);
game.start();
public void goToGameOverActivity()
game.stop();
Intent intent = new Intent(this, GameOverActivity.class);
startActivity(intent);
finish();
public void updateScoreTextView(int score)
scoreTextView.setText(String.valueOf(score));
scoreTextView.invalidate();
我用来更新分数 TextView 的 Runnables 并使用 runOnUIThread() 发出游戏结束的信号:
package com.group18.spacerocks.play.view;
import com.group18.spacerocks.play.PlayActivity;
public class UpdateScoreTextView implements Runnable
protected int newScore;
protected PlayActivity playActivity;
public UpdateScoreTextView(int newScore, PlayActivity playActivity)
System.out.println("Setting ScoreTextView to " + newScore + ".");
this.newScore = newScore;
this.playActivity = playActivity;
@Override
public void run()
playActivity.updateScoreTextView(newScore);
package com.group18.spacerocks.play;
public class GoToGameOverActivity implements Runnable
protected PlayActivity playActivity;
public GoToGameOverActivity(PlayActivity playActivity)
this.playActivity = playActivity;
@Override
public void run()
playActivity.goToGameOverActivity();
GameThread 类扩展线程的 run() 方法更新游戏:
public void run()
super.run();
while (running)
while (paused)
currentTime = System.nanoTime();
lastUpdateTime = currentTime;
try
sleep(1);
catch (InterruptedException e)
e.printStackTrace()
currentTime = System.nanoTime();
timeSinceLastUpdate = currentTime - lastUpdateTime;
if (timeSinceLastUpdate >= TARGET_UPDATE_LENGTH)
currentUpdateLength = currentTime - lastUpdateTime;
game.update(currentUpdateLength);
gameView.invalidate();
lastUpdateTime = currentTime;
UPS++;
timeSinceLastUpdate = 0;
if (currentTime - lastSecondTime >= 1000000000)
System.out.println("UPS: " + UPS);
UPS = 0;
lastSecondTime = currentTime;
最后是我的 JoystickView 类的代码,它有 2 个用于运动和射击的子类:
package com.group18.spacerocks.play.joystick;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import com.group18.spacerocks.R;
import com.group18.spacerocks.play.CartesianPoint2D;
import com.group18.spacerocks.play.PlayActivity;
import com.group18.spacerocks.play.Game;
public class JoystickView extends View implements View.OnTouchListener
protected boolean inUse;
protected float radius;
protected float buttonRadius;
protected CartesianPoint2D center;
protected CartesianPoint2D buttonOffset;
protected PlayActivity playActivity;
protected Game game;
protected Paint outlinePaint;
protected Paint buttonPaint;
public JoystickView(PlayActivity playActivity, float radius, CartesianPoint2D center, Game game)
super(playActivity);
inUse = false;
this.radius = radius;
buttonRadius = radius / 2;
this.center = center;
this.buttonOffset = CartesianPoint2D.getInstance();
this.playActivity = playActivity;
this.game = game;
setLayoutParams(new ViewGroup.LayoutParams((int) radius * 2, (int) radius * 2));
setX(center.getX() - radius);
setY(center.getY() - radius);
outlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
outlinePaint.setAlpha(100);
outlinePaint.setStyle(Paint.Style.STROKE);
outlinePaint.setColor(Color.WHITE);
outlinePaint.setStrokeWidth(1);
buttonPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
buttonPaint.setAlpha(100);
buttonPaint.setStyle(Paint.Style.STROKE);
buttonPaint.setColor(playActivity.getResources().getColor(R.color.textTitleColor));
buttonPaint.setStrokeWidth(10);
setOnTouchListener(this);
ViewGroup viewGroup = playActivity.findViewById(R.id.playActivityLayout);
viewGroup.addView(this);
public void onDraw(Canvas canvas)
canvas.drawCircle(radius, radius, radius, outlinePaint);
canvas.drawCircle(radius + buttonOffset.getX(), radius + buttonOffset.getY(), buttonRadius, buttonPaint);
public boolean onTouch(View view, MotionEvent motionEvent)
if (motionEvent.getAction() == MotionEvent.ACTION_UP)
inUse = false;
buttonOffset.setX(0);
buttonOffset.setY(0);
else
inUse = true;
CartesianPoint2D motionPosition = new CartesianPoint2D(motionEvent.getX(), motionEvent.getY());
CartesianPoint2D centerOffset = new CartesianPoint2D(motionPosition.getX() - radius, motionPosition.getY() - radius);
if (centerOffset.getMagnitude() > radius)
buttonOffset = CartesianPoint2D.createFromMagnitudeAndDirection(radius, centerOffset.getDirection());
else
buttonOffset = centerOffset;
invalidate();
return true;
感谢任何见解,我已经尝试解决这个问题几天了,但我很茫然。
【问题讨论】:
【参考方案1】:仍然不知道确切的原因是什么,但我通过将游戏视图作为 SurfaceView 的扩展来修复它,这样我就可以在主线程以外的线程中绘制。
【讨论】:
以上是关于Android 应用程序在完成活动时冻结,runOnUiThread() 停止工作的主要内容,如果未能解决你的问题,请参考以下文章
单击嵌入式网络中的文本框时冻结的网络视图 - Android 4.3