Android — onPause и WidgetLocker

Я пишу базовое приложение для проверки поведения акселерометра. В настоящее время я регистрирую и отменяю регистрацию акселерометра следующим образом:

public void pause() {
    mSensorManager.unregisterListener(this);
}

public void resume(Context context) {
    mSensorManager.registerListener(this, mAccelerometer,     
            SensorManager.SENSOR_DELAY_FASTEST);        
}

У меня есть методы паузы и возобновления, запускаемые Activity, поскольку акселерометр работает в дочернем классе. Когда я блокирую экран, программа ломается. На самом деле мне нужно завершить процесс, чтобы программа снова заработала. В настоящее время я использую WidgetLocker. Программа отлично работает со стоковым локскрином. Я предполагаю, что резюме никогда не запускается.

Есть ли способ убедиться, что датчик будет перерегистрирован? Я не хочу, чтобы моя программа сломалась, если они используют что-то за пределами обычного экрана блокировки.

EDIT: добавлен полный исходный код. Основная логика по этому вопросу находится в нижней части MainGamePanel.java.

TiltBallActivity.java

package com.tornquist.nathan;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class TiltBallActivity extends Activity{
    /** Called when the activity is first created. */
    MainGamePanel viewPanel;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //Window state functions.
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        //This works without declaring a viewPanel instance here.
        //The instance declaration is needed to pass the 
        //onPause and onResume commands though.
        viewPanel = new MainGamePanel(this);
        setContentView(viewPanel);
    }

    //Restarts the accelerometer after onPause
    protected void onResume() {

        viewPanel.resume(this);
        super.onResume();
    }

    //Standard Method run when the Application loses focus.
    //This runs the pause() function in the viewPanel so that
    //the accelerometer can be paused.
    protected void onPause() {

        viewPanel.pause();
        super.onPause(); 
    }
}

MainThread.java

package com.tornquist.nathan;

import com.tornquist.nathan.MainGamePanel;
import android.graphics.Canvas;
import android.view.SurfaceHolder;

public class MainThread extends Thread {

    private SurfaceHolder surfaceHolder;
    private MainGamePanel gamePanel;
    private boolean running;
    public void setRunning(boolean running) {
        this.running = running;
    }

    public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
        super();
        this.surfaceHolder = surfaceHolder;
        this.gamePanel = gamePanel;
    }

    @Override
    public void run() 
    {
        Canvas canvas;
        while (running) {
            canvas = null;
            // try locking the canvas for exclusive pixel editing on the surface
            try {
                canvas = this.surfaceHolder.lockCanvas();
                synchronized (surfaceHolder) {
                    // update game state
                    this.gamePanel.update();

                    // draws the canvas on the panel
                    this.gamePanel.onDraw(canvas);
                }
            } finally {
                // in case of an exception the surface is not left in
                // an inconsistent state
                if (canvas != null) {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }   // end finally
        }
    }
}

MainGamePanel.java

package com.tornquist.nathan;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MainGamePanel extends SurfaceView implements SensorEventListener, SurfaceHolder.Callback 
{
    //Variable Declarations.
    private MainThread thread;
    public int xColor;
    public int yColor;
    public int zColor;

    public float xPos;
    public float yPos;
    public float oldXPos;
    public float oldYPos;

    public int screenWidth;
    public int screenHeight;

    private SensorManager mSensorManager;
    private Sensor mAccelerometer;    

    Paint paint;

    public MainGamePanel(Context context)
    {
        //Standard Initialization
        super(context);

        //This line adds a callback for the touch screen.  
        //This allows you to actually capture touch input.
        getHolder().addCallback(this);

        thread = new MainThread(getHolder(),this);

        xColor = 100;
        yColor = 100;
        zColor = 100;

        paint = new Paint();
        paint.setAntiAlias(true);

        Display display = ((Activity) context).getWindowManager().getDefaultDisplay(); 
        screenWidth = display.getWidth();
        screenHeight = display.getHeight();

        yPos = screenHeight/2;
        xPos = screenWidth/2;
        oldYPos = yPos;
        oldXPos = xPos;

        //This registers the accelerometer.  Without registering it, the onSensorChanged
        //event will never be called, and you cannot get the accelerometer values.
        mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);

        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

        mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST);

        //Also required for touch input.
        setFocusable(true);
    }

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

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // at this point the surface is created and
        // we can safely start the game loop
        thread.setRunning(true);
        thread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        while (retry) {
            try {
                thread.join();
                retry = false;
            } catch (InterruptedException e) {
                // try again shutting down the thread
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
                        // check if in the lower part of the screen we exit
            if (event.getY() > getHeight() - 50) {
                thread.setRunning(false);
                ((Activity)getContext()).finish();
            }

            if (xColor < 235)
                xColor = xColor + 20;
            else
                xColor = 0;
            if (yColor < 235)
                yColor = yColor + 20;
            else
                yColor = 0;
            if (zColor < 235)
                zColor = zColor + 20;
            else
                zColor = 0;

            yPos = event.getY();
            xPos = event.getX();
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) 
    {
        //canvas.drawColor(Color.CYAN);
        canvas.drawColor(Color.rgb(xColor, yColor, zColor));
        int xInvert = (int) (255 - xColor);
        int yInvert = (int) (255 - yColor);
        int zInvert = (int) (255 - zColor);

        paint.setColor(Color.rgb(xInvert, yInvert, zInvert));
        paint.setStyle(Style.FILL);
        canvas.drawCircle(xPos, yPos, 50, paint);
    }

    public void update() {
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {        
    }

    @Override
    public void onSensorChanged(SensorEvent event) {

        oldYPos = yPos;
        oldXPos = xPos;

        xColor = (int) (255 + (event.values[0])*11);
        if (xColor > 255)
            xColor = 255;
        if (xColor < 0)
            xColor = 0;

        yColor = (int) (255 + (event.values[1])*11);
        if (yColor > 255)
            yColor = 255;
        if (yColor < 0)
            yColor = 0;

        zColor = (int) (255 + (event.values[2])*11);
        if (zColor > 255)
            zColor = 255;
        if (zColor < 0)
            zColor = 0;

        xPos = xPos + -1*(event.values[0])*5;
        yPos = yPos + event.values[1]*5;

        if (xPos < 50)
            xPos = 50;
        if (xPos > screenWidth - 50)
            xPos = screenWidth - 50;
        if (yPos < 50)
            yPos = 50;
        if (yPos > screenHeight - 50)
            yPos = screenHeight - 50;

        if ((oldYPos == yPos) && (oldXPos == xPos))
        {
            invalidate();
        }

    }

    public void pause() {
        mSensorManager.unregisterListener(this);

        //thread.setRunning(false);
        //((Activity)getContext()).finish();
    }

    public void resume(Context context) {
        //mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);

        //mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

        mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST);      
    }


}

person Nathan Tornquist    schedule 17.01.2012    source источник
comment
Вы используете pause и resume или onPause и onResume?   -  person asaelr    schedule 17.01.2012
comment
onPause и onResume. пауза и возобновление находятся в дочернем классе. Они выполняются методами onPause и onResume. Как я уже сказал выше, это прекрасно работает, когда я не использую WidgetLocker. Мне нужно запрограммировать для любой ситуации, хотя.   -  person Nathan Tornquist    schedule 17.01.2012
comment
Можете ли вы также опубликовать свой код активности?   -  person Reuben Scratton    schedule 19.01.2012


Ответы (2)


Я думаю, вы могли бы использовать действия Intent ACTION_SCREEN_ON и ACTION_SCREEN_OFF, и вам не нужно иметь дело с onPause и onResume. Что-то вроде этого:

mReceiver = new BroadcastReceiver() {
     public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();
    if (Intent.ACTION_SCREEN_OFF.equals(action)) {
        //deregister
    }
    else if (Intent.ACTION_SCREEN_ON.equals(action)) {
        //register
    }
}};

IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
registerReceiver(mReceiver, filter); 

filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(mReceiver, filter);

и отмените регистрацию этого приемника в методе onDestroy()


Хорошо, я думаю, что нашел решение. Часть thread.join в коде метода onSurfaceDestroyed каким-то образом блокирует активность. Я сделал несколько изменений в вашем коде, и это работает для меня:

public MainGamePanel(Context context)
{
    ...

    //Also required for touch input.
    setFocusable(true);

    //start thread
    thread.setRunning(true);
    thread.start();
}

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

@Override
public void surfaceCreated(SurfaceHolder holder) {
    //continue the thread
    synchronized (thread) {
        thread.pleaseWait = false;
        thread.notifyAll();
    }
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    //pause the thread
    synchronized (thread) {
        thread.pleaseWait = true;
    }
}

И класс потока

public class MainThread extends Thread {

private SurfaceHolder surfaceHolder;
private MainGamePanel gamePanel;
private boolean running;
public boolean pleaseWait = true;
public void setRunning(boolean running) {
    this.running = running;
}

public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
    super();
    this.surfaceHolder = surfaceHolder;
    this.gamePanel = gamePanel;
}

@Override
public void run() 
{
    Canvas canvas;
    while (running) {
        if(!pleaseWait) {
            canvas = null;
            // try locking the canvas for exclusive pixel editing on the surface
            try {
                canvas = this.surfaceHolder.lockCanvas();
                synchronized (surfaceHolder) {
                    // update game state
                    this.gamePanel.update();

                    // draws the canvas on the panel
                    this.gamePanel.onDraw(canvas);
                }
            } finally {
                // in case of an exception the surface is not left in
                // an inconsistent state
                if (canvas != null) {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }   // end finally            
        }
        else {
            synchronized (this) {
                try {
                    wait();
                } catch (Exception e) { }
            }
        }
    }
}
}

И уничтожить поток в методе onDestroy

person tungi52    schedule 21.01.2012
comment
Я, конечно, попробую это, но в конечном итоге это не решит проблему. Если кто-то переключает приложения, мое приложение будет по-прежнему разряжать батарею. Единственный способ, которым это решило бы проблему, — это если бы они всегда блокировали экран из моего приложения и разблокировали экран, чтобы вернуться непосредственно к нему. - person Nathan Tornquist; 22.01.2012
comment
Я отлаживаю ваш код: я думаю, что проблема с разрядкой батареи связана с потоком. Я думаю, вам следует сделать паузу. Еще один момент: когда я отлаживаю ваш код, onResume у меня не срабатывает. - person tungi52; 22.01.2012
comment
Вся проблема, о которой я спрашиваю, заключается в том, что onResume не срабатывает. - person Nathan Tornquist; 22.01.2012
comment
Кажется, это работает отлично. Можете ли вы попытаться объяснить, что на самом деле было не так? Поток не приостанавливался, и в конечном итоге приостановка половины приложения сломала его? И нужно ли мне добавить метод onDestroy? --- РЕДАКТИРОВАТЬ: Могу ли я просто использовать thread = null; в методе onDestroy()? - person Nathan Tornquist; 23.01.2012
comment
Да, основная проблема с вашим кодом — это часть thread.join. OnSurfaceDestroy будет срабатывать всегда после onPause, и ваш код блокирует процесс действия, потому что вы не вызываете thread.setRunning(false). Это не решит вашу проблему, потому что вам нужно повторно инициализировать поток в onSurfaceCreate. Более элегантное решение — приостановить поток и продолжить его. В onDestroy: создайте метод остановки в MainGamePanel и скопируйте туда код остановки потока, но установите для текущего поля значение false. Вы не можете установить для потока значение null, потому что поток продолжит выполнение - person tungi52; 23.01.2012
comment
Хорошо, я настроил систему остановки, как показано здесь: stackoverflow.com/questions/680180/ поэтому я делаю это: public void destroy() { thread.setRunning(false); if (thread != null) { Thread killThread = thread; поток = ноль; killThread.interrupt(); } } ---- Это правильно? Программа вроде работает. - person Nathan Tornquist; 24.01.2012

Я думаю, что если вы используете onCreate() для регистрации и onDestroy() для отмены регистрации, это плохо решит вашу проблему. Потому что ваш onDestory не будет вызываться, пока ваш экран заблокирован, и это может решить вашу проблему.

person Ravi Bhojani    schedule 20.01.2012
comment
Мне нужно отменить регистрацию акселерометра, когда экран заблокирован. В противном случае аккумулятор будет очень быстро разряжаться. - person Nathan Tornquist; 20.01.2012