为什么我的简单android java游戏滞后? (使用画布)

时间:2015-01-17 06:26:59

标签: java android performance canvas lag

我对Android开发很陌生 - 我用一个教程制作了一个游戏,现在我正在创作一个原创作品,但是我没有遇到严重的滞后问题并且唯一的事情是应用到目前为止只是滚动背景图像。

我使用本教程制作的游戏没有任何延迟,我使用的是相同的框架,但是它相当糟糕 - 达到每秒3-4帧的程度。

值得注意的是,这种滞后是间歇性的。

它会延迟几秒钟或几分钟,然后背景滚动可能会按照预期几秒钟或几分钟流动,然后更加滞后然后更平滑的动画等,并且没有似乎有时解释为什么帧速率如此之低。在Eclipse中查看DDMS透视图并没有提供任何见解 - 后台没有其他应用程序或同步发生导致CPU或RAM峰值。

以下是我认为相关的课程:

ScreenMainMenu.java(显示带滚动背景的主菜单)

package com.kittykazoo.gamecore;

import java.util.List;

import com.kittykazoo.framework.Game;
import com.kittykazoo.framework.Graphics;
import com.kittykazoo.framework.Screen;
import com.kittykazoo.framework.Input.TouchEvent;

public class ScreenMenuMain extends Screen {

    boolean firstCreate = true;
    int bgPos = 0;

    public ScreenMenuMain(Game game) {

        super(game);

        // Cue music
        if (firstCreate) {
            //          GameAssets.ostMenuMain.play();
            firstCreate = false;
        }

    }

    @Override
    public void update(float deltaTime) {

        if (bgPos >= 1280) {
            bgPos = 0;
        } else {
            bgPos += 1;
        }

        // Handle Touch Events
        List<TouchEvent> touchEvents = game.getInput().getTouchEvents();
        int len = touchEvents.size();
        for (int i = 0; i < len; i++) {

            try {

                TouchEvent event = touchEvents.get(i);

                if (event.type == TouchEvent.TOUCH_DOWN) {

                    // Start Game
                    if (inBounds(event, 168, 441, 463, 145)) {
                        Assets.click.play(3.0f);
                        // game.setScreen(new ScreenGame(game));
                    }

                    // Options Menu
                    if (inBounds(event, 168, 624, 463, 145)) {
                        Assets.click.play(3.0f);
                        // game.setScreen(new ScreenOptions(game));
                    }

                    // Help Screen
                    if (inBounds(event, 168, 807, 463, 145)) {
                        Assets.click.play(3.0f);
                        // game.setScreen(new ScreenHelp(game));
                    }

                    // Quit
                    if (inBounds(event, 100, 1053, 463, 145)) {
                        Assets.click.play(3.0f);
                        quitGame();
                    }

                }

            } catch (IndexOutOfBoundsException e) {
            }

        }
    }

    private boolean inBounds(TouchEvent event, int x, int y, int width,
            int height) {

        if (event.x > x && event.x < x + width - 1 && event.y > y
                && event.y < y + height - 1)
            return true;
        else
            return false;

    }

    @Override
    public void paint(float deltaTime) {
        Graphics g = game.getGraphics();
        g.drawImage(Assets.menuMainBg, 0, bgPos);
        g.drawImage(Assets.menuMainBg, 0, bgPos - 1280);
        g.drawImage(Assets.menuMainInterface, 0, 0);
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {

    }

    @Override
    public void dispose() {

    }

    @Override
    public void backButton() {

        // TODO: Display "Exit Game?" Box

        quitGame();

    }

    public static void quitGame() {
        android.os.Process.killProcess(android.os.Process.myPid());
    }

}

ScreenLoading.java(在加载所有资产后调用ScreenMainMenu)

package com.kittykazoo.gamecore;

import com.kittykazoo.framework.Game;
import com.kittykazoo.framework.Graphics;
import com.kittykazoo.framework.Graphics.ImageFormat;
import com.kittykazoo.framework.Screen;
import com.kittykazoo.gamecore.Assets;

public class ScreenLoading extends Screen {

    boolean assetsLoaded = false;

    public ScreenLoading(Game game) {
        super(game);
    }

    @Override
    public void update(float deltaTime) {

        loadAssets();

        if (assetsLoaded) {
            game.setScreen(new ScreenMenuMain(game));
        }

    }

    public void loadAssets() {

        Graphics g = game.getGraphics();

        // Load all assets from:
        // com.kittykazoo.gamecore.Assets.java

        Assets.menuMainBg = g.newImage("menu_main_bg.png",
                ImageFormat.RGB565);
        Assets.menuMainInterface = g.newImage("menu_main_interface.png",
                ImageFormat.RGB565);
        Assets.click = game.getAudio().createSound("click.ogg");

        assetsLoaded = true;

    }

    @Override
    public void paint(float deltaTime) {

        Graphics g = game.getGraphics();

        g.drawImage(Assets.splash_bg, 0, 0);    

    }

    @Override
    public void pause() {
        // TODO Auto-generated method stub

    }

    @Override
    public void resume() {
        // TODO Auto-generated method stub

    }

    @Override
    public void dispose() {
        // TODO Auto-generated method stub

    }

    @Override
    public void backButton() {
        // TODO Auto-generated method stub

    }

}

如果它有用,这里是框架接口和实现:

Game.java

package com.kittykazoo.framework;

public interface Game {

    public Audio getAudio();

    public Input getInput();

    public FileIO getFileIO();

    public Graphics getGraphics();

    public void setScreen(Screen screen);

    public Screen getCurrentScreen();

    public Screen getInitScreen();

}

AndroidGame.java(游戏的实施)

package com.kittykazoo.framework.implementation;

import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.view.Window;
import android.view.WindowManager;

import com.kittykazoo.framework.Audio;
import com.kittykazoo.framework.FileIO;
import com.kittykazoo.framework.Game;
import com.kittykazoo.framework.Graphics;
import com.kittykazoo.framework.Input;
import com.kittykazoo.framework.Screen;

public abstract class AndroidGame extends Activity implements Game {

    AndroidFastRenderView renderView;
    Graphics graphics;
    Audio audio;
    Input input;
    FileIO fileIO;
    Screen screen;
    WakeLock wakeLock;

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
        int frameBufferWidth = isPortrait ? 800: 1280;
        int frameBufferHeight = isPortrait ? 1280: 800;
        Bitmap frameBuffer = Bitmap.createBitmap(frameBufferWidth,
                frameBufferHeight, Config.RGB_565);

        float scaleX = (float) frameBufferWidth
                / getWindowManager().getDefaultDisplay().getWidth();
        float scaleY = (float) frameBufferHeight
                / getWindowManager().getDefaultDisplay().getHeight();

        renderView = new AndroidFastRenderView(this, frameBuffer);
        graphics = new AndroidGraphics(getAssets(), frameBuffer);
        fileIO = new AndroidFileIO(this);
        audio = new AndroidAudio(this);
        input = new AndroidInput(this, renderView, scaleX, scaleY);
        screen = getInitScreen();
        setContentView(renderView);

        PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "TEMPLATE");
    }

    @Override
    public void onResume() {
        super.onResume();
        wakeLock.acquire();
        screen.resume();
        renderView.resume();
    }

    @Override
    public void onPause() {
        super.onPause();
        wakeLock.release();
        renderView.pause();
        screen.pause();

        if (isFinishing())
            screen.dispose();
    }

    @Override
    public Input getInput() {
        return input;
    }

    @Override
    public FileIO getFileIO() {
        return fileIO;
    }

    @Override
    public Graphics getGraphics() {
        return graphics;
    }

    @Override
    public Audio getAudio() {
        return audio;
    }

    @Override
    public void setScreen(Screen screen) {
        if (screen == null)
            throw new IllegalArgumentException("Screen must not be null");

        this.screen.pause();
        this.screen.dispose();
        screen.resume();
        screen.update(0);
        this.screen = screen;
    }

    public Screen getCurrentScreen() {

        return screen;
    }
}

Graphics.java

package com.kittykazoo.framework;

import android.graphics.Paint;

public interface Graphics {

    public static enum ImageFormat {
        ARGB8888, ARGB4444, RGB565
    }

    public Image newImage(String fileName, ImageFormat format);

    public void clearScreen(int color);

    public void drawLine(int x, int y, int x2, int y2, int color);

    public void drawRect(int x, int y, int width, int height, int color);

    public void fillRect(int x, int y, int width, int height, int color);

    public void drawImage(Image image, int x, int y, int srcX, int srcY,
            int srcWidth, int srcHeight);

    public void drawImage(Image Image, int x, int y);

    public void drawImage(Image Image, int x, int y, Paint paint);

    public void drawScaledImage(Image Image, int x, int y, int width,
            int height, int srcX, int srcY, int srcWidth, int srcHeight);

    public void drawScaledImage(Image Image, int x, int y, int width,
            int height, int srcX, int srcY, int srcWidth, int srcHeight, Paint paint);

    void drawString(String text, int x, int y, Paint paint);

    public int getWidth();

    public int getHeight();

    public void drawARGB(int i, int j, int k, int l);

}

AndroidGraphics.java(图形的实现)

package com.kittykazoo.framework.implementation;

import java.io.IOException;
import java.io.InputStream;

import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;

import com.kittykazoo.framework.Graphics;
import com.kittykazoo.framework.Image;

public class AndroidGraphics implements Graphics {

    public static AssetManager assets;
    Bitmap frameBuffer;
    Canvas canvas;
    Paint paint;
    Rect srcRect = new Rect();
    Rect dstRect = new Rect();

    public AndroidGraphics(AssetManager assets, Bitmap frameBuffer) {
        this.assets = assets;
        this.frameBuffer = frameBuffer;
        this.canvas = new Canvas(frameBuffer);
        this.paint = new Paint();
    }

    @Override
    public Image newImage(String fileName, ImageFormat format) {

        Config config = null;
        if (format == ImageFormat.RGB565)
            config = Config.RGB_565;
        else if (format == ImageFormat.ARGB4444)
            config = Config.ARGB_4444;
        else
            config = Config.ARGB_8888;

        Options options = new Options();
        options.inPreferredConfig = config;

        InputStream in = null;
        Bitmap bitmap = null;
        try {
            in = assets.open(fileName);
            bitmap = BitmapFactory.decodeStream(in, null, options);
            if (bitmap == null)
                throw new RuntimeException("Couldn't load bitmap from asset '"
                        + fileName + "'");
        } catch (IOException e) {
            throw new RuntimeException("Couldn't load bitmap from asset '"
                    + fileName + "'");
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                }
            }
        }

        if (bitmap.getConfig() == Config.RGB_565)
            format = ImageFormat.RGB565;
        else if (bitmap.getConfig() == Config.ARGB_4444)
            format = ImageFormat.ARGB4444;
        else
            format = ImageFormat.ARGB8888;

        return new AndroidImage(bitmap, format);

    }

    @Override
    public void clearScreen(int color) {
        canvas.drawRGB((color & 0xff0000) >> 16, (color & 0xff00) >> 8,
                (color & 0xff));
    }

    @Override
    public void drawLine(int x, int y, int x2, int y2, int color) {
        paint.setColor(color);
        canvas.drawLine(x, y, x2, y2, paint);
    }

    @Override
    public void drawRect(int x, int y, int width, int height, int color) {
        paint.setColor(color);
        paint.setStyle(Style.STROKE);
        canvas.drawRect(x, y, x + width - 1, y + height - 1, paint);
    }

    @Override
    public void fillRect(int x, int y, int width, int height, int color) {
        paint.setColor(color);
        paint.setStyle(Style.FILL);
        canvas.drawRect(x, y, x + width - 1, y + height - 1, paint);
    }

    @Override
    public void drawARGB(int a, int r, int g, int b) {
        paint.setStyle(Style.FILL);
        canvas.drawARGB(a, r, g, b);
    }

    @Override
    public void drawString(String text, int x, int y, Paint paint) {
        canvas.drawText(text, x, y, paint);
    }

    public void drawImage(Image Image, int x, int y, int srcX, int srcY,
            int srcWidth, int srcHeight) {

        srcRect.left = srcX;
        srcRect.top = srcY;
        srcRect.right = srcX + srcWidth;
        srcRect.bottom = srcY + srcHeight;

        dstRect.left = x;
        dstRect.top = y;
        dstRect.right = x + srcWidth;
        dstRect.bottom = y + srcHeight;

        canvas.drawBitmap(((AndroidImage) Image).bitmap, srcRect, dstRect, null);

    }

    @Override
    public void drawImage(Image Image, int x, int y) {
        canvas.drawBitmap(((AndroidImage) Image).bitmap, x, y, null);
    }


    public void drawImage(Image Image, int x, int y, Paint paint) {
        canvas.drawBitmap(((AndroidImage) Image).bitmap, x, y, paint);
    }

    public void drawScaledImage(Image Image, int x, int y, int width,
            int height, int srcX, int srcY, int srcWidth, int srcHeight) {

        srcRect.left = srcX;
        srcRect.top = srcY;
        srcRect.right = srcX + srcWidth;
        srcRect.bottom = srcY + srcHeight;

        dstRect.left = x;
        dstRect.top = y;
        dstRect.right = x + width;
        dstRect.bottom = y + height;

        canvas.drawBitmap(((AndroidImage) Image).bitmap, srcRect, dstRect, null);

    }

    public void drawScaledImage(Image Image, int x, int y, int width,
            int height, int srcX, int srcY, int srcWidth, int srcHeight, Paint paint) {

        srcRect.left = srcX;
        srcRect.top = srcY;
        srcRect.right = srcX + srcWidth;
        srcRect.bottom = srcY + srcHeight;

        dstRect.left = x;
        dstRect.top = y;
        dstRect.right = x + width;
        dstRect.bottom = y + height;

        canvas.drawBitmap(((AndroidImage) Image).bitmap, srcRect, dstRect, paint);

    }   

    @Override
    public int getWidth() {
        return frameBuffer.getWidth();
    }

    @Override
    public int getHeight() {
        return frameBuffer.getHeight();
    }

}

Screen.java

package com.kittykazoo.framework;

public abstract class Screen {

    protected final Game game;

    public Screen(Game game) {
        this.game = game;
    }

    public abstract void update(float deltaTime);

    public abstract void paint(float deltaTime);

    public abstract void pause();

    public abstract void resume();

    public abstract void dispose();

    public abstract void backButton();

}

我认为最后一堂课可能与框架中的这一课有关:

AndroidFastRenderView.java

package com.kittykazoo.framework.implementation;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class AndroidFastRenderView extends SurfaceView implements Runnable {

    AndroidGame game;
    Bitmap framebuffer;
    Thread renderThread = null;
    SurfaceHolder holder;
    volatile boolean running = false;

    public AndroidFastRenderView(AndroidGame game, Bitmap framebuffer) {
        super(game);
        this.game = game;
        this.framebuffer = framebuffer;
        this.holder = getHolder();
    }

    public void resume() {
        running = true;
        renderThread = new Thread(this);
        renderThread.start();
    }

    public void run() {

        Rect dstRect = new Rect();
        long startTime = System.nanoTime();

        while (running) {

            if (!holder.getSurface().isValid())
                continue;

            float deltaTime = (System.nanoTime() - startTime) / 10000000.000f;
            startTime = System.nanoTime();

            if (deltaTime > 3.15) {
                deltaTime = (float) 3.15;
            }

            game.getCurrentScreen().update(deltaTime);
            game.getCurrentScreen().paint(deltaTime);

            Canvas canvas = holder.lockCanvas();
            canvas.getClipBounds(dstRect);
            canvas.drawBitmap(framebuffer, null, dstRect, null);
            holder.unlockCanvasAndPost(canvas);

        }

    }

    public void pause() {

        running = false;

        while (true) {
            try {
                renderThread.join();
                break;
            } catch (InterruptedException e) {
                // retry
            }
        }

    }

}

对于导致减速的原因,我们将非常感激!

编辑:仍希望有人在那里解释为什么会这样。我在同一台设备上有很多其他游戏,这些游戏资源密集程度更高,而且根本没有延迟,所以我只能假设在所有游戏中应该有一些基本的东西让它们顺利运行,我错过了或者是没有包括在我阅读的任何教程中。

我应该在运行时预留额外的内存吗?强行一些我不强迫的旗帜?这真让我疯了。

编辑2(重新EpicPandaForce的建议):这是我正在使用的框架中使用的Screen.java(来自教程)。该框架基于LibGDX的Mario Zechner所着的“Beginning Android Games”一书中的开源框架。

public abstract class Screen {

    protected final Game game;

    public Screen(Game game) {
        this.game = game;
    }

    public abstract void update(float deltaTime);

    public abstract void paint(float deltaTime);

    public abstract void pause();

    public abstract void resume();

    public abstract void dispose();

    public abstract void backButton();

}

1 个答案:

答案 0 :(得分:0)

我不确定这是否真正解决了问题的根源,但我设法通过一个数组预先分配整个堆来实现看起来像功能性的解决方法,该数组填满了每个备用字节的内存然后立即制作那个阵列无法到达:

ArrayList<Character> mReserve = new ArrayList<Character>();
long maxMem = Runtime.getRuntime().maxMemory();

for (int i = 0; i < maxMem; i++) {
    try {
        mReserve.add('s');
    } catch (OutOfMemoryError e) {
        break;
    }
}
mReserve.clear();
mReserve = null;

在我看来,这一定是一个hacky解决方案 - 我无法想象推荐的行动方案是开始投掷肘部并清除整个舞池,这样一个应用程序可以突然移动......但是它似乎工作,主菜单屏幕上的所有动画(以及我随后完成编码的游戏的其余部分)现在工作得很好。