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

Пользователь запускает мое приложение и регистрируется.
Выбирает время ожидания сеанса до 5 минут.
Выполняет некоторые операции над приложением. (все на переднем плане)
Теперь пользователь выводит Myapp на задний план и запускает другое приложение.
---- > Таймер обратного отсчета запускается и выводит пользователя через 5 минут
ИЛИ пользователь выключает экран.
---- > Таймер обратного отсчета запускается и выводит пользователя через 5 минут

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

Ответ 1

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 ===> логика здесь не показана, так как она выходит за рамки вопроса

Вы можете разбудить блокировку процессора, используя код устройства below-

  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);
      }
    }

Ответ 2

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

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();
    }
}

Ответ 3

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

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

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

Ответ 4

Я думаю, вы должны пойти с этим кодом, это за 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 
}

Ответ 5

@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));
}

Ответ 6

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

Ответ 7

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

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

Сначала я расскажу вам, что вам нужно делать, если вы используете Timer или Handlers.

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

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

  1. НЕТ обработчик или таймер.
  2. НЕТ Менеджера тревог.
  3. НЕ управляющий приложением LifeCycle.
  4. НЕТ ACTION_SCREEN_ON/ACTION_SCREEN_OFF Broadcast Receiver.

Самое простое и надежное решение

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

Вот 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.
    }
}

Ответ 8

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

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();

Ответ 9

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

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 секунд бездействия пользователя.

Ответ 10

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

Так вот так:

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

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

Довольно просто.

Теперь, когда пот таймера будет выглядеть так:

  • Возьмите текущее время и вычтите сохраненное время взаимодействия, чтобы получить timeDelta​​li >
  • Если timeDelta = > 5 минут, вы делаете
  • Если timeDelta < 5 минут снова запустите таймер, но на этот раз используйте 5 минут - время хранения. Другими словами, 5 минут образуют последнее взаимодействие

Ответ 11

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

Основываясь на ответе @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: Очистить стек активности

Этот класс обработчика должен быть статическим, иначе могут возникнуть утечки

Ответ 12

onUserInteraction() пользователя можно обнаружить с помощью onUserInteraction() переопределения 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");

    }

}

Ответ 13

Лучше всего справиться с этим во всем приложении (при условии, что у вас есть несколько действий), зарегистрировав AppLifecycleCallbacks в приложении Calss. Вы можете использовать registerActivityLifecycleCallbacks() в классе Application со следующими обратными вызовами (я рекомендую создать класс 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);
}

Ответ 14

Обработка пользователя во время ожидания взаимодействия в KOTLIN:

     //Declare handler
      private var timeoutHandler: Handler? = null
      private var interactionTimeoutRunnable: Runnable? = null

 override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_aspect_ratio)

       //Initialise handler
      timeoutHandler =  Handler();
      interactionTimeoutRunnable =  Runnable {
         // Handle Timeout stuffs here
          }

      //start countdown
      startHandler()
}

// reset handler on user interaction
override fun onUserInteraction() {
      super.onUserInteraction()
      resetHandler()
}

 //restart countdown
fun reset() {
      timeoutHandler!!.removeCallbacks(interactionTimeoutRunnable);
      timeoutHandler!!.postDelayed(interactionTimeoutRunnable, 10*1000); //for 10 second

}

 // start countdown
fun startHandler() {
    timeoutHandler!!.postDelayed(interactionTimeoutRunnable, 10*1000); //for 10 second
}

Ответ 15

Я знаю, что уже слишком поздно, чтобы добавить этот ответ, но это для всех новых зрителей впереди, я прошел большинство кодов выше и попробовал себя. Но никто из них не работал для меня. Приведенный ниже код работал именно так, как будет искать новый зритель. Этот код будет работать:

public class MainActivity extends AppCompatActivity {

        private Timer timer;
        private Toolbar toolbar;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            toolbar = (Toolbar) findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
        }

        @Override
        protected void onPause() {
            super.onPause();

            timer = new Timer();
            Log.i("Main", "Invoking logout timer");
            LogOutTimerTask logoutTimeTask = new LogOutTimerTask();
            timer.schedule(logoutTimeTask, 300000); //auto logout in 5 minutes
        }

        @Override
        protected void onResume() {
            super.onResume();
            if (timer != null) {
                timer.cancel();
                Log.i("Main", "cancel timer");
                timer = null;
            }
        }

        private class LogOutTimerTask extends TimerTask {

            @Override
            public void run() {

                //redirect user to login screen
                Intent i = new Intent(MainActivity.this, YourActivity.class); //where 
             you want to load when inactive 
                i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(i);
                finish();
            }
        }
    }