Android에서 사용자 비활성을 감지하는 방법
사용자가 내 앱을 시작하고 로그인
합니다. 세션 시간 초과를 5 분으로 선택합니다.
앱에서 일부 작업을 수행합니다. (모두 전경)
이제 사용자는 Myapp을 백그라운드로 가져오고 다른 앱을 시작합니다.
----> 카운트 다운 타이머가 시작되고 5 분 후에
사용자가 로그 아웃하거나 사용자가 화면을 끕니다.
----> 카운트 다운 타이머가 시작되고 5 분 후에 사용자가 로그 아웃됩니다.
앱이 포 그라운드에있을 때에도 동일한 동작을 원하지만 사용자가 6 ~ 7 분 동안 앱과 상호 작용하지 않습니다. 화면이 항상 켜져 있다고 가정합니다. 사용자 비활성 (앱이 포 그라운드에 있어도 앱과 상호 작용 하지 않음) 을 감지 하고 카운트 다운 타이머를 시작하고 싶습니다.
Fredrik Wallenius의 답변을 기반으로 매우 간단하다고 생각되는 솔루션을 찾았습니다. 이것은 모든 활동에 의해 확장되어야하는 기본 활동 클래스입니다.
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
}
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 ===> 논리는 질문의 범위를 벗어나므로 여기에 표시되지 않습니다.
아래 장치 코드를 사용하여 CPU 잠금을 해제 할 수 있습니다.
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);
}
}
@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
브로드 캐스트를 넘어 OS 수준에서 "사용자 비활성"이라는 개념이 없습니다 . 자신의 애플리케이션 내에서 어떻게 든 "비 활동"을 정의해야합니다.
@gfrigon 또는 @AKh 솔루션으로 요구 사항을 관리 할 수도 있습니다 .
그러나 여기에 타이머 및 핸들러 무료 솔루션 이 있습니다. 이미 잘 관리 된 Timer 솔루션이 있습니다. 하지만 타이머 및 핸들러 무료 솔루션을 성공적으로 구현했습니다.
먼저 Timer 또는 Handlers를 사용 하는 경우 관리 해야 할 사항 을 알려드립니다 .
- 앱이 사용자 나 옵티 마이저에 의해 종료되는 경우 모든 콜백이 삭제되므로 앱이 자동으로 로그 아웃되지 않습니다. ( 일부 알람 관리자 또는 서비스를 관리 하시겠습니까? )
- 모든 기본 클래스에 타이머가있는 것이 좋은가요? 로그 아웃 프로세스를 호출하기 위해 많은 스레드를 만들고 있습니다 ( 앱 수준에서 정적 핸들러 또는 타이머 관리? ).
- 사용자가 백그라운드에있는 경우 사용자가 앱 외부에서 다른 작업을 수행하는 경우 핸들러가 로그인 활동을 시작합니다. ( 앱 전경 또는 배경을 관리 하시겠습니까? ).
- 화면이 자동으로 꺼지면 어떨까요? ( 브로드 캐스트 수신기에서 화면 끄기를 관리 하시겠습니까? )
마지막으로 나는 솔루션을 구현했습니다.
- 핸들러 또는 타이머가 없습니다.
- 알람 관리자가 없습니다.
- App LifeCycle을 관리하지 않습니다.
- 아니오
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.
}
}
내 활동 기본 클래스에서 보호 된 클래스를 만들었습니다.
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 초 동안 사용자 활동이 없으면 작업이 완료됩니다.
사용자 비활성은 onUserInteraction()
Android에서 재정의 방법을 사용하여 감지 할 수 있습니다.
@Override
public void onUserInteraction() {
super.onUserInteraction();
}
다음은 사용자가 비활성 상태 일 때 3 분 후 로그 아웃 (HomeActivity-> LoginActivity) 하는 샘플 코드입니다.
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");
}
}
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
}
타이머와 마지막 활동 시간을 결합해야한다고 생각합니다.
그래서 이렇게 :
onCreate (Bundle savedInstanceState)에서 타이머를 시작합니다 (예 : 5 분).
onUserInteraction ()에서는 현재 시간 만 저장합니다.
지금까지는 매우 간단합니다.
이제 타이머 팝이 다음과 같이 할 때 :
- 현재 시간에서 저장된 상호 작용 시간을 빼서 timeDelta를 얻습니다.
- timeDelta가 5 분 이상이면 완료된 것입니다.
- timeDelta가 5 분 미만이면 타이머를 다시 시작하지만 이번에는 저장된 시간 인 5 분을 사용합니다. 즉, 마지막 상호 작용에서 5 분
나는 1 분 동안 사용자 비활성을 추적 한 다음 사용자를 활동을 시작하도록 리디렉션해야하는 SO 질문과 비슷한 상황을 가졌고 활동 스택도 정리해야했습니다.
@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();
}
}
보완 자료
이 핸들러 클래스는 정적이어야합니다. 그렇지 않으면 누수가 발생할 수 있습니다.
가장 좋은 점은 AppLifecycleCallbacks
Application calss 에 등록하여 전체 앱에서이 문제를 처리하는 것입니다 (여러 활동이 있다고 가정) . registerActivityLifecycleCallbacks()
다음 콜백과 함께 Application 클래스에서 사용할 수 있습니다 (ActivityLifecycleCallbacks를 확장하는 AppLifecycleCallbacks 클래스를 만드는 것이 좋습니다).
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);
}
다음은 몇 분 (예 : 3 분) 후 사용자 비활성을 처리하는 완벽한 솔루션입니다. 이렇게하면 시간 초과시 앱이 백그라운드에있을 때 활동이 전경으로 점프하는 것과 같은 일반적인 문제가 해결됩니다.
첫째, 다른 모든 활동이 확장 할 수있는 BaseActivity를 만듭니다.
이것은 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();
}
}
}
다음으로 "로그 아웃 리스너"를위한 인터페이스를 생성합니다.
package com.example.timeout;
public interface LogoutListener {
void onSessionLogout();
}
마지막으로 "Application"을 확장하는 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>
참고 URL : https://stackoverflow.com/questions/4208730/how-to-detect-user-inactivity-in-android
'Programing' 카테고리의 다른 글
매크로가 아직 정의되지 않은 경우에만 정의하는 이유는 무엇입니까? (0) | 2020.08.31 |
---|---|
angular2에서 타이머를 만드는 방법 (0) | 2020.08.31 |
프록시 뒤의 NuGet (0) | 2020.08.31 |
오늘의 타임 스탬프가있는 행을 선택하는 방법은 무엇입니까? (0) | 2020.08.31 |
모의 프레임 워크 대 MS Fakes 프레임 워크 (0) | 2020.08.31 |