Android-Code Optimization

Hello! I display a background that will scroll slowly (as in Parallax Scrolling), the size of the scrollable image is 16k+ by 720 pixels (7mb). When launched on powerful devices, it shows FPS 29 (HTC One), and on cheap Chinese tablets only 3 FPS (Texet TM-7024), and then throws it out altogether. Please help, how else can I optimize this code? I attach all the code of the class in which it goes scrolling.


package ru.zein4.g_break.views;

import android.annotation.SuppressLint;
import android.content.*;
import android.graphics.*;
import android.util.*;
import android.view.*;
import android.widget.*;

public class background_menu extends SurfaceView implements SurfaceHolder.Callback
{
    GameThread thread;
    int screenW;
    int screenH;
    int bgrW;
    int bgrH;
    int bgrScroll;
    int dBgrY; //Скорость прокрутки background'a
    Bitmap bgr, bgrReverse;
    boolean reverseBackroundFirst;

    long timeNow;
    long timePrev = 0;
    long timePrevFrame = 0;
    long timeDelta;

    Paint exPaint = new Paint();

    Rect fromRect1, toRect1, fromRect2, toRect2;

    public background_menu(Context context)
    {
        super(context);
        init();
    }

    public background_menu (Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public background_menu (Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public void init() {
BitmapFactory.Options op = new BitmapFactory.Options();
op.inPreferenceConfig = Bitmap.Config_RGB565
        bgr = BitmapFactory.decodeResource(getResources(), R.drawable.so_large_background, op);

        reverseBackroundFirst = false;

        //инициализируемся
        bgrScroll = 0;  //Начальная позиция прокрутки
        dBgrY = 1; //Скорость прокрутки

        getHolder().addCallback(this);

        setFocusable(false);

        exPaint.setAntiAlias(false);
exPaint.setFilterBitmap(true);
    }

    @Override
    public void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);
        screenW = w;
        screenH = h;

        bgr = Bitmap.createScaledBitmap(bgr, w * 2, h, true);
        bgrW = bgr.getWidth();
        bgrH = bgr.getHeight();

        //Делаем второй отзеркаленный битмап
        Matrix matrix = new Matrix();
        matrix.setScale(-1, 1); //Отражение по горизонали
        bgrReverse = Bitmap.createBitmap(bgr, 0, 0, bgrW, bgrH, matrix, true); //Создаём битмап из отзеркаленной матрицы
    }

    @Override
    public void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);

        fromRect1 = new Rect(0, 0, bgrW - bgrScroll, bgrH);
        toRect1 = new Rect(bgrScroll, 0, bgrW, bgrH);

        fromRect2 = new Rect(bgrW - bgrScroll, 0, bgrW, bgrH);
        toRect2 = new Rect(0, 0, bgrScroll, bgrH);

        if (!reverseBackroundFirst) {
            canvas.drawBitmap(bgr, fromRect1, toRect1, exPaint);
            canvas.drawBitmap(bgrReverse, fromRect2, toRect2, exPaint);
        }
        else{
            canvas.drawBitmap(bgr, fromRect2, toRect2, exPaint);
            canvas.drawBitmap(bgrReverse, fromRect1, toRect1, exPaint);
        }

        if ((bgrScroll += dBgrY) >= bgrW)
        {
            bgrScroll = 0;
            reverseBackroundFirst = !reverseBackroundFirst;
        }
    }

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

    @Override
    public void surfaceCreated(SurfaceHolder holder)
    {
        thread = new GameThread(getHolder(), this);
        thread.setRunning(true);
        thread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder)
    {
        boolean retry = true;
        thread.setRunning(false);
        while (retry)
        {
            try
            {
                thread.join();
                retry = false;
            }
            catch (Exception e) {e.printStackTrace();}
        }
    }

    class GameThread extends Thread
    {
        private SurfaceHolder surfaceHolder;
        private background_menu gameView;
        private boolean run = false;

        public GameThread(SurfaceHolder surfaceHolder, background_menu gameView)
        {
            this.surfaceHolder = surfaceHolder;
            this.gameView = gameView;
        }

        public void setRunning(boolean run)
        {
            this.run = run;
        }

        public SurfaceHolder getSurfaceHolder()
        {
            return surfaceHolder;
        }

        @SuppressLint("WrongCall")
        @Override
        public void run()
        {
            Canvas c;
            while (run)
            {
                c = null;

                //Делаем движение background'a кадронезависимым. Число 16 говорит о том, что максимально допустимый FPS будет 60
                timeNow = System.currentTimeMillis();
                timeDelta = timeNow - timePrevFrame;
                if (timeDelta < 16)
                {
                    try
                    {
                        Thread.sleep(16 - timeDelta);
                    }
                    catch (Exception e)
                    {
                        e.printStackTrace();
                    }
                }
                timePrevFrame = System.currentTimeMillis();

                try
                {
                    c = surfaceHolder.lockCanvas(null);
                    if (c != null)
                    {
                        synchronized (surfaceHolder)
                        {
                            gameView.onDraw(c);
                        }
                    }
                }
                finally
                {
                    if (c != null)
                    {
                        surfaceHolder.unlockCanvasAndPost(c);
                    }
                }
            }
        }
    }
}

Now I have calculated the amount of RAM required to decode the resource: 16200*720*4 = примерно 47 мб. I've heard somewhere that the amount of memory needed can be reduced by using Bitmap.Config_RGB565. How do I apply this config?


UPDATE So, now I screwed the config to the bitmap and made drawing without anti-aliasing, FPS increased from 3 to 14 on Chinese devices. But optimization is still required.


Author: Helisia, 2013-10-24

3 answers

I have exhausted the limit of comments to the question, so I will answer this way.

I didn't see any good articles on the tile graphics directly(and I didn't look for them, to be honest). I saw an example of tile graphics in one book, but the author of this book is deservedly insulted all over the Internet (there is something for that).




Bicycle:

The idea of tiles+caching (always with a cache!) very simple

  1. creating an object фон inside we store shared map (array pixels)
  2. creating an object строитель фона which inside stores фон and size background in cells(N x M), it also stores information about the size of the cell in pixels (and what does not fit will be painted over default color)
  3. creating a function that inserts the tile to cell
  4. when all tiles are inserted by one draw the entire array{[20] in one fell swoop]}

We season this hell soup with spices in the form of parallel loading of tiles, etc. buns.




Correct implementation

  1. we take the tile editor (there are a lot of them!) , adjust the size in pixels and the number of tiles, and draw a little (or copy-paste).
  2. at the output we have a bunch of tiles, sorted and sawn, as well as the same xml document describing the map, in particularly good editors, also the xmlki sets (house, bridge, tree, etc)
  3. finding a library for working with tiles (they are probably already there holy shit)
  4. writing an adapter that pulls out of xmlki order of tiles and shoves into the library along with the tiles themselves

Small remnants (those that can not be cut into tiles) are filled with a stretchable image (9Patch)

The normal library will also implement tile animation and may even combine N tiles into one.

 1
Author: ProkletyiPirat, 2013-10-24 11:18:23

And why not make 10-20 images, and turn them into a control that will solve memory problems for you? (for example ListView)

 2
Author: jimpanzer, 2013-10-24 06:59:31

Is this background something special? Or are there sequences there?

  1. You can look in the direction of 9Patch.

  2. And one more piece of advice. Make the most compressed jpge image. I think that you just don't need PNG, since you already have the entire area filled.

  3. I also recommend looking at the old devices 2.3. Most likely there will be OutOfMemory immediately.

  4. Split the image into tiles and use them.

 0
Author: Andrii Stakhov, 2013-10-24 07:22:56