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

如何检测android中的活动冻结和滞后原因?

Android startactivity 冻结应用

在 onStop() 之后,带有线程的 Android 活动被破坏

Android,完成所有活动

用于 android 开发的 adt-bundle 中的 Eclipse 通过自动完成冻结