Как обнаружить бездействие пользователя в Android
пользователь запускает мое приложение и входит в систему.
Выбирает тайм-аут сеанса, равный 5 мин.
Выполняет некоторые операции над приложением. (все на переднем плане)
Теперь пользователь приносит Myapp в фоновом режиме и запускает какое-то другое приложение.
---- >Таймер обратного отсчета запускается и выходит из системы пользователя через 5 минут
Или пользователь выключает экран.
---- >Таймер обратного отсчета запускается и выходит из системы пользователя через 5 минут
Я хочу такое же поведение, даже когда приложение находится на переднем плане, но пользователь не взаимодействовать с приложением в течение длительного времени говорят 6-7 минут. Предположим, что экран включен все время. Я хочу обнаружить вид неактивности пользователя (нет взаимодействия с приложением, даже если приложение находится на переднем плане) и начните свой отсчет таймера.
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 решений.
но я думал о некоторых таймеров и обработчиков свободных решений для этого. У меня уже есть хорошо управляемое решение таймера для этого. Но я успешно реализовал таймер и обработчик бесплатное решение.
сначала я скажу тебе что вы должны управлять если вы используете таймер или Дрессировщики.
- если ваше приложение убито пользователем или оптимизатором, ваше приложение никогда не выйдет автоматически, потому что все ваши обратные вызовы будут уничтожены. (управлять какой-то менеджер сигнализации или службы?)
- хорошо ли иметь таймер в каждом базовом классе? Вы делаете много потоков для простого вызова процесса выхода из системы (управление статическим обработчиком или таймером на уровне приложения?).
- что делать, если пользователь находится в фоновом режиме, ваш обработчик начнет Войдите в систему, если пользователь выполняет какую-либо другую работу вне вашего приложения. (управление приложения переднего плана или фона?).
- что делать, если экран отключается автоматически. (управление экран выключен на широковещательном приемнике?)
наконец-то я реализовал решение, которое
- вы не должны использовать Хандер или таймер.
- вам не нужно использовать Alarm Manager.
- вы не должны использовать приложение Жизненный цикл.
- вы не должны использовать
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. } }
Я думаю, что это должно быть путем объединения таймера с последним временем активности.
таким образом:
в onCreate (Bundle savedInstanceState) запустите таймер, скажем, 5 минут
в onUserInteraction () просто сохраните текущее время
до сих пор довольно просто.
теперь, когда таймер поп сделать так:
- возьмите текущее время и вычесть сохраненное время взаимодействия чтобы получить timeDelta
- если timeDelta является >= 5 минут, вы сделали
- если 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