简单的游戏可以在平板电脑上运行,但有时会滞后 phone

Simple game works on tablet but sometimes lags on phone

我正在开发简单的 Android 游戏,但在测试时 运行 遇到了问题。当我在 Lenovo Tab3 7 平板电脑 (Android 5.0.1) 或 LG P880 phone (Android 4.0.3) 上 运行 它工作正常。当我 运行 它在三星 S7 phone (Android 7.0) 游戏通常 运行 没问题。我的意思是我可以 运行 连续 10 次都没有问题,但有时游戏会暂停 5-30 秒或停止响应。这通常发生在新 Activity 开始期间或之后不久。

游戏有 4 个 Activities 使用扩展 SurfaceView 作为布局。所有 SurfaceViews 实现 Runnable。活动包括:启动画面(清单中的 noHistory = "true")、菜单、难度选择和游戏。

我只使用 mdpi drawables 并根据所有屏幕尺寸按比例缩放它们。使用 BitmapFactory.decodeResourceBitmapFactory.Options inDensity = 1inScaled = false.

加载位图

出现问题时 logcat 仅显示垃圾回收。有时游戏 "pauses"(没有点击被注册)5-30 秒并正常恢复,有时由于无响应而不得不重新启动。我觉得游戏出于某种原因停止收集输入。通过覆盖 onTouchEvent 并检查 ACTION_UP 是否在点击的图像边界内来处理输入。正如我所说,这只发生在 S7 上(我在两个 phone 上试过),而不是在平板电脑或 P880 上,所以我认为这可能与 Nougat 或我强制降低density 在 phone.

所以,由于我 运行 不知道是什么原因导致的,而且我是 Android 游戏开发的新手,有没有人 know/have 知道我应该在哪里正在寻找解决方案?我应该 setting/checking 有什么特定于牛轧糖的东西吗?强制像素密度会以任何方式影响设备性能吗?

编辑 1

globalApp

public class globalApp extends Application {
SoundPool soundPool;
SoundPool.Builder soundPoolBuilder;

AudioAttributes audioAttributes;
AudioAttributes.Builder audioAttributesBuilder;

int soundTap, soundCorrect, soundIncorrect, soundVictory, soundDefeat;
int soundBarrelVerySlow, soundBarrelSlow, soundBarrelNormal, soundBarrelFast, soundBarrelVeryFast;

@Override
public void onCreate() {
    super.onCreate();

}

public void buildSoundPool(){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        audioAttributesBuilder = new AudioAttributes.Builder();
        audioAttributesBuilder.setUsage(AudioAttributes.USAGE_GAME);
        audioAttributesBuilder.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION);
        audioAttributes = audioAttributesBuilder.build();

        soundPoolBuilder = new SoundPool.Builder();
        soundPoolBuilder.setMaxStreams(2);
        soundPoolBuilder.setAudioAttributes(audioAttributes);
        soundPool = soundPoolBuilder.build();
    }
    else {
        soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
    }
}

public void loadSounds(){
    soundBarrelVerySlow = soundPool.load(this,R.raw.very_slow_move, 1);
    soundBarrelSlow = soundPool.load(this, R.raw.slow_move, 1);
    soundBarrelNormal = soundPool.load(this, R.raw.slow_move, 1);
    soundBarrelFast = soundPool.load(this,R.raw.fast_move, 1);
    soundBarrelVeryFast = soundPool.load(this,R.raw.very_fast_move, 1);
    soundTap = soundPool.load(this, R.raw.tap_sound, 1);
    soundCorrect = soundPool.load(this, R.raw.correct, 1);
    soundIncorrect = soundPool.load(this, R.raw.incorrect, 1);
    soundVictory = soundPool.load(this, R.raw.victory, 1);
    soundDefeat = soundPool.load(this, R.raw.defeat, 1);
}

public void playTap(){
    soundPool.play(soundTap, 1, 1,1, 0, 1);
}

public void playCorrect(){
    soundPool.play(soundCorrect, 1, 1,1, 0, 1);
}

public void playIncorrect(){
    soundPool.play(soundIncorrect, 1, 1,1, 0, 1);
}

public void playVictory(){
    soundPool.play(soundVictory, 1, 1,1, 0, 1);
}

public void playDefeat(){
    soundPool.play(soundDefeat, 1, 1,1, 0, 1);
}

public void playBarrelVerySlow(){soundPool.play(soundBarrelVerySlow, 1, 1, 1, 0, 1);}

public void playBarrelSlow(){soundPool.play(soundBarrelSlow, 1, 1, 1, 0, 1);}

public void playBarrelNormal(){
    soundPool.play(soundBarrelNormal, 1, 1,1, 0, 1);
}

public void playBarrelFast(){soundPool.play(soundBarrelFast, 1, 1, 1, 0, 1);}

public void playBarrelVeryFast(){soundPool.play(soundBarrelVeryFast, 1, 1, 1, 0, 1);}
}

菜单项

public class MenuItem {
private Bitmap bmp;
private Context context;

private Rect sourceRect;
private RectF destRect;

private int srcWidth;
private int srcHeight;

private int destW, destH;

private int x, y;
private int screenH;

public MenuItem(Context ctx, String bmpName, int w, int x, int y, int sX, int sY){

    context = ctx;

    BitmapFactory.Options bmpFOptions = new BitmapFactory.Options();
    bmpFOptions.inDensity = 1;
    bmpFOptions.inScaled = false;

    int res = context.getResources().getIdentifier(bmpName, "drawable", ctx.getPackageName());
    bmp = BitmapFactory.decodeResource(ctx.getResources(), res, bmpFOptions);

    srcWidth = w;
    srcHeight = bmp.getHeight();

    this.x = x;
    this.y = y;

    screenH = sY;

    sourceRect = new Rect(0,0, srcWidth, srcHeight);
    destRect = new RectF();

    setProportionalDestinationRect(sX, sY);
}

private void setProportionalDestinationRect(int scrX, int scrY) {
    if (scrX != 1024 || scrY != 552){
        float propX = (float)scrX/1024;
        float propY = (float)scrY/600;
        // All drawables are designed for 1024x600 screen
        // if device screen is different, scale image proportionally

        destW = (int)(srcWidth * propX);
        destH = (int) (srcHeight * propY);
        x = (int) (x*propX);
        y = (int) (y*propY);
    }
    else {
        destW = srcWidth;
        destH = srcHeight;
    }
    destRect.set(x,y, x+destW,y+destH);
}

public void update(){
}

public Bitmap getBmp() {
    return bmp;
}

public void setBmp(Bitmap bmp) {
    this.bmp = bmp;
}

public Rect getSourceRect() {
    return sourceRect;
}

public void setSourceRect(Rect sourceRect) {
    this.sourceRect = sourceRect;
}

public RectF getDestRect() {
    return destRect;
}

public void setDestRect(RectF destRect) {
    this.destRect = destRect;
}

public boolean contains(int x, int y){
    if (destRect.left <= x && destRect.right >= x)
        if (destRect.top <= y && destRect.bottom >= y)
            return true;
    return false;
}

public void setY(int y) {
    this.y = y;
    if (screenH != 552){
        float propY = (float)screenH/600;
        y = (int) (y*propY);
    }
    destRect.set(x,y, x+destW,y+destH);
}
}

主要Activity

public class MainActivity extends Activity {
private boolean backPressedOnce = false;
long backPressedTime = 0;

private MainActivitySurface mainActivitySurface;

globalApp app;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //Setting full screen
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

    View decorView = getWindow().getDecorView();
    int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
    decorView.setSystemUiVisibility(uiOptions);


    int x = getIntent().getIntExtra("screenWidth", 500);
    int y = getIntent().getIntExtra("screenHeight", 500);

    app = (globalApp) getApplication();
    app.buildSoundPool();
    app.loadSounds();

    mainActivitySurface = new MainActivitySurface(this, app, x, y);
    mainActivitySurface.setParentActivity(MainActivity.this);

    setContentView(mainActivitySurface);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == 1001) {
        if (resultCode == RESULT_OK) {
            int result = data.getIntExtra("difficulty", 3);
            mainActivitySurface.setResultDifficulty(result);
        }
    }
}

@Override
protected void onPause() {
    super.onPause();
    mainActivitySurface.pause();
}

@Override
protected void onResume() {
    super.onResume();
    backPressedOnce = false;

    mainActivitySurface.resume();
}

@Override
public void onBackPressed() {
        if (backPressedOnce && backPressedTime + 2000 > System.currentTimeMillis()) {
            Process.killProcess(Process.myPid());
            System.exit(1);
        } else {
            Toast.makeText(this, "Press back again to exit.", Toast.LENGTH_SHORT).show();
            backPressedOnce = true;
        }
        backPressedTime = System.currentTimeMillis();
}
}

主要Activity表面

public class MainActivitySurface extends SurfaceView implements Runnable {

private Context context;
private SurfaceHolder surfaceHolder;
private Canvas canvas;

private Thread thread = null;

volatile private boolean running = false;
private boolean surfaceCreated = false;

private Intent playIntent;
private Intent difficultyIntent;

// Screen size
private int screenWidth, screenHeight;

//Menu items
private MenuItem menuItemPlay, menuItemDifficulty, middleBarrel, bg;
private int difficulty = 3;

private Activity parentActivity;
private globalApp app;

public  MainActivitySurface(Context ctx, globalApp a, int scrW, int scrH){
    super(ctx);

    context = ctx;
    screenHeight = scrH;
    screenWidth = scrW;

    app = a;

    surfaceHolder = getHolder();

    surfaceHolder.addCallback(new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            surfaceCreated = true;
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {

        }
    });

    bg = new MenuItem(context, "main_activity_background_single", 1024, 0, 0, scrW, scrH);
    menuItemPlay = new MenuItem(context, "menu_item_play_single", 233,(1024-233)/2,100, scrW, scrH);
    menuItemDifficulty = new MenuItem(ctx, "menu_item_difficulty_single", 520,(1024 - 520)/2,400,scrW,scrH);
    middleBarrel = new MenuItem(ctx, "middle_barrel_single", 323,(1024-323)/2,200,scrW,scrH);

    playIntent = new Intent(context, GameActivity.class);
    playIntent.putExtra("screenWidth", screenWidth);
    playIntent.putExtra("screenHeight", screenHeight);
}

@Override
public void run() {
    while (running){
        draw();
    }
}

private void draw() {
    if(surfaceHolder.getSurface().isValid()){
        canvas = surfaceHolder.lockCanvas();

        canvas.drawBitmap(bg.getBmp(), bg.getSourceRect(), bg.getDestRect(), null);
        canvas.drawBitmap(menuItemPlay.getBmp(), menuItemPlay.getSourceRect(), menuItemPlay.getDestRect(), null);
        canvas.drawBitmap(menuItemDifficulty.getBmp(), menuItemDifficulty.getSourceRect(), menuItemDifficulty.getDestRect(), null);
        canvas.drawBitmap(middleBarrel.getBmp(), middleBarrel.getSourceRect(), middleBarrel.getDestRect(), null);

        surfaceHolder.unlockCanvasAndPost(canvas);
    }
}

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

public void pause(){
    running = false;
    boolean retry = false;
    while (retry) {
        try {
            thread.join();
            retry = false;
        } catch (InterruptedException e) {
            e.printStackTrace();
            Log.d("info", "MainActivitySurface: Error joining thread");
        }
    }
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction() & event.ACTION_MASK){
        case MotionEvent.ACTION_UP:
            if (menuItemPlay.contains((int) event.getX(), (int) event.getY())){
                app.playTap();
                parentActivity.startActivity(playIntent);
                parentActivity.overridePendingTransition(0,0);
                break;
            }
            if (menuItemDifficulty.contains((int) event.getX(), (int) event.getY())){
                app.playTap();
                difficultyIntent = new Intent(parentActivity, DifficultyActivity.class);
                difficultyIntent.putExtra("screenWidth", screenWidth);
                difficultyIntent.putExtra("screenHeight", screenHeight);
                difficultyIntent.putExtra("difficulty", difficulty);
                parentActivity.startActivityForResult(difficultyIntent, 1001);
                parentActivity.overridePendingTransition(0, 0);
                break;
            }
    }
    return true;
}

public void setParentActivity(Activity act){
    parentActivity = act;
}

public void setResultDifficulty(int diff){
    difficulty = diff;
    playIntent.putExtra("difficulty", difficulty);
}
}

难度[​​=92=]

public class DifficultyActivity extends Activity {

private DifficultySurface surface;
private globalApp app;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //Setting full screen
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

    View decorView = getWindow().getDecorView();
    int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
    decorView.setSystemUiVisibility(uiOptions);

    app = (globalApp) getApplication();

    surface = new DifficultySurface(this, app, getIntent().getIntExtra("screenWidth", 500), getIntent().getIntExtra("screenHeight", 500));
    setContentView(surface);
}

@Override
protected void onPause() {
    super.onPause();
    app.soundPool.release();
    surface.pause();
    overridePendingTransition(0, 0);
}

@Override
protected void onResume() {
    super.onResume();
    app.buildSoundPool();
    app.loadSounds();
    surface.resume();
}
}

难度面

public class DifficultySurface extends SurfaceView implements Runnable {

private SurfaceHolder surfaceHolder;
private Thread thread = null;
private Canvas canvas;
private Context context;
private globalApp app;

private boolean surfaceCreated = false;
private boolean running = false;

private MenuItem bgProp, arrowBarrel, okButton, diffVeryEasy, diffEasy, diffNormal, diffHard, diffVeryHard;

private int difficulty;

public DifficultySurface(Context ctx, globalApp a, int scrW, int scrH){
    super(ctx);

    context = ctx;

    app = a;

    surfaceHolder = getHolder();

    surfaceHolder.addCallback(new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            surfaceCreated = true;
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {

        }
    });

    difficulty = ((Activity)context).getIntent().getIntExtra("difficulty", 3);

    bgProp = new MenuItem(ctx, "difficulty_background", 1024, 0, 0, scrW, scrH);

    diffVeryEasy = new MenuItem(ctx, "very_easy",796, 100, 100, scrW, scrH);
    diffEasy = new MenuItem(ctx, "easy",796, 100, 200 , scrW, scrH);
    diffNormal = new MenuItem(ctx, "normal",796, 100, 300, scrW, scrH);
    diffHard = new MenuItem(ctx, "hard",796, 100, 400 , scrW, scrH);
    diffVeryHard = new MenuItem(ctx, "very_hard",796, 100, 500, scrW, scrH);
    okButton = new MenuItem(ctx, "ok_button", 100, 924, 500, scrW, scrH);

    arrowBarrel = new MenuItem(ctx, "barrel_arrow", 100, 0, 100*difficulty, scrW, scrH);
}

@Override
public void run() {
    while (running) {
        if (surfaceCreated) {
            update();
            draw();
        }
    }
}

private void update() {
    arrowBarrel.setY(difficulty*100);
}

private void draw() {

    if (surfaceHolder.getSurface().isValid()){
        canvas = surfaceHolder.lockCanvas();


        canvas.drawBitmap(bgProp.getBmp(), bgProp.getSourceRect(), bgProp.getDestRect(), null);

        canvas.drawBitmap(arrowBarrel.getBmp(), arrowBarrel.getSourceRect(), arrowBarrel.getDestRect(), null);

        canvas.drawBitmap(diffVeryEasy.getBmp(), diffVeryEasy.getSourceRect(), diffVeryEasy.getDestRect(), null);
        canvas.drawBitmap(diffEasy.getBmp(), diffEasy.getSourceRect(), diffEasy.getDestRect(), null);
        canvas.drawBitmap(diffNormal.getBmp(), diffNormal.getSourceRect(), diffNormal.getDestRect(), null);
        canvas.drawBitmap(diffHard.getBmp(), diffHard.getSourceRect(), diffHard.getDestRect(), null);
        canvas.drawBitmap(diffVeryHard.getBmp(), diffVeryHard.getSourceRect(), diffVeryHard.getDestRect(), null);

        canvas.drawBitmap(okButton.getBmp(), okButton.getSourceRect(), okButton.getDestRect(), null);

        surfaceHolder.unlockCanvasAndPost(canvas);
    }

}

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction() & event.ACTION_MASK){
        case MotionEvent.ACTION_UP:{
            if (diffVeryEasy.contains((int) event.getX(), (int) event.getY())){
                app.playTap();
                difficulty = 1;                }
            if (diffEasy.contains((int) event.getX(), (int) event.getY())){
                app.playTap();
                difficulty = 2;
            }
            if (diffNormal.contains((int) event.getX(), (int) event.getY())){
                app.playTap();
                difficulty = 3;
            }
            if (diffHard.contains((int) event.getX(), (int) event.getY())){
                app.playTap();
                difficulty = 4;
            }
            if (diffVeryHard.contains((int) event.getX(), (int) event.getY())){
                app.playTap();
                difficulty = 5;
            }
            if (okButton.contains((int)event.getX(), (int) event.getY())){
                app.playTap();
                ((Activity)context).getIntent().putExtra("difficulty", difficulty);
                ((Activity)context).setResult(Activity.RESULT_OK, ((Activity)context).getIntent());
                ((Activity)context).finish();
                ((Activity)context).overridePendingTransition(0, 0);
            }
            break;
        }
    }
    return true;
}

public void pause(){
    running = false;
    boolean retry = true;
    while (retry) {
        try {
            thread.join();
            retry = false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    ((Activity)context).overridePendingTransition(0, 0);
}

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

游戏Activity

public class GameActivity extends Activity {

private GameSurface surface;
private globalApp app;

private int difficulty;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //Setting full screen
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

    View decorView = getWindow().getDecorView();
    int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
    decorView.setSystemUiVisibility(uiOptions);

    difficulty = getIntent().getIntExtra("difficulty", 3);

    app = (globalApp) getApplication();

    surface = new GameSurface(this, app, getIntent().getIntExtra("screenWidth", 500), getIntent().getIntExtra("screenHeight", 500), difficulty);

    surface.setParentActivity(this);

    setContentView(surface);
}

@Override
protected void onPause() {
    super.onPause();
    app.soundPool.release();
    surface.pause();
}

@Override
protected void onPostResume() {
    super.onPostResume();
    app.buildSoundPool();
    app.loadSounds();
    surface.resume();
}

@Override
protected void onStop() {
    super.onStop();
    surface.stop();
}

@Override
public void onBackPressed() {
    super.onBackPressed();
    finish();
}
}

游戏暂停发生在我开始 DificultyActivity(我点击一个 MenuItem 对象但没有任何反应)或当我开始 GameActivity(游戏仍然显示 MainActivity + MainActivitySurface).

Android 监视器显示分配的内存少于 40MB,因此在我看来位图应该不是问题。我尝试回收所有位图,但问题仍然存在(这就是我选择仅使用 mdpi drawables 的原因;起初我使用了所有像素密度,但尝试降低资源以防导致停止)。

不看代码很难发现问题。没有特定于牛轧糖的资源处理方式。

但是 android N 声称拥有更好的内存管理,并且由于您抱怨大量垃圾回收,这可能是原因之一。确保回收未使用的位图。并使用 RGB_565 作为首选位图配置,它需要比 RGB_8888 一半的内存。

我的问题已经解决了。发布问题后,我遇到了 。看来我们遇到了同样的问题。当我放慢绘图速度(使用 thread.sleep)时,不再有问题。

感谢帮助过我的人