Страницы

Поиск по вопросам

понедельник, 13 апреля 2020 г.

Как оптимизировать код?

#android #canvas

                    
Нужно сделать так, чтобы за пальцем двигалась картинка (Bitmap f), а  и выполнялось
"стирание",
но алгоритм движения картинки очень сильно нагружает устройство, и в итоге FPS=8.
Без движения картинки "стирание" происходит при довольно большой частоте кадров. Как
можно ускорить/оптимизировать этот код, чтобы картинка двигалась без лагов? Заранее
ОГРОМНОЕ спасибо!
public class WScratchView extends SurfaceView implements SurfaceHolder.Callback
{
    private Context mContext;
    public WScratchViewThread mThread;
    List mPathList = new ArrayList();
    private int mOverlayColor;
    private Paint mOverlayPaint;
    private int mRevealSize;
    private boolean mIsScratchable = true;
    private boolean mIsAntiAlias = false;
    private Path path;
    private float startX = 0;
    private float startY = 0;
    private boolean mScratchStart = false;
    private MediaPlayer phrase;
    Boolean timerRunned = false;
    int fWidth,fHeight, fX,fY, fIndent;
    boolean ignore_drawable = false;
    Bitmap bitmap, f;
    public static long scores = 0;
    public Paint paint, paintf;
    public Rect destin, destinf;
    public int fLeft, fTop, fRight, fBottom;
    Handler h;
    Runnable updateScores;

    public WScratchView(Context ctx, AttributeSet attrs)
    {
        super(ctx, attrs);
        init(ctx, attrs);
    }

    public WScratchView(Context context)
    {
        super(context);
        init(context, null);
        }

    private void init(Context context, AttributeSet attrs)
    {
        mContext = context;

        mOverlayColor = DEFAULT_COLOR;
        mRevealSize = DEFAULT_REVEAL_SIZE;

        setZOrderOnTop(true);   
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
        holder.setFormat(PixelFormat.TRANSPARENT);

        mOverlayPaint = new Paint();   
        mOverlayPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR)); 
        mOverlayPaint.setStyle(Paint.Style.STROKE);
        mOverlayPaint.setStrokeCap(Paint.Cap.ROUND);
        mOverlayPaint.setStrokeJoin(Paint.Join.ROUND);

        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.layer);
        f = BitmapFactory.decodeResource(getResources(), R.drawable.f);

        Resources res = getResources();
        fHeight = res.getInteger(R.integer.fHeight);
        fWidth  = res.getInteger(R.integer.fWidth);
        fIndent = res.getInteger(R.integer.fIndent);

        h = new Handler();
        updateScores = new Runnable() {

            @Override
            public void run()
            {
                Main.tvScores.setText(Long.toString(scores));                   
            }
        };

        paint = new Paint();
        paintf = new Paint();
        paint.setFilterBitmap(true);
        paintf.setFilterBitmap(false);

        Log.d("LOG_", "init complete");
    }

    @SuppressLint("DrawAllocation") @Override
    public void onDraw(Canvas canvas)
    { 
        if (ignore_drawable)return;
        destin = new Rect(0, 0, getWidth(), getHeight());
        canvas.drawBitmap(bitmap, null, destin, paint);

        //Log.d("LOG_","draw start");
        for (Path path: mPathList)
        {
            mOverlayPaint.setAntiAlias(mIsAntiAlias);
            mOverlayPaint.setStrokeWidth(mRevealSize);

            canvas.drawPath(path, mOverlayPaint);
        }

        destinf = new Rect(fLeft, fTop, fRight, fBottom);
        canvas.drawBitmap(f, null, destinf, paintf);
    }

    @Override
    public boolean onTouchEvent(MotionEvent me)
    {        
        ignore_drawable = true;
        synchronized (mThread.getSurfaceHolder())
        {
            ignore_drawable = false;    
            if (!mIsScratchable)
            {
                return true;
            }

            if (!timerRunned)
            {
                timerRunned = true;
                Main.cdt.start();
            }
            Log.d("LOG_", "bye");

            switch (me.getAction())
            {
                case MotionEvent.ACTION_DOWN:
                    path = new Path();
                    path.moveTo(me.getX(), me.getY());
                    startX = me.getX();
                    startY = me.getY();
                    mPathList.add(path);
                    break;
                case MotionEvent.ACTION_MOVE:
                    playrandom();
                    fX = (int)me.getRawX();
                    fY = (int)me.getRawY();
                    fLeft = fX - fIndent;
                    fTop = fY - fIndent - fIndent;
                    if (fLeft < 0)fLeft = 0;
                    if (fTop < 0) fTop =  0;
                    fRight = fX - fIndent + fWidth;
                    fBottom = fY - (fIndent*2) + fHeight;
                    if (mScratchStart)
                    {
                        path.lineTo(me.getX(), me.getY());
                    }
                    else
                    {
                        if (isScratch(startX, me.getX(), startY, me.getY()))
                        {
                            mScratchStart = true;
                            path.lineTo(me.getX(), me.getY());
                        }
                    }
                    scores += Main.level;   
                    h.post(updateScores);
                    invalidate();
                    break;
                case MotionEvent.ACTION_UP:
                    mScratchStart = false;
                    try
                    {phrase.stop();}
                    catch (Exception ex) {}
                    break;
            }
            invalidate();
            return true;
        }
    }

    public void playrandom()
    {
        //Тут идёт очень большой метод, не связанный с вопросом. Я его пропущу.
    }

    private boolean isScratch(float oldX, float x, float oldY, float y)
    {
        float distance = (float) Math.sqrt(Math.pow(oldX - x, 2) + Math.pow(oldY
- y, 2));
        if (distance > mRevealSize * 2)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {}

    @Override
    public void surfaceCreated(SurfaceHolder arg0)
    {
        mThread = new WScratchViewThread(getHolder(), this); 
        mThread.setRunning(true);
        mThread.setPriority(1);
        mThread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder arg0)
    {
        boolean retry = true;
        mThread.setRunning(false);
        while (retry)
        {
            try
            {               
                mThread.join();

                retry = false;
            }
            catch (InterruptedException e) {}
        }

    }

    class WScratchViewThread extends Thread
    {
        private SurfaceHolder mSurfaceHolder;
        private WScratchView mView;
        private boolean mRun = false;

        public WScratchViewThread(SurfaceHolder surfaceHolder, WScratchView view)
        {
            mSurfaceHolder = surfaceHolder;
            mView = view;
        }

        public void setRunning(boolean run)
        {
            mRun = run;
        }

        public SurfaceHolder getSurfaceHolder()
        {           
            return mSurfaceHolder;
        }

        @SuppressLint("WrongCall") @Override
        public void run()
        {           
            Canvas c;
            while (mRun)
            {
                c = null;
                try
                {
                    c = mSurfaceHolder.lockCanvas(null);
                    if (c != null)
                        synchronized (mSurfaceHolder)
                        {
                            {   
                                mView.onDraw(c);
                            }
                        }
                }
                finally
                {
                    if (c != null)
                    {
                        mSurfaceHolder.unlockCanvasAndPost(c);
                    }
                }

                try
                {
                    TimeUnit.MILLISECONDS.sleep(50);
                }
                catch (InterruptedException e) {}
            }
        }
    }

    public void resetView()
    {
        synchronized (mThread.getSurfaceHolder())
        {
            mPathList.clear();
        }
    }

    public boolean isScratchable()
    {
        return mIsScratchable;
    }

    public void setScratchable(boolean flag)
    {
        mIsScratchable = flag;
    }

    public void setOverlayColor(int ResId)
    {
        mOverlayColor = ResId;
    }

    public void setRevealSize(int size)
    {
        mRevealSize = size;
    }

    public void setAntiAlias(boolean flag)
    {
        mIsAntiAlias = flag;
    }
}
    


Ответы

Ответ 1



Только по коду сказать что-то конкретное сложно. Стоит прогнать ваше приложение через профайлер (насколько я помню, он входит в состав Android SDK) и проверить, какой метод отъедает больше всего времени. Точно не скажу, но мне кажется, что это метод onTouchEvent. Во-превых, лучше не использовать блок synchronized, такая синхронизация отъедает слишком много ресурсов. Вместо него лучше использовать локи (также почитайте про методы lockCanvas() и unlockCanvasAndPost()). Во-вторых, этот метод будет отрабатывать очень много раз в секунду на каждое касание, что скорее всего и нагружает процессор. Для того, чтобы этого избежать стоит ограничить число срабатываний этого метода в секунду. Для этого подойдёт проверка на время предыдкщего срабатывания метода: если он отработал меньше чем, скажем, 50 милисекунд назад, то не вызываем обработчик.

Комментариев нет:

Отправить комментарий