안드로이드 잠금 화면 해제 소스 - andeuloideu jamgeum hwamyeon haeje soseu

예제들을 찾아봤다.

어렵다. 무슨 말인지 모르겠다.

심지어 예제들이 잘 돌아가지 않는다.

그래서 내 스타일대로 만드려고 한다.

뭐든 되면 되니까.

내가 생각 한 것은

1. 우선 액티비티 레이아웃을 풀스크린 설정

// Activity FullScreen
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);

2. 액티비티를 최상위

getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);

3. 참고블로그에서 참고하여 액티비티를 스와이프로 끄는 동작이 가능하도록 함

4.  브로드 캐스트 리시버와 서비스로 화면이 켜질때마다 동작하도록 한다.

참고블로그

@Override

public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(Intent.ACTION_SCREEN_ON))
{
Log.e("onReceive", "SCREEN_ON");
Intent i = new Intent(context, LockActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, i, 0);
try {
pendingIntent.send();
}catch (PendingIntent.CanceledException e){
e.printStackTrace();
}
}
else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF))
{
Log.e("onReceive", "SCREEN_OFF");
}
else if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED))
{
Log.e("onReceive", "BOOT_COMPLETED");
}
}

처음에는 화면이 켜질 때 액티비티가 켜지도록 하였다. 그러니 좀 늦게 켜져서 화면이 꺼질 때 액티비티가 켜지도록 하였다. 

그런데 내 앱의 잠금화면이 어떠한 앱의 잠금화면보다 최상위에 있길 원했는데 캐시슬라이드의 잠금화면보다 아래에 있었다.

액티비티켜지는게 좀 늦지만, 최상위에 있으면 더 좋겠다고 생각해서 SCREEN_ON에서 켜지도록 하였다.

---------------------------------------------------------------------------------

++참고

SCREEN_ON 

최상위 순서 :  내가 만든 잠금화면 - 캐시슬라이드의 잠금화면 -  벅스의 잠금화면

SCREEN_OFF

최상위 순서 캐시슬라이드의 잠금화면 - 내가 만든 잠금화면 -  벅스의 잠금화면

어느 액티비티가 최상위에 올라 가는 지 확인했다.

---------------------------------------------------------------------------------

메인액티비티에선 

// 서비스 시작
Intent intent = new Intent(
getApplicationContext(),//현재제어권자
StateService.class); // 이동할 컴포넌트
startService(intent);

화면의 상태를 체크하는 서비스를 시작해줘야한다

5.  최근 앱에 안나오게 하기 위해 매니페스트에서 아래와 같이 설정

<activity

        android:label="@string/app_name"
        android:excludeFromRecents="true"
        >

출처: http://arabiannight.tistory.com/entry/안드로이드Android-최근-사용한-앱홈키-롱클릭-에서-제외-하기 [아라비안나이트]

6. 잠금화면인것처럼 보이기 위해서 투명도를 설정

참고블로그

<style name="Theme.AppCompat.Translucent">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@android:style/Animation</item>
<item name="android:background">#80000000</item>
</style>
<activity android:name=".MainActivity"
android:excludeFromRecents="true"
android:theme="@style/Theme.AppCompat.Translucent"
android:launchMode="singleTop">

++ 나는 이 잠금화면에 스와이프로 리스트뷰 아이템을 제거할 수 있는 리스트뷰를 구현할 것인데

http://itmir.tistory.com/455

http://thdev.net/369

https://github.com/romannurik/Android-SwipeToDismiss

이  URL을 참고하였다

7. 계속 동작하는 잠금화면을 만들고 싶다.

잠금화면이니 우선 핸드폰을 껐다가 키거나 앱을 켠지 오래된 상태에도 동작이 되어야하는데

되지않는다.

(참조: airpage)

onStartCommand 메서드에 startForeground 메서드 사용하기

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {

startForeground(1, new Notification());
}
return START_STICKY;
}

이걸 하더라도 강제로 죽이면 다시 살아나는데 시간이 걸리는데

public void registerRestartAlarm(boolean isOn){
Intent intent = new Intent(StateService.this, StateReceiver.class);
intent.setAction(RestartReceiver.ACTION_RESTART_SERVICE);
PendingIntent sender = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, 0);

AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
if(isOn){
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 1000, 60000, sender);
Log.e("확인", "am");
}else{
am.cancel(sender);
}
}

@Override
public void onCreate() { //서비스가 생성될 때 호출되는 콜백 메소드이며, 여기에서는 (액티비티의 onCreate()와 마찬가지로) 서비스가 살아있는 동안 사용할 멤버 변수들을 셋팅하는 일을 합니다.
super.onCreate();
registerRestartAlarm(true);
}

@Override
public void onDestroy() {
super.onDestroy();
registerRestartAlarm(false);
}

이런식으로 onCreate할 때 registerRestartAlarm에 true를 넘겨주고 onDestroy할 때 false를 넘겨주는 식으로 관리하면 

죽여도 바로 살 수 있도록 할 수 있다.

출처: 참고블로그

에러 1.

그리고 예제프로젝트를 따로 만들어서 실행을 하고

예제프로젝트에 있는 액티비티를 컨트롤씨브이로 내 실제 앱프로젝트에 파일 자체를 옮겼다.

그리고 매니페스트에도 액티비티에 대해 선언해줬다.

그런데 실행이 되지않는다.

개 삽질하다가 메인액티비티에서 인텐트로 잠금화면 액티비티를 띄었다.

[android] unable to find explicit activity class 라며

액티비티를 매니페스트에 선언해주지않았다는 에러가 떴다.

헐... 무슨 이유인지는 모르겠지만

매니페스트를 선언한것을 인식하지 못했다.

후.... 복사붙여넣기한 액티비티는 삭제하고

똑같은 파일명으로 액티비티를 생성한 후에 실행하니 정상적으로 동작..

후 이런 ..뭐같은 경우가 있나 ㅠ

에러 2.


E/FA: Failed to send current screen to service
E/FA: Discarding data. Failed to send event to service

시계때문에 쓰레드를 쓰고 있다.

액티비티가 켜있는 상황에서 화면을 껐다가 키면 쓰레드가 중복되어 실행되기때문에

onPause때 쓰레드를 정지시켜줘야한다.

나 같은 경우 AsynkTask로 쓰레드를 동작시키기 때문에

onPause에서 쓰레드를 지워주고


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

timeAsyncTask = new TimeAsyncTask();

timeAsyncTask.cancel(true); // 액티비티가 켜진 상태에서 화면을 끄면 화면이
}

onResume에서 쓰레드를 새로 생성해야한다.

@Override
protected void onResume() {
super.onResume();
if (timeAsyncTask.isCancelled()) {
timeAsyncTask = new TimeAsyncTask();
timeAsyncTask.execute();
}
}

에러 3.

The content of the adapter has changed but ListView did not receive a notification.
Make sure the content of your adapter is not modified from a background thread,
but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes.
[in ListView(2131689647, class android.widget.ListView) with Adapter(class com.blank.android.crafter.lock.ListAdapter)]

참고블로그1 이 블로거가 구글에 문의했다고 한다.

리스트뷰가 보이는 상황에서 데이터를 추가하는 경우에 발생하는 에러라고 한다.

이 블로그같은경우는 동적으로 추가삭제하는 예가 아니라서 패스~

블로그2 대로 해보자

public void addItem(String columnNo, String msgNo, String msgContent, String differNo) { // 로우번호, 메시지번호(댓글이나 쪽지번호), 메시지내용, 구분번호
item = new ListItem();

item.setColumnNo(columnNo);
item.setMsgNo(msgNo);
item.setMsgContent(msgContent);
item.setDifferNo(differNo);
// Log.e("ListAdapter", item.getColumnNo() + "," + item.getMsgNo() + "," + item.getMsgContent() + "," + item.getDifferNo());
listDataList.add(0, item); // 0은 거꾸로 데이터 넣기를 말함
notifyDataSetChanged(); // The content of the adapter has changed but ListView did not receive a notification.
// 에러로 listAdapter.notifyDataSetChanged();를 여기다가 넣었다.
}

지금 당장은 에러가 나지는 않았다.

하지만 fcm으로 동적추가하는 것이다보니 조금 더 테스트해봐야겠다.

+ 혹시 액티비티에서 fcm 데이터를 가져올 때 아래와 같은 sql문으로 select를 해와서 문제가 아닌지 의심이 간다.

"SELECT * FROM FCM_LOCK_TB ORDER BY num_no DESC limit 1

fcm 알림이 오면 잠금액티비티로 알림을 알려주고, 액티비티에서 받았을 당시 최신 로우를 조회하여 리스트뷰안에 넣는 방법인데

계속 알림이 올 때 저걸 누르면 꼬일 수 도 있겠다고 생각해서,

fcm 알림이 오자마자 위와 같은 sql문으로 최신 로우를 검색하고, 그 로우의 번호를 알아와

잠금 액티비티로 넘기어 잠금 액티비티에서 로우번호로 조회하는 방식을 택했다.

에러 4.

보르드캐스트 리시버 onResume때 브로드캐스트리시버 등록,  onPause때 브로드캐스트리시버 해제해줬다.

해제시엔 

// 리시버 해제
private void unregisterReceiver() {
if (fcmReceiver != null) {
this.unregisterReceiver(fcmReceiver);

}
}

이런 식으로 해줬는데, 나중에 시간이 지나 다시 해보니

java.lang.IllegalArgumentException: Receiver not registered

라는 에러가 뜨면 앱이 죽었다.

참고 스택오버플로우를 보니 fcmReceiver = null; 줘보라고 해서 해보니 에러 사라짐