Android:Activity屏幕旋转后方向并不总是改变

Android: Activity orientation is not always changed after screen rotation

我有一个简单的 android 应用程序有问题。它有一个简单绘图的 SurfaceView,但 activity 屏幕旋转后方向有时似乎没有改变。

这是 activity 在纵向模式下的样子:

横向模式:

但有时,当我旋转屏幕时,activity 在纵向模式下看起来像这样:

MainActivity.java:

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback
{
    private Thread mGameThread;
    private GameApp mGameApp;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SurfaceView mainView = (SurfaceView)findViewById(R.id.mainView);
        SurfaceHolder holder = mainView.getHolder();
        holder.addCallback(this);

        mGameApp = new GameApp(getResources(), holder);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder)
    {
        mGameThread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                mGameApp.run();
            }
        });
        mGameThread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
    {
        mGameApp.setSurfaceSize(width, height);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder)
    {
        boolean retry = true;
        mGameApp.setRunning(false);
        while (retry)
        {
            try
            {
                mGameThread.join();
                retry = false;
            }
            catch (InterruptedException e)
            {
            }
        }
    }
}

GameApp.java:

package com.example.myapplication;

import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.view.SurfaceHolder;

public class GameApp
{
    private Resources mResources;
    private SurfaceHolder mSurfaceHolder;
    private int mCanvasHeight = 1;
    private int mCanvasWidth = 1;
    private volatile boolean mRun = false;

    public GameApp(Resources resources, SurfaceHolder surfaceHolder)
    {
        mResources = resources;
        mSurfaceHolder = surfaceHolder;
    }

    public void setSurfaceSize(int width, int height)
    {
        synchronized (mSurfaceHolder)
        {
            mCanvasWidth = width;
            mCanvasHeight = height;
        }
    }

    public void run()
    {
        setRunning(true);

        while (mRun)
        {
            Canvas canvas = null;
            try
            {
                canvas = mSurfaceHolder.lockCanvas(null);

                synchronized (mSurfaceHolder)
                {
                    if (mRun && canvas != null)
                    {
                        draw(canvas);
                    }
                }
            }
            finally
            {
                if (canvas != null)
                {
                    mSurfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }

    public void setRunning(boolean b)
    {
        mRun = b;
    }

    private void draw(Canvas canvas)
    {
        canvas.drawColor(Color.GREEN);

        Drawable cellImage = mResources.getDrawable(R.drawable.cell);

        final float cellWidth = mCanvasWidth / 6;
        final float cellHeight = mCanvasHeight / 6;
        for (int i = 0; i < 6; i++)
        {
            for (int j = 0; j < 6; j++)
            {
                float x = i * cellWidth;
                float y = j * cellHeight;

                cellImage.setBounds(Math.round(x), Math.round(y), Math.round(x + cellWidth), Math.round(y + cellHeight));
                cellImage.draw(canvas);
            }
        }
    }
}

最终答案:

问题是您在 GameApp 的线程中执行 while 循环,该循环锁定 canvas 然后解锁,中间没有任何长时间阻塞或休眠。 SurfaceHolder#lockCanvas 文档指出:

If null is not returned, this function internally holds a lock until the corresponding unlockCanvasAndPost(Canvas) call, preventing SurfaceView from creating, destroying, or modifying the surface while it is being drawn.

所以这意味着来自主线程的 运行 的销毁代码需要 运行 在 unlockCanvasAndPost 和下一个 lockCanvas 之间。但是由于您没有睡眠或中间没有其他代码(while 条件检查除外),因此发生这种情况的可能性非常小,并且 - 根据设备的不同 - 实际上可能需要很长时间。

要解决此问题,请在您的游戏应用中休眠以达到所需的帧率, 最简单的就是这样。

class GameApp

    ...
    while (mRun)
        {
            Canvas canvas = null;
            try
            {
                canvas = mSurfaceHolder.lockCanvas(null);

                synchronized (mSurfaceHolder)
                {
                    if (mRun && canvas != null)
                    {
                        draw(canvas);
                    }
                }
            }
            finally
            {
                if (canvas != null)
                {
                    mSurfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
            // Add some sleep time depending on how fast you want to refresh
            try {
                Thread.sleep(1000/60); //~60 fps
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

原始答案 1:在处理方向变化的情况下有什么帮助

似乎表面并不总是 re-drawn 配置更改。

尝试覆盖 activity 的 onConfigurationChanged 方法,然后触发 surfacere-layout

MainActivity.java中:

...
@Override
public void onConfigurationChanged(Configuration newConfig) {
   // You should save (SurfaceView)findViewById(R.id.mainView) in a field for better performance, but I'm putting it here for shorter code.
   SurfaceView mainView = (SurfaceView)findViewById(R.id.mainView);
   mainView.invalidate();
   mainView.requestLayout();
}
...

关于这些方法的更多信息在这里得到了很好的解释: Usage of forceLayout(), requestLayout() and invalidate()

原回答2:一种检查线程是否被阻塞的方法

另一种可能是你的主线程有线程锁问题。

用于检查您是否可以像这样更改 activity:

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
    private Thread mGameThread;
    private GameApp mGameApp;
    private static View currMainView;
    private static Thread logPosterThread = new Thread(new Runnable() {
        volatile boolean mainLogDone = true;
        @Override
        public void run() {
            Runnable mainThreadLogger = new Runnable() {
                @Override
                public void run() {
                    Log.d("THREAD_CHECK", "Main Thread is ok");
                    mainLogDone = true;
                }
            };
            while (true) {
                try {
                    int sleepTime = 1000;
                    Thread.sleep(sleepTime);
                   
                    if (currMainView != null) {
                        if (mainLogDone) {
                            mainLogDone = false;
                            Log.d("THREAD_CHECK", "Main Thread doing post");

                            currMainView.post(mainThreadLogger);
                        } else {
                            Log.w("THREAD_CHECK", "Main Thread did not run the runnable within " + sleepTime + "ms");
                            mainLogDone = true;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });
    static {
        logPosterThread.start();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final SurfaceView mainView = (SurfaceView) findViewById(R.id.mainView);
        currMainView = mainView;
        SurfaceHolder holder = mainView.getHolder();
        holder.addCallback(this);
        mGameApp = new GameApp(getResources(), holder);
    }
    @Override
    public void onPause() {
...

然后查看这些日志是否仍会在问题发生时间隔 1000 毫秒打印。

当您尝试这样做时,您会发现发生这种情况时实际上主线程已挂起。