Как обнаружить бездействие пользователя в Android



пользователь запускает мое приложение и входит в систему.

Выбирает тайм-аут сеанса, равный 5 мин.

Выполняет некоторые операции над приложением. (все на переднем плане)

Теперь пользователь приносит Myapp в фоновом режиме и запускает какое-то другое приложение.

---- >Таймер обратного отсчета запускается и выходит из системы пользователя через 5 минут

Или пользователь выключает экран.

---- >Таймер обратного отсчета запускается и выходит из системы пользователя через 5 минут



Я хочу такое же поведение, даже когда приложение находится на переднем плане, но пользователь не взаимодействовать с приложением в течение длительного времени говорят 6-7 минут. Предположим, что экран включен все время. Я хочу обнаружить вид неактивности пользователя (нет взаимодействия с приложением, даже если приложение находится на переднем плане) и начните свой отсчет таймера.

749   13  

13 ответов:

Я придумал решение, которое я нахожу довольно простым на основе ответа Фредрика Валлениуса. Это базовый класс действий, который должен быть расширен всеми действиями.

public class MyBaseActivity extends Activity {

    public static final long DISCONNECT_TIMEOUT = 300000; // 5 min = 5 * 60 * 1000 ms


    private Handler disconnectHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // todo
            return true;
        }
    });

    private Runnable disconnectCallback = new Runnable() {
        @Override
        public void run() {
            // Perform any required operation on disconnect
        }
    };

    public void resetDisconnectTimer(){
        disconnectHandler.removeCallbacks(disconnectCallback);
        disconnectHandler.postDelayed(disconnectCallback, DISCONNECT_TIMEOUT);
    }

    public void stopDisconnectTimer(){
        disconnectHandler.removeCallbacks(disconnectCallback);
    }

    @Override
    public void onUserInteraction(){
        resetDisconnectTimer();
    }

    @Override
    public void onResume() {
        super.onResume();
        resetDisconnectTimer();
    }

    @Override
    public void onStop() {
        super.onStop();
        stopDisconnectTimer();
    }
}

Я не знаю, как отслеживать бездействие, но есть способ отслеживать активность пользователей. Вы можете поймать обратный вызов под названием onUserInteraction() в вашей деятельности, которая вызывается каждый раз, когда пользователь выполняет любое взаимодействие с приложением. Я бы предложил сделать что-то вроде этого:

@Override
public void onUserInteraction(){
    MyTimerClass.getInstance().resetTimer();
}

Если ваше приложение содержит несколько действий, почему бы не поместить этот метод в абстрактный суперкласс (расширение Activity), а затем все ваши действия, расширяющие его.

public class MyApplication extends Application {
      private int lastInteractionTime;
      private Boolean isScreenOff = false; 
      public void onCreate() {
        super.onCreate();
        // ......   
        startUserInactivityDetectThread(); // start the thread to detect inactivity
        new ScreenReceiver();  // creating receive SCREEN_OFF and SCREEN_ON broadcast msgs from the device.
      }

      public void startUserInactivityDetectThread() {
        new Thread(new Runnable() {
          @Override
          public void run() {
            while(true) {
              Thread.sleep(15000); // checks every 15sec for inactivity
              if(isScreenOff || getLastInteractionTime()> 120000 ||  !isInForeGrnd)
                {
                  //...... means USER has been INACTIVE over a period of
                  // and you do your stuff like log the user out 
                }
              }
          }
        }).start();
      }

      public long getLastInteractionTime() {
        return lastInteractionTime;
      }

      public void setLastInteractionTime(int lastInteractionTime) {
        this.lastInteractionTime = lastInteractionTime;
      }

      private class ScreenReceiver extends BroadcastReceiver {

        protected ScreenReceiver() {
           // register receiver that handles screen on and screen off logic
           IntentFilter filter = new IntentFilter();
           filter.addAction(Intent.ACTION_SCREEN_ON);
           filter.addAction(Intent.ACTION_SCREEN_OFF);
           registerReceiver(this, filter);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
          if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
            isScreenOff = true;
          } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
            isScreenOff = false;
          }
        }
      }
    }

isInForeGrnd ===> логика здесь не показана, так как она выходит за рамки вопроса

вы можете разбудить блокировку процессора с помощью кода устройства ниже -

  if(isScreenOff || getLastInteractionTime()> 120000 ||  !isInForeGrnd)
    {
      //...... means USER has been INACTIVE over a period of
      // and you do your stuff like log the user out 

      PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);

      boolean isScreenOn = pm.isScreenOn();
      Log.e("screen on.................................", "" + isScreenOn);

      if (isScreenOn == false) {

        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "MyLock");

        wl.acquire(10000);
        PowerManager.WakeLock wl_cpu = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyCpuLock");

        wl_cpu.acquire(10000);
      }
    }

Я думаю, что вы должны пойти с этим кодом, это для 5мин простоя тайм-аут сессии: ->

Handler handler;
Runnable r;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    handler = new Handler();
    r = new Runnable() {

       @Override
       public void run() {
            // TODO Auto-generated method stub
            Toast.makeText(MainActivity.this, "user is inactive from last 5 minutes",Toast.LENGTH_SHORT).show();
        }
    };
    startHandler();
}
@Override
public void onUserInteraction() {
     // TODO Auto-generated method stub
     super.onUserInteraction();
     stopHandler();//stop first and then start
     startHandler();
}
public void stopHandler() {
    handler.removeCallbacks(r);
}
public void startHandler() {
    handler.postDelayed(r, 5*60*1000); //for 5 minutes 
}
@Override
public void onUserInteraction() {
    super.onUserInteraction();
    delayedIdle(IDLE_DELAY_MINUTES);
}

Handler _idleHandler = new Handler();
Runnable _idleRunnable = new Runnable() {
    @Override
    public void run() {
        //handle your IDLE state
    }
};

private void delayedIdle(int delayMinutes) {
    _idleHandler.removeCallbacks(_idleRunnable);
    _idleHandler.postDelayed(_idleRunnable, (delayMinutes * 1000 * 60));
}

нет понятия "бездействие пользователя" на уровне ОС, за пределами ACTION_SCREEN_OFF и ACTION_USER_PRESENT трансляции. Вам нужно будет как-то определить "бездействие" в вашем собственном приложении.

в моем базовом классе activity я создал защищенный класс:

protected class IdleTimer
{
    private Boolean isTimerRunning;
    private IIdleCallback idleCallback;
    private int maxIdleTime;
    private Timer timer;

    public IdleTimer(int maxInactivityTime, IIdleCallback callback)
    {
        maxIdleTime = maxInactivityTime;
        idleCallback = callback;
    }

    /*
     * creates new timer with idleTimer params and schedules a task
     */
    public void startIdleTimer()
    {
        timer = new Timer();            
        timer.schedule(new TimerTask() {

            @Override
            public void run() {             
                idleCallback.inactivityDetected();
            }
        }, maxIdleTime);
        isTimerRunning = true;
    }

    /*
     * schedules new idle timer, call this to reset timer
     */
    public void restartIdleTimer()
    {
        stopIdleTimer();
        startIdleTimer();
    }

    /*
     * stops idle timer, canceling all scheduled tasks in it
     */
    public void stopIdleTimer()
    {
        timer.cancel();
        isTimerRunning = false;
    }

    /*
     * check current state of timer
     * @return boolean isTimerRunning
     */
    public boolean checkIsTimerRunning()
    {
        return isTimerRunning;
    }
}

protected interface IIdleCallback
{
    public void inactivityDetected();
}

Так onResume метод-вы можете указать действие в своем обратном вызове, что вы хотите с ним сделать...

idleTimer = new IdleTimer(60000, new IIdleCallback() {
            @Override
            public void inactivityDetected() {
                ...your move...
            }
        });
        idleTimer.startIdleTimer();

во время моего поиска я нашел много ответов, но это лучший ответ, который я получил. Но ограничением этого кода является то, что он работает только для деятельности, а не для всего приложения. Возьмите это в качестве ссылки.

myHandler = new Handler();
myRunnable = new Runnable() {
    @Override
    public void run() {
        //task to do if user is inactive

    }
};
@Override
public void onUserInteraction() {
    super.onUserInteraction();
    myHandler.removeCallbacks(myRunnable);
    myHandler.postDelayed(myRunnable, /*time in milliseconds for user inactivity*/);
}

например, вы использовали 8000, задача будет выполнена после 8 секунд бездействия пользователя.

поздно лучше, чем никогда

даже вы можете управлять вашим требованием с @gfrigon или @AKh решений.

но я думал о некоторых таймеров и обработчиков свободных решений для этого. У меня уже есть хорошо управляемое решение таймера для этого. Но я успешно реализовал таймер и обработчик бесплатное решение.

сначала я скажу тебе что вы должны управлять если вы используете таймер или Дрессировщики.

  • если ваше приложение убито пользователем или оптимизатором, ваше приложение никогда не выйдет автоматически, потому что все ваши обратные вызовы будут уничтожены. (управлять какой-то менеджер сигнализации или службы?)
  • хорошо ли иметь таймер в каждом базовом классе? Вы делаете много потоков для простого вызова процесса выхода из системы (управление статическим обработчиком или таймером на уровне приложения?).
  • что делать, если пользователь находится в фоновом режиме, ваш обработчик начнет Войдите в систему, если пользователь выполняет какую-либо другую работу вне вашего приложения. (управление приложения переднего плана или фона?).
  • что делать, если экран отключается автоматически. (управление экран выключен на широковещательном приемнике?)

наконец-то я реализовал решение, которое

  1. вы не должны использовать Хандер или таймер.
  2. вам не нужно использовать Alarm Manager.
  3. вы не должны использовать приложение Жизненный цикл.
  4. вы не должны использовать ACTION_SCREEN_ON/ACTION_SCREEN_OFF Радиовещательный Приемник.

решение

мы не будем наблюдать бездействие пользователя по таймеру, а не будем проверять время последней активности на активности пользователя. Поэтому, когда пользователь взаимодействует с приложением в следующий раз, я проверяю последнее время взаимодействия.

здесь BaseActivity.class который вы будете расширять из каждого класса активности вместо LoginActivity. Вы будете определять время выхода из системы в поле TIMEOUT_IN_MILLI в этом классе.

import android.content.Intent;
import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;

public class BaseActivity extends AppCompatActivity {
    public static final long TIMEOUT_IN_MILLI = 1000 * 20;
    public static final String PREF_FILE = "App_Pref";
    public static final String KEY_SP_LAST_INTERACTION_TIME = "KEY_SP_LAST_INTERACTION_TIME";

    @Override
    public void onUserInteraction() {
        super.onUserInteraction();
        if (isValidLogin())
            getSharedPreference().edit().putLong(KEY_SP_LAST_INTERACTION_TIME, System.currentTimeMillis()).apply();
        else logout();
    }

    public SharedPreferences getSharedPreference() {
        return getSharedPreferences(PREF_FILE, MODE_PRIVATE);
    }

    public boolean isValidLogin() {
        long last_edit_time = getSharedPreference().getLong(KEY_SP_LAST_INTERACTION_TIME, 0);
        return last_edit_time == 0 || System.currentTimeMillis() - last_edit_time < TIMEOUT_IN_MILLI;
    }

    public void logout() {
        Intent intent = new Intent(this, LoginActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivity(intent);
        finish();
        Toast.makeText(this, "User logout due to inactivity", Toast.LENGTH_SHORT).show();
        getSharedPreference().edit().remove(KEY_SP_LAST_INTERACTION_TIME).apply(); // make shared preference null.
    }
}

Я думаю, что это должно быть путем объединения таймера с последним временем активности.

таким образом:

  1. в onCreate (Bundle savedInstanceState) запустите таймер, скажем, 5 минут

  2. в onUserInteraction () просто сохраните текущее время

до сих пор довольно просто.

теперь, когда таймер поп сделать так:

  1. возьмите текущее время и вычесть сохраненное время взаимодействия чтобы получить timeDelta
  2. если timeDelta является >= 5 минут, вы сделали
  3. если timeDelta

У меня была аналогичная ситуация с вопросом SO, где мне нужно было отслеживать бездействие пользователя в течение 1 минуты, а затем перенаправить пользователя на запуск активности, мне также нужно было очистить стек активности.

на основе ответа @gfrigon я придумал это решение.

ActionBar.java

public abstract class ActionBar extends AppCompatActivity {

    public static final long DISCONNECT_TIMEOUT = 60000; // 1 min

    private final MyHandler mDisconnectHandler = new MyHandler(this);

    private Context mContext;


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

        mContext = this;
    }



    /*
    |--------------------------------------------------------------------------
    | Detect user inactivity in Android
    |--------------------------------------------------------------------------
    */

    // Static inner class doesn't hold an implicit reference to the outer class

    private static class MyHandler extends Handler {

        // Using a weak reference means you won't prevent garbage collection

        private final WeakReference<ActionBar> myClassWeakReference;

        public MyHandler(ActionBar actionBarInstance) {

            myClassWeakReference = new WeakReference<ActionBar>(actionBarInstance);
        }

        @Override
        public void handleMessage(Message msg) {

            ActionBar actionBar = myClassWeakReference.get();

            if (actionBar != null) {
                // ...do work here...
            }
        }
    }


    private Runnable disconnectCallback = new Runnable() {

        @Override
        public void run() {

            // Perform any required operation on disconnect

            Intent startActivity = new Intent(mContext, StartActivity.class);

            // Clear activity stack

            startActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

            startActivity(startActivity);
        }
    };

    public void resetDisconnectTimer() {

        mDisconnectHandler.removeCallbacks(disconnectCallback);
        mDisconnectHandler.postDelayed(disconnectCallback, DISCONNECT_TIMEOUT);
    }

    public void stopDisconnectTimer() {

        mDisconnectHandler.removeCallbacks(disconnectCallback);
    }

    @Override
    public void onUserInteraction(){

        resetDisconnectTimer();
    }

    @Override
    public void onResume() {

        super.onResume();
        resetDisconnectTimer();
    }

    @Override
    public void onStop() {

        super.onStop();
        stopDisconnectTimer();
    }
}

дополнительные ресурсы

Android: Очистить Стек Активности

этот класс обработчика должен быть статическим или утечки могут происходят

неактивности пользователя можно обнаружить с помощью onUserInteraction() переопределить метод в android

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

    }

вот пример кода, выход (HomeActivity-- > LoginActivity) после 3мин когда пользователь неактивен

public class HomeActivity extends AppCompatActivity {

    private static String TAG = "HomeActivity";
    private Handler handler;
    private Runnable r;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);


        handler = new Handler();
        r = new Runnable() {

            @Override
            public void run() {

                Intent intent = new Intent(getApplicationContext(), LoginActivity.class);
                startActivity(intent);
                Log.d(TAG, "Logged out after 3 minutes on inactivity.");
                finish();

                Toast.makeText(HomeActivity.this, "Logged out after 3 minutes on inactivity.", Toast.LENGTH_SHORT).show();
            }
        };

        startHandler();

    }

    public void stopHandler() {
        handler.removeCallbacks(r);
        Log.d("HandlerRun", "stopHandlerMain");
    }

    public void startHandler() {
        handler.postDelayed(r, 3 * 60 * 1000);
        Log.d("HandlerRun", "startHandlerMain");
    }

    @Override
    public void onUserInteraction() {
        super.onUserInteraction();
        stopHandler();
        startHandler();
    }

    @Override
    protected void onPause() {

        stopHandler();
        Log.d("onPause", "onPauseActivity change");
        super.onPause();

    }

    @Override
    protected void onResume() {
        super.onResume();
        startHandler();

        Log.d("onResume", "onResume_restartActivity");

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopHandler();
        Log.d("onDestroy", "onDestroyActivity change");

    }

}

лучше всего справиться с этим через все ваше приложение (при условии, что у вас есть несколько видов деятельности), зарегистрировавшись AppLifecycleCallbacks в приложении calss. Вы можете использовать registerActivityLifecycleCallbacks() в классе приложения со следующими обратными вызовами (я рекомендую создать класс AppLifecycleCallbacks, который расширяет ActivityLifecycleCallbacks):

public interface ActivityLifecycleCallbacks {
    void onActivityCreated(Activity activity, Bundle savedInstanceState);
    void onActivityStarted(Activity activity);
    void onActivityResumed(Activity activity);
    void onActivityPaused(Activity activity);
    void onActivityStopped(Activity activity);
    void onActivitySaveInstanceState(Activity activity, Bundle outState);
    void onActivityDestroyed(Activity activity);
}

Comments

    Ничего не найдено.