摘自:http://nwhy.org/android-configchanges.html
这是hipak那边测试反馈回来的一个问题,说来惭愧,一直没注意到这个问题的存在。以为Power键就是onPause处理就完了,结果不是。
这里边google的设计或许也有点问题,在竖屏情况下也许是一样处理的,不过当你的app是横屏,那就要注意了。
每次Power键的时候,app是会强制回到竖屏状态的,并且会重新调用Activity的onCreate(),当然很多时候这不是我们想要的。所以就需要用到android:configChanges了,在配置文件里设置android:configChanges="keyboardHidden|orientation",这样在屏幕方向改变的时候就不会重新调用Activity的onCreate(),而是调用onConfigurationChanged(),然后在Activity里重载下
@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
if(newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE){
//横向
}else{
//竖向
}
}
一般就这么处理下就可以了,要命的是用到了SurfaceView,而SurfaceView和Thread的生命周期是不一样的,唉,这里要说一下Google提供的sample了,里边有bug!!
由于每次Power键的时候会调用SurfaceView的surfaceDestroyed(SurfaceHolder holder),但是回到app的时候又没有执行surfaceCreated(SurfaceHolder holder),于是就咯屁了~~
目前想到一个能解决的方案是在onConfigurationChanged(Configuration newConfig)里手动处理,surfaceDestroyed(SurfaceHolder holder)+urfaceCreated(SurfaceHolder holder)+pause()处理。。。
唉,希望可以找到一个比较好的吧。
Well, after searching for this for the last two days it seems that either no one has figured it out, or no one has shared such information.
So, here goes.
I read about people trying this with opening new threads and trying to put the loading on the update thread or the UI thread to no avail. I found the AsyncTask for android after remembering how we did it with XNA.
Making an AsyncTask is simple, here's the API from google: Here
For those who would like a little more instruction and some code, read on.
As far as how I implement my game as some background, I don't use different activities, I just use scenes. This is my first game and I only need 3 different scenes, I'll probably do activities in the future. (by no means is my way the best way I'm sure, this is for illustrative purposes)
I made my own subclass of scene:
import org.anddev.andengine.engine.Engine; import org.anddev.andengine.entity.scene.Scene; public abstract class GameScene extends Scene { protected Engine _engine; public GameScene(int pLayerCount, Engine baseEngine) { super(pLayerCount); _engine = baseEngine; } // =========================================================== // Inherited Methods // =========================================================== protected abstract void onLoadResources(); protected abstract void onLoadScene(); protected abstract void unloadScene(); protected abstract void onLoadComplete(); // =========================================================== // Methods // =========================================================== public void LoadResources(boolean loadSceneAutomatically){ this.onLoadResources(); if(loadSceneAutomatically){ this.onLoadScene(); } } public void LoadScene(){ this.onLoadScene(); } }
From here I've got a subclass of this for each of my three game scenes. I create the scene, tell it to load it's resources and then set the scene as the main scene.
If I provide true to LoadResources it will load the scene automatically. I would use this for anything that doesn't have a lot to load. My PlayScene on the other hand, has all of my game images to load.
Here's where the AsyncLoader comes in.
You have to create your own subclass of AsyncTask as stated in the API. From here you send to it the parameters. I made life simple by creating an interface IAsyncCallback to use as a parameter:
public interface IAsyncCallback { // =========================================================== // Methods // =========================================================== public abstract void workToDo(); public abstract void onComplete(); }
Now, here's my subclass of AsyncTask:
import android.os.AsyncTask; public class AsyncTaskLoader extends AsyncTask<IAsyncCallback, Integer, Boolean> { // =========================================================== // Fields // =========================================================== IAsyncCallback[] _params; // =========================================================== // Inherited Methods // =========================================================== @Override protected Boolean doInBackground(IAsyncCallback... params) { this._params = params; int count = params.length; for(int i = 0; i < count; i++){ params[i].workToDo(); } return true; } @Override protected void onPostExecute(Boolean result) { int count = this._params.length; for(int i = 0; i < count; i++){ this._params[i].onComplete(); } } }
This simply calls the workToDo method on each of the parameters (no idea why you would want more than one in this case but that's how AsyncTask is built) and then calls onComplete when it's done.
From here you have to simply create an IAsyncCallback in your onLoadResources with all of your asset loading in the workToDo method, and onLoadScene in the onComplete. Then execute the AsyncTask:
@Override protected void onLoadResources(){ IAsyncCallback callback = new IAsyncCallback() { @Override public void workToDo() { TextureRegionFactory.setAssetBasePath("gfx/game/"); FontFactory.setAssetBasePath("font/"); PlayScene.this._backgroundTexture = new Texture(512, 1024, TextureOptions.BILINEAR_PREMULTIPLYALPHA); PlayScene.this._backgroundTextureRegion = TextureRegionFactory.createFromAsset(PlayScene.this._backgroundTexture, Game.Instance, "background0.png", 0, 0); PlayScene.this._backgroundTextureRegion2 = TextureRegionFactory.createFromAsset(PlayScene.this._backgroundTexture, Game.Instance, "background0.png", 0, 0); PlayScene.this._backgroundTextureRegion2.setFlippedVertical(true); PlayScene.this._faceTexture = new Texture(32,32,TextureOptions.BILINEAR_PREMULTIPLYALPHA); PlayScene.this._faceTextureRegion = TextureRegionFactory.createTiledFromAsset(PlayScene.this._faceTexture,Game.Instance,"face_box.png", 0,0,1,1); PlayScene.this._iffishTexture = new Texture(512,512,TextureOptions.BILINEAR_PREMULTIPLYALPHA); PlayScene.this._iffishTextureTile = TextureRegionFactory.createTiledFromAsset(PlayScene.this._iffishTexture, Game.Instance, "Iffisch.png", 0, 0, 5, 3); PlayScene.this._enemyTextures = new Texture(512,512,TextureOptions.BILINEAR_PREMULTIPLYALPHA); PlayScene.this._blowFishEnemyTextureTile = TextureRegionFactory.createTiledFromAsset(PlayScene.this._enemyTextures, Game.Instance, "blowey.png", 0, 0, 1, 1); PlayScene.this._faerieTexture = new Texture(64,64,TextureOptions.BILINEAR_PREMULTIPLYALPHA); PlayScene.this._faerieTextureRegion = TextureRegionFactory.createFromAsset(PlayScene.this._faerieTexture,Game.Instance,"faerie.png",0,0); PlayScene.this._inGameFontTexture = new Texture(512, 512, TextureOptions.BILINEAR_PREMULTIPLYALPHA); PlayScene.this._respawnFont = FontFactory.createFromAsset(PlayScene.this._inGameFontTexture, Game.Instance, "ANDYB.TTF", 32, true, Color.WHITE); PlayScene.this._hudFontTexture = new Texture(512, 512, TextureOptions.BILINEAR_PREMULTIPLYALPHA); PlayScene.this._hudFont = FontFactory.createFromAsset(PlayScene.this._hudFontTexture, Game.Instance, "ANDYB.TTF", 24, true, Color.BLACK); PlayScene.this._analogControlTexture = new Texture(512,256, TextureOptions.BILINEAR_PREMULTIPLYALPHA); PlayScene.this._analogBGTextureRegion = TextureRegionFactory.createFromAsset(PlayScene.this._analogControlTexture, Game.Instance, "analogControls.png", 0, 0); PlayScene.this._analogStickTextureRegion = TextureRegionFactory.createFromAsset(PlayScene.this._analogControlTexture, Game.Instance, "analogStick.png", 0, 75); PlayScene.this._scoreHudTextureRegion = TextureRegionFactory.createFromAsset(PlayScene.this._analogControlTexture, Game.Instance, "scoreHud.png", 49, 75); PlayScene.this._engine.getTextureManager().loadTextures(_enemyTextures, _backgroundTexture, _iffishTexture, _faceTexture, _inGameFontTexture, _faerieTexture, _analogControlTexture, _hudFontTexture); PlayScene.this._engine.getFontManager().loadFonts(_respawnFont, _hudFont); } @Override public void onComplete() { PlayScene.this.onLoadScene(); } }; new AsyncTaskLoader().execute(callback); }
Then, just make sure you have setScene in your onLoadScene:
@Override protected void onLoadScene() { // set this as the main scene after loading this._engine.setScene(this); . . . your scene load stuff here . . }
Just about done! Now you create a new GameScene (or PlayScene in this case) and pass false to the LoadResources.
This way it will create the scene, LoadResources will call
onLoadResources which will execute the AsyncTask of loading the
resources and then call onLoadScene which sets that scene as the main
scene and loads up:
I do this from within another scene (I'm working on unloading it after the next one is loaded, so that you'll have to sort out yourself :) ) which creates a loading animation. It will play this animation until all of the next scene's resources are loaded and the new one sets itself as the main scene.
import org.anddev.andengine.engine.Engine; import org.anddev.andengine.engine.handler.IUpdateHandler; import org.anddev.andengine.engine.handler.timer.ITimerCallback; import org.anddev.andengine.engine.handler.timer.TimerHandler; import org.anddev.andengine.entity.scene.background.ColorBackground; import org.anddev.andengine.entity.sprite.AnimatedSprite; import org.anddev.andengine.opengl.texture.Texture; import org.anddev.andengine.opengl.texture.TextureOptions; import org.anddev.andengine.opengl.texture.region.TextureRegionFactory; import org.anddev.andengine.opengl.texture.region.TiledTextureRegion; public class LoadingScene extends GameScene { public LoadingScene(int pLayerCount, Engine baseEngine) { super(pLayerCount, baseEngine); } // =========================================================== // Fields // =========================================================== private Texture _loadingTexture; private TiledTextureRegion _loadingTextureRegion; // =========================================================== // Constants // =========================================================== // =========================================================== // Inherited Methods // =========================================================== @Override protected void onLoadResources() { TextureRegionFactory.setAssetBasePath("gfx/"); _loadingTexture = new Texture(512, 128, TextureOptions.BILINEAR_PREMULTIPLYALPHA); _loadingTextureRegion = TextureRegionFactory.createTiledFromAsset(_loadingTexture, Game.Instance, "loading.png", 0, 0, 2, 2); this._engine.getTextureManager().loadTexture(_loadingTexture); } @Override protected void onLoadScene() { this.setBackground(new ColorBackground(1,1,1)); AnimatedSprite loader = new AnimatedSprite(0, 0, _loadingTextureRegion); loader.setPosition((Game.CAMERA_WIDTH / 2) - (loader.getWidthScaled() / 2), (Game.CAMERA_HEIGHT / 2) - (loader.getHeightScaled() / 2)); loader.animate(300, true); this.getTopLayer().addEntity(loader); final PlayScene gameScene = new PlayScene(3,this._engine); gameScene.LoadResources(false); } @Override protected void unloadScene() {} @Override protected void onLoadComplete() {} // =========================================================== // Methods // =========================================================== }
Whew! Extremely loooong winded and pretty specific to my game but this should give a decent idea as to how this works :)
Any questions, comments or feedback is more than welcome. I'm also putting this up in the wiki.
BaseGameActivity:
The BaseGameActivity is the root of a game, that contains an Engine and manages to create a SurfaceView the contents of the Engine will be drawn into.
There is always exactly one Engine for one BaseGameActivity. You can
proceed from one BaseGameActivity to another using common Android
mechanisms.
BaseGameActivity是游戏的基础,它包含一个Engine并负责完成游戏页面的初始化设定工作。
1.public abstract class BaseGameActivity extends BaseActivity implements IGameInterface {}
可以看出BaseGameAcitivity继承自BaseActivity,所以它本质上就是一个Activity,自然也就应该实现Activity
protected void onCreate(final Bundle pSavedInstanceState) { super.onCreate(pSavedInstanceState); this.mPaused = true; this.mEngine = this.onLoadEngine(); this.applyEngineOptions(this.mEngine.getEngineOptions()); this.onSetContentView(); }
时,
this.mEngine = this.onLoadEngine(); this.applyEngineOptions(this.mEngine.getEngineOptions());
这句代码会调用BaseGameActivity中四个onLoadEngine(), onLoadScene(),onLoadComplete(),onLoadResource()中的onLoadEngine()执行引擎的初始化操作,
并设置主View
this.onSetContentView();
其中onSetContentView()的具体代码如下: protected void onSetContentView() { this.mRenderSurfaceView = new RenderSurfaceView(this); this.mRenderSurfaceView.setEGLConfigChooser(false); this.mRenderSurfaceView.setRenderer(this.mEngine); this.setContentView(this.mRenderSurfaceView, this.createSurfaceViewLayoutParams()); }
可以看到在上面的代码中主要对SurfaceView进行了设置,引擎将在它上面进行绘制操作。
this.setContentView(this.mRenderSurfaceView, this.createSurfaceViewLayoutParams());
设置主视图,与使用Activity的setContentView一样。
2.
public interface IGameInterface { // =========================================================== // Final Fields // =========================================================== // =========================================================== // Methods // =========================================================== public Engine onLoadEngine(); public void onLoadResources(); public void onUnloadResources(); public Scene onLoadScene(); public void onLoadComplete(); public void onGamePaused(); public void onGameResumed(); }
baseGameActivity实现了IGameInterface,这个类主要是定义了游戏中需要的主要回调方法,可以理解成类似于Activity的生命周期方法。