하드디스크 무료 소프트웨어 복구 방법


2012에 구매한 하디드스크를 쓰고 있다가 최근에 큰 봉변을 당했다.
DropBox GoogleDrive auto-sync를 켜둔 덕분에 간당 간당 HDD가 결국 인식불능에 도달 했다.

HDD는 2년이상이되면 급속도로 배드섹터가 많이 생기고 신뢰성이 떨어지는 장비인데 이것을 6년 가까이 사용했으니 당연한 일이다.

석박사기간 통틀어 6년간의 연구데이터가 몽땅 날라가 버리니 정말 후회가 몰려왔다. 가장 최근에 백업한 데이터가 2017.2월이니 난감한 상황이다.

어떻게든 공짜로 해결하려고 사투한 2주간의 기록을 남긴다.

  • 하드 인식이 안되면 외장 도커를 이용해서 연결
  • 절대로 포맷하지 않음
  • 문제가 생기면 Write는 무조건 중지

주의: 정말 소중한 데이터라면 전문가에게 돈을 주고 의뢰 하는게 맞습니다. 이 방법대로 했다가 데이터 날라가는것은 저는 책임지지 않습니다.

배드섹터 복구

아래 프로그램을 사용해서 복구 했다.
DRevitalize+2.42 (한글 버전)

DRevitalize+2.42.egg

연구실 후배가 설치해서 돌려준 프로그램이다.
실제 공식 홈페이지에가면 영문으로 더 상위 버전이 있긴하다.

아래와 같이 2TB 가량의 하드에 있는 배트섹터를 복구 했다.

실제론 약 3만개가 있었고 2주간 걸렸다.
배트섹터 없는 구간은 빠르지만 BAD Sector 구간에 오면 10KB이하로 속도가 급격히 떨어져서 시간이 엄청나게 오래 걸린다.

하지만 확실히 모두 고치고나면 인식이 된다.

파티션 복구

파티션까지 없어진 상황이면 이것도 복구해야 한다. 필자는 그랬다.

검색하면 도구가 많이 나온다. 하지만 대부분 Scan완료후 복구를 시도하면 10GB이상은 Pro구매 하라는 팝업창만 나온다.

정말 사람 답답한데 이런거 가지고 과금청구하는 악덕 프로그래머들에게 화가날 뿐이다.

검색 끝에 TestDisk라는 GPL라이센스를 따르는 도구를 찾았다.
역사가 오래된만큼 Windows, , uBuntu등의 다양한 OS를 지원 했다.

실제로도 상용툴에서 보여준 성능을 그대로 보여주었다.

TestDisk Dwonload
TestDisk 사용 공식 설명서

필자는 7.1 버전을 윈도우즈10 에서 사용하요 성공적으로 파티션을 복구 했다.

성공 로그
재부팅하면 파티션이 정상 복구되어 있다.

  • Partition Find and Mount
    • 무료지만 Full scan으로 검색하면 partition이 나오긴 하지만 Fragement되어서 나와서 신뢰가 안간다.


멀티 부팅 윈도우즈 설치 후 우분투 설치 (Install uBuntu alongside Windows10)


Legacy 설치 기준

  • 윈도우즈10과 Ubuntu LTS 16.04 기준
  • UEFI가 둘다 아니다.
  • legacy로 둘다 설치
  • 윈도우즈 10부터 먼저 설치한다.

BIOS 설정

  • Fast Boot를 disable
  • Secure Boot를 disable

윈도우즈 10을 Legacy BIOS에서 설치

쉽게 설치 가능

기존 파티선 축소 (윈도우즈10)

디스크 관리자에서 기존 운영체제가 설치된 파티션을 축소한다.
그래서 새 볼륨을 생성한다.

파티션 설정

우분투 설치 창에서 기타를 선택한다.
자동 옵션들은 잘 동작하지 않는다.

SWAP 파티션 생성

메모리 넉넉하면 생성안해도 된다고 한다.
그래도 일단 생성 했다.

아까 쭐인 남은공간을 선택해서 아래와 같이 설정해서 SWAP영역을 생성한다.
메모리 8GB여서 4GB 정도만 주었다.

루트 파티션 생성

이제 남은 모든 공간을 아래와 같이 루트 파티션으로 생성해 준다.

부트로더 설치경로 설정

아래와 같이 물리적 저장 장치들이 해당 이름으로 연결 되어 있다고 하면,

  • sda가 HDD
  • sdb가 SDD

기본적으로 부트로더를 설치할 장치가 보통 /dev/sda로 잡힌다.
sdb에 즉 SSD에 운영체제가 있다면 반드시 변경해서 /dev/sdb에 설치해야한다.

그렇지 않으면 부팅시에 GRUB 부트로더가 나오지 않는다.

정상적으로 설치되면 아래와 같이 GRUB으로 선택 화면이 나오게 된다.

요점은 windows boot loader가 설치된 곳에 ubuntu의 GRUB을 설치해야 멀티 부팅 화면이 제대로 나오게 된다. 추후 UEFI 모드가 활성화된 방법에서는 EFI 파티션이 보이므로 좀 더 쉽게 이 부분을 찾을 수 있다.

참고자료

UEFI 설치 기준 (Widnows10, ubuntu 둘다)

legacy 방법과 기본적으로 파티션 설정은 같다.
우분투설치시 기타선택 후에 아래의 스크린샷과 같이 GRUB이 설치될 위치를 윈도우즈 부트 매니저가 있는 EFI 파티션으로 선택해 준다.

partition

위 스샷에서 부트로더 설치할 장치는 EFI가 있는 nvme0n1p1이 된다. 이 부분을 잘 못 선택하면 EFI 경고가 뜨고 부팅이 제대로 이뤄지지 않을 수 있음을 알려 준다.

계속을 누르면 아래와 같이 설치가 진행 되고
설치

완료되어 재부팅하면 아래와 같이 나온다.
Ubuntu를 선택하면 우분투로 들어가며, Windows Boot Manager를 선택하면 윈도우즈로 접속 된다.
Grub

참고자료


몬슨 파워모니터 (Monsoon Power Monitor)를 이용한 일체형 배터리 타입의 스마트폰 전력 소모 측정


이전 포스트에서 Monsoon Power Monitor를 이용해서 전력 소모를 측정하는 법을 다뤘다.

요즘 Mobile Device는 대부분 non-removable 배터리를 채택 하고 있기 때문에 이러한 장치들에 대해서 어떻게 측정하는지를 이번 포스트에서 다룬다.

보통 논문을 보면 사진을 작게 집어 넣어서 사실 잘 안보인다.

그래서 직접 크게 사진을 찍어서 아래와 같이 첨부한다.

전극 기판 추출

일단 스마트폰을 teardown 해야 한다.
아래 사진은 nexus 5를 한것이다.
Google에 teardown 치면 왠만한 기기들은 다 있다. 보면서 공구 사서 따라하면 된다.

그 다음 battery를 추출한 다음 아래와 같이 기판을 분리한다.

다시 충전 하지 않으면 보통 배터리가 터지진 않으니 너무 겁먹지 말고 기판을 칼로 잘라내면 된다.

자르기 쉽게 고분자로 구성된 이음 부분이 있다. 그 부만 제거하면 쉽게 분리된다.

몬슨과 연결

이제 해당 기판을 다시 스마트폰에 연결하고
양극 음극에 맞춰서 몬슨 VCC Ground를 해준다.

그럼 정상적으로 스마트폰이 켜지고 전력 소모가 측정된다.

어떤 스마트폰이나 스마트시계와 같은 웨러블 장치는 기판에 보호 회로가 있어서 무한대 저항이 걸려서 cutoff 될 수도 있다. 그럴 때는 납땜을 해서 연결해야 한다.

사실 이러한 저전력 연구는 제조사에서 하면 별것도 아닌데 이런 off the shelf 제품을 가지고 직접 하려고하면 완제품이다보니 이래저래 잔손이 많이 간다.


Docker를 ubuntu 16.04 LTS에 설치하기


https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-using-the-convenience-script

가장 쉽게 설치 할수 있는 방법은 convenience script를 이용하는 것이다.
get.docker.com test.docker.com에서 각각 stable 버전과 testing 버전을 Docker Comunity Edition으로 받을 수 있다.
이러한 자동 스크립트 방식은 하나씩 설치하는 것이 비해서 간편하지만 아래와 같은 불이익이 있음을 공식 홈페이지에서 언급한다.

  • The scripts require root or sudo privileges in order to run. Therefore, you should carefully examine and audit the scripts before running them.
  • The scripts attempt to detect your Linux distribution and version and configure your package management system for you. In addition, the scripts do not allow you to customize any installation parameters. This may lead to an unsupported configuration, either from Docker’s point of view or from your own organization’s guidelines and standards.
  • The scripts install all dependencies and recommendations of the package manager without asking for confirmation. This may install a large number of packages, depending on the current configuration of your host machine.
  • Do not use the convenience script if Docker has already been installed on the host machine using another mechanism.

스크립트 실행

jaynux@jaynux-desktop:~$ curl -fsSL get.docker.com -o get-docker.sh
jaynux@jaynux-desktop:~$ sudo sh get-docker.sh

설치됨

# Executing docker install script, commit: 11aa13e
+ sh -c apt-get update -qq >/dev/null
+ sh -c apt-get install -y -qq apt-transport-https ca-certificates curl software-properties-common >/dev/null
+ sh -c curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" | apt-key add -qq - >/dev/null
+ sh -c echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial edge" > /etc/apt/sources.list.d/docker.list
+ [ ubuntu = debian ]
+ sh -c apt-get update -qq >/dev/null
+ sh -c apt-get install -y -qq --no-install-recommends docker-ce >/dev/null
+ sh -c docker version
Client:
 Version:      17.11.0-ce
 API version:  1.34
 Go version:   go1.8.3
 Git commit:   1caf76c
 Built:        Mon Nov 20 18:37:39 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.11.0-ce
 API version:  1.34 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   1caf76c
 Built:        Mon Nov 20 18:36:09 2017
 OS/Arch:      linux/amd64
 Experimental: false
If you would like to use Docker as a non-root user, you should now consider
adding your user to the "docker" group with something like:

  sudo usermod -aG docker your-user

Remember that you will have to log out and back in for this to take effect!

WARNING: Adding a user to the "docker" group will grant the ability to run
         containers which can be used to obtain root privileges on the
         docker host.
         Refer to https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface
         for more information.

참고문헌

초보를 위한 도커 안내서 -설치하고 컨테이너 실행하기


NotificationListenerService 이상 종료 문제 및 디버깅 방법


Debugging 방법

notification access를 끈다음 debug rerun을 하고 다시 enable하면 잘된다.

원글: https://stackoverflow.com/questions/37774499/cant-use-debugger-with-notificationlistenerservice

NotificationListenerService가 호출되지 않는 문제

Android caching 문제라고 한다.
즉, 장치에 app을 업로드 할 때 OS는 해당 서비스를 notification manager에 connection하고 있으므로 다시 앱을 재실행 한다고해서 그 서비시를 재 연결하지 않는다. 따라서 그 후로 이상하게 onPosted나 onRemoved 같은 callback 함수가 호출이 안되는 것이다.

해결 방법: 서비스 이름을 변경 한다. 앱을 pushing하기 전에

onListenerConnected 해당 API를 이용하면 해당 Listener가 연결 되었는지를 알 수 있다.

원글: https://stackoverflow.com/questions/33530807/why-is-this-notificationlistenerservice-not-working

Cannot get the NotificationListenerService class to work

If your APK/binary changes and your NotificationListenerService stops:

  • Rebooting fixes it.
  • Going back in to Notification access and disabling and re-enabling your app it fixes it.

원글: https://stackoverflow.com/questions/17911883/cannot-get-the-notificationlistenerservice-class-to-work

Ask the user to enable the setting (Notification Access) if needed

if (!Utilities.hasNotificationAccess(this)) 
{
     Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
     startActivity(intent);
     Log.i(TAG,"hasNotificationAccess NO");
}
else
{
    Log.i(TAG,"hasNotificationAccess YES");

    // without this startService, it never works with Android 4.4...
    // but this is not needed in Android 6... 
    Intent mServiceIntent = new Intent(this, NLService.class);
    startService(mServiceIntent);
}


Firebase 설정 방법


홈페이지에 워낙 잘 나와있지만 그냥 정리 차원에서 다뤄 본다.

앱등록

debug key 값 알아내기

  • Gradle-> Tasks-> android -> signingReport -> SHA1 key 값

그 다음 아래와 같이 등록한다.

구성 파일 다운로드

  • google-services.json 다운로드 한다음 프로젝트에다가 넣어준다.

주의할 것은 app폴더안에다가 넣어야 한다는 것이다. build가 아니다. 스크린샷을 보고 잘 해야 한다.

Firebase SDK 추가

Gradle Scripts의 첫 번째 build.gradle dependencies라는 항목안에다가 classpath 추가한다.

  • 'com.google.gms:google-services:3.1.0'의 내용을 추가 한다.

두 번째 build.gradle에서는 맨 하단에다가 apply plugin: 'com.google.gms.google-services'을 추가한다.

사용 가능한 라이브러리 추가

원하는 Firebase 기능 구현에 따라 아래의 라이브러리를 gradle에 추가한다.

  • com.google.firebase:firebase-core:11.0.4 애널리틱스
  • com.google.firebase:firebase-database:11.0.4 실시간 데이터베이스
  • com.google.firebase:firebase-storage:11.0.4 저장소
  • com.google.firebase:firebase-crash:11.0.4 오류 보고
  • com.google.firebase:firebase-auth:11.0.4 인증
  • com.google.firebase:firebase-messaging:11.0.4 클라우드 메시징
  • com.google.firebase:firebase-config:11.0.4 원격 구성
  • com.google.firebase:firebase-invites:11.0.4 초대 및 동적 링크
  • com.google.firebase:firebase-ads:11.0.4 AdMob
  • com.google.firebase:firebase-appindexing:11.0.4 앱 색인 생성
  • com.google.firebase:firebase-perf:11.0.4 성능 모니터링


The device 'SAMSUNG_Android' was unable to connect to its ideal host controller.

An attempt will be made to connect this device to the available host controller. This might result in undefined behavior for this device.


그냥 USB 2.0에해서 해결 했다.


https://communities.vmware.com/thread/446744

APK 바이너리 수정후 리패키징(repack)


APK 추출 방법

사용자 앱의 apk 저장 위치는 /data/packageName이다.
system 앱의 경우 apk 저장위치는 /system이다.
각종 앱 데이터 파일들은 (Database, so 파일)
/data/data/<appname>에 존재 한다.

이런 정보들은 기본적으로 adb shell pm 명령어를 통해서도 알 수 있다.

root@jemin-virtual-machine:~/AndroidStudioProjects# adb -d shell pm list packages -f -e com.mobisystems.android.notifications
package:/data/app/com.mobisystems.android.notifications-1/base.apk=com.mobisystems.android.notifications
# -f // see their associated file.
# -e // filter to only show enabled packages.

Resource XML 파일 분석 및 smali/baksmali 수준 분석

java 코드 생성과는 상관 없다.

APKTool

단순히 unzip을 할경우 .xml 파일과 같은 것들이 정상적으로 그 값을 표시하지 못하고 있다.
따라서 apktool를 이용해서 압축을 해제해야 한다.
apk를 디컴파일해서 구조를 살펴 볼 수 있도록 로우 레벨에서 도움을 준다. pirate 목적으로는 쓰지말라고 한다.

공식사이트: http://ibotpeaches.github.io/Apktool/

설치방법
1.5.2 이상의 apktool를 사용하기 위해서는 Java 1.7 이상이 필요하다.
java -version 을 실행해서 자신의 PC에 자바가 설치되어 있는지 확인 하자.

상세한 설치방법은 공식 사이트 참조
현재 사용한 버전은 Apktool v2.0.2이다.
2015.10.12일에 release된 것이다.
jar 파일 다운받고 -> script 다운받고 -> apk d test.apk하면 depack 된다.

Linux:

  • Download Linux wrapper script (Right click, Save Link As apktool)
  • Download apktool-2 (find newest here)
  • Make sure you have the 32bit libraries (ia32-libs) downloaded and installed by your linux package manager, if you are on a 64bit unix system.
  • (This helps provide support for the 32bit native binary aapt, which is required by apktool)
  • Rename downloaded jar to apktool.jar
  • Move both files (apktool.jar & apktool) to /usr/local/bin (root needed)
  • Make sure both files are executable (chmod +x)
  • Try running apktool via click

사용방법

$ apktool d testapp.apk
I: Using Apktool 2.0.0 on testapp.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: 1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
$

APK Studio

Apkstudio: http://www.vaibhavpandey.com/apkstudio/
GUI로 구성된 것이다.
smali code 할 때 하이라이팅이 되어서 편하다.
depackaging / re-packaging 모두 버튼으로 가능 하다.

APK Manager

ApkManager : http://forum.xda-developers.com/showthread.php?t=1310151
여러 기능이 있다.

Dalvik Executable (.dex) 파일을 jar 파일로 변경

관련 사이트
github 공식사이트
bitbucket:
사용법
wiki

UserGuide

  1. Install JDK7
// For Ubuntu 
sudo apt-get install openjdk-7-jre
  1. Download dex2jar
    2015.03.16일자로 dex2jar-2.0.zip이 최신 버전이다.

  2. 압축을 풀어서 실행 스크립트 파일을 생성한다.

# For Linux
unzip -x dex2jar-version.zip -d /home/panxiaobo
  1. dex2jar를 이용해서 xx.apk파일을 xx-dex2jar.jar로 변환 한다.
  • bat은 윈도우
  • sh는 리눅스
# For Linux, Mac OSX, Cygwin
# 경로 /root/repackToolBox/reversingTools/dex2jar-2.0
d2j-dex2jar.sh xx.apk
  1. use a decompiler to view the source.
    아래 세개중 하나의 decopiler를 이용해서 source code를 볼 수 있다. 필자는 2012년 부터 개발된 jd-gui를 사용한다.
    jd-gui: http://jd.benow.ca/
    JAD: http://varaneckas.com/jad/
    Procyon: https://bitbucket.org/mstrobel/procyon

wiki 정리 (FAQ)
이것은 decomplier 인가?
-> 이것은 단순히 .dex format을 또다른 format인 .class format으로 변경 해주는 것이다. 이 또한 하나의 binary format 이다. 이것은 source code가 아니다.

JD-GUI 사용해서 소스코드 보기

jar파일을 decompile해서 java source code 레벨로 보여준다.
smali code를 바로 수정하기 어렵기 때문에 이것을 이용해서 해당 코드를 찾는다.

java용 GUI 도구를 설치하자.
eclipse와 InteliJ용도 있다.

File>Save All Sources를 선택하면 소스코드로 저장 할 수 있다.
이것과 이전에 Apktool로 unpack한것을 같이 묶어서 Eclipse로 분석하면, 좀 더 편하게 작업할 수 있다.

Building (APK Repack)

수정한 APK를 다시 repack 하는 방법이다.

# The build option can be invoked either from b or build like shown below
# builds a directory into new.apk
$ apktool b directory_name -o new.apk

모든 build 관련 명령어

$ apktool b foo.jar.out
// builds foo.jar.out folder into foo.jar.out/dist/foo.jar file

$ apktool build foo.jar.out
// builds foo.jar.out folder into foo.jar.out/dist/foo.jar file

$ apktool b bar
// builds bar folder into bar/dist/bar.apk file

$ apktool b .
// builds current directory into ./dist

$ apktool b bar -o new_bar.apk
// builds bar folder into new_bar.apk

$ apktool b bar.apk
// WRONG: brut.androlib.AndrolibException: brut.directory.PathNotExist: apktool.yml
// Must use folder, not apk/jar file

Signing Your App Manullay without Android Studio

SDK와 JDK에 있는 standard tool을 이용해서 apk를 signing 할 수도 있다.

1. Generate a private key using keytool

$ keytool -genkey -v -keystore my-release-key.keystore
-alias alias_name -keyalg RSA -keysize 2048 -validity 10000

2. Compile your app in release mode to obtain an unsigned APK

3. Sign your app with your private key using jarsigner
아래의 명령어를 이용할경우 하나의 APK에 대해서 서로다른 sign으로 여러번 서명을 할 수 있게 된다.

$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1
-keystore my-release-key.keystore my_application.apk alias_name

4. Verify that your APK is signed
아래의 명령어를 통해서 해당 App이 정상적으로 해당 Key로 sign 되었는지를 알 수 있다.

$ jarsigner -verify -verbose -certs my_application.apk

저장 위치: \jdk\bin

5. Align the final APK package using zipalign
zipalign을 이용해서 모든 비압축 데이터는 특정한 bye alignment로 시작하게 된다. 이를 통해서 해당 앱에 의해서 사용되는 RAM의 양을 줄일 수 있다.

$ zipalign -v 4 your_project_name-unaligned.apk your_project_name.apk

추후 확인 사이트

http://www.cs.bham.ac.uk/~axm514/NotifyMe/#

악성코드 정적분석 네이버블로그: http://suspected.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%95%85%EC%84%B1%EC%BD%94%EB%93%9C%EC%A0%95%EC%A0%81-%EB%B6%84%EC%84%9D-%EB%B0%A9%EB%B2%95

APK Studio 관련: http://secuinfo.tistory.com/entry/Android-Smali

Smali: https://github.com/JesusFreke/smali
http://0x616b616d61.tistory.com/21
http://strawberryit.tistory.com/142
http://secuinfo.tistory.com/entry/Android-Smali


'Computer Science > Reverse Engineering' 카테고리의 다른 글

smali/baksmali 수준의 코드 수정  (3) 2015.12.16

Ch 09 Recursion and Dynamic Programming


재귀적 방법

  • 상향식과 하향식으로 나눠 진다.

동적 프로그래밍의 예: 피보나치 수
중간과정을 저장하면 그게 동적 프로그래밍이다.

$n^2$ 복잡도의 일반 코드

int fibonacci(int i){
	if(i==0) return 0;
	if(i==1) return 1;
	return fibonacci(i-1) + fibonacci(i-2);
}

Time complexity를 $O(n)$으로 줄이는 코드

int[] fib = new int[max];
int fibonacci(int i){
	if (i==0) return 0;
	if (i==1) return 1;
	if (fib[i] != 0) return fib[i]; // 캐시된 결과 반환
	fib[i] = fibonacci(i-1) + fibonacci(i-2); // 계산 결과 캐시
	return fib[i];
}

동적 프로그래밍을 구현 할 때의 전략은 다음과 같다.

  • 재귀함수를 먼저 구현
  • 캐쉬 부분을 구현


'Computer Science > Coding Interview' 카테고리의 다른 글

Ch02 LinkedList  (0) 2017.08.11
Ch01 Array and Strings  (0) 2017.08.10
Sorting  (0) 2017.08.06
Graph Search  (0) 2017.08.06
String  (0) 2017.07.31

Handler를 이용한 시간제한 기능 구현


BLE Scanning 주기 조절

private static final long SCAN_PERIOD = 1000

private void scanLeDevice(final boolean enable){
	if(enable){
		mHandler.postDelyaed(new Runnable(){
			@Override
			public void run(){
				mScanning = false;
				mBluetoothAdapter.stopLeScan(mLeScanCallback);				
			}
		}, SCAN_PERIOD);
	mScanning = true;
}

백키 두번 이상 연속해서 누를 때만 액티비티 종료

private boolean isBackPressedOnce = false;
private static final long BACKKEY_DELAY = 5000;

@Override
public void onBackPressed(){
	if (isBackPressedOnce){
		super.onBackPressed(); // 종료한다.
	} else {
		Toast.makeText(this, R.string.backpressed_message, Toast.LENGTH_SHORT).show();
		isBackPressedOnce = true; // 한 번 백키 누른것을 저장한다.
		mHandler.postDelayed(timerTask, BACKKEY_DELAY); // 5초후 작업을 지정한다.
	}
}

// 5초가 지나면 한 번 누른 백키를 초기화 한다.
private final Runnable timerTask = new Runnable(){
	@Override
	public void run(){
		isBackPressedOnce = false;
	}

}


'Computer Science > Android Application' 카테고리의 다른 글

NotificationListenerService 이상 종료 문제 및 디버깅 방법  (0) 2017.10.31
Firebase 설정 방법  (0) 2017.10.31
AIDL과 Remote Service  (1) 2017.08.22
NDK 사용  (0) 2017.08.20
Preference  (0) 2017.08.20

Java-Builder Pattern


  • Telescoping Pattern은 생성자 overloading을 말하며 인수가 여러개면 가독서이 떨어진다.
  • JavaBeans Pattern은 getter, sgetter 메소드를 이용하는 방법을 말한다. 하지만 중간에 객체 상태가 노출되고 초기화 과정에서 객체를 참조하면 문제가 된다. 또한 언제든지 객체가 변경 될 수 있기 때문에 immutable하지 않다.

이러한 객체 초기화 문제점들을 Builder Pattern으로 해결한다.

public class BuilderPattern { 
    private int a; 
    private int b; 
    private int c; 
    private int d; 
     
    public static class Builder{ 
        private int a; 
        private int b; 
        private int c; 
        private int d; 
         
        public Builder a(int a){ 
            this.a = a; 
            return this; 
        } 
        public Builder b(int b){ 
            this.b = b; 
            return this; 
        } 
        public Builder c(int c){ 
            this.c = c; 
            return this; 
        } 
        public Builder d(int d){ 
            this.d = d; 
            return this; 
        } 
        public BuilderPattern build(){ 
            return new BuilderPattern(this); 
        } 
    } 
    private BuilderPattern(Builder builder){ 
        a = builder.a; 
        b = builder.b; 
        c = builder.c; 
        d = builder.d; 
    } 
}

BuilderPattern builderPattern = new BuilderPattern.Builder().a(1).b(2).c(3).d(4).build();


출처

http://cleancodes.tistory.com/15


'Computer Science > Design Pattern' 카테고리의 다른 글

Java-Singleton Pattern  (0) 2017.08.22

Java-Singleton Pattern


  1. 생성자를 private으로 만든다. new를 막기 위함이다.
  2. private static final 변수를 내부에서 초기화
public class Singleton{
	private static final Singleton instance = new Singleton();
	private Singleton(){ }
	public static Singleton getInstance(){ return instance; }
}

Lazy Initialization: 필요할 때 객체를 생성해서 자원 낭비를 막는다.

public class Singleton{
	private static Singleton instance;
	private Singleton{}{ }
	
	public synchronized static Singleton getInstance(){
		if(instance == null){
			instance = new Singleton();
		}
		return instance;
	}
}

이 경우 getInstance 메소드 호출할 때 마다 동기화를 수행하기 때문에 성능 저하가 심하다.

DCL (Double-Checking Locking)방법

  • 인스턴스가 null인지 확인 한 후, null이면 동기화를 얻고 객체를 생성한다. 그리고 그 다음부터는 동기화를 하지 않아도 된다.
public class Singleton{
	private volatitle static Singleton instance;
	
	private Singleton(){}

	public static Singleton getInstance(){
		if(instance == null){
			synchronized(Singleton.class){
				if(instance == null){
					instance = new Singleton();
				}
			}
		}
	}
}

참고자료

http://cleancodes.tistory.com/14


'Computer Science > Design Pattern' 카테고리의 다른 글

Java-Builder Pattern  (0) 2017.08.22

AIDL과 Remote Service


AIDL(Android Interface Definition Language)은 전에 다뤄본 다른 IDL과 유사합니다. 클라이언트와 서비스가 프로세스간 통신(IPC)을 사용하여 서로 소통하는 데 동의한 프로그래밍 인터페이스를 정의할 수 있습니다. Android에서는 한 프로세스가 다른 프로세스의 메모리에 정상적으로 액세스할 수 없습니다. 따라서 객체들을 운영 체제가 이해할 수 있는 원시 유형으로 해체하고 해당 경계에 걸쳐 마샬링해야 합니다. 이 마샬링을 위해 코드를 작성하는 일은 상당히 지루한 작업인데, Android는 AIDL을 이용해 그 일을 대신 해줍니다.

마샬링: 한 객체의 메모리에서의 표현 방식을 저장 또는 전송에 적합한 다른 데이터 형식으로 변환하는 과정이다. 마샬링은 직렬화와 유사하며 한 오브젝트 여기서는 직렬화 된 오브젝트로 멀리 떨어진 오브젝트와 통신하기 위해 사용된다.

AIDL 인터페이스 정의

AIDL을 사용하여 바인드된 서비스를 생성하려면 다음 단계를 따라야 한다.

  1. aidl 파일 생성

    • 이 파일은 메서드 서명으로 프로그래밍 인터페이스를 정의한다.
  2. 인터페이스 구현

    • Android SDK 도구는 .aidl파일을 기반으로 Java 프로그래밍 언어로 인터페이스를 생성한다. 이 인터페이스는Binder를 확장하고 AIDL 인터페이스로부터 메서드를 구현하는 Stub라는 내부 추상 클래스를 가지고 있다. Stub클래스를 확장하고 메서드를 구현해야 한다.
  3. 클라이언트에게 인터페이스 노출

    • Service를 구현하고 onBind()를 재정의하여 Stub 클래스의 구현을 반환한다.

리모트 바인딩

  • 리모트 바인딩 서비스는 다른 프로세스에서 접근하는 것을 전제로 만들어진다. 따라서 로컬에서만 사용하는 서비스라면 리모트 바인딩 서비스를 굳이 만들 필요가 없다.

생성방법

  • 바인딩한 클라이언트에 제공하는 메서드를 aidl 인터페이스로 작성한 다음에 서비스에서 stub클래스의 추상 메서드를 구현해 주면된다.

aidl 인터페이스와 생성 클래스

package com.cnu.eslab.suite;

interface ITrainingService{
	boolean setServiceMode(String filename);
}

이렇게하면 Android Studio에서는 자동으로build/generated/source/aidl디렉터리에IRemoteService.java가 생성된다.

Service에 Stub 구현

  • Service에서는 추상 클래스인 Stub 구현체를 만든다.
public class RemoteService extends Service{
    @Override
    public IBinder onBind(Intent intent) {
            return mBinder;
    }
	
	ITrainingService.Stub mBinder = new ITrainingService.Stub() {
		
		@Override
		public boolean setServiceMode(String filename) throws RemoteException {
			// TODO Auto-generated method stub
			ResultFileName = filename;
			return true;
		}
	};

위와 같이 Stub의 내부 기능을 구현하면 된다.

클라이언트에서 서비스 바인딩

Activity에서 바인딩해서 사용해야 한다.
bindService()는 바인딩 결과를 비동기로 받기 때문에, 콜백으로 사용할 Service Connection인스턴스를 bindService()메서드에 파라미터로 전달한다.

리모트 서비스 바인딩

ITrainingService counterService;

@Override
pulbic void onCreate(Bundle savedInstanceState){
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
	
	conn = new TrainingConnection();
}

private class TrainingConnection implements ServiceConnection {

	public void onServiceConnected(ComponentName className, 
                                   IBinder boundService) {
      counterService = ITrainingService.Stub.asInterface((IBinder)boundService);
    }
	
    //bind service가 연결 해지 됬을 때 실행 된다.
    public void onServiceDisconnected(ComponentName className) {
      counterService = null;
    }
  } 
    @Override
	protected void onResume() {
		// TODO Auto-generated method stub
    	//restorePrefs();
	   	super.onResume();
	   	lock.disableKeyguard();
	   	bindService(serviceIntent, conn, 0);
	}
  • connectionCall: ServiceConnection을 생성한다.
  • Stub.asInterface() 메서드를 통해서 로컬인 경우는 Stub 인스턴스, 리모트인 경우 Proxy 인스턴스가 mIRemoteService에 대입된다.
  • 연결이 끊길 때는 mIRemoteService를 null로 만든다.
  • bindService()에 ServiceConnection을 전달한다.
  • mIRemoteService의 메서드를 호출할 때는 먼저 null인지 체크한다.


'Computer Science > Android Application' 카테고리의 다른 글

Firebase 설정 방법  (0) 2017.10.31
Handler를 이용한 시간제한 기능 구현  (0) 2017.08.22
NDK 사용  (0) 2017.08.20
Preference  (0) 2017.08.20
Broadcast Receiver  (0) 2017.08.16

NDK 사용


NDK를 이용해서 간단한 예제를 개발해 본다.

  • NDK (Native Developement Kit): C/C++로 안드로이드 앱을 개발할 수 있도록 도와주는 도구이다.

  • JNI (Java Native Interface): Java와 Native 언어들 간에 서로 호출하고 호출될 수 있도록 인터페이스를 제공한다.

1. NDK 설치

  • SDK Manager를 통해서 3가지를 설치한다.

    • Android NDK (Native Development Kit): Android에서 C 및 C++ 코드를 사용할 수 있도록 지원하고 네이티브 액티비티를 관리하고 물리적 구성 요소 (예: 센서 및 터치 입력)에 엑세스할 수 있는 플랫폼 라이브러리를 제공하는 툴바이다.
    • CMake: Gradle과 함께 작동하여 네이티브 라이브러리를 빌드하는 외부 빌드 도구이다.ndk-build만 사용하려는 경우에는 이 구성 요소가 필요하지는 않다.
    • LLDB: Android Studio가 네이티브 코드를 디버그하는데 사용하는 디버거이다.
  • File -> Project Structure -> SDK Location -> Android NDK location에 설정 되었는지 확인 한다.

2. Project 생성

평상시와 같이 생성하고 단지 옵션에서 include C/C++항목을 체크해 준다.

  • 그냥 자동으로 샘플 코드를 생성해 준다.

샘플 예제를 실행하면 아래와 같다.

빌드과정

  • cpp: 해당 파일은 stringFromJNI()함수를 제공해서 문자열을 반환하게 된다.
  • External Build Files: 해당 그룹에서 CMake또는 ndk-build용 빌드 스크립트를 확인 할 수 있다. build.gradle 파일이 Gradle에 앱을 빌드하는 방법을 알리는 방법과 유사하게 CMake ndk-build에서 네이티브 라이브러리를 빌드하는 방법을 파악하려면 빌드 스크립트가 필요하게 된다. 새로운 프로젝트의 경우 Android Studio CMake 빌드 스크립트 CMakeLists.txt를 생성하여 모듈의 루트 디렉토리에 배치한다.

샘플 앱 빌드 및 실행

  • Gradle이 외부 빌드 스크립트 CMakeLists.txt를 호출
  • CMake가 이 빌드 스크립트에 포함된 명령을 따라 C++소스 파일 native-lib.cpp를 공유 객체 라이브러리로 컴파일하고 이를 libnative-lib.so로 명명한다. 그러면 Gradle이 이를 APK로 패키징한다.
  • 런타임에 앱의 MainActivity System.loadLibrary()를 사용하여 네이티브 라이브러리를 로드한다. 이제 앱에서 라이브러리의 네이티브 함수 stringFromJNI()를 사용할 수 있다.
  • MainActivity.onCreate()가 Hello from C++를 반환하는 stringFromJNI()를 호출하고 이를 사용하여 TextView를 업데이트 한다.

기존 프로젝트에 C/C++ 코드 추가 (Eclipse Migration 포함)

네이티브 코드를 기존 프로젝트에 추가할 수 있다.

  • 새로운 네이티브 소스 파일 생성

이미 있으면 생략

  • CMake 빌드 스크립트 생성

이미 있으면 생략, ndk-build를 사용하기 위해서 Android.mk가 있는 경우에도 생략

  • CMake 또는 ndk-build 스크립트 파일의 경로를 제공하여 Gradle에 네이티브 라이브러리를 링크 시킨다. 방법은 아래와 같다.
    • Android Studio UI를 이용할 경우 ANdroid View에서 앱 모듈에 마우스 오른쪽 버튼을 클릭한 후 메뉴에서 Link C++ Project with Gradle을 선택한다. CMake또는ndk-build를 선택한다. CMake의 경우 CMakeLists.txt 스크립트 파일을 경로에 설정하고 ndk-build의 경우 Android.mk파일을 지정한다. 설정창은 아래와 같다.

참고문헌

http://yucaroll.tistory.com/1
공식사이트


'Computer Science > Android Application' 카테고리의 다른 글

Handler를 이용한 시간제한 기능 구현  (0) 2017.08.22
AIDL과 Remote Service  (1) 2017.08.22
Preference  (0) 2017.08.20
Broadcast Receiver  (0) 2017.08.16
Service  (0) 2017.08.16

Preference


프리퍼런스를 관리하는 클래스는 SharedPreferences이다.

  • SharedPreferences getSharedPreferences (String name, int mode)

    • 첫 번째 인수는 프레퍼런스를 저장할 XML 파일의 이름
    • mode 인수는 이 파일의 공유 모드로 0이면 읽기 쓰기가 가능 하다.
  • SharedPreferences getPreferences (int mode)

    • 파일 인수가 생략되어 있는데 이 경우 액티비티의 이름과 같은 xml 파일이 생성된다.
  • int getInt (String key, int defValue)

  • String getString (String key, String defValue)

  • boolean getBoolean (String key, boolean defValue)

값을 기록하는 메서드는 내부 클래스인 SharedPreferences.Editor가 제공된다.

  • SharedPreferences.Editor putInt(String key, int value)
  • SharedPreferences.Editor putBoolean(String key, boolean value)
  • SharedPreferences.Editor putstring(String key, String value)
  • SharedPreferences.Editor remove(String key)
  • boolean commit()
  • SharedPreferences.Editor clear()

문자열과 정수를 저장하는 간단한 예제

public class PrefTest extends Activity {
	TextView textName;
	TextView textStNum;
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.preftest);

		textName = (TextView)findViewById(R.id.name);
		textStNum = (TextView)findViewById(R.id.stnum);

		SharedPreferences pref = getSharedPreferences("PrefTest",0);
		String Name = pref.getString("Name", "이름없음");
		textName.setText(Name);

		int StNum = pref.getInt("StNum",20101234);
		textStNum.setText("" + StNum);
	}

	public void onPause() {
		super.onPause();

		SharedPreferences pref = getSharedPreferences("PrefTest",0);
		SharedPreferences.Editor edit = pref.edit();

		String Name = textName.getText().toString();
		int StNum = 0;
		try {
			StNum = Integer.parseInt(textStNum.getText().toString());
		}
		catch (Exception e) {}

		edit.putString("Name", Name);
		edit.putInt("StNum", StNum);

		edit.commit();
	}
}
  • getSharedPreferences메서드로 프레퍼런스 객체를 얻는다.
  • onPause에서 edit메서드로 기록할 데이터를 프레퍼런스에 기록 한다.
  • onCreate에서 객체를 읽어온다.

PreferenceActivity

사용자가 설정값을 입력하고 불러 올 수 있는 UI를 미리 만들어서 제공하는 자동화된 방법이다.
xml만 잘 작성하고 Activity에서 PreferenceActivity만 잘 상속해서 사용하면 된다.

<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
	android:key="age"
	android:title="나이"
	android:summary="너 도대체 몇 살이니?"
	android:defaultValue="19" 
/>
<CheckBoxPreference
	android:key="male"
	android:title="성별"
	android:summary="남자면 체크"
	android:defaultValue="true" 
/>
</PreferenceScreen>
public class PrefActivity extends PreferenceActivity {
	@SuppressWarnings("deprecation")
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		addPreferencesFromResource(R.layout.prefactivity);
	}
}

TextPref

xml을 이용한 방법은 parser를 이용해야 하기 때문에 속도가 느리다.
빈번하게 onPause에서 현재 상태를 저장해야 한다면 문제가 발생 한다.

그래서 그냥 단순히 text에 기록하고 불러오는 방법을 사용한다.

테스트 코드

public class TextLogTest extends Activity {
	LinearLayout mLinear;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.textlogtest);

		// onCreate에서 로그 유틸리티 초기화
		TextLog.init(this);
		TextLog.mAppendTime = true;
		TextLog.mReverseReport = true;

		mLinear = (LinearLayout)findViewById(R.id.linear);
		mLinear.setOnTouchListener(new View.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
					// 필요할 때 로그 기록
					lg.o("down. x = " + (int)event.getX() + 
							", y = " + (int)event.getY());
					return true;
				case MotionEvent.ACTION_MOVE:
					lg.o("move. x = " + (int)event.getX() + 
							", y = " + (int)event.getY());
					return true;
				}
				return false;
			}
		});
	}

	// 다음 두 메서드를 디버깅 프로젝트의 엑티비티에 추가한다.
	public boolean onCreateOptionsMenu(Menu menu) {
		super.onCreateOptionsMenu(menu);
		TextLog.addMenu(menu);
		return true;
	}

	public boolean onOptionsItemSelected(MenuItem item) {
		if (TextLog.execMenu(item) == true) {
			return true;
		}
		return false;
	}
}
//텍스트 파일에 설정 정보를 저장하는 클래스. 안드로이드의 프레프런스가 너무 느려 새로 만듬
//Ready()를 호출하여 입출력 준비하고 기록할 때는 CommitWrite, 읽기만 했을 때는 EndReady를 호출한다.
class TextPref {
	String mPath;
	StringBuilder mBuf;
	static final String HEADER = "__Text Preference File__\n"; 

	// 생성자로 프레퍼런스의 완전 경로를 전달한다. 
	public TextPref(String Path) throws Exception {
		mPath = Path;
		File file = new File(mPath);
		if (file.exists() == false) {
			FileOutputStream fos = new FileOutputStream(file);
			fos.write(HEADER.getBytes());
			fos.close();
		}
	}

	// 설정 파일을 삭제한다.
	public void Reset() {
		File file = new File(mPath);
		file.delete();
	}

	// 버퍼를 준비하여 읽기 및 쓰기 준비를 한다.
	public boolean Ready() {
		try {
			FileInputStream fis = new FileInputStream(mPath);
			int avail = fis.available();
			byte[] data = new byte[avail];
			while (fis.read(data) != -1) {;}
			fis.close();
			mBuf = new StringBuilder(avail);
			mBuf.append(new String(data));
		}
		catch (Exception e) {
			return false;
		}
		return true;
	}

	// 버퍼의 내용을 파일로 기록한다.
	public boolean CommitWrite() {
		File file = new File(mPath);
		try {
			FileOutputStream fos = new FileOutputStream(file);
			fos.write(mBuf.toString().getBytes());
			fos.close();
		} 
		catch (Exception e) {
			return false;
		}
		mBuf = null;
		return true;
	}

	// 버퍼를 해제하고 읽기를 종료한다. 변경한 내용은 모두 취소된다.
	public void EndReady() {
		mBuf = null;
	}
	
	// name키의 위치를 검색하여 = 다음 위치를 리턴한다. 없으면 -1을 리턴한다.
	// 우연한 중복 방지를 위해 키 이름앞에 __를 붙인다.
	int FindIdx(String name) {
		String key = "__" + name + "=";
		int idx = mBuf.indexOf(key);
		if (idx == -1) {
			return -1;
		} else {
			return idx + key.length();
		}
	}

	// 문자열 키를 기록한다. 이미 있으면 대체한다.
	public void WriteString(String name, String value) {
		int idx = FindIdx(name);
		if (idx == -1) {
			mBuf.append("__");
			mBuf.append(name);
			mBuf.append("=");
			mBuf.append(value);
			mBuf.append("\n");
		} else {
			int end = mBuf.indexOf("\n", idx);
			mBuf.delete(idx, end);
			mBuf.insert(idx, value);
		}
	}

	// 문자열 키를 읽는다. 없으면 디폴트를 리턴한다.
	public String ReadString(String name, String def) {
		int idx = FindIdx(name);
		if (idx == -1) {
			return def;
		} else {
			int end = mBuf.indexOf("\n", idx);
			return mBuf.substring(idx, end);
		}
	}

	// 정수를 읽는다. 일단 문자열 형태로 읽은 후 변환한다.
	public void WriteInt(String name, int value) {
		WriteString(name, Integer.toString(value));
	}

	// 정수를 기록한다. 문자열 형태로 변환하여 기록한다.
	public int ReadInt(String name, int def) {
		String s = ReadString(name, "__none");
		if (s.equals("__none")) {
			return def;
		}
		try {
			return Integer.parseInt(s);
		}
		catch (Exception e) {
			return def;
		}
	}
	
	public void WriteLong(String name, long value) {
		WriteString(name, Long.toString(value));
	}
	
	public long ReadLong(String name, long def) {
		String s = ReadString(name, "__none");
		if (s.equals("__none")) {
			return def;
		}
		try {
			return Long.parseLong(s);
		}
		catch (Exception e) {
			return def;
		}
	}
	
	// 진위값은 true, false가 아닌 1, 0으로 기록한다.
	public void WriteBoolean(String name, boolean value) {
		WriteString(name, value ? "1":"0");
	}

	public boolean ReadBoolean(String name, boolean def) {
		String s = ReadString(name, "__none");
		if (s.equals("__none")) {
			return def;
		}
		try {
			return s.equals("1") ? true:false;
		}
		catch (Exception e) {
			return def;
		}
	}
	
	public void WriteFloat(String name, float value) {
		WriteString(name, Float.toString(value));
	}

	public float ReadFloat(String name, float def) {
		String s = ReadString(name, "__none");
		if (s.equals("__none")) {
			return def;
		}
		try {
			return Float.parseFloat(s);
		}
		catch (Exception e) {
			return def;
		}
	}
	
	// 한꺼번에 값을 삽입하기 위해 준비한다. 헤더 작성하고 충분한 버퍼를 할당한다.
	void BulkWriteReady(int length) {
		mBuf = new StringBuilder(length);
		mBuf.append(HEADER);
		mBuf.append("\n");
	}

	// 문자열 형태로 받은 값을 무조건 뒤에 덧붙인다.
	void BulkWrite(String name, String value) {
		mBuf.append("__");
		mBuf.append(name);
		mBuf.append("=");
		mBuf.append(value);
		mBuf.append("\n");
	}

	// 키를 삭제한다. 
	void DeleteKey(String name) {
		int idx = FindIdx(name);
		if (idx != -1) {
			int end = mBuf.indexOf("\n", idx);
			mBuf.delete(idx - (name.length() + 3), end + 1);
		}
	}
}

출처

[1] 안드로이드 정복 4판, 김상형


'Computer Science > Android Application' 카테고리의 다른 글

AIDL과 Remote Service  (1) 2017.08.22
NDK 사용  (0) 2017.08.20
Broadcast Receiver  (0) 2017.08.16
Service  (0) 2017.08.16
Activity  (0) 2017.08.16

Broadcast Receiver


보내는 쪽은 하나이고 받는 쪽은 다수인 개념으로 Broadcast라는 용어를 통상 쓴다.

BroadcastReceiver클래스를 재정하여 사용 한다.

  • void onReceive (Context context, Intent intent)

메인 스레드에서 동작하기 때문에 간결하게 작성하고 신속하게 리턴해야한다.

시스템에서 응용프로그램으로 방송하는 것이 일반적이지만 응용 프로그램도 특별한 변화를 유발시켰다거나 특이한 변화를 발견했다면
다른 응용 프로그램에게 방송을 보낼 수 있다.
그래서 방송은 응용 프로그램끼리 통신하는 공식적인 수단으로 활용된다. 응용 프로그램이 방송할 때는 다음 메서드를 호출한다.

  • void sendBroadcast (Intent intent [, String receiverPermission])
  • void sendOrderdBroadcast (Intent intent, String receiverPermission)

일반 방송은 비동기적으로 동작하여 호출시 즉시 리턴한다. 수신자가 방송을 수신했는지의 여부는 관여하지 않으며 누가 먼저 방송을 받을지 알 수 없다. 비동기적이며 비순서적이므로 효율이 좋다.

BroadCast 발생과 수신

임의로 응용프로그램에서 BR을 발생시키는 코드이다.

public class DetectFree extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.detectfree);
	}

	public void mOnClick(View v) {
		Intent intent = new Intent();
		intent.setAction("andexam.ver6.FREEWIFI");
		sendBroadcast(intent);
	}
}

수신하는 코드는 다음과 같다.

public class FreeBR extends BroadcastReceiver {
	public void onReceive(Context context, Intent intent) {
		Intent intent2 = new Intent(context, 
				andexam.ver6.c28_network.AsyncDownHtml.class);
		intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		context.startActivity(intent2);
	}
}

매니패시트

<receiver android:name=".c29_br.FreeBR">
    <intent-filter>
        <action android:name="andexam.ver6.FREEWIFI" />
    </intent-filter>
</receiver>

BR이 수신되면 html을 다운받는 새로운 Activity를 실행하는 코드를 작성해 두었다.

동적 BR 등록

매니페스트에 등록하지 않고 코드상에서도 할 수 있다.
즉 필요할 때만 등록하고 해지 시킬 수 있다.

  • Intent registerReceiver (BroadcastReceiver receiver, IntentFilter filter)
  • void unregisterReceiver (BroadcastReceiver receiver)

등록 메서드로 BR 객체와 인텐트 필터를 직접 전달한다.

  • onResume() 등록
  • onPause() 해제

코드는 아래와 같다.

public class OnSaveZone extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.onsavezone);
	}
	
	public void onResume() {
		super.onResume();
		IntentFilter filter = new IntentFilter();
		filter.addAction("andexam.ver6.SAVEZONE");
		registerReceiver(mSaveZoneBR, filter);
	}
	
	public void onPause() {
		super.onPause();
		unregisterReceiver(mSaveZoneBR);
	}

	BroadcastReceiver mSaveZoneBR = new BroadcastReceiver() {
		public void onReceive(Context context, Intent intent) {
			Toast.makeText(context, "아싸! 공짜다.", 
					Toast.LENGTH_LONG).show();
		}
	};
}

배터리 감시용 코드

public class WatchBattery extends Activity {
	TextView mStatus;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.watchbattery);

		mStatus = (TextView)findViewById(R.id.status);
	}
	
	public void onResume() {
		super.onResume();
		IntentFilter filter = new IntentFilter();
		filter.addAction(Intent.ACTION_BATTERY_CHANGED);
		filter.addAction(Intent.ACTION_BATTERY_LOW);
		filter.addAction(Intent.ACTION_BATTERY_OKAY);
		filter.addAction(Intent.ACTION_POWER_CONNECTED);
		filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
		registerReceiver(mBRBattery, filter);
	}	

	public void onPause() {
		super.onPause();        
		unregisterReceiver(mBRBattery);
	}

	BroadcastReceiver mBRBattery = new BroadcastReceiver() {
		int Count = 0;
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();
			Count++;
			if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
				onBatteryChanged(intent);
			}
			if (action.equals(Intent.ACTION_BATTERY_LOW)) {
				Toast.makeText(context, "배터리 위험 수준", Toast.LENGTH_LONG).show();
			}
			if (action.equals(Intent.ACTION_BATTERY_OKAY)) {
				Toast.makeText(context, "배터리 양호", Toast.LENGTH_LONG).show();
			}
			if (action.equals(Intent.ACTION_POWER_CONNECTED)) {
				Toast.makeText(context, "전원 연결됨", Toast.LENGTH_LONG).show();
			}
			if (action.equals(Intent.ACTION_POWER_DISCONNECTED)) {
				Toast.makeText(context, "전원 분리됨", Toast.LENGTH_LONG).show();
			}
		}

		public void onBatteryChanged(Intent intent) {
			int plug, status, scale, level, ratio;
			String sPlug = "";
			String sStatus = "";

			if (intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false) == false){
				mStatus.setText("배터리 없음");
				return;
			}

			plug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
			status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, 
					BatteryManager.BATTERY_STATUS_UNKNOWN);
			scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
			level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
			ratio = level * 100 / scale;

			switch (plug) {
			case BatteryManager.BATTERY_PLUGGED_AC:
				sPlug = "AC";
				break;
			case BatteryManager.BATTERY_PLUGGED_USB:
				sPlug = "USB";
				break;
			default:
				sPlug = "Battery";
				break;
			}

			switch (status) {
			case BatteryManager.BATTERY_STATUS_CHARGING:
				sStatus = "충전중";
				break;
			case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
				sStatus = "충전중 아님";
				break;
			case BatteryManager.BATTERY_STATUS_DISCHARGING:
				sStatus = "방전중";
				break;
			case BatteryManager.BATTERY_STATUS_FULL:
				sStatus = "만충전";
				break;
			default:
			case BatteryManager.BATTERY_STATUS_UNKNOWN:
				sStatus = "알 수가 없어";
				break;
			}

			String str = String.format("수신 회수:%d\n연결: %s\n상태:%s\n레벨:%d%%", 
					Count, sPlug, sStatus, ratio);
			mStatus.setText(str);
		}
	};
}


'Computer Science > Android Application' 카테고리의 다른 글

NDK 사용  (0) 2017.08.20
Preference  (0) 2017.08.20
Service  (0) 2017.08.16
Activity  (0) 2017.08.16
쓰레드 (Thread)  (0) 2017.08.04

Service


  • 백그라운드 데몬: 배경에서 계속 실행되는 프로세스이다. 클라이언트가 가동시켜 놓기만 하면 사용자의 명령이 없이도 지속적으로 실행된다. MP3 플레이어가 대표적인 예이다.

  • 바운드 서비스: 클라이언트를 위해 특정한 기능을 제공하는 역할을 한다. 자신의 기능을 메서드로 노출시키며 클라이언트는 메서드를 호출함으로써 서비스를 이용한다. 다른 운영체제의 COM, CORBAR에 대응되는 개념이다.

두 개의 서로다른 서비스는 Life Cycle에서 차이가 존재 한다.

백그라운드 데몬 형태 예제

백그라운드에서 동작하며 뉴스 데이터를 출력하는 예제

public class NewsService extends Service {
    boolean mQuit;

    public void onCreate() {
        super.onCreate();
    }

    public void onDestroy() {
        super.onDestroy();

        Toast.makeText(this, "Service End", Toast.LENGTH_SHORT).show();
        mQuit = true;
    }

    public int onStartCommand (Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);

        mQuit = false;
        NewsThread thread = new NewsThread(this, mHandler);
        thread.start();
        return START_STICKY;
    }

    public IBinder onBind(Intent intent) {
        return null;
    }

    class NewsThread extends Thread {
        NewsService mParent;
        Handler mHandler;
        String[] arNews = {
                "일본, 독도는 한국땅으로 인정",
                "번데기 맛 쵸코파이 출시",
                "춘천 지역에 초거대 유전 발견",
                "한국 월드컵 결승 진출",
                "국민 소득 6만불 돌파",
                "학교 폭력 완전 근절된 것으로 조사",
                "안드로이드 점유율 아이폰을 앞질렀다",
        };
        public NewsThread(NewsService parent, Handler handler) {
            mParent = parent;
            mHandler = handler;
        }
        public void run() {
            for (int idx = 0;mQuit == false;idx++) {
                Message msg = new Message();
                msg.what = 0;
                msg.obj = arNews[idx % arNews.length];
                mHandler.sendMessage(msg);
                try { Thread.sleep(5000);} catch (Exception e) { ; }
            }
        }
    }

    Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            if (msg.what == 0) {
                String news = (String)msg.obj;
                Toast.makeText(NewsService.this, news, Toast.LENGTH_SHORT).show();
            }
        }
    };
}

매니페스트 등록 내용

<service android:name=".c30_service.NewsService" android:enabled="true">
    <intent-filter>
        <action android:name="andexam.ver6.NEWS" />
    </intent-filter>
</service>

주요 착안점

  • Service는 메인 스레드에서 실행되므로 긴 작업을 위해서 스레드를 별도로 생성함
  • Thread는 toast update가 불가능 하므로 handler를 이용해서 UI를 업데이트함
  • 원격 호출을 지원하지 않으므로 onBind는 null을 리턴한다.

서비스의 시작과 종료 방법
같은 패키지에 있는 서비스의 경우 서비스 클래스명으로 지정하는 것이 가장 간편하다.

  • ComponentName startService (Intent service)
  • boolean stopService (Intent service)

호출은 중첩되지 않으므로 몇 번을 시작했건 간에 stopService를 한 번만 호출해도 즉시 종료된다.

서비스는 유일한 이름이 있으므로 외부 패키지나 외부 프로그램에서도 호출 가능하다. 외부에서 다른 패키지에 속한 클래스를 직접 참조할 수 없으므로 이름을 사용하여 다음과 같이 호출한다.
itent = new Intent(“packageName.className”);
startService(intent);
하지만 5.0 이후로는 보안상 문제가 있어서 금지 되었다. Service intent must be explicit예외가 발생된다.

IntentService

비동기적으로 기능을 해결해주는 컴포넌트이다.
startService(Intent)로 요청하면 작업 스레드를 생성해서 해당 작업을 실행시켜 준다.

work queue processor패턴으로 일반적으로 이용되는 것이 IntentService이다.
즉, main thread에서 수행해야할 작업을 쉽게 offloading 하는 역할을 한다.

요약

  • The IntentService runs on a separate worker thread.
  • The IntentService is triggered using an Intent, it spawns a new worker thread and the method onHandleIntent() is called on this thread.
  • The IntentService cannot run tasks in parallel. Hence all the consecutive Intents will go into the message queue for the worker thread and will execute sequentially.
  • The IntentService stops the service after all start requests have been handled, so you never have to call stopSelf().
public class NewsService2 extends IntentService {
    public NewsService2() {
        super("NewsThread");
    }

    protected void onHandleIntent(Intent intent) {
        String[] arNews = {
                "4T SSD 10만원대 진입",
                "갤럭시 S8 판매 호조",
                "핵융합 발전소 건설 완료",
        };
        for (int idx = 0;idx < arNews.length;idx++) {
            Message msg = new Message();
            msg.what = 0;
            msg.obj = arNews[idx % arNews.length];
            mHandler.sendMessage(msg);
            try { Thread.sleep(5000);} catch (Exception e) { ; }
        }
    }

    Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            if (msg.what == 0) {
                String news = (String)msg.obj;
                Toast.makeText(NewsService2.this, news, Toast.LENGTH_SHORT).show();
            }
        }
    };
}

Bind Service

Bind service는 특정 기능을 제공하는 메서드를 클라이언트에게 노출한다.
클라이언트는 서비스에 연결하여 메서드를 호출함으로써 통신을 수행하며 자신이 직접 구현하지 않은 기능을 사용한다.

연산을 처리하는 부분

public class CalcService extends Service {
    public class CalcBinder extends Binder {
        CalcService getService() { return CalcService.this; }
    }

    CalcBinder mBinder = new CalcBinder();

    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public int getLCM(int a, int b) throws RemoteException {
        int i;
        for (i = 1; ;i++) {
            if (i % a == 0 && i % b == 0) break;
        }
        return i;
    }

    public boolean isPrime(int n) throws RemoteException {
        int i;
        for (i = 2;i < n; i++) {
            if (n % i == 0) return false;
        }
        return true;
    }
}

onBind를 호출하는 부분

public class CalcClient extends Activity {
    CalcService mCalc;
    TextView mResult;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.calcclient);

        mResult = (TextView)findViewById(R.id.result);
    }

    public void mOnClick(View v) {
        switch (v.getId()) {
            case R.id.btnLCM:
                int LCM = 0;
                try {
                    LCM = mCalc.getLCM(6, 8);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                mResult.setText("6과 8의 최소 공배수 = " + LCM);
                break;
            case R.id.btnPrime:
                boolean prime = false;
                try {
                    prime = mCalc.isPrime(7);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                mResult.setText("7의 소수 여부 = " + prime);
                break;
        }
    }

    public void onResume() {
        super.onResume();
        Intent intent = new Intent(this, CalcService.class);
        bindService(intent, srvConn, BIND_AUTO_CREATE);
    }

    public void onPause() {
        super.onPause();
        unbindService(srvConn);
    }

    ServiceConnection srvConn = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder binder) {
            mCalc = ((CalcService.CalcBinder)binder).getService();
        }

        public void onServiceDisconnected(ComponentName className) {
            mCalc = null;
        }
    };
}


클라이언트에서 서비스에 연결하거나 해제할 때 다음 메서드를 호출하여 바인딩한다. 바인딩이란 클라이언트와 서비스를 연결하는 동작이다.

  • boolean bindService(Intent service, ServiceConnection conn, int flags)
  • void unbindService (ServiceConnection conn)

bindService의 첫 번째 인수는 같은 패키지에 있으면 클래스명으로 지정하고 외부에 있다면 서비스의 액션명을 사용한다.
두 번째 인수 conn은 서비스가 연결, 해제될 때의 동작을 정의하는 연결 객체이다.
서비스를 사용하는 클라이언트는 ServiceConnection 인터페이스를 구현하는데 클라이언트와 서비스가 연결되거나 해제될 때 호출되는 콜백 메서드를 정의한다. 아래의 코드가 이에 해당한다.

    ServiceConnection srvConn = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder binder) {
            mCalc = ((CalcService.CalcBinder)binder).getService();
        }

        public void onServiceDisconnected(ComponentName className) {
            mCalc = null;
        }
    };

onResume()에서 bindService(intent,srvConn,BIND_AUTO_CREATE)를 하기 때문에 해당 callback method를 구현해야 한다.
만약 그냥 별도의 anonymous class 생성 없이 activity에 구현 했다고 하면 this가 된다.

마지막 인수 flag는 서비스 바인딩 바익을 지정하는데 통상 BIND_AUTO_CREATE로 지정하여 서비스를 자동으로 가동시킨다.

아주 복잡한 기능들을 공통으로 서비스에 작성해두고 이것을 원격으로 콜하는 방식으로 구현할 수 있다.

완전 원격 서비스

서비스는 로컬에서뿐만 아니라 원격으로도 호출할 수 있는데 그렇게 하려면 자신의 메서드 목록을 인터페이스로 정의해야 한다.
이는 단순히 메서드의 원형을 선언하는 것과 수준이 다른데 원격에서 호출되는 메서드는 응용 프로그램의 경계를 넘어서 인수를 전달해야 하는 어려움이 있다. 각 응용프로그램이 사용하는 메모리가 완전히 분리되어 있어 통상의 방법으로는 인수를 넘기기 어렵다.

따라서 전달할 수 있는 인수의 타입은 자바기본 타입이나 Parcelable정도로 제한되며 그 외에도 몇 가지 제약이 존재한다.

자바 수준에서 인터페이스를 직접 정의하기는 대단히 어려워 원격 인터페이스를 정의하는 AIDL이라는 별도의 언어를 제공한다.

interface A{
    int getLCM();
    boolean isPrime();
}

위와 같이 인터페이스안에 노출할 메서드의 선언문만 작성해 두면 AIDL 컴파일러가 이 인터페이스를 구현하는 자바 파일을 생성해 준다.

예전에 구현했던 내용
네이버 블로그 포스트


'Computer Science > Android Application' 카테고리의 다른 글

Preference  (0) 2017.08.20
Broadcast Receiver  (0) 2017.08.16
Activity  (0) 2017.08.16
쓰레드 (Thread)  (0) 2017.08.04
이벤트 (Event)  (0) 2017.08.04

Activity


엑티비티 생성과 호출

코드

public class CallActivity extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.callactivity);
	}

	public void mOnClick(View v) {
		Intent intent = new Intent(this, SubActivity.class);
		startActivity(intent);
	}
}
<activity android:name=".c17_activity.CallActivity" android:label="CallActivity" />
  • name의 경우 액티비티의 패키지명을 포함한 풀 경로를 지정하되 같은 패키지에 속해 있을 때는 앞에 . 을 찍거나 클래스명만 적어도 무방하다.

Intent

생성자의 종류

  • 1 Intent (): 잘 사용 안함
  • 2 Intent (Intent o)
  • 3 Intent (String action [, Uri uri])
  • 4 Intent (Context packageContext, Class<?> cls): 서브 액티비티 호출할 때 사용
  • 5 Intent (String action, Uri uri, Context packageContext, Class<?> cls)

4 번째 구조로 만든 intent이다. Explicit Intent라고 한다.

Intent intent = new Intent(this, SubActivity.class)
startActivity(intent);

Implicit Intent

  • Action
    • ACTION_CALL // Activity // 통화를 시작한다.
    • ACTION_BATTERY_LOW // BR // 배터리가 부족하다.

거의 왠만한 액션들은 모두 정의가 되어 있다. String 타입으로 define 되어 있는 것이다.
사용자가 개인적으로 정의해서 사용할 수도 있다. 그 경우 중복을 막기위해서 package name을 앞에 붙인다.
액션을 조사하거나 변경 할때는 gecAction setAction을 사용 한다.

  • Data: 목적어에 해당 한다.
  • Type: 자동으로 판별이 가능하다.
    • http:// 웹페이지
    • te: 전화번호
    • jpg 이미지
  • Category: 여러개를 줄 수 있다.
  • Component: 이 속성이 지정되면 Explicit로 바뀐다.
  • Extras: 추가적인 정보를 전달 할 때 사용 한다. 키와 값의 쌍으로 저장되어 컴포넌트에게 전달 되며 리턴 용도로도 사용된다. 이름만 중복되지 않으면 얼마든지 많은 정보를 전달할 수 있다.
    • putExtra로 여러 벌 오버로딩 되어 있으므로 저장되는 정보 타입을 그냥 넣어주면 알아서 맞게 동적 로딩 된다.
    • getIntExtra getStringExtra등으로 제공된다.
  • Flags: 액티비티를 띄울 방법이나 액티비티를 관리하는 방법 등에 대한 옵션 정보가 저장된다.

액티비티간의 통신

값의 전달

  • Intent putExtra (String name, int value)
  • Intent putExtra (String name, String value)
  • Intent putExtra (String name, boolean value)

값 읽기

  • int getIntExtra (String name, int deafultValue)
  • String getStringExtra (String name)
  • boolean getBooleanExtra (String name, boolean defaultValue)

리턴값을 돌려줘야 할 경우 다음의 메서드를 사용 한다.

  • void startActivityForResult (Intent intent, int requestCode)
    • 두 번째 인수가 하나 더 추가되는데 호출한 대상을 나타내는 식별자이며 리턴 시에 누구에 대한 리턴인지 구분할 때 사용한다.

호출된 액티비티가 종료되면 다음의 메서드가 호출 된다.

  • void onActivityResult (int requestCode, int resultCode, Intent data)
    • requestCode는 액티비티를 호출할 때 전달한 요청코드이며 requestCode는 액티비티의 실행 결과이다.

예제코드
호출하는 부분

public class CommActivity extends Activity {
	TextView mText;
	final static int ACT_EDIT = 0;
	
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.commactivity);

		mText = (TextView)findViewById(R.id.text);
	}
	
	public void mOnClick(View v) {
		switch (v.getId()) {
		case R.id.btnedit:
			Intent intent = new Intent(this, ActEdit.class);
			intent.putExtra("TextIn", mText.getText().toString());
			startActivityForResult(intent,ACT_EDIT);
			break;
		}
	}
	
	protected void onActivityResult (int requestCode, int resultCode, Intent data) {
		switch (requestCode) {
		case ACT_EDIT:
			if (resultCode == RESULT_OK) {
				mText.setText(data.getStringExtra("TextOut"));
			}
			break;
		}
	}
}

처리하는 부분

public class ActEdit extends Activity {
	EditText mEdit;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.actedit);

		mEdit = (EditText)findViewById(R.id.stredit);

		Intent intent = getIntent();
		String text = intent.getStringExtra("TextIn");
		if (text != null) {
			mEdit.setText(text);
		}
	}

	public void mOnClick(View v) {
		switch (v.getId()) {
		case R.id.btnok:
			Intent intent = new Intent();
			intent.putExtra("TextOut", mEdit.getText().toString());
			setResult(RESULT_OK,intent);
			finish();
			break;
		case R.id.btncancel:
			setResult(RESULT_CANCELED);
			finish();
			break;
		}
	}
}
  • getIntent
  • void setResult(int resultCode, Intent data)
  • getStringExtra

Implicit Intent

public class CallOther extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.callother);
	}

	public void mOnClick(View v) {
		Intent intent;
		switch (v.getId()) {
		case R.id.web:
			intent = new Intent(Intent.ACTION_VIEW, 
					Uri.parse("http://www.google.com"));
			startActivity(intent); 
			break;
		case R.id.dial:
			intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:015-123-4567"));
			startActivity(intent);
			break;
		case R.id.picture:
			intent = new Intent(Intent.ACTION_VIEW);
			String sd = Environment.getExternalStorageDirectory().getAbsolutePath();
			Uri uri = Uri.fromFile(new File(sd + "/test.jpg"));
			intent.setDataAndType(uri, "image/jpeg");
			startActivity(intent);
			break;
		case R.id.other:
			intent = new Intent(Intent.ACTION_MAIN);
			intent.setComponent(new ComponentName("exam.memo", "exam.memo.Memo"));
			//intent.setClassName("exam.memo", "exam.memo.Memo");
			startActivity(intent);
			break;
		}
	}
}

액티비티 라이프 사이클

엑티비티의 라이프 사이클은 아래와 같다.


  • onCreate: 액티비티를 초기화한다. 중지했다가 재시작하는 경우라면 액티비티의 이전 상태 정보인 Bundle이 전달되며 이 정보대로 재초기화한다.

  • onRestart: 재시작될 때 호출된다. 특별히 할일은 없다.

  • OnResume: 사용자와 상호작용을 하기 직전에 호출된다. 이 단계에서 스택의 제일 위로 올라온다.

  • onPause: another activity comes into the foreground. 단순히 Pause고 곧 있다가 실행이 될 때를 의미한다. 요즘 멀티 엑티비티가 되면서 더 빈번하게 차이점이 생기는 문제이다. 포커스는 잃었지만 사용자에게 보이는 상태이다. 위쪽에 다른 엑티비티가 있지만 화면 전체를 다 가리지 않았거나 반투명한 경우가 이에 해당한다. 즉 뭔가 다이얼로그 창 같은게 위에 있는 상태이다. 살아 있는 상태와 같지만 시스템에 의해 강제 종료될 수 있다. 그리고 다른 액티비티가 실행 될 때도 onPause 이후에 onCreate가 수행 되므로 너무 오랜시간을 작업해서는 안된다. 즉, 이 단계에서 미저장한 데이터가 있으면 저장하고 애니메이션은 중지해야 한다. 이 메서드가 리턴되어야 새 액티비티가 활성화되므로 시간을 너무 오래 끌어서는 안 된다.

  • onStop: The activity is no longer visible. 이 상태에서는 액티비티가 완전히 가려져서 사용자에게 보이지 않는 상태이다. 이 상태에서는 액티비티가 백그라운드에 있는것으로 간주 된다. 정지되어 있는 동안 액티비티 인스턴스 및 멤버 변수와 같은 모든 상태 정보가 유지되지만, 어떠한 코드도 실행할 수 없다.

  • onDestory: 액티비티가 파괴될 때 호출된다. 시스템에 의해 강제로 종료되는 것인지 아니면 finish 메서드 호출에 의해 스스로 종료하는 것인지 isFinishing 메서드로 조사할 수 있다. 대부분의 앱은 이 메서드를 구현할 필요가 없다. 왜냐하면 액티비티와 함께 지역 클래스 참조가 소멸되고 액티비티가 onPause() 및 onStop 중에 대부분 정리 작업을 수행하기 때문이다. 하지만 액티비티가 onCreate 중에 생성한 백그라운드 스레드 또는 제대로 닫지 않으면 메모리 누수를 야기할 수 있는 다른 장시간 실행되는 리소스를 포함하는 경우 onDestroy중에 액티비티를 중단시켜야 한다. finish()를 호출한 경우 onPuase onStop을 거치지 않고 즉시 onDestroy를 호출 한다.

호출 순서 분석
각각의 callback 함수에 logcat으로 메시지를 출력하게 한후 새로운 액티비티를 실행해서 lifecycle을 분석 한다.

public class ActParent extends Activity {
	static final String TAG = "ActParent"; 

	public void onCreate(Bundle savedInstanceState) {
		Log.i(TAG, "onCreate");
		super.onCreate(savedInstanceState);
		setContentView(R.layout.actparent);
	}
	
	public void mOnClick(View v) {
		Log.i(TAG, "startActivity");
		Intent intent = new Intent(this, ActChild.class);
		startActivity(intent);
	}
	
	public void onStart() {
		super.onStart();
		Log.i(TAG, "onStart");
	}

	public void onResume() {
		super.onResume();
		Log.i(TAG, "onResume");
	}

	public void onPause() {
		super.onPause();
		Log.i(TAG, "onPause");
	}

	public void onRestart() {
		super.onRestart();
		Log.i(TAG, "onRestart");
	}

	public void onStop() {
		super.onStop();
		Log.i(TAG, "onStop");
	}

	public void onDestroy() {
		super.onDestroy();
		Log.i(TAG, "onDestroy");
	}
}
public class ActChild extends Activity {
	static final String TAG = "ActChild"; 

	public void onCreate(Bundle savedInstanceState) {
		Log.i(TAG, "onCreate");
		super.onCreate(savedInstanceState);
		setContentView(R.layout.actchild);
	}
	
	public void onStart() {
		super.onStart();
		Log.i(TAG, "onStart");
	}

	public void onResume() {
		super.onResume();
		Log.i(TAG, "onResume");
	}

	public void onPause() {
		super.onPause();
		Log.i(TAG, "onPause");
	}

	public void onRestart() {
		super.onRestart();
		Log.i(TAG, "onRestart");
	}

	public void onStop() {
		super.onStop();
		Log.i(TAG, "onStop");
	}

	public void onDestroy() {
		super.onDestroy();
		Log.i(TAG, "onDestroy");
	}
}

실행 결과 분석

또 다른 예제로 Rotation했을 때를 분석 한다.

나중에 핸들러, 스레드, 연기된 러너블등이 개입하면 좀 더 복잡해 진다.

상태 저장

Rotation으로 테스트 한다. 쉽게 강제 종료를 시뮬레이션 할 수 있다.

시스템은 액티비티를 종료하기 직전에 onSaveInstanceState 메서드를 호출하여 정보를 저장할 기회를 제공한다.
인수로 Bundle객체를 전달하는데 Bundle은 문자열로 된 이름과 임의 타입의 값을 저장하는 일종의 맵이다.

인텐트의 Extras 멤버가 바로 Bundle 타입이며 임의의 정보를 원하는 만큼 저장할 수 있다.
따라서 onSaveInstanceState를 재정의하여 저장하고자 하는 값을 번들에 저장해 두고 종료한다.

아무리 강제종료 되어도 onSaveInstanceState는 한번 호출되니 이곳에서 Bundle에 key와 value 쌍으로 데이터를 저장하면 된다.

아래 그림은 공식 홈페이지에서 발췌 한 것이다. 이것만으론 이해하기 어려워서 추가적인 실험도 아래와 같이 진행 했다.

시스템은 액티비티 중지 작업을 시작할 때 onSaveInstanceState()(1)를 호출한다. 따라서 Activity 인스턴스가 재생성되어야 하는 경우 저장하고자 하는 추가 상태 데이터를 지정할 수 있다. 액티비티가 소멸되고 동일한 인스턴스가 재생성되어야 하는 경우, 시스템은 onCreate() 메서드(2) 및 onRestoreInstanceState() 메서드(3) 모두에 (1)에서 정의된 상태 데이터를 전달한다.

onSaveInstanceState 호출 시점

rotation을 할 경우 onPause이후에 호출 된다.

08-20 14:04:58.340 10849-10849/andexam.ver6 I/SaveState2: onPause
08-20 14:04:58.341 10849-10849/andexam.ver6 I/SaveState2: onSaveInstanceState
08-20 14:04:58.341 10849-10849/andexam.ver6 I/SaveState2: onStop
08-20 14:04:58.341 10849-10849/andexam.ver6 I/SaveState2: onDestroy

home버튼을 누를 경우 onPause이후에 호출 된다.

08-20 14:09:00.965 10849-10849/andexam.ver6 I/SaveState2: onPause
08-20 14:09:00.996 10849-10849/andexam.ver6 I/SaveState2: onSaveInstanceState
08-20 14:09:00.996 10849-10849/andexam.ver6 I/SaveState2: onStop

back버턴을 눌러서 destropy를 호출시키면 실행 되지 않는다.

08-20 14:08:39.397 10849-10849/andexam.ver6 I/SaveState2: onPause
08-20 14:08:39.709 10849-10849/andexam.ver6 I/SaveState2: onStop
08-20 14:08:39.709 10849-10849/andexam.ver6 I/SaveState2: onDestroy

phone call일 때도 호출되지 않는다.

  • onPause-> onResume

onRestoreInstance 호출 시점

rotation할 때는 onStart이후에 호출된다.

08-20 14:04:58.352 10849-10849/andexam.ver6 I/SaveState2: onStart
08-20 14:04:58.352 10849-10849/andexam.ver6 I/SaveState2: onRestoreInstanceState
08-20 14:04:58.353 10849-10849/andexam.ver6 I/SaveState2: onResume

home버튼을 누르고 다시 시작 했을 때는 실행 되지 않는다.

08-20 14:10:34.761 10849-10849/andexam.ver6 I/SaveState2: onRestart
08-20 14:10:34.762 10849-10849/andexam.ver6 I/SaveState2: onStart
08-20 14:10:34.762 10849-10849/andexam.ver6 I/SaveState2: onResume

phone call일 때도 호출되지 않는다.

  • onPause-> onResume

뭔가 애매한대 reference문서의 가이드 라인은 아래와 같다.

void onRestoreInstanceState (Bundle savedInstanceState)

  • This method is called between onStart() and onPostCreate(Bundle).

void onSaveInstanceState (Bundle outState)

  • If called, this method will occur before onStop(). There are no guarantees about whether it will occur before or after onPause().

저장 복구 패턴 1

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		if (savedInstanceState == null) {
			x = 50;
		} else {
			x = savedInstanceState.getInt("x");
		}
		y = 50;
		vw = new MyView(this);
		vw.setFocusable(true);
		vw.setFocusableInTouchMode(true);
		setContentView(vw);
	}

	public void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putInt("x", x);
	}

onCreate에서 하지 않고 onRestoreInstanceState에서 수행 해도 된다.
onRestoreInstanceState는 내부 적으로 복원할 저장 상태가 있을 경우에만 호출되므로 Bundle null인지 확인할 필요는 없다.

	public void onRestoreInstanceState(Bundle outState) {
		super.onRestoreInstanceState(outState);
		x = outState.getInt("x");
	}

앱이 종료 되었다가 실행되어도 값을 저장하고 있으려면, 실행 시간 동안만 유지되는 Bundle에 저장해서는 안된다.
이 때는 Preference를 이용해서 저장한다.

public class SaveState3 extends Activity {
	private MyView vw;
	int x;
	int y;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		if (savedInstanceState == null) {
			x = 50;
		} else {
			x = savedInstanceState.getInt("x");
		}
		SharedPreferences pref = getSharedPreferences("SaveState",0);
		y = pref.getInt("y", 50);
		vw = new MyView(this);
		vw.setFocusable(true);
		vw.setFocusableInTouchMode(true);
		setContentView(vw);
	}

	protected void onPause() {
		super.onPause();
		SharedPreferences pref = getSharedPreferences("SaveState",0);
		SharedPreferences.Editor edit = pref.edit();
		edit.putInt("y", y);
		edit.commit();
	}

	public void onSaveInstanceState(Bundle outState) {
		outState.putInt("x", x);
	}
}

객체 저장

객체를 신속하게 저장 할 때는 자바의 Serialization을 사용 한다.
사용하는 메서드는 다음과 같다.

  • void putSerializable (String key, Serializable value)
  • Serializable getSerializable (String key)

이름과 객체만 전달하면 저장 및 복구가 자동으로 수행된다.
저장 하려고 하는 객체는 interface Serializable 키워드만 상속 받으면 된다.
하지만 interface인대도 구현할 매서드가 존재하지 않는다. 이러한 interface Marker Interface라고 부른다.
이렇게 하는 이유는 다중상속을 가능하게하고 추후에 instanceof에 의해서 해당 class가 Serilalizable에서 파생되었는지 알아내고 그에 대한 처리를 하기 위함이다.

코드

	public void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putSerializable("Curve", arVertex);
	}

	public class Vertex implements Serializable {
		private static final long serialVersionUID = 100L;
		Vertex(float ax, float ay, boolean ad) {
			x = ax;
			y = ay;
			draw = ad;
		}
		float x;
		float y;
		boolean draw;
	}
	

또 다른 방법 Parcelable인터페이스를 구현하는 방법
Parcel은 원래 프로세스간의 통신을 위한 메시지 저장 장치인데 번들이 꾸러미 입출력 기능을 제공하므로 같은 프로세스의 세션간 데이터 저장 및 복구에도 사용 가능하다.

저장 대상인 클래스는 Parcelable 인터페이스를 상속받아야 하며,
describeContents() writeToParcel을 구현 해야한다.

class Vertex implements Parcelable {
	Vertex(float ax, float ay, boolean ad) {
		x = ax;
		y = ay;
		draw = ad;
	}
	float x;
	float y;
	boolean draw;

	public int describeContents() {
		return 0;
	}

	public void writeToParcel(Parcel dest, int flags) {
		dest.writeFloat(x);
		dest.writeFloat(y);
		dest.writeBooleanArray( new boolean[] {draw} );
	}
	
	public static final Parcelable.Creator<Vertex> CREATOR = new Creator<Vertex>() {
		public Vertex createFromParcel(Parcel source) {
			int x = source.readInt();
			int y = source.readInt();
			boolean[] td = new boolean[1];
			source.readBooleanArray(td);
			return new Vertex(x, y, td[0]);
		}

		public Vertex[] newArray(int size) {
			return new Vertex[size];
		}
		
	};
}
  • describeContents() 별다른 기능이 없으믈 0을 리턴한다.
  • writeToParcel이 부분을 구현 해야 한다.
    boolean타입에 대해서는 단일값 입출력 메서드를 제공하지 않으므로 배열 형태로 포장해서 출력 해야 한다.

CREATOR라는 이름의 정적 객체는 저장된 정보를 Parcel로 부터 읽어들여 객체를 생성하는 역할을 한다.

생성 방법

  • publci static final Parcelable.Creator CREATOR = new Creator()
  • T createFromParcel (Parcel source)
    • 해당 메서드는 source로 부터 정수 두 개와 논리값 하나를 순서대로 읽어 Vertex 객체 하나를 생성한다.
  • Vertex[] newArray(int size)
    • newArray 메서드는 전달된 크기대로 배열을 생성하여 리턴한다.

참고문헌

  1. d.android: 액티비티 재생성
  2. d.android: 액티비티 시작
  3. 안드로이드 정복 4판, 김상형


'Computer Science > Android Application' 카테고리의 다른 글

Broadcast Receiver  (0) 2017.08.16
Service  (0) 2017.08.16
쓰레드 (Thread)  (0) 2017.08.04
이벤트 (Event)  (0) 2017.08.04
Gradle  (0) 2016.06.03

Ch02 LinkedList


2.1 정렬되지 않은 linkedList에서 중복된 값을 삭제하는 코드를 작성 한다.

해법1
set자료 구조에 하나씩 저장하면서 순회하는 방식을 사용한다.
단 한번의 순회로 모두 처리 할 수 있다.
복잡도는 O(n)이다.

def remove_dups(ll):
    if ll.head is None:
        return

    current = ll.head
    seen = set([current.value])
    while current.next:
        if current.next.value in seen:
            current.next = current.next.next
        else:
            seen.add(current.next.value)
            current = current.next

    return ll

실행결과

# 제거 전
1 -> 6 -> 1 -> 4 -> 3 -> 2 -> 4 -> 2 -> 2 -> 5 -> 5 -> 4 -> 2 -> 3 -> 8 -> 6 -> 2 -> 0 -> 1 -> 6 -> 3 -> 6 -> 2 -> 2 -> 6 -> 2 -> 1 -> 6 -> 2 -> 4 -> 2 -> 4 -> 7 -> 8 -> 4 -> 4 -> 3 -> 8 -> 5 -> 5 -> 0 -> 7 -> 7 -> 4 -> 1 -> 1 -> 1 -> 8 -> 5 -> 0 -> 7 -> 5 -> 9 -> 3 -> 0 -> 9 -> 2 -> 0 -> 4 -> 0 -> 0 -> 3 -> 1 -> 3 -> 5 -> 1 -> 0 -> 0 -> 5 -> 9 -> 7 -> 7 -> 0 -> 3 -> 3 -> 1 -> 7 -> 5 -> 5 -> 9 -> 7 -> 4 -> 2 -> 4 -> 6 -> 5 -> 3 -> 2 -> 9 -> 9 -> 8 -> 5 -> 1 -> 7 -> 4 -> 1 -> 8 -> 4 -> 3 -> 9

# 제거 후
1 -> 6 -> 4 -> 3 -> 2 -> 5 -> 8 -> 0 -> 7 -> 9

제약사항

  • temporary buffer를 허용하지 않는다.

해법
버퍼를 사용할 수 없다면 두 개의 포인터를 사용해 순회하여 문제를 해결할 수 있다. current라는 포인터로는 연결 리스트를 순회하고, runner로는 그 뒤에 중복이 있는지 확인 하는 것이다.

하지만 하나의 current node에 대해서 반복을 수행을 계속 해야 하므로 공간 복잡도는 O(1)이 되지만, 시간 복잡도는 O(N^2)이 된다.

def remove_dups_followup(ll):
    if ll.head is None:
        return

    current = ll.head
    while current:
        runner = current
        while runner.next:
            if runner.next.value == current.value:
                runner.next = runner.next.next
            else:
                runner = runner.next
        current = current.next

    return ll.head

실행결과

# 제거 전
1 -> 7 -> 8 -> 1 -> 5 -> 7 -> 8 -> 4 -> 9 -> 6 -> 5 -> 9 -> 0 -> 3 -> 8 -> 8 -> 9 -> 3 -> 2 -> 8 -> 7 -> 6 -> 7 -> 9 -> 9 -> 8 -> 5 -> 8 -> 9 -> 5 -> 1 -> 2 -> 6 -> 5 -> 6 -> 9 -> 2 -> 9 -> 2 -> 4 -> 4 -> 0 -> 8 -> 1 -> 5 -> 9 -> 9 -> 5 -> 0 -> 2 -> 9 -> 8 -> 0 -> 6 -> 1 -> 2 -> 1 -> 3 -> 0 -> 7 -> 6 -> 8 -> 1 -> 4 -> 5 -> 0 -> 2 -> 4 -> 9 -> 9 -> 9 -> 5 -> 3 -> 8 -> 0 -> 5 -> 7 -> 2 -> 8 -> 0 -> 3 -> 2 -> 8 -> 0 -> 0 -> 8 -> 2 -> 9 -> 0 -> 5 -> 3 -> 6 -> 8 -> 2 -> 1 -> 7 -> 8 -> 3 -> 7 -> 6

# 제거 후
1 -> 7 -> 8 -> 5 -> 4 -> 9 -> 6 -> 0 -> 3 -> 2

2.2 단방향 Linked List에서 k번째 요소를 찾는 코드를 구현 해야 한다.

해법1
재귀함수를 이용하는 방법

순회하고 매번 index를 저장하기 때문에 O(n)의 복잡도를 가지게 된다. 해당 위치에서 값을 출력하고 index는 return하는 방법으로 구현 했다.

def recursive_kth_to_last(ll, k):
    if ll is None:
        return 0
    i = recursive_kth_to_last(ll.next, k) + 1

    if i is k:
        print(ll.value)
    return i

실행결과

# 생성된 linked list
71 -> 83 -> 3 -> 2 -> 59 -> 25 -> 33 -> 40 -> 21 -> 93

# 뒤에서 3번 째 index라고 하면
40 #

해법2
순환적(iterarive) 방법으로 해결할 수 있다.
직관적이지는 않지만 좀 더 최적인 방법은, 순환적으로 푸는 것이다. 두 개의 포인터 p1과 p2를 사용한다. p1이 리스트 시작 노드를 가리키게 한 다음, p2를 k 노드만큼 움직여서 p1과 p2가 k 노드만큼 떨어져 있도록 만든다. 그런 다음, p1과 p2를 보조를 맞춰 같이 이동시킨다. p2는 LENGTH-k번 후에 연결 리스트의 맨 마지막 노드에 도달할 것이다. 바로 그 시점에, p1은 LENGTH-k번 노드, 그러니까 뒤에서부터 k번째 노드를 가리키게 된다.

코드

def kth_to_last(ll, k):
    runner = current = ll.head
    for i in range(k):
        if runner is None:
            return None
        runner = runner.next

    while runner:
        current = current.next
        runner = runner.next

    return current

실행결과

11 -> 11 -> 83 -> 92 -> 74 -> 2 -> 80 -> 97 -> 76 -> 88
97

2.3 Delete Middle Node

중간 노드를 삭제하는 알고리즘을 구현 한다. 즉 head나 tail을 제외한 아무 노드나 삭제 할 수 있어야 한다.

그렇다고 정확히 middle일 필요도 없다.

example
input: the node c from the linked list a->b->c->d->e->f
result: nothing is returned, but the new linked list looks like a->b->d->e->f

python에서는 쉽게 구현이 가능하다.

def delete_middle_node(node):
    node.value = node.next.value
    node.next = node.next.next

ll = LinkedList()
ll.add_multiple([1, 2, 3, 4])
middle_node = ll.add(5)
ll.add_multiple([7, 8, 9])

print(ll)
delete_middle_node(middle_node)
print(ll)

실행결과

1 -> 2 -> 3 -> 4 -> 5 -> 7 -> 8 -> 9
1 -> 2 -> 3 -> 4 -> 7 -> 8 -> 9

2.4 x값을 갖는 노드를 기준으로 연결 리스트를 나누는 코드를 작성하라. x 보다 작은 값을 갖는 노드가 x와 같거나 더 큰 값을 갖는 노드들보다 앞쪽에 오도록 하면 된다.

연결리스트를 사용하면 쉽게 해결 된다.
x를 기준으로 작은 값을 저장하는 것과 그렇지 않은것을 저장 하는 것으로 나누면 된다.

index를 1개만 이용해서 앞에서 부터 채우게 하면 좀 더 메모리를 절약할 수 있다.

def partition(ll, x):
    current = ll.tail = ll.head

    while current:
        nextNode = current.next
        current.next = None
        if current.value < x:
            current.next = ll.head
            ll.head = current
        else:
            ll.tail.next = current
            ll.tail = current
        current = nextNode
        
    # Error check in case all nodes are less than x
    if ll.tail.next is not None:
        ll.tail.next = None


ll = LinkedList()
ll.generate(10, 0, 99)
print(ll)
partition(ll, ll.head.value)
print(ll)

실행결과
partition 46으로 선택했을 때의 결과이다.

46 -> 53 -> 52 -> 69 -> 48 -> 3 -> 23 -> 59 -> 2 -> 91
2 -> 23 -> 3 -> 46 -> 53 -> 52 -> 69 -> 48 -> 59 -> 91

2.5 Sum Lists

  • 2개의 연결 리스트가 있고 각각은 싱글 digit을 포함하고 있다. 그리고 이 숫자는 역순으로 정렬되어 있다. 즉 1의 자리숫자가 맨 앞에 있다는 뜻이 된다. 이 두개의 연결 리스트를 더해서 그 결과를 반환하는 코드를 작성하라.

Example
Input: (7->1->6) + (5->9->2). That is, 617 + 295
Output: 2->1->9. That is 912.

코드

def sum_lists(ll_a, ll_b):
    n1, n2 = ll_a.head, ll_b.head
    ll = LinkedList()
    carry = 0
    while n1 or n2:
        result = carry
        if n1:
            result += n1.value
            n1 = n1.next
        if n2:
            result += n2.value
            n2 = n2.next

        ll.add(result % 10)
        carry = result // 10

    if carry:
        ll.add(carry)

    return ll

실행 결과

5 -> 3 -> 7 -> 7
8 -> 6 -> 8
3 -> 0 -> 6 -> 8

Follow up problem

  • 길이가 맞지 않는 경우
  • 계산결과를 head에 붙여서 처리하는 방식으로 구현

코드

def sum_lists_followup(ll_a, ll_b):
    # Pad the shorter list with zeros
    if len(ll_a) < len(ll_b):
        for i in range(len(ll_b) - len(ll_a)):
            ll_a.add_to_beginning(0)
    else:
        for i in range(len(ll_a) - len(ll_b)):
            ll_b.add_to_beginning(0)

    # Find sum
    n1, n2 = ll_a.head, ll_b.head
    result = 0
    while n1 and n2:
        result = (result * 10) + n1.value + n2.value
        n1 = n1.next
        n2 = n2.next

    # Create new linked list
    ll = LinkedList()
    ll.add_multiple([int(i) for i in str(result)])

    return ll

실행결과

뒤에서 부터 1의 자리로 생각 하기 때문에 결과가 다르다.

5 -> 3 -> 7 -> 7
8 -> 6 -> 8
6 -> 2 -> 4 -> 5

2.6 주어진 연결 리스트가 회문(palindrome)인지 검사하는 함수를 작성하라

palindrome이라 함은 앞에서 보다 뒤에서 보다 같은 구조를 뜻한다.

0->1->2->1->0

해법1: 뒤집어 비교한다.

  • 포인트는 절반만 비교한다는 것이다.

해법2: 순환적(iterative) 접근법

  • 리스트 앞 절반을 뒤집는 것을 스택을 사용해서 구현 한다.
  • 연결리스트의 길이를 알 경우 절반만 push 하면 된다 (홀수인 경우에도 올바르게 처리 해줘야 한다).
  • 길이를 모를 경우 fast runner slow runner를 적절히 조합해서 사용 한다.

해법3: 재귀적 접근법

코드

def is_palindrome(ll):
    fast = slow = ll.head
    stack = []

    while fast and fast.next:
        stack.append(slow.value)
        slow = slow.next
        fast = fast.next.next

    if fast:
        slow = slow.next

    while slow:
        top = stack.pop()

        if top != slow.value:
            return False

        slow = slow.next

    return True


ll_true = LinkedList([1, 2, 3, 4, 5, 4, 3, 2, 1])
print(is_palindrome(ll_true))
ll_false = LinkedList([1, 2, 3, 4, 5, 6, 7, 8, 9])
print(is_palindrome(ll_false))

실행 결과

True
False


'Computer Science > Coding Interview' 카테고리의 다른 글

Ch 09 Recursion and Dynamic Programming  (0) 2017.08.23
Ch01 Array and Strings  (0) 2017.08.10
Sorting  (0) 2017.08.06
Graph Search  (0) 2017.08.06
String  (0) 2017.07.31

Ch01 Array and Strings


1.1 문자열에 포함된 다른 문자들이 전부 유일한지를 검사하는 알고리즘 구현. 다른 자료구조를 사용할 수 없는 상황에서의

문자가 a~z로 총 26개라면 int 변수 1개로 count해서 이를 판별할 수 있다.

Code

public class QuestionB {

	/* Assumes only letters a through z. */
	public static boolean isUniqueChars(String str) {
		if (str.length() > 26) { // Only 26 characters
			return false;
		}
		int checker = 0;
		for (int i = 0; i < str.length(); i++) {
			int val = str.charAt(i) - 'a';
			if ((checker & (1 << val)) > 0) return false;
			checker |= (1 << val);
		}
		return true;
	}
	
	public static void main(String[] args) {
		String[] words = {"abcde", "hello", "apple", "kite", "padle"};
		for (String word : words) {
			System.out.println(word + ": " + isUniqueChars(word));
		}
		String a = "abcde";
	}
}

1.2 Permuation을 확인 하는 방법, Anagram 이랑 동일

정렬된 Algorihtm을 가지고 비교 한다.

Sroting을 해서 비교하는 방법이다.

public class QuestionA {	
	public static String sort(String s) {
		char[] content = s.toCharArray();
		java.util.Arrays.sort(content);
		return new String(content);
	}
	
	public static boolean permutation(String s, String t) {
		return sort(s).equals(sort(t));
	}	
	
	public static void main(String[] args) {
		String[][] pairs = {{"apple", "papel"}, {"carrot", "tarroc"}, {"hello", "llloh"}};
		for (String[] pair : pairs) {
			String word1 = pair[0];
			String word2 = pair[1];
			boolean anagram = permutation(word1, word2);
			System.out.println(word1 + ", " + word2 + ": " + anagram);
		}
	}
}

Ascii 코드를 이용한 방법이다.

public class QuestionB {	
	public static boolean permutation(String s, String t) {
		if (s.length() != t.length()) return false; // Permutations must be same length
		
		int[] letters = new int[128]; // Assumption: ASCII
		for (int i = 0; i < s.length(); i++) {
			letters[s.charAt(i)]++;
		}
		  
		for (int i = 0; i < t.length(); i++) {
			letters[t.charAt(i)]--;
		    if (letters[t.charAt(i)] < 0) {
		    	return false;
		    }
		}
		  
		return true; // letters array has no negative values, and therefore no positive values either
	}
	
	public static void main(String[] args) {
		String[][] pairs = {{"apple", "papel"}, {"carrot", "tarroc"}, {"hello", "llloh"}};
		for (String[] pair : pairs) {
			String word1 = pair[0];
			String word2 = pair[1];
			boolean anagram = permutation(word1, word2);
			System.out.println(word1 + ", " + word2 + ": " + anagram);
		}
	}
}

1.3 URLify 모든 빈 공간을 %20으로 채워라

예제
input: "Mr John Smith "
Output: "Mr%20John%20Smith"

뒷 쪽에 충분한 Margin이 있으므로 뒤에서 부터 앞으로 작업하는 것이 일반적인 방법이다.

two scan approach를 채택 한다.

  • 첫 번째에서는 number of spaces를 카운트 한다. 그 다음 3배를 해서 최종적으로 얼마나 많은 추가 character가 필요한지 알 수 있다.
  • 두 번째에서는 실제 string의 순서를 조절하게 된다. 즉 space가 있으면 %20을 채우고 no space면 orignal character를 채운다.
# O(N)
import unittest

def urlify(string, length):
    '''function replaces single spaces with %20 and removes trailing spaces'''
    new_index = len(string)

    for i in reversed(range(length)):
        if string[i] == ' ':
            # Replace spaces
            string[new_index - 3:new_index] = '%20'
            new_index -= 3
        else:
            # Move characters
            string[new_index - 1] = string[i]
            new_index -= 1

    return string

class Test(unittest.TestCase):
    '''Test Cases'''
    # Using lists because Python strings are immutable
    data = [
        (list('much ado about nothing      '), 22,
         list('much%20ado%20about%20nothing')),
        (list('Mr John Smith    '), 13, list('Mr%20John%20Smith'))]

    def test_urlify(self):
        for [test_string, length, expected] in self.data:
            actual = urlify(test_string, length)
            self.assertEqual(actual, expected)

if __name__ == "__main__":
    unittest.main()

문자열 압축 (String Compression)

# O(N)
import unittest

def string_compression(string):
    compressed = []
    counter = 0

    for i in range(len(string)):
        if i != 0 and string[i] != string[i - 1]:
            compressed.append(string[i - 1] + str(counter))
            counter = 0
        counter += 1

    # add last repeated character
    compressed.append(string[-1] + str(counter))

    # returns original string if compressed string isn't smaller
    return min(string, ''.join(compressed), key=len)


class Test(unittest.TestCase):
    '''Test Cases'''
    data = [
        ('aabcccccaaa', 'a2b1c5a3'),
        ('abcdef', 'abcdef')
    ]

    def test_string_compression(self):
        for [test_string, expected] in self.data:
            actual = string_compression(test_string)
            self.assertEqual(actual, expected)

if __name__ == "__main__":
    unittest.main()

Rotate Matrix

Matrix를 Rotation 하게 된다. 외부에서 안으로 진행하는 방식으로 처리 한다.

# O(NxN)
import unittest


def rotate_matrix(matrix):
    '''rotates a matrix 90 degrees clockwise'''
    n = len(matrix)
    for layer in range(n // 2):
        first, last = layer, n - layer - 1
        for i in range(first, last):
            # save top
            top = matrix[layer][i]

            # left -> top
            matrix[layer][i] = matrix[-i - 1][layer]

            # bottom -> left
            matrix[-i - 1][layer] = matrix[-layer - 1][-i - 1]

            # right -> bottom
            matrix[-layer - 1][-i - 1] = matrix[i][- layer - 1]

            # top -> right
            matrix[i][- layer - 1] = top
    return matrix


class Test(unittest.TestCase):
    '''Test Cases'''
    data = [
        ([
            [1, 2, 3, 4, 5],
            [6, 7, 8, 9, 10],
            [11, 12, 13, 14, 15],
            [16, 17, 18, 19, 20],
            [21, 22, 23, 24, 25]
        ], [
            [21, 16, 11, 6, 1],
            [22, 17, 12, 7, 2],
            [23, 18, 13, 8, 3],
            [24, 19, 14, 9, 4],
            [25, 20, 15, 10, 5]
        ])
    ]

    def test_rotate_matrix(self):
        for [test_matrix, expected] in self.data:
            actual = rotate_matrix(test_matrix)
            self.assertEqual(actual, expected)

if __name__ == "__main__":
    unittest.main()

1.7 M x N 행렬의 한 원소가 0일 경우 해당 원소가 속한 행과 열의 모든 원소를 0으로 설정하는 알고리즘을 작성

생각1
단순히 발견하는 0의 행과 열을 0으로 변경하면서 진행하면 금방 전부다 0이 되어 버린다.

생각2
추가적인 공간 O(MN)에 0의 위치를 기록하고 그 다음 해당 열과 행을 0으로 초기화 하는 방법이 있다.

생각3
그런데 정말로 O(MN) 만큼의 공간이 필요한가? 그렇지 않다. 같은 행과 열의 모든 원소의 값을 0으로 만들 것이므로, 0인 원소가 정확히 몇 번째 행에 몇 번째 열의 원소였는지 알 필요는 없다. 우리는 그저 어떤 행 안에 0 값을 갖는 원소가 있다는 사실만 기록하면 되고, 어떤 열 안에 0값을 갖는 원소가 있다는 사실만 기록하면 된다. 어차피 그 행과 열의 모든 원소를 0으로 만들 것인데, 왜 정확한 위치를 기록해야 하는가.

코드는 아래와 같다. 0이 있는 행과 열을 추적하기 위한 배열을 두 개 사용 했다. 그런 다음, 이 배열의 갑에 따라서 행과 열을 전부 0으로 만들도록 했다.

코드

# O(MxN)
import unittest


def zero_matrix(matrix):
    m = len(matrix)
    n = len(matrix[0])
    rows = []
    cols = []

    for x in range(m):
        for y in range(n):
            if matrix[x][y] == 0:
                rows.append(x)
                cols.append(y)

    for row in rows:
        nullify_row(matrix, row)

    for col in cols:
        nullify_col(matrix, col)

    return matrix


def nullify_row(matrix, row):
    for i in range(len(matrix[0])):
        matrix[row][i] = 0


def nullify_col(matrix, col):
    for i in range(len(matrix)):
        matrix[i][col] = 0


class Test(unittest.TestCase):
    '''Test Cases'''
    data = [
        ([
            [1, 2, 3, 4, 0],
            [6, 0, 8, 9, 10],
            [11, 12, 13, 14, 15],
            [16, 0, 18, 19, 20],
            [21, 22, 23, 24, 25]
        ], [
            [0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0],
            [11, 0, 13, 14, 0],
            [0, 0, 0, 0, 0],
            [21, 0, 23, 24, 0]
        ])
    ]

    def test_zero_matrix(self):
        for [test_matrix, expected] in self.data:
            actual = zero_matrix(test_matrix)
            self.assertEqual(actual, expected)

if __name__ == "__main__":
    unittest.main()

1.8 한 단어가 다른 단어에 포함된 문자열인지 판별하는 isSubstring이라는 메서드가 있다고 하자. s1과 s2의 두 문자열이 주어졌을 때, s2가 s1을 회전시킨 결과인지 판별하는 코드를 isSubstring을 한 번만 호출하도록 하여 작성하라.

s2가 s1을 회전시켜 얻은 문자열이라면, 회전된 지점이 어딘지를 알아봐야 한다.
가령 waterbottle을 회전시켜 erbbottlewat을 얻어다고 해보자.
회전시킬 때, s1을 x와 y의 두 부분으로 나눈 다음 다시 배열하여 s2를 얻었을 것이다.

s1 = xy = waterbottle
x = wat
y = erbottle
s2 = yx = erbottlewat

하지만 중요한점은 x와 y를 나눈 지점이 어디인가 상관없이, yx는 언제나 xyxy의 부분 문자열이다. 다시 말해서, s2는 언제나 s1s1의 부분 문자열이란 이야기이다.

즉, isSubstring(s1s1,s2)인지 알아보면 된다는 것이다.

# O(N)
import unittest


def is_substring(string, sub):
    return string.find(sub) != -1


def string_rotation(s1, s2):
    if len(s1) == len(s2) != 0:
        return is_substring(s1 + s1, s2)
    return False


class Test(unittest.TestCase):
    '''Test Cases'''
    data = [
        ('waterbottle', 'erbottlewat', True),
        ('foo', 'bar', False),
        ('foo', 'foofoo', False)
    ]

    def test_string_rotation(self):
        for [s1, s2, expected] in self.data:
            actual = string_rotation(s1, s2)
            self.assertEqual(actual, expected)

if __name__ == "__main__":
    unittest.main()


'Computer Science > Coding Interview' 카테고리의 다른 글

Ch 09 Recursion and Dynamic Programming  (0) 2017.08.23
Ch02 LinkedList  (0) 2017.08.11
Sorting  (0) 2017.08.06
Graph Search  (0) 2017.08.06
String  (0) 2017.07.31

Sorting


Bubble

시간복잡도는 $O(N^2)$이다.
공간복잡도는 $O(1)$

def bubbleSort(n, a):
    cumulatedNumSwap = 0
    for i in range(0, n):
        numSwap = 0
        for j in range(0, n-1):
            if a[j] > a[j+1]:
                temp = a[j]
                a[j] = a[j+1]
                a[j+1] = temp
                numSwap += 1
        cumulatedNumSwap += numSwap
        if numSwap == 0:
            break
    return cumulatedNumSwap, a

if __name__ == "__main__":
    a = [2, 1, 3, 1, 2]
    numSwap, sortedA = bubbleSort(len(a), a)

    print("Array is sorted in %d swaps."%numSwap)
    print("First Element: %d"%sortedA[0])
    print("Last Element: %d"%sortedA[-1])

Insertion

Time Complexity:

  • best: $O(n)$
  • worst: $O(n^2)$
    Space Complexity: $O(1)$

간단한 삽입정렬

def InsertionSort(input):
    for i in range(len(input)-1):
        for j in range(i+1,len(input)):
            if(input[j] < input[i]):
                input[i], input[j] = input[j], input[i]
    return input
import random

def InsertionSort(input):

    for idx, valueToInsert in enumerate(input):
        # select the hole position where number is to be inserted
        holePosition = idx

        # check if previous no. is larger than value to be inserted
        while holePosition > 0 and input[holePosition-1] > valueToInsert:
            input[holePosition - 1], input[holePosition] = input[holePosition], input[holePosition-1]
            holePosition = holePosition - 1

    return input

if __name__ == "__main__":
    A = [random.randint(0, 100) for _ in range(15)]  # 임의로 0~100까지 15개 수를 뽑습니다.
    print(A)
    InsertionSort(A)
    print(A)

실행결과

[84, 59, 97, 41, 6, 93, 80, 62, 19, 37, 83, 24, 86, 95, 46]
[6, 19, 24, 37, 41, 46, 59, 62, 80, 83, 84, 86, 93, 95, 97]

Selection

Time complexity: $O(n^2)$
Space complexity: $O(1)$

import random
def selectionSort(input):
    for i in range(len(input) - 1):
        # assume the min is the first element
        idx_min = i
        j = i + 1

        # test against elements after i to find the smallest
        while j < len(input):
            if(input[j] < input[idx_min]):
                # found new minimum; remember its index
                idx_min = j
            j = j + 1

        if idx_min is not i:
            # swap
            input[idx_min], input[i] = input[i], input[idx_min]

    return input

if __name__ == "__main__":
    A = [random.randint(0, 100) for _ in range(15)]  # 임의로 0~100까지 15개 수를 뽑습니다.
    print(A)
    selectionSort(A)
    print(A)

실행결과

[72, 10, 31, 7, 51, 11, 97, 9, 45, 54, 35, 26, 61, 55, 13]
[7, 9, 10, 11, 13, 26, 31, 35, 45, 51, 54, 55, 61, 72, 97]

Merge

worst와 average모두 $O(n \log n)$인 엄청나게 좋은 알고리즘이다.
하지만 공간 복잡도는 $O(n)$을 메모리는 많이 사용 합니다.
그래도 기본적인 Dvide and Conquer를 적용한 알고리즘이기에 확실히 알고 넘어가면 좋다.

위키피디아에 나온 그림으로 나타낸 동작 방법은 아래와 같다.

$T(n)=2T\left(\frac{n}{2}\right)+n$
$\begin{aligned}
T(n)&=2\left(2T\left(\frac{n}{4}\right)+\frac{n}{2}\right)+n \newline
&=4T\left(\frac{n}{4}\right)+n+n\newline
&=…\newline
&\approx O(\underbrace{n+n+…+n}_{\log n})\newline
&=O(n\log n)
\end{aligned}$

코드는 아래와 같다.

import random

# Time Complexity O(nLogn)
# Space Complexity O(n)

def mergeSort(A):
    ### Base case ###

    if len(A) <= 1:
        return A

    ### A를 반으로 쪼개서 recursive call을 해 주고 정렬된 반쪽짜리들을 받아옵니다.
    left = mergeSort(A[:int(len(A)/2)])
    right = mergeSort(A[int(len(A)/2):])

    ### 여기서부터 두 개의 쪼개졌던 list를 합치는 Merge 부분입니다.
    ### 여기서 포인트는 정렬된 상태로 돌아왔기 때문에 앞에서부터 순차적으로 한번만 돌면 정렬시킬 수 있다는 것입니다.
    i, j, k = 0, 0, 0
    while i<len(left) and j<len(right):
        if left[i] < right[j]:
            A[k] = left[i]
            i += 1
        else:
            A[k] = right[j]
            j += 1
        k += 1
    if i == len(left): #만약 left의 원소를 모두 채웠고, right가 남아있을 때.
        while j<len(right):
            A[k] = right[j]
            j += 1
            k += 1
    elif j == len(right): #만약 right의 원소를 모두 채웠고, left가 남아있을 때.
        while i<len(left):
            A[k] = left[i]
            i += 1
            k += 1
    return A #마지막으로 정렬된 list를 리턴합니다.

if __name__ == "__main__":

    A=[random.randint(1,100) for _ in range(10)]
    print("unsorted List:")
    print(A)
    print("Sorted List:")
    print(mergeSort(A))

실행결과

unsorted List:
[94, 22, 47, 26, 28, 29, 33, 53, 5, 54]
Sorted List:
[5, 22, 26, 28, 29, 33, 47, 53, 54, 94]

참고자료
https://smlee729.github.io/python/algorithm/2015/03/03/1-merge-sort.html

Quick

분할 정복 알고리즘으로 Sorting한다.
Merge Sort와 다르게 분할 할 때 복잡하고 병합할 때는 복잡하지 않다.

평균 시간 복잡도는 $O(nlogn)$이다. 메모리 측면에서 좀 더 효율 적이다. 따라서 실제로 동작하는 속도가 좀 더 빠르다.
$$T(n)\approx n+2T(\frac{n}{2})$$

하지만, 최악의 상황인 이미 정렬이 완료된 List를 정렬할 경우 $T(n)=n+T(n-1)$으로 분할 된다.
왜냐하면 pivot이 가장 큰 값이라면 left side n-1개가 존재하고 이 부분을 계속 quick sort로 recursive하게 호출이 발생하기 때문이다. 결국 $O(n^2)$의 복잡도를 가진다.

Pivot이 Median값이라면 merge sort와 같은 복잡도를 가진다. 최악의 경우를 피하는 방법은 통상 random choice를 이용한다.

import random

# 아까 말씀드렸다시피, pivot을 선택한 후에(parition 함수의 결과값으로 pivot의 최종 index가 나옵니다.) recursive call로 쪼갭니다.
def quicksort(A, start, end):
    if start < end:
        p = partition(A, start, end)
        quicksort(A, start, p - 1)
        quicksort(A, p + 1, end)


# 이제 pivot을 뽑는 과정을 알아봅시다.
def partition(A, start, end):
    pivot = end
    wall = start
    # 나머지 원소들은 A[hi]와 비교하면서 재정렬합니다.
    for i in range(start, end):
        if A[i] < A[pivot]:
            #swap
            A[i], A[wall] = A[wall], A[i]
            wall += 1
    A[pivot], A[wall] = A[wall], A[pivot] # 마지막으로 pivot을 자신보다 큰 원소들의 첫번째 원소와 바꿔줍니다. (전반부, 후반부를 나누기 위해서)
    return wall

if __name__ == "__main__":
    A = [random.randint(0, 100) for _ in range(15)]  # 임의로 0~100까지 15개 수를 뽑습니다.
    print(A)
    quicksort(A, 0, len(A)-1)
    print(A)


'Computer Science > Coding Interview' 카테고리의 다른 글

Ch02 LinkedList  (0) 2017.08.11
Ch01 Array and Strings  (0) 2017.08.10
Graph Search  (0) 2017.08.06
String  (0) 2017.07.31
HackerRank- Trees: Is This a Binary Search Tree  (0) 2017.07.31

Graph Search


아래와 같은 Graph가 있다고 가정한다.

Depth Frist Search (DFS)

  • 임의의 노드n을 선택
  • n을 방문했다고 표시
  • n의 방문하지 않은 인접 vertex를 방문한다.

Breadth First Search (BFS)

  • 노드 n부터 시작하고, 방문했다고 표시하면서 큐에 삽입
  • 큐가 empty일때까지 계속 반복하면서,
  • 큐에서 첫번째 원소를 뽑는다. 그리고 뽑은 원소의 방문하지 않은 이웃들을 방문했다고 표시 후에 큐의 끝에 지어넣는다.
class graph:
    def __init__(self, vertexList, edgeList):
        self.vertexList = vertexList
        self.edgeList = edgeList
        self.adjacentList = [[] for id in range(len(vertexList))]
        for edge in edgeList:
            self.adjacentList[edge[0]].append(edge[1])

def recursiveDFS(graph,node, visited=[]):
    visited.append(node)
    for neighbor in graph.adjacentList[node]:
        if neighbor not in visited:
            recursiveDFS(graph, neighbor, visited)
    return visited

# queue에 insert 할 때 넣는 방법
def BFS(graph,node, visited=[]):
    queue = [node] # 맨 처음 버텍스를 삽입 한다.
    visited.append(node) # 방문한 리스트에 맨 처음 버텍스를 넣는다.
    while queue: # 큐가 비었는지 확인 한다
        current = queue.pop() # 큐에서 데이터를 꺼내온다.
        for neighbor in graph.adjacentList[current]: # 인접 vertex에서 값을 하나 가져온다.
            if not neighbor in visited: # 현재 인접 노드의 값이 방문한 것이 아닌지 확인 한다.
                queue.insert(0,neighbor)
                visited.append(neighbor)
    return visited

# queue에 pop할 때 visit 하는 방법
def BFS2(graph,node, visited=[]):
    queue = [node]
    #visited.append(node)

    while queue:
        current = queue.pop()
        for vertex in graph.adjacentList[current]:
            if vertex not in visited:
                queue.insert(0, vertex)
        visited.append(current)
    return visited



if __name__ == "__main__":

    # Depth First Search (DFS)
    vertexList = ['0', '1', '2', '3', '4', '5', '6']
    edgeList = [(0, 1), (0, 2), (1, 0), (1, 3), (2, 0), (2, 4), (2, 5), (3, 1), (4, 2), (4, 6), (5, 2), (6, 4)]

    graphObj = graph(vertexList, edgeList)
    print("vertex list")
    print(graphObj.vertexList)
    print("edge list")
    print(graphObj.edgeList)
    print("adjacent list")
    print(graphObj.adjacentList)

    print("DFS Traverse:")
    print(recursiveDFS(graphObj,0))
    print("BFS Traverse:")
    print(BFS(graphObj,0))

실행결과

vertex list
['0', '1', '2', '3', '4', '5', '6']
edge list
[(0, 1), (0, 2), (1, 0), (1, 3), (2, 0), (2, 4), (2, 5), (3, 1), (4, 2), (4, 6), (5, 2), (6, 4)]
adjacent list
[[1, 2], [0, 3], [0, 4, 5], [1], [2, 6], [2], [4]]
DFS Traverse:
[0, 1, 3, 2, 4, 6, 5]
BFS Traverse:
[0, 1, 2, 3, 4, 5, 6]

참고자료
https://smlee729.github.io/python/network%20analysis/2015/04/09/1-networkx-dfs-bfs.html


'Computer Science > Coding Interview' 카테고리의 다른 글

Ch01 Array and Strings  (0) 2017.08.10
Sorting  (0) 2017.08.06
String  (0) 2017.07.31
HackerRank- Trees: Is This a Binary Search Tree  (0) 2017.07.31
Binary Search Tree  (0) 2017.07.31

쓰레드 (Thread)


Thread 생성 기법

Thread를 생성하는 방법은 아래 2가지 이다.

Thread()
Thread(Runnable runnable)

첫 번째 방법인 Thread의 경우 run에 해당 기능을 구현한다.

// Activity

BackThread thread = new BackThread():
thread.setDaemon(true);
thread.start();

class BackThread extends Thread {
	public void run(){
		while(true){
			mValue++;
			try {Thread.sleep(1000);} catch {InterruptedException e){;}
		}
	}
}

두 번째 방법인 Runnable은 interface 내부에 run을 구현 하는 방식이다.

Back Runnable runnable = new BackRunnable();
Thread thread = new Thread(runnable);
thread.setDeamon(true); // main thread가 종료 될 때만 종료된다 라는 의미이다.
thread.start();

class BackRunnable implements Runnable {
	public void run(){
		while(true){
			mBackValue ++;
			try {Thread.sleep(1000);} catch (interruptedException e) {;}
		}
	}
}

구지 복잡하게 자바에서 Runnable Interface를 지원하는 이유는 다중상속을 지원하지 않는 특성상
Thread만 구현 하고 싶을 때 Runnable을 이용하라는 의미이다.

Thread간의 통신 Handler

Handler 클래스로 표현되는 핸들러는 Tread간에 메시지 Runnable 객체를 통해 메시지를 주고 받는 장치이다.

  • 핸들러는 생성하는 Thread에 부착 된다. 부착된 Thread의 메시지큐를 통해서 다른 Thread와 통신 한다.
  • Handler는 Message를 MessageQueue에 넣는 기능과 MessageQueue에서 꺼내 처리하는 기능을 함께 제공한다.

Handler 생성자 이해

  • Handler()
  • Handler(Handler.Callback callback)
  • Handler(Looper looper)
  • Handler(Looper looper, Handler.Callback callback)

메시지 수신에 사용되는 메서드

  • void handleMessage (Message msg)
    인수 Message는 아래와 같은 추가 필드를 가진다.
  • int what: 메시지의 의미를 설명한다. 의미가 정해져 있지는 않으며 핸들러별로 지역적이므로 다른 핸들러와 충돌할 위험은 없다.
  • int arg1: 메시지의 추가 정보이다.
  • int arg2: 메시지의 추가 정보이다.
  • Object obj: 정수만으로 메시지를 기술할 수 없을 때 임의의 객체를 보낸다.
  • Messenger replyTo: 메시지에 대한 응답을 받을 객체를 지정한다.

메시지를 보내는 쪽에서는 전달하고자 하는 내용을 Message 객체에 저장하여 핸들러로 전송한다.
사용되는 메서드는 다음과 같다.

  • boolean Handler.sendEmptyMessage (int what)
  • boolean Handler.sendMessage (Message msg)
  • boolean Handler.sendEmptyMessageAtTime(int what, long uptimeMillis)
  • boolean Handler.sendMessageAtTime(Message msg, long uptimeMillis)
  • boolean sendMessageAtFrontOfQueue (Message msg)

실행 코드인 Runnable object를 전송하는 메서드는 다음과 같다.

  • boolean Handler.post(Runnable r)
  • boolean Handler.postDelayed(Runnable r, long delayMillis)
  • boolean Handler.postAtTime(Runnable r, Object token, long uptimeMillis)
  • boolean Handler.postAtTime(Runnable r, long uptimeMillis)
  • boolean Handler.postAtFrontofQueue(Runnable r)

메시지 전송 예제

public class HandlerTest extends Activity {
	int mMainValue = 0;
	int mBackValue = 0;
	TextView mMainText;
	TextView mBackText;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.threadtest);

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);

		BackThread thread = new BackThread();
		thread.setDaemon(true);
		thread.start();
	}

	public void mOnClick(View v) {
		mMainValue++;
		mMainText.setText("MainValue : " + mMainValue);
	}

	class BackThread extends Thread {
		public void run() {
			while (true) {
				mBackValue++;
				mHandler.sendEmptyMessage(0);
				try { Thread.sleep(1000); } catch (InterruptedException e) {;}
			}
		}
	}

	Handler mHandler = new Handler() {
		public void handleMessage(Message msg) {
			if (msg.what == 0) {
				mBackText.setText("BackValue : " + mBackValue);
			}
		}
	};
}

Runnable 객체 전송 예제
위 예제와 다르게 실행 객체를 전송하므로 mHandler는 구체적으로 정의할 필요 없이 생성만 해두면
내부 Runnable Code가 실행 된다.

public class HandlerTest extends Activity {
	int mMainValue = 0;
	int mBackValue = 0;
	TextView mMainText;
	TextView mBackText;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.threadtest);

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);

		BackThread thread = new BackThread();
		thread.setDaemon(true);
		thread.start();
	}

	public void mOnClick(View v) {
		mMainValue++;
		mMainText.setText("MainValue : " + mMainValue);
	}

	class BackThread extends Thread {
		public void run() {
			while (true) {
				mBackValue++;
				mHandler.post(new Runnable() {
					public void run() {
						mBackText.setText("BackValue : " + mBackValue);
					}
				});
				try { Thread.sleep(1000); } catch (InterruptedException e) {;}
			}
		}
	}

	Handler mHandler = new Handler();
}

위 예제에서는 Anonymous class 기법이 사용 되었다.

// non-anonymous class
Runnable runUpdate = new Runnable(){
	public void run() {
		mBackText.setText();
	}
};
mHandler.post(runUpdate);

// anonymous class
mHandler.post(new Runnable(){
	public void run() {
		mBackText.setText();
	}
};

runOnUiThread를 통한 Main UI Thread와의 통신

  • runOnUiThread 메서드 호출 위치가 UI 스레드라면 Runnable은 즉시 실행된다.

  • 작업 스레드에서 runOnUiThread가 호출 되었다면 자동으로 UI 스레드 큐를 찾아서 Runnable을 그 뒤에 붙여 준다. 즉, RunnableHandler.post를 하지 않아도 내부적으로 이와 동일한 작업을 해주는 것이다. mHandler가 없어도 상관 없기 때문에 훨씬 간편하다.

익명 class를 runnable로 runOnUiThread로 전달하는 예제

//* runOnUiThread 메서드로 익명 임시 객체 전달
public class HandlerTest extends Activity {
	int mMainValue = 0;
	int mBackValue = 0;
	TextView mMainText;
	TextView mBackText;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.threadtest);

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);

		BackThread thread = new BackThread();
		thread.setDaemon(true);
		thread.start();
	}

	public void mOnClick(View v) {
		mMainValue++;
		mMainText.setText("MainValue : " + mMainValue);
	}

	class BackThread extends Thread {
		public void run() {
			while (true) {
				mBackValue++;
				runOnUiThread(new Runnable() {
					public void run() {
						mBackText.setText("BackValue : " + mBackValue);
					}
				});
				try { Thread.sleep(1000); } catch (InterruptedException e) {;}
			}
		}
	}
}

Activity 외부에 Thread가 선언된 좀 더 일반적인 경우

public class HandlerTest extends Activity {
	int mMainValue = 0;
	TextView mMainText;
	TextView mBackText;
	BackThread mThread;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.threadtest);

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);
		mThread = new BackThread(mHandler);
		mThread.setDaemon(true);
		mThread.start();
	}

	public void mOnClick(View v) {
		mMainValue++;
		mMainText.setText("MainValue : " + mMainValue);
	}

	Handler mHandler = new Handler() {
		public void handleMessage(Message msg) {
			if (msg.what == 0) {
				mBackText.setText("BackValue : " + msg.arg1);
			}
		}
	};
}

class BackThread extends Thread {
	int mBackValue = 0;
	Handler mHandler;

	BackThread(Handler handler) {
		mHandler = handler;
	}

	public void run() {
		while (true) {
			mBackValue++;
			Message msg = new Message();
			msg.what = 0;
			msg.arg1 = mBackValue;
			mHandler.sendMessage(msg);
			try { Thread.sleep(1000); } catch (InterruptedException e) {;}
		}
	}
}

지금 까지 예제와 다른점은 분리된 파일에 각각의 코드가 정의 되었다고 가정하므로
BackThread Class의 생성자를 통해서 main activity handler를 넘겨 주어야 한다는 점이다.
Message에도 좀 더 구체적으로 값을 넣어서 보내줘야 Main Activity Handler에서 처리가 가능하다.

성능향상을 위해서 obtain함수를 이용해서 message를 생성 할 수 있다.
Cache에 있는 이전에 생성한 Message를 재사용하는 방식이다.

class BackThread extends Thread {
	int mBackValue = 0;
	Handler mHandler;

	BackThread(Handler handler) {
		mHandler = handler;
	}

	public void run() {
		while (true) {
			mBackValue++;
			Message msg = Message.obtain(mHandler, 0, mBackValue, 0);
			mHandler.sendMessage(msg);
			try { Thread.sleep(1000); } catch (InterruptedException e) {;}
		}
	}
}

Looper (루퍼)

서로 다른 thread는 message를 이용해서 통신한다.

  • Message Queue: 메시지가 차곡차곡 쌓이는 것을 말한다.
  • Looper: 큐에서 메시지를 꺼내 핸들러로 전달하는 것을 담당한다. 단, MainThread는 기본적으로 Looper를 가지고 있기 때문에 따로 구현할 필요는 없다. WorkingThread의 경우에만 직접 구현하면 된다.
  • Handler: 메시지를 받기도하고 보내기도 한다.

응용프로그램을 시작할 때 생성되는 메인 스레드는 루퍼를 가지고 있으며 이미 동작하므로 이 경우는 루퍼나 메시지 큐의 존재에 대해 상세히 몰라도 상관없다. 핸들러를 만들어 놓고 전달되는 메시지를 처리하기만 하면 된다.

루퍼를 직접 프로그래밍해야 하는 경우는 작업 스레드가 메시지를 받아야 할 때이다.

백그라운드 스레드는 Looper가 없다. 백그라운드 스레드에서 기본 핸들러 생성자 Handler()를 사용하면 따라서 에러가 발생한다.
Can't create handler inside thread that has not called Looper.prepare라는 메시지가 나온다.

Looper가 실행하는 관련 함수는 다음과 같다.

static void prepare()
static void loop()
void quit()
  • prepare는 현재 스레드를 위한 루퍼를 준비한다. 내부적으로 메시지를 저장하는 MessageQueue를 생성하고 그외 메시지 전송에 필요한 조치를 한다.
  • loop는 큐에서 메시지를 꺼내 핸들러로 전달하는 루프를 생성 한다.
  • quit는 루프의 종료롤 나타낸다.

다음은 관련된 스레드와 루퍼를 구하는 메서드

Thread getThread()  
static Looper getMainLooper()  
static Looper myLooper() 
  • getThread: 루퍼와 연결된 스레드를 구한다.
  • getMainLooper: 응용프로그램 수준에서의 주 루퍼를 반환
  • myLooper: 현재 스레드의 루퍼를 구한다. 모든 스레드가 루퍼가 있는것은 아니므로 null이 리턴될 수도 있다.

다음 예제 코드는 메인 스레드와 작업 스레드가 메시지를 통해 양방향으로 통신하는 예를 보여준다.
메인에서 메시지를 보내 연산을 시키고 스레드가 연산 결과를 리턴할 때도 메시지를 사용한다.

Main Thread와 Working Thread 모두 Looper가 존재하는 코드

public class LooperTest extends Activity {
	int mMainValue = 0;
	TextView mMainText;
	TextView mBackText;
	EditText mNumEdit;
	CalcThread mThread;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.loopertest);

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);
		mNumEdit = (EditText)findViewById(R.id.number);

		mThread = new CalcThread(mHandler);
		mThread.setDaemon(true);
		mThread.start();
	}

	public void mOnClick(View v) {
		Message msg;
		switch (v.getId()) {
		case R.id.increase:
			mMainValue++;
			mMainText.setText("MainValue : " + mMainValue);
			break;
		case R.id.square:
			msg = new Message();
			msg.what = 0;
			msg.arg1 = Integer.parseInt(mNumEdit.getText().toString());
			mThread.mBackHandler.sendMessage(msg);
			break;
		case R.id.root:
			msg = new Message();
			msg.what = 1;
			msg.arg1 = Integer.parseInt(mNumEdit.getText().toString());
			mThread.mBackHandler.sendMessage(msg);
			break;
		}
	}

	Handler mHandler = new Handler() {
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case 0:
				mBackText.setText("Square Result : " + msg.arg1);
				break;
			case 1:
				mBackText.setText("Root Result : " + ((Double)msg.obj).doubleValue());
				break;
			}
		}
	};
}

class CalcThread extends Thread {
	Handler mMainHandler;
	Handler mBackHandler;

	CalcThread(Handler handler) {
		mMainHandler = handler;
	}

	public void run() {
		Looper.prepare();
		mBackHandler = new Handler() {
			public void handleMessage(Message msg) {
				Message retmsg = new Message();
				switch (msg.what) {
				case 0:
					try { Thread.sleep(200); } catch (InterruptedException e) {;}
					retmsg.what = 0;
					retmsg.arg1 = msg.arg1 * msg.arg1;
					break;
				case 1:
					try { Thread.sleep(200); } catch (InterruptedException e) {;}
					retmsg.what = 1;
					retmsg.obj = new Double(Math.sqrt((double)msg.arg1));
					break;
				}
				mMainHandler.sendMessage(retmsg);
			}
		};
		Looper.loop();
	}
}
  • 메인스레드 작업스레드의 핸들러를 객체를 통해서 바로 호출한다.
  • 작업스레드 메인스레드의 핸들러를 객체 생성시 생성자를 통해서 받아서 그것을 통해서 호출한다.

중요한점은 이러한 메시지기반 통신 방법은 스레드 간의 통신 장치일 뿐이다. 그 이상인 응용프로그램간의 통신에는 사용할 수 없다.
공식적으로 응용프로그램 레벨에서의 통신은 Intent BR을 사용해야 한다.

작업 스케줄링

메시지큐

메시지큐에 메시지를 전달하고 원하는 시간에 꺼내서 처리 될 수 있도록 한다.

// 절대 시간
boolean sendMessageAtTime (Message msg, long uptimeMillis)
boolean postAtTime (Runnable r, long uptimeMillis)

// 상대 시간
boolean sendMessageDelayed (Message msg, long delayMillis)
boolean postDelayed (Runnable r, long delayMillis)

아래와 같은 예제를 만들 수 있다.

// 러너블 전달 
public class Post extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.upload);
	}

	public void mOnClick(View v) {
		new AlertDialog.Builder(this)
		.setTitle("질문")
		.setMessage("업로드 하시겠습니까?")
		.setPositiveButton("", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int whichButton) {
				mHandler.postDelayed(new Runnable() {
					public void run() {
						doUpload();
					}
				},10);
			}
		})
		.setNegativeButton("아니오", null)
		.show();
	}

	Handler mHandler = new Handler();

	void doUpload() {
		for (int i = 0; i < 20; i++) {
			try { Thread.sleep(100); } catch (InterruptedException e) {;}
		}
		Toast.makeText(this, "업로드를 완료했습니다.", 0).show();
	}
}

위 예제는 Handler의 메시지 큐에 메시지를 전달 하는 것이다.
Runnable로 전달 하기 때문에 따로 Handler내부를 구현할 필요는 없다.

//* 뷰의 postDelayed 호출
public class Post extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.upload);
	}

	public void mOnClick(View v) {
		new AlertDialog.Builder(this)
		.setTitle("질문")
		.setMessage("업로드 하시겠습니까?")
		.setPositiveButton("", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int whichButton) {
				Button btnUpload = (Button)findViewById(R.id.upload);
				btnUpload.postDelayed(new Runnable() {
					public void run() {
						doUpload();
					}
				},10);
			}
		})
		.setNegativeButton("아니오", null)
		.show();
	}

	void doUpload() {
		for (int i = 0; i < 20; i++) {
			try { Thread.sleep(100); } catch (InterruptedException e) {;}
		}
		Toast.makeText(this, "업로드를 완료했습니다.", 0).show();
	}
}

위 예제는 더 재밌는 것으로 View에 있는 스레드 큐를 사용 한다.
메인 View에 메시지큐가 있다는 것이다.

ANR (Application Not Responding)

Application Not Reponsding (ANR)이 발생하는 조건은 아래 두 가지이다.

  • 응용프로그램이 5초 이상 사용자의 입력에 반응하지 않을 때
  • 브로드캐스트 리시버 (BR)가 10초 내로 리턴하지 않을 때
public class ANR2 extends Activity {
	boolean bUploading = false;
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.anr);
	}
	
	public void mOnClick(View v) {
		switch (v.getId()) {
		case R.id.btnincrease:
			TextView textCounter=(TextView)findViewById(R.id.txtcounter);
			int count = Integer.parseInt(textCounter.getText().toString());
			textCounter.setText(Integer.toString(count + 1));
			break;
		case R.id.btnupload:
			if (bUploading) return;
			Thread uploadThread = new Thread() {
				public void run() {
					doUpload();
					mCompleteHandler.sendEmptyMessage(0);
				}
			};
			bUploading = true;
			uploadThread.start();
			break;
		}
	}

	public Handler mCompleteHandler = new Handler() {
		public void handleMessage(Message msg) {
			bUploading = false;
			Toast.makeText(ANR2.this, "업로드를 완료했습니다.", 0).show();
		}
	};
	
	void doUpload() {
		for (int i = 0; i < 100; i++) {
			try { Thread.sleep(100); } catch (InterruptedException e) {;}
		}
	}
}

위 코드는 두 가지로 나눠 지는데
첫 번째는 장기간 걸리는 작업을 Thread를 생성해서 doUpload()를 호출 했다는 점이다.
두 번째는 최종적인 결과 메시지 반환을 main thread의 handler호출로 처리 했다는 점이다.

StricMode

ANR 디버깅을 위해서 사용 하는 것으로 재미있는 기능 같다.

LogTime working을 Progress bar로 표현 하기

public class LongTime4 extends Activity {
	int mValue;
	TextView mText;
	ProgressDialog mProgress;
	boolean mQuit;
	UpdateThread mThread;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.longtime);

		mText=(TextView)findViewById(R.id.text);
	}
	
	@SuppressWarnings("deprecation")
	public void mOnClick(View v) {
		mValue = 0;
		showDialog(0);
		mQuit = false;
		mThread = new UpdateThread();
		mThread.start();
	}

	@SuppressWarnings("deprecation")
	protected Dialog onCreateDialog(int id) {
		switch (id) {
		case 0:
			mProgress = new ProgressDialog(this);
			mProgress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
			mProgress.setTitle("Updating");
			mProgress.setMessage("Wait...");
			mProgress.setCancelable(false);
			mProgress.setButton("Cancel", new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int whichButton) {
					mQuit = true;
					dismissDialog(0);
				}
			});
			return mProgress;
		}
		return null;
	}

	Handler mHandler = new Handler() {
		@SuppressWarnings("deprecation")
		public void handleMessage(Message msg) {
			mValue = msg.arg1;
			mText.setText(Integer.toString(mValue));
			if (mValue < 100) {
				mProgress.setProgress(mValue);
			} else {
				mQuit = true;
				dismissDialog(0);
			}
		}
	};

	class UpdateThread extends Thread {
		public void run() {
			while (mQuit == false) {
				mValue++;
				Message msg = mHandler.obtainMessage();
				msg.arg1 = mValue;
				mHandler.sendMessage(msg);
				try { Thread.sleep(50); } catch (InterruptedException e) {;}
			}
		}
	}
}

AsyncTask

백그라운드 작업을 하기 위해서는 스레드, 핸들러 등을 각각 만들어야 하고 작업 중에 핸들러를 주기적으로
호출해야 하는 번거로움이 있다.

이것을 간편하게 해주는 Helper 함수 AsyncTask이다.
AsyncTask는 내부적으로 작업 스레드를 생성하며 필요할 때마다 UI 스레드에서 실행되는 콜백 메서드를 호출한다.
콜백 메서드에서 정해진 작업만 처리하면 간단하게 백그라운드 작업을 수행할 수 있다.

AsyncTask자체는 추상 클래스이므로 파생 클래스를 생성하고 콜백 메서드를 구현해야 한다.

Generic parameters를 사용한다.

  • Params: 실행할 때 전달할 인수의 타입이다. 즉 배경 작업거리이다.
  • Progress: 매 작업 단계마다 진행 상태를 표기하는 타입이다.
  • Result: 작업의 결과로 리턴될 타입이다.

콜백 함수들은 아래와 같다.
doInBackground 메서드만 wroking thread에서 수행된다.
나머지 모든 메서드들은 Main UI 스레드에서 실행 된다.

  • void onPreExecute()
    • 작업이 시작되기 전에 호출되며 UI 스레드에서 실행 된다.
    • 계산을 위한 초기화나 프로그래스 대화상자를 준비하는 등의 작업을 수행한다.
  • Result doInBackground(Params... params)
    • 배경 작업을 수행하며 분리된 스레드에서 실행된다.
    • execute 메서드로 전달한 작업거리가 params 인수로 전달되는데 여러 개의 인수를 전달할 수 있으므로 배열이다.
    • 하나의 인수만 필요하다면 params[0]만 사용하면 된다.
    • 작업중에 publishProgress 메서드를 호출하여 작업 경과를 UI 스레드로 보고한다.
    • 최종적으로 작업된 결과를 Result 타입으로 리턴한다.
  • void onProgressUpdate(Progress... values)
    • doInBackground에서 publishProgress 메서드를 호출할 때 작업 경과 표시를 위해 호출되며 UI 스레드에서 실행된다. 프로그래스바에 진행 상태를 표시하는 역할을 한다.
  • void onPostExecute(Result result)
    • 백그라운드 작업이 끝난 후 UI 스레드에서 실행된다. 인수로 작업의 결과가 전달되는데 취소되었거나 예외가 발생했으면 null을 리턴 한다.
  • void onCancelled()
    • cancel 메서드로 작업을 취소했을 때 호출되며 UI 스레드에서 실행 된다.

아래의 메서드들은 만드시 UI 스레드에서 호출해야 하는 것들이다.

  • AsyncTask <Params, Progress, Result> execute (Params... params)
    • 인수로 작업거리에 해당하는 값을 넘겨주되 같은 타입의 인수 여러 개를 동시에 넘길 수 있다.
    • 이 호출에 의해 AsyncTask의 각 메서드가 순서대로 호출되어 배경 작업을 수행한다.
    • 스레드 내에서 적당한 때에 콜백 메서드를 호출하므로 UI 스레드에서 콜백 메서드를 직접 호출해서는 안 된다.
    • 리턴되는 AsyncTask 객체는 메인 스레드에서 참조 가능하며 다음 메서드로 작업을 관리한다.
  • boolean cancel (boolean mayIntteruptIfRunning)
  • boolean isCancelled()
    • 작업을 취소하라고 지시한다.
    • 작업 취소가 항상 성공적이지 않으며 실패할 수도 있는데 이미 완료되었거나 취소되었을 때 또는 기타의 이유로 취소할 수 없는 경우도 있다. 만약 작업을 아직 시작하기 전이라면 취소는 성공한 것으로 평가된다.
    • 인수는 작업을 실행중인 스레드를 중지할 것인가 지정한다.
    • isCancelled 메서드는 정상 종료되지 않고 취소되었는지 조사한다.
  • Result get ([long timeout, TimeUnit unit])
    • 작업이 완료되기까지 대기하며 작업 결과를 돌려 받는다.
    • 필요할 경우 대기할 타임아웃값을 지정한다.
  • AsyncTask.Status getStatus()
    • 작업의 현재 상태를 조사한다.
    • 아직 시작하지 않은 상태이면 PENDING이 리턴되며 이미 실행중이면 RUNNING, 작업이 완료되었으면 FINISHED가 리턴된다.

전체적인 메서드 실행 흐름은 아래와 같다.

AsyncTask의 작성 코드는 아래와 같다.

package andexam.ver6.c19_thread;

import andexam.ver6.*;
import andexam.ver6.c19_thread.LongTime4.*;
import android.app.*;
import android.content.*;
import android.os.*;
import android.view.*;
import android.widget.*;

public class LongTime5 extends Activity {
	int mValue;
	TextView mText;
	ProgressDialog mProgress;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.longtime);

		mText = (TextView)findViewById(R.id.text);
	}
	
	public void mOnClick(View v) {
		new AccumulateTask().execute(100);
	}

	class AccumulateTask extends AsyncTask<Integer, Integer, Integer> {
		@SuppressWarnings("deprecation")
		protected void onPreExecute() {
			mValue = 0;
			mProgress = new ProgressDialog(LongTime5.this);
			mProgress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
			mProgress.setTitle("Updating");
			mProgress.setMessage("Wait...");
			mProgress.setCancelable(false);
			mProgress.setProgress(0);
			mProgress.setButton("Cancel", new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int whichButton) {
					cancel(true);
				}
			});
			mProgress.show();
		}
		
		protected Integer doInBackground(Integer... arg0) {
			while (isCancelled() == false) { 
				mValue++;
				if (mValue <= 100) {
					publishProgress(mValue);
				} else {
					break;
				}
				try { Thread.sleep(50); } catch (InterruptedException e) {;}
			}
			return mValue;
		}
		
		protected void onProgressUpdate(Integer... progress) {		 
			mProgress.setProgress(progress[0]);	 
			mText.setText(Integer.toString(progress[0]));	 
		}
		
		protected void onPostExecute(Integer result) { 
			mProgress.dismiss();
		}
		
		protected void onCancelled() {
			mProgress.dismiss();
		}
	}
}

결국 실행 흐름과 코드를 가지고 이해해 보면,
메인 스레드와 작업 스레드를 알아서 안드로이드 시스템이 스위칭하면서 백그라운드 작업과 UI 갱신을 순서대로 실행하는 구조이다.

결국 기존 방식과 유사하되 각 단계에서 해당하는 부분이 AsyncTask의 콜백 메서드로 정형화되어 있다는 점만 다르다.
doInBackground메서드는 작업 스레드에 해당하며 onProgressUpdate는 핸들러에 해당하는 셈이다.
doInBackground내부에서 publishProgress를 실행함으로써 Progress Bar Update한다.

대화상자를 클래스 내부에서 Cancel버튼을 추가하여 생성 한다.

스레드와 핸들러를 직접 만들지 않아도 된다는 점에서 구조가 깔끔해 보이지만 약간 이해하는데 어려움이 있을 수도 있다.
전달 가능한 인수의 개수는 제한이 없지만 타입이 하나로 고정되어 범용성에 약간 제약이 있다.

하지만 많은 Open app code에서 해당 기능으로 구현을 많이 하므로 남의 코드를 이해하기 위해서는 익숙해질 필요가 있다.

참고문헌

안드로이드 정복 4판, 김상


'Computer Science > Android Application' 카테고리의 다른 글

Service  (0) 2017.08.16
Activity  (0) 2017.08.16
이벤트 (Event)  (0) 2017.08.04
Gradle  (0) 2016.06.03
Android Plot Libraries  (0) 2016.01.22

이벤트 (Event)


이벤트를 받아서 처리하는 각각의 방법들에 대해서 다룬다.

1. 콜백 메서드 재정의

가장 쉬운 방법으로 상속을 받아서 해당 callback함수를 재정의해서 사용하는 방식이다.

단점

  • 메서드 재정의하기 위해 반드시 슈퍼 클래스를 상속받아야 한다. MyButton등을 매번 만들어야 하는 번거로움이 있다.
  • 모든 이벤트에 대해서 콜백 메서드가 존재하는 것은 아니다.

Code

//* 1.핸들러 메소드 재정의 - 상속을 받아야만 재정의 가능하다.
public class HandleEvent extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		View vw = new MyView(this);
		setContentView(vw);
	}

	class MyView extends View {
		public MyView(Context context) {
			super(context);
		}

		public boolean onTouchEvent(MotionEvent event) {
			super.onTouchEvent(event);
			if (event.getAction() == MotionEvent.ACTION_DOWN) {
				Toast.makeText(HandleEvent.this,"Touch Event Received",
						Toast.LENGTH_SHORT).show();
				return true;
			}
			return false;
		}
	}
}

2. 리스너 인터페이스 구현

3. 액티비티가 리스너 구현

4. 뷰가 리스너 구현

5. 익명 내부 클래스 사용

2번 째 방법에서 파생된 기법으로 리스너 하나를 위해 클래스를 일일이 선언하기 번거롭다는 점을 해결한 것이다.
TouchListenerClass onTouch 메서드 구현을 위한 것이며 오로지 TouchListener객체 하나만을 위해 선언한 것이다.
더 이상의 메서드를 추가할 필요도 없고 객체를 두 개 이상 만들 필요도 없다.

객체 지향 언어이다 보니 메서드가 홀로 존재할 수 없으며 클래스 안에 넣어야 하고 객체를 생성한 후 등록하는 번거러운 과정을 거쳐야 한다.
자바는 이런 경우를 위해 언어 차원에서 익명 내부 클래스라는 문법을 제공 한다.
상위 클래스나 인터페이스의 메서드 하나를 재정의하기 위해 클래스를 선언하는 경우, 그리고 그 클래스의 객체가 단하나만 필요한 경우는 굳이 클래스를 선언할 필요 없이 상속과 재정의를 동시에 할 수 있다.

익명 클래스의 구현 예

public class HandleEvent extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		View vw = new View(this);
		vw.setOnTouchListener(TouchListener);
		setContentView(vw);
	}

	View.OnTouchListener TouchListener = new View.OnTouchListener() {
		public boolean onTouch(View v, MotionEvent event) {
			if (event.getAction() == MotionEvent.ACTION_DOWN) {
				Toast.makeText(HandleEvent.this,"Touch Event Received",
						Toast.LENGTH_SHORT).show();
				return true;
			}
			return false;
		}
	};
}

인터페이스의 인스턴스인 것 처럼 보이지만 실제로는 그렇지 않다.
인터페이스 자체는 구현이 없으므로 객체를 생성하지 못하기 때문이다.

익명 내부 클래스를 정의하는 문법을 간략하게 소개하면 다음과 같다.

# 축약된 코드
Interface obj = new Interface(){
	메서드 구현
};

6. 익명 내부 클래스의 임시 객체 사용

public class HandleEvent extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		View vw = new View(this);
		vw.setOnTouchListener(new View.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				if (event.getAction() == MotionEvent.ACTION_DOWN) {
					Toast.makeText(HandleEvent.this,"Touch Event Received",
							Toast.LENGTH_SHORT).show();
					return true;
				}
				return false;
			}
		});
		setContentView(vw);
	}
}

5번과의 개념적 차이는 아래와 같다.

# 이름 있는 객체 사용
Class obj = new Class();
Method(obj);

# 임시 객체 사용
Method (new Class());

임시 객체를 사용하면 클래스 선언, 객체 생성문이 필요 없으므로 액티비티가 생성될 때인 onCreate에서 이벤트 등록 및 구현이 모두 가능하여 코드가 아주 짧아진다.

7. Java8에서의 Lambda를 이용한 방법

https://mayojava.github.io/android/java/using-java8-lambda-expressions-in-android/


'Computer Science > Android Application' 카테고리의 다른 글

Activity  (0) 2017.08.16
쓰레드 (Thread)  (0) 2017.08.04
Gradle  (0) 2016.06.03
Android Plot Libraries  (0) 2016.01.22
Android wear app  (0) 2015.08.25

String


Reverse

input = list("abcdefg")

mid = int(len(input)/2)
left = 0
right = len(input)-1

while left < right:
    input[left], input[right] = input[right], input[left]
    left += 1
    right -= 1

print(input)

Super Reduced String

  • pair string을 줄이는 작업을 수행 한다.
def super_reduced_string(S):
    LS = list(S)
    i = 0
    while i < len(LS)-1:
        if LS[i]==LS[i+1]:
            del LS[i]
            del LS[i]
            i = 0
            if len(LS) == 0:
                return 'Empty String'
        else:
            i+=1
    return ''.join(LS)

CamelCase

eBay, iPhone처럼 중간에 띄어쓰기 없이 대소문자로만 단어를 구분하는 것을 말한다.

이렇게 표시된 unique한 단어가 몇개인지 찾아서 출력 해야 한다.

#!/bin/python3

import sys
import string

s = input().strip()

sl = list(s)

i = 0
count = 1
while i < len(sl):
    if sl[i].isupper():
        count += 1
    i += 1

print(count)

한줄 코드

print(sum(map(str.isupper, input())) + 1)

Sliding Window

https://scipher.wordpress.com/2010/12/02/simple-sliding-window-iterator-in-python/

Two Characters

번갈아 가면서 두개의 서로다른 character를 배열 해야 한다.

  • (o) xyxyx or yxyxy
  • (x) xxyy or xyyx

들어온 입력에 대해서 가능한한 가장 긴 두개의 charater로만 생성 가능한 그리고 번갈아 가면서(alternating)으로 만들 수 있는 string을 반환하는게 목적이다.

불가능 하다면 0을 리턴 한다.

예제

10
beabeefeab

해설

  • If we delete e and f, the resulting string is babab. This is a valid as there are only two distinct characters (a and b), and they are alternating within the string.
  • If we delete a and f, the resulting string is bebeeeb. This is not a valid string because there are three consecutive e's present.
  • If we delete only e, the resulting string is babfab. This is not a valid string because it contains three distinct characters.
  • Thus, we print the length of babab, which is , as our answer.
import sys

def validate(inp):
    for i in range(len(inp)-1):
        if inp[i+1]==inp[i]:
            return False
    return True    
    

s_len = int(input().strip())
s = input().strip()

ans = 0
chtoindex = []
for ch in set(s):
    chtoindex.append((ch, len([j for j, x in enumerate(s) if x == ch])))
    

for i, pack in enumerate(chtoindex[:-1]):
    char_i, lenchar_i = pack[0], pack[1]
    for j, otherpack in enumerate(chtoindex[i+1:]):
        char_j, lenchar_j = otherpack[0], otherpack[1]
        if abs(lenchar_i-lenchar_j)<2:
            c = [cha for cha in s if cha is char_i or cha is char_j]
            if validate(c):
                ans = max(ans, lenchar_j+lenchar_i)
print(ans)

참고자료

문자열관련 Python 유용함수 모음 블로그


'Computer Science > Coding Interview' 카테고리의 다른 글

Sorting  (0) 2017.08.06
Graph Search  (0) 2017.08.06
HackerRank- Trees: Is This a Binary Search Tree  (0) 2017.07.31
Binary Search Tree  (0) 2017.07.31
HackerRank Queues: A Tale of Two Stacks  (0) 2017.07.30

HackerRank- Trees: Is This a Binary Search Tree


해당 트리가 이진 탐색 트리인지 확인 하는 문제이다.

단순히 특정 노드의 자식들 값이 부모보다 작은지 큰지를 가지고 판단할 경우 아래의 경우에 문제가 발생 한다.

이유는 4의 경우 root 보다 큰대도 불구하고 자식 노드의 자식으로 분류되어 있다.
즉, 더 윗 부분의 부모 노드에서 판단하는 부분이 제외 되기 때문에 문제가 발생 한다.

방법1

  • 재귀적으로 최소 최대 값을 확인하며 루프를 도는 방법
  • 일단 데이터에 음수가 없다는 가정에서 출발 한다.
def checkBST(root):
    return(check_in_order(root,[-1]))
    
def check_in_order(root,prev):
    result = True
    if root.left is not None:
        result &= check_in_order(root.left,prev)
    if prev[0] >= root.data:
        return False
    prev[0] = root.data
    if root.right is not None:
        result &= check_in_order(root.right,prev)
    return result

방법2

  • Inorder traversal을 해서 확인하는 방법.
  • 올바른 Binary Search라면 출력이 순차적이게 된다.
  • 해당 문제에서 보면 중독된 노드는 이진탐색트리로 허용하지 않으니 그 문제에 대한 해결을 필요로 한다.
""" Node is defined as
class node:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None
"""

def checkBST(root):
    # traversal by inOrder 
    resList = []
    resList = inOrder(root,resList)
    sortedList = sorted(resList)
    
    if len(set(resList)) != len(resList):
        return False
    
    if len([i for i, j in zip(resList, sortedList) if i == j]) == len(resList):
        return True
    else:
        return False
    
def inOrder(node,resList):
    if node.left is not None:
        resList = inOrder(node.left, resList)
    resList.append(node.data)
    if node.right is not None:
        resList = inOrder(node.right, resList)
    return resList


'Computer Science > Coding Interview' 카테고리의 다른 글

Graph Search  (0) 2017.08.06
String  (0) 2017.07.31
Binary Search Tree  (0) 2017.07.31
HackerRank Queues: A Tale of Two Stacks  (0) 2017.07.30
Stacks: Balanced Brackets  (0) 2017.07.30

Binary Search Tree

Traverse 방식

pre order traverse (depth-first traverse라고도함.)

  • visit the root
  • traverse the left subtree
  • traverse the right subtree

사용처는 byte-stream으로 tree 정보를 다른 곳으로 전송 할 때 사용 한다.

in-order traverse (symmetric traverse)

  • visit left
  • visit parent
  • visit right

순차적으로 데이터를 출력 할 때 사용 한다.

post order traverse

  • visit left
  • visit right
  • visit root

노드를 지워야 할 때 사용한다.

구현 코드

생성한 트리의 모습은 아래와 같다.

class Node:

    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

    def insert(self, value):
        if value <= self.data:
            if self.left is None:
                self.left = Node(value)
            else:
                self.left.insert(value)
        elif value > self.data:
            if self.right is None:
                self.right = Node(value)
            else:
                self.right.insert(value)
        else:
            self.data = value

    def inOrder(self):
        if self.left is not None:
            self.left.inOrder()
        print(self.data)
        if self.right is not None:
            self.right.inOrder()

    def preOrder(self):
        print(self.data)
        if self.left is not None:
            self.left.preOrder()
        if self.right is not None:
            self.right.preOrder()

    def postOrder(self):
        if self.left is not None:
            self.left.postOrder()
        if self.right is not None:
            self.right.postOrder()
        print(self.data)

if __name__ == "__main__":
    char = None
    root = Node(4)
    root.insert(3)
    root.insert(6)
    root.insert(2)
    root.insert(1)
    root.insert(5)
    root.insert(7)
    root.insert(8)

    print("inOrder traversal:")
    root.inOrder()
    print("preOrder traversal:")
    root.preOrder()
    print("PostOrder traversal:")
    root.postOrder()

참고문헌

한글로 설명된 깔끔한 블로그
설명 잘 되어 있는 외국 사이트


'Computer Science > Coding Interview' 카테고리의 다른 글

String  (0) 2017.07.31
HackerRank- Trees: Is This a Binary Search Tree  (0) 2017.07.31
HackerRank Queues: A Tale of Two Stacks  (0) 2017.07.30
Stacks: Balanced Brackets  (0) 2017.07.30
HackerRank Data Structure  (0) 2017.07.30

HackerRank Queues: A Tale of Two Stacks


입력

  • q, 쿼리의 숫자
  • 쿼리 타입, 1만 뒤에 추가적인 값이 따라온다.

쿼리 타입

  • 1: x를 enqueue 한다.
  • 2: dequeue
  • 3: print 1개만

이 challenge에서는 반드시 queue를 두개의 stack을 이용해서 구현하라고 한다.
이게 front에서 팝이랑 삽입이 모두 이뤄져야 하기 때문에 통상의queue랑은 다르다.

List를 이용한 직접 작성한 Queue

class MyQueue(object):
    def __init__(self):
        self.q = [] 
        self.right = 0
        
    def peek(self):
        if self.q:
            return self.q[0]
        
    def pop(self):
        if self.q:
            self.q.pop()
            self.right -= 1
                
    def put(self, value):        
        self.q.insert(self.right,value)
        self.right += 1

새로운 queue

Class Queue:
    def __init__(self):
        self.items = []

    def enqueue(self, item):
        self.items.insert(0, item)

    def dequeue(self):
        self.items.pop()

    def print_queue(self):
        print(self.items)

    def is_empty(self):
        return self.items == []

    def size(self):
        return len(self.items)

어려운 Test Case

10
1 76
1 33
2
1 23
1 97
1 21
3
3
1 74
3

정답

33
33
33

정답

class MyQueue(object):
    def __init__(self):
        self.old_to_new = []
        self.new_to_old = []
   
    def peek(self):
        if not self.new_to_old:
            while self.old_to_new:
                self.new_to_old.append(self.old_to_new.pop())
        val = self.new_to_old.pop()
        self.new_to_old.append(val)
        return val
       
    def pop(self):
        if not self.new_to_old:
            while self.old_to_new:
                self.new_to_old.append(self.old_to_new.pop())
        return self.new_to_old.pop()
       
    def put(self, value):
        self.old_to_new.append(value)


        
queue = MyQueue()
t = int(input())
for line in range(t):
    values = map(int, input().split())
    values = list(values)
    if values[0] == 1:
        queue.put(values[1])        
    elif values[0] == 2:
        queue.pop()
    else:
        print(queue.peek())


'Computer Science > Coding Interview' 카테고리의 다른 글

HackerRank- Trees: Is This a Binary Search Tree  (0) 2017.07.31
Binary Search Tree  (0) 2017.07.31
Stacks: Balanced Brackets  (0) 2017.07.30
HackerRank Data Structure  (0) 2017.07.30
HackerRank MergeSort Counting Inversions  (0) 2017.07.29

Stacks: Balanced Brackets


bracket은 (), {}, [] 이 세가지를 모두 포함한다.
이것을 매칭하는 문제이다.

구현코드

def is_matched(expression):
    stack = []
    for bracket in expression:
        if bracket in ['[', '{', "("]:
            stack.append(bracket)
        else:
            if stack:
                if bracket == ']':
                    leftBracket = stack.pop()
                    if leftBracket != '[':
                        return False
                elif bracket == '}':
                    leftBracket = stack.pop()
                    if leftBracket != '{':
                        return False
                elif bracket == ')':
                    leftBracket = stack.pop()
                    if leftBracket != '(':
                        return False
            else:
                return False
    return True

t = int(input().strip())
for a0 in range(t):
    expression = list(input().strip())
    if is_matched(expression) == True:
        print("YES")
    else:
        print("NO")

실행결과

# Input
3
{[()]}
{[(])}
{{[[(())]]}}

# Output
YES
NO
YES
  • 하지만 이 방법은 runtime issue를 발생 시킨다.

새로운 솔루션

def is_matched(expression):
    stack = []
    for bracket in expression:
        if bracket == '{':
            stack.append('}')
        elif bracket == '[':
            stack.append(']')
        elif bracket == '(':
            stack.append(')')
        else:
            if len(stack) == 0 or bracket != stack[-1]:
                return False
            stack.pop()
    return len(stack) == 0

t = int(input().strip())
for a0 in range(t):
    expression = input().strip()
    if is_matched(expression) == True:
        print("YES")
    else:
        print("NO")

Dictionary를 이용한 방법

Advantage

  • 다른 bracket을 필요로 하다면 쉽게 확장이 가능하다.
  • if-else 또는 switch case를 다중으로 작성할 필요가 없다.
  • O(1) call time을 가진다 (hash table이니 당연한 말이긴 하다).
def is_matched(expression):
    stack = []
    dicty = {'(':')', '[':']', '{':'}'}
    for x in expression:
        if x in dicty:
            stack.append(dicty[x])
        elif stack and x == stack[-1]:
            stack.pop()
        else:
            return False
            
    return not stack

t = int(input().strip())
for a0 in range(t):
    expression = input().strip()
    if is_matched(expression) == True:
        print("YES")
    else:
        print("NO")


'Computer Science > Coding Interview' 카테고리의 다른 글

Binary Search Tree  (0) 2017.07.31
HackerRank Queues: A Tale of Two Stacks  (0) 2017.07.30
HackerRank Data Structure  (0) 2017.07.30
HackerRank MergeSort Counting Inversions  (0) 2017.07.29
Binary Search  (0) 2017.07.28

HackerRank Data Structure


Stack

class Stack:
    def __init__(self):
        self.data = []

    # for print
    def __iter__(self):
        return self

    def __str__(self):
        values = [x for x in self]
        return ' -> '.join(values)

    def push(self, data):
        self.data.append(data)

    def pop(self):
        if len(self.data) is not 0:
            return self.data.pop()
        else:
            print("stack is empty")

    def print_stack(self):
        print(self.data)

if __name__ == "__main__":

    myStack = Stack()
    myStack.push(1)
    myStack.push(2)
    myStack.push(3)
    myStack.push(4)
    myStack.print_stack()

    print(myStack.pop())
    print(myStack.pop())

    myStack.print_stack()

실행 결과

[1, 2, 3, 4]
4
3
[1, 2]

Queue

class Queue:

    def __init__(self):
        self.items = []

    def enqueue(self, item):
        self.items.insert(0, item)

    def dequeue(self):
        self.items.pop()

    def print_queue(self):
        print(self.items)

if __name__ == "__main__":
    queue = Queue()
    queue.enqueue(1)
    queue.enqueue(2)
    queue.enqueue(3)
    queue.enqueue(4)
    queue.print_queue()
    queue.dequeue()
    queue.print_queue()

실행결과

[4, 3, 2, 1]
[4, 3, 2]

LinkedList

class Node(object):
    def __init__(self, data=None, next_node=None, prev_node=None):
        self.data = data
        self.next = next_node
        self.prev = prev_node

    def __str__(self):
        return str(self.data)

class LinkedList:
    def __init__(self, data=None):
        self.head = None
        self.tail = None
        if data is not None:
            self.insert(data)

    # for print
    def __iter__(self):
        current = self.head
        while current:
            yield current
            current = current.next

    def __str__(self):
        values = [str(x) for x in self]
        return ' -> '.join(values)

    def __len__(self):
        result = 0
        node = self.head
        while node:
            result += 1
            node = node.next
        return result

    def insert(self, data):
        if self.head is None:
            self.tail = self.head = Node(data)
        else:
            self.tail.next = Node(data)
            self.tail = self.tail.next
        return self.tail

    def delete(self, data):
        if self.head is None:
            print("It is empty!")
            return
        elif self.head.data is data:
            self.head = self.head.next
        else:
            current = self.head
            while current.next is not None:
                if current.data is data:
                    print("Delete the number: %d"%(data))
                    current.data = current.next.data
                    current.next = current.next.next
                    return
                current = current.next
            print("It is not founded")


if __name__ == "__main__":
    ll = LinkedList()
    ll.insert(1)
    ll.insert(2)
    ll.insert(3)
    ll.insert(4)
    ll.insert(5)
    print(ll)
    ll.delete(3)
    ll.delete(6)
    print(ll)

실행 결과

1 -> 2 -> 3 -> 4 -> 5
Delete the number: 3
It is not founded
1 -> 2 -> 4 -> 5


'Computer Science > Coding Interview' 카테고리의 다른 글

HackerRank Queues: A Tale of Two Stacks  (0) 2017.07.30
Stacks: Balanced Brackets  (0) 2017.07.30
HackerRank MergeSort Counting Inversions  (0) 2017.07.29
Binary Search  (0) 2017.07.28
HackerRank: Basic Problems  (0) 2017.07.25

+ Recent posts