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 毫秒打印。
当您尝试这样做时,您会发现发生这种情况时实际上主线程已挂起。
我有一个简单的 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 毫秒打印。
当您尝试这样做时,您会发现发生这种情况时实际上主线程已挂起。