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

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

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

21 ответ

Решение
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);
      }
    }

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

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) и тогда все ваши действия расширяют его.

Я думаю, что вы должны пойти с этим кодом, это для 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 трансляций. Вам придется как-то определить "неактивность" в вашем собственном приложении.

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

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

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

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

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

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

  1. Вам не нужно использовать Hander или Timer.
  2. Вам не нужно использовать Alarm Manager.
  3. Вам не нужно использовать App Lifecycle.
  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.
    }
}

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

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

Обработка пользователя во время ожидания взаимодействия в 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
}

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

    }

}

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

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

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

Во-первых, мы создаем BaseActivity, которую могут расширять все остальные Activity.

Это код BaseActivity.

package com.example.timeout;

import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;


import javax.annotation.Nullable;

public class BaseActivity extends AppCompatActivity implements LogoutListener {

    private Boolean isUserTimedOut = false;
    private static Dialog mDialog;



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

        ((TimeOutApp) getApplication()).registerSessionListener(this);
        ((TimeOutApp) getApplication()).startUserSession();

    }

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


    }

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

        if (isUserTimedOut) {
            //show TimerOut dialog
            showTimedOutWindow("Time Out!", this);

        } else {

            ((TimeOutApp) getApplication()).onUserInteracted();

        }

    }

    @Override
    public void onSessionLogout() {


        isUserTimedOut = true;

    }


    public void showTimedOutWindow(String message, Context context) {


        if (mDialog != null) {
            mDialog.dismiss();
        }
        mDialog = new Dialog(context);


        mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        mDialog.setContentView(R.layout.dialog_window);

        mDialog.setCancelable(false);
        mDialog.setCanceledOnTouchOutside(false);

        TextView mOkButton = (TextView) mDialog.findViewById(R.id.text_ok);
        TextView text_msg = (TextView) mDialog.findViewById(R.id.text_msg);

        if (message != null && (!TextUtils.isEmpty(message)) && (!message.equalsIgnoreCase("null"))) {
            text_msg.setText(message);

        }


        mOkButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if (mDialog != null){

                    mDialog.dismiss();

                    Intent intent = new Intent(BaseActivity.this, LoginActivity.class);
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                    startActivity(intent);

                    finish();
                }


            }
        });

        if(!((Activity) context).isFinishing())
        {
            //show dialog
            mDialog.show();
        }

    }

}

Затем мы создаем интерфейс для нашего "Logout Listener".

package com.example.timeout;

public interface LogoutListener {

    void onSessionLogout();

}

Наконец, мы создаем класс Java, который расширяет "Приложение"

package com.example.timeout;

import android.app.Application;

import java.util.Timer;
import java.util.TimerTask;

public class TimeOutApp extends Application {

    private LogoutListener listener;
    private Timer timer;
    private static final long INACTIVE_TIMEOUT = 180000; // 3 min


    public void startUserSession () {
        cancelTimer ();

        timer = new Timer ();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {

                listener.onSessionLogout ();

            }
        }, INACTIVE_TIMEOUT);

    }

    private void cancelTimer () {
        if (timer !=null) timer.cancel();
    }

    public void registerSessionListener(LogoutListener listener){
        this.listener = listener;
    }

    public void onUserInteracted () {
        startUserSession();
    }


}

Примечание. Не забудьте добавить класс TimeOutApp в тег приложения внутри файла манифеста.

<application
        android:name=".TimeOutApp">
        </application>

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

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

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

Итак, основываясь на верхнем ответе и комментариях:

      public class SActivity extends AppCompatActivity {
    public static Runnable dcCallback = null;
    public static long dcTimeout = 60000;
    private static Handler dcHandler = new Handler();

    public static void resetDisconnectTimer() {
        dcHandler.removeCallbacks(dcCallback);
        dcHandler.postDelayed(dcCallback, dcTimeout);
    }

    public static void stopDisconnectTimer() {
        dcHandler.removeCallbacks(dcCallback);
    }

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

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

Вот некоторые случаи, которые, я думаю, стоит упомянуть:

  1. AlertDialog показывает иonUserInteraction()больше не работает? -> Поскольку мы сделалиresetDisconnectTimer()static, мы можем просто вызвать это в любом месте нашей деятельности (например, вам нужно добавитьaddTextChangedListenerв EditTexts вашего AlertDialog и вызовите его там)
  2. Несколько экземпляров вашего изображения отображаются после автоматической блокировки приложения? или в вашем приложении есть опция, позволяющая пользователю заблокировать приложение вручную? -> как и в предыдущем случае, вызовитеstopDisconnectTimer()перед запуском.
  3. Не забудьте спроектировать свойLockscreenActivityкаким-то образом проверяется, заблокировано ли приложение внутри или пользователь впервые пытается войти в систему; и если оно заблокировано самим приложением, переопределитеonBackPressedи закройте приложение, чтобы не столкнуться с мусорной безопасностью: P Вот пример того, как это сделать:
      // In LockscreenActitivty's onCreate:
innerLock = getIntent().getBooleanExtra("innerLock", false);

@Override
public void onBackPressed() {
    if (innerLock) finishAffinity();
    else super.onBackPressed();
}

Таким образом, в основном расширьте приведенный выше класс на все ваши действия, которые вы хотите защитить (за исключением экрана блокировки). И помните, поскольку мы сделали Runnable статическим, мы должны инициализировать его с помощью нашего самого первого расширяющегося действия (того, которое открывается после входа пользователя в систему). Это будет выглядеть примерно так:

      dcCallback = () -> {
    stopDisconnectTimer();
    startActivity(new Intent(context, LockscreenActivity.class).putExtra("innerLock", true))
};

И вы можете получить доступ и изменитьdcTimeoutнепосредственно в любой деятельности, которая простираетсяSActivity

Хотя может быть уже слишком поздно и могут быть решения получше этого, но надеюсь, что это кому-то поможет :)

Настоящий способ

Вы можете использовать этот метод, чтобы определить, как долго пользователь был неактивен (даже когда приложение находится в фоновом режиме).

  1. Создать SharedPreference& его объект Editor. Затем объявите 3 длинных варибала, например:
      mMillisUntilFinished = pref.getLong("millisUntilFinished",60*1000); // Replace with your time
long userExitedMillis = pref.getLong("userExitedMillis",0);
long timeLeft = mMillisUntilFinished - (System.currentTimeMillis() - userExitedMillis);
  1. Проходить timeLeftкак millisInFuture. Внутри таймера назначьте millisUntilFinished общедоступной переменной в каждом тике.
      new CountDownTimer(timeLeft,1000){
            @Override
            public void onTick(long millisUntilFinished) {
                Log.d("TAG", "Time left : " + millisUntilFinished/1000 + " sec");
                mMillisUntilFinished = millisUntilFinished;
            }

            @Override
            public void onFinish() {
                // Timer completed
            }
        }.start();

  1. Сохранить это mMillisUntilFinishedпеременная и текущее время в общих предпочтениях в onStop () .
      @Override
    protected void onStop() {
        super.onStop();
        editor.putLong("millisUntilFinished",mMillisUntilFinished);
        editor.putLong("userExitedMillis",System.currentTimeMillis());
        editor.apply();
    }

Совет : если вычесть userExitedMillis из System.currentTimeMillis()тогда вы получите неактивное время активности (в миллисекундах) .

переопределите метод sendTouchEvent() в своей активности
, и вы будете получать уведомления о каждом касании в вашем приложении.

      override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
    ev?.let { event ->
        if (event.action == ACTION_DOWN) {
            //start counting user's inactivity
            //or launch a coroutine with delay before screen dimming
        }
    }
    return super.dispatchTouchEvent(ev)
}

Я запустил сопрограмму с задержкой() перед неактивным действием оттуда

Решение для нескольких действий, которое можно использовать, даже если приложение находится в фоновом режиме.

Итак, я увидел, что принятый ответ поддерживает только 1 действие, а также, если пользователь сворачивает приложение, обратный вызов DC не происходит. Итак, я закончил тем, что изменил ответ gfrigon и исправил их:

      public class SActivity extends AppCompatActivity {

    private static Handler dcHandler = new Handler();
    public static Runnable dcCallback = null;
    public static long dcTimeout = 30000; // 30 seconds here for example, you can update it just by calling dcTimeout = x; in your activities that extend this class
    public static boolean internalCause;
    public static boolean dcRequestWhileInBackground;
    public static boolean activityFocusGone;

    public static void resetDisconnectTimer() {
        dcHandler.removeCallbacks(dcCallback);
        dcHandler.postDelayed(dcCallback, dcTimeout);
    }

    public static void stopDisconnectTimer() {
        dcHandler.removeCallbacks(dcCallback);
    }

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

    @Override
    protected void onPause() {
        super.onPause();
        activityFocusGone = true;
    }

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

        resetDisconnectTimer();
        activityFocusGone = false;
        if (dcRequestWhileInBackground && !internalCause) {
            dcRequestWhileInBackground = false;
            dcCallback.run();
        }
        internalCause = false;
    }
}

В основном, что вам нужно сделать, это:

  1. Расширьте все свои действия из вышеуказанного класса (кроме вашего)
  2. НастройкаdcCallbackв самом первом действии, которое открывается после (которое должно продолжатьсяSActivityкстати)
              dcCallback = () -> {
            if (activityFocusGone)
                SActivity.dcRequestWhileInBackground = true;
            startActivity(new Intent(getApplicationContext(), Lockscreen.class));
        };
  1. Поместив эти 2 строки во все остальные оставшиеся классыonCreateметод, который вы хотите защитить (чтобы избежать запуска несколькихLockscreenActivity, а также предотвращение блокировки приложения после того, как пользователь переключает действия)
      internalCause = true;
resetDisconnectTimer();

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

open class SubActivity : AppCompatActivity() {
    var myRunnable:Runnable
    private var myHandler = Handler()

    init {
        myRunnable = Runnable{
            toast("time out")
            var intent = Intent(this, MainActivity::class.java)
            startActivity(intent)

        }
    }

    fun toast(text: String) {
        runOnUiThread {
            val toast = Toast.makeText(applicationContext, text, Toast.LENGTH_SHORT)
            toast.show()
        }
    }

   override fun onUserInteraction() {
        super.onUserInteraction();
        myHandler.removeCallbacks(myRunnable)
        myHandler.postDelayed(myRunnable, 3000)
    }

    override fun onPause() {
        super.onPause()
        myHandler.removeCallbacks(myRunnable)
    }

    override fun onResume() {
            super.onResume()
            myHandler.postDelayed(myRunnable, 3000)
    }
}

Расширьте свою деятельность с помощью

YourActivity:SubActivity(){}

чтобы перейти к MainActivity, когда пользователь неактивен после 3000 миллисекунд на YourActivity

Я использовал предыдущий ответ и преобразовал его в котлин.

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

Ну вот так:

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

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

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

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

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

У меня была похожая ситуация с вопросом 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: Очистить стек активности

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

Другие вопросы по тегам