Android: 屏幕旋转后,Activity 的方向并不总是会发生改变。

huangapple go评论66阅读模式
英文:

Android: Activity orientation is not always changed after screen rotation

问题

以下是您提供的内容的翻译:

我在一个简单的 Android 应用程序中遇到了一个问题。它有一个带有简单绘图的 SurfaceView,但在屏幕旋转后,活动方向有时似乎没有改变。

以下是活动在纵向模式下的外观:
Android: 屏幕旋转后,Activity 的方向并不总是会发生改变。

横向模式下的外观:
Android: 屏幕旋转后,Activity 的方向并不总是会发生改变。

但有时,当我旋转屏幕时,活动在纵向模式下的外观会变成这样:
Android: 屏幕旋转后,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);
            }
        }
    }
}
英文:

I have a problem with a simple android application. It has a SurfaceView with simple drawing, but activity orientation sometimes seems not to be changed after a screen rotation.

This is how an activity looks in the portrait mode:
Android: 屏幕旋转后,Activity 的方向并不总是会发生改变。

landscape mode:
Android: 屏幕旋转后,Activity 的方向并不总是会发生改变。

but sometimes, when I rotate the screen, an activity looks like this in the portrait mode:
Android: 屏幕旋转后,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 &amp;&amp; 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 &lt; 6; i++)
{
for (int j = 0; j &lt; 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);
}
}
}
}

答案1

得分: 2

Eventual answer:

问题在于你在 GameApp 的线程中执行了一个 while 循环,该循环锁定画布,然后在之间没有任何长时间的阻塞或休眠的情况下解锁。

SurfaceHolder#lockCanvas 的文档中指出:

> 如果返回值不是 null,则此函数在内部保持锁定,直到相应的 unlockCanvasAndPost(Canvas) 调用,防止 SurfaceView 在绘制时创建、销毁或修改表面。

这意味着从主线程运行的销毁代码需要在 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);
}
}
// 根据希望的刷新速率添加一些休眠时间
try {
Thread.sleep(1000/60); //~60 fps
} catch (InterruptedException e) {
e.printStackTrace();
}
}
英文:

Eventual answer:

The problem is that you are doing a while loop in your GameApp's thread that locks the canvas and then unlocks without any long blocking or sleep in between.
The SurfaceHolder#lockCanvas documentation states:

> 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.

So this means that the destroy code which is run from main thread needs to run between the unlockCanvasAndPost and the next lockCanvas. But since you have no sleep or even other code in between (except for the while condintion check), the chance of this happening is very small, and - depending on the device - could practically take forever.

To fix this, put a sleep in your game app to achieve the wanted framerate,
in it's most simple from this could look like this.

class GameApp

    ...
while (mRun)
{
Canvas canvas = null;
try
{
canvas = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder)
{
if (mRun &amp;&amp; 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();
}
}

Original answer 1: What could have helped in case of handling orientation changes

Seems like the surface is not always re-drawn on a config change.

Try overriding the onConfigurationChanged method of the activity and then triggering a re-layout of the surface

in MainActivity.java:

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

More info on these methods nicely explained here:
https://stackoverflow.com/questions/13856180/usage-of-forcelayout-requestlayout-and-invalidate

Original answer 2: A way to check if your threads are blocked

Another possibility is that you have a thread lock issue on your main thread.

For checking that you could change your activity like this:

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(&quot;THREAD_CHECK&quot;, &quot;Main Thread is ok&quot;);
mainLogDone = true;
}
};
while (true) {
try {
int sleepTime = 1000;
Thread.sleep(sleepTime);
if (currMainView != null) {
if (mainLogDone) {
mainLogDone = false;
Log.d(&quot;THREAD_CHECK&quot;, &quot;Main Thread doing post&quot;);
currMainView.post(mainThreadLogger);
} else {
Log.w(&quot;THREAD_CHECK&quot;, &quot;Main Thread did not run the runnable within &quot; + sleepTime + &quot;ms&quot;);
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() {
...

And then see if those logs still get printed with 1000ms in between when the issue happens.

When you try this, you will see that actually the main thread is hanging when this happens.

huangapple
  • 本文由 发表于 2020年8月30日 21:24:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/63657893.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定