임베디드/기반지식2015. 4. 22. 14:35


make


파일관리유틸리티로 각파일간의 종속관계를 파악하고 Makefile에 기술된대로 컴파일명령이나 쉘명령을 순차적으로 실행해주는것으로 빌드를 자동화하여 개발자의 수고를 덜어준다.

프로그램의 종속구조를 파악하고 관리가 용이해진다.

all : diary

diary : main.o calendar.o memo.o

gcc -W -Wall -o diary memo.o calendar.o main.o

main.o : main.c

gcc -W -Wall -c -o main.o main.c

calendar.o : calendar.c

gcc -W -Wall -c -o calendar.o calendar.c

memo.o : memo.c

gcc -W -Wall -c -o memo.o memo.c


첫번째 타겟 의 dependency인 diary로 이동 diary: 는 main, calendar, memo 디펜던시가 있는데 main.o calendar.o main.o가 생성이 되었는지 확인하고 안되어 있으면 해당 타겟을 먼저 실행한다.

해당 타겟이 모두 준비되면 gcc -W -Wall -o diary memo.o calendar.o main.o 가 실행되어 diary라는 실행파일을 만들어낸다.


빌드 및 실행하면..

leeyosam$ make

gcc -W -Wall -c -o main.o main.c

gcc -W -Wall -c -o calendar.o calendar.c

gcc -W -Wall -c -o memo.o memo.c

gcc -W -Wall -o diary memo.o calendar.o main.o

leeyosam$ ./diary 

function memo.

function calendar.


위 상태에서 calendar.c파일을 수정하면 이 파일만 다시 빌드 한 후 링크한다.

leeyosam$ make

gcc -W -Wall -c -o calendar.o calendar.c

gcc -W -Wall -o diary memo.o calendar.o main.o


Makefile기본 구조 및 규칙


CC    = gcc

target1 : dependency1 dependency2

command1

command2

1) 명령의 시작은 반드시 탭으로 시작된다.

2) 비어 있는 행은 무시된다.

3) #을 만나면 개행을 만날때까지 무시한다.

4) 행이 길어지면 \ 문자로 다음행에 이어서 기술이 가능하다.

diary : memo.o calendar.o\

main.o

==> diary : memo.o calendar.o main.o

5) ;는 명령행을 나눌때 사용한다.

target1 : dependency1 dependency2; command1

6) 종속항목이 없는 타겟도 가능하다

clean :

rm -rf *.0 target1 target2


매크로 규칙


1) 매크로명    = 매크로내용 형식

2) #은 주석의 시작

3) 여러행 기술시 \ 사용

4) 매크로 참조시에는 소괄호나 중괄호사용 가능하고 $를 앞에 붙이는데 쉘의 환경변수를 사용할때 중괄호를 사용하므로 해깔리지 않도록 소괄호사용이 권장된다.

NAME    = string

$(NAME)

5) 정의되지 않은 매크로는 빈문자(null)로 치환된다.

6) 중복정의하게 되면 마지막 값이 사용된다.

7) 매크로 정의시 이전에 정의된 매크로참조가 가능하다.

8) =, :=, +=, ?= 등 대입이 가능하다.

=  는  매크로정의 순서와 상관없이 매크로들을 모두 반영한다.

:= 는 매크로정의 순서 그대로 반영한다.

+= 는 기존 매크로에 추가한다.

?= 는 정의되지 않았으면 새로 반영하고 정의되어 있으면 반영하지 않는다.

A    = $(B) BB

B    = $(C) CC

C    = D

==> 이 경우 A는 D CC BB 가 된다.

a    := $(b) bb

b    := $(c) cc

c    := d

==> 이 경우 a 는 bb가 된다. 순차적으로 반영이 되므로 a를 정의할때의 bb가 b의 값이 새로 정의되더라도 a에 반영되지 않는다.


내부적으로 정의되어 있는 매크로의 사용


make -p명령으로 내부 매크로리스트를 확인할 수 있다. 미리 정의된 매크로들을 추가로 정의할 필요가 없다. 

많이 사용하는 매크로들은 다음 명령어로 확인 가능하다

make -p | grep ^[[:alpha:]]*[[:space:]]*=[[:space:]]

매크로 이름

설명

기본값

AR

아카이브 관리 프로그램

ar

AS

어셈블러

as

CC

C 컴파일러

cc

CXX

C++ 컴파일러

g++

CO

RCS checkout 프로그램

co

CPP

C 전처리기

cc E

FC

포트란 컴파일러

f77

GET

SCCS 관련 프로그램

get

LD

링크

ld

LEX

스캐너 코드 생성기

lex

PC

파스칼 컴파일러

pc

YACC

파서 코드 생성기

yacc

MAKEINFO

Texinfo 파일을 Info 파일로 변환

makeinfo

TEX

TeX 문서로부터 TeX DVI 생성

tex

TEXI2DVI

Texinfo 파일을 dvi 파일로 변환 프로그램

texi2dvi

WEAVE

Web TeX로 변환

weave

CWEAVE

C Web TeX로 변환

cweave

TANGLE

Web을 파스칼로 변환

tangle

CTANGLE

C Web C로 변환

ctangle

RM

파일을 지우는 명령

rm f

ARFLAGS

ar 플래그

rv

ASFLAGS

어셈블러 플래그

 

CFLAGS

C 컴파일러 플래그

 

CXXFLAGS

C++ 컴파일러 플래그

 

COFLAGS

RCS co 플래그

 

CPPFLAGS

C 전처리기 플래그

 

FFLAGS

포트란 컴파일러 플래그

 

GFLAGS

SCCS get 플래그

 

LDFLAGS

링크 플래그

 

LFLAGS

Lex 플래그

 

PFLAGS

파스칼 컴파일러 플래그

 

YFLAGS

Yacc 플래그

 


make -p로 확인불가능한 추가 내부 매크로

$?

현재의 타겟보다 최근에 변경된 종속 항목 리스트

(확장자 규칙에서 사용 불가)

$^

현재 타겟의 종속 항목 리스트

(확장자 규칙에서 사용 불가)

$@

현재 타겟의 이름

$<

현재 타겟보다 최근에 변경된 종속 항목 리스트

(확장자 규칙에서만 사용 가능)

$*

현재 타겟보다 최근에 변경된 현재 종속 항목의 이름(확장자 제외)

(확장자 규칙에서만 사용 가능)

$%

현재의 타겟이 라이브러리 모듈일 때 .o 파일에 대응되는 이름

${@F}현재 타겟의 파일 부분(target)

${<F}: 현재 디펜던시항목의 파일 부분(test.c)

${@D}: 현재 타겟의 디렉토리 부분(/temp)

${<D}: 현재 디펜던시항목의 디렉토리 부분(/usr/local/c)


$@와 $^는 다음과 같이 사용될 수 있다.

target : dependency1.c dependency2.c

             gcc -o target dependency1.c dependency2.c

==> 

target : dependency1.c dependency2.c

             gcc -o $@ $^


${@F}와 ${<F}, 그리고 ${@D}와 ${<D}는 다음 타겟과 디펜던시가 있을 경우

 /temp/target : /usr/local/c/test.c

==>

${@F} : target

${<F} : test.c

${@D} : /temp

${<D} : /usr/local/c


확장자 규칙의 사용


make -p로 출력되는 내용 중에 %.o : %c 가 있다.

%o : %c

$(COMPILE.c) $(OUTPUT_OPTION) $<

이는 *.o타겟이고 확장자가 c인 c 코드파일을 만나면 c -o $@ $<를 수행하라는것이다.

다음 Makefile은 맨처음 이 문서에서 작성한 Makefile과 동일한 동작을 수행한다.

OBJECTS    = memo.o calendar.o main.o

all : diary

diary : $(OBJECTS)

$(CC) -o $@ $^

OBJECTS에 정의된 dependency들을 처리하는데 타겟이 *.o이고 확장자가 *.c인것들을 만나게 된다. 이 때 make는 다음 순서로 처리하게 된다.

1) Makefile에서  diary를 생성하기 위해 make는 dependency들을 살펴보고 각 각을 타겟으로 설정한다.

2) diary는 memo.o에 의존하고 있고 memo.o는 만들어져 있지 않으면 memo.o를 만들어야 하는게 룰이 정의되어 있지 않다. 

3) 따라서 make는 내부 확장자 규칙 .o를 참조하여 다음 기준으로 현재 디렉토리에서 memo.o를 생성할 파일을 찾는다

- 확장자를 제외하고 memo.0와 같은 이름이 있어야 한다. memo.c가 해당된다.

- 중요확장자를 가지고 있어야 한다. .c, .cc, .cpp, .s, .S, ... 등

- 내부확장자 규칙에 따라 memo.o를 만드는데 사용할 수 있어야 한다. make -p로 확인해본바 %.o : %.c가 있기 떄문에 가능하다.

4) 내부정의 확장자 규칙에 따라 memo.o를 생성한다. cc -c -o memo.o memo.c 가 수행된다.


내부확장자 규칙을 변경하고자 하는 경우에는 .SUFFIXES 타겟으로 가능하다. 다음 Makefile예는 -DDEBUG 옵션을 추가하는 예제이다.

OBJECTS    = memo.o calendar.o main.o

.SUFFIXES : .o .c

$(CC) -DDEBUG -c -o $@ $<

all : diary

diary : $(OBJECTS)

$(CC) -o $@ $^



명령어 사용 규칙

echo :
@echo "echo test!"
위 명령어를 실행하면 새로운 쉘이 뜨고 echo명령이 실행된다고 함. 그런데 우분투와 맥에서 안그러던데???. 내가 참조한 책이 좀 옜날 얘기를 하는것인지 모르겠음.
무튼 주의할것은...
어떤 명령이 성공했을때만 다음 작업을 해야 하는 경우가 있을 수 있다.
del :
cd ./backup
rm -rf*
위 명령의 cd ./backup이 실패한다면 현재 폴더의 모든 내용이 삭제될것이다. 아마 머리를 쥐어잡고 울부짖게 되겠지...
이걸 피하기 위해서 아래와 같이 한다.
del :
cd ./backup && rm -rf*
&&은 명령이 성공하였는지를 추가로 검사한다.
명령의 성공이라함은 c의 main함수가 반환하는 수가 0인경우를 말한다.
c로 만든 프로그램에서 0반환은 성공을 의미하며 0이 아닌 수를 반환하는것은 어떤 에러가 났음을 의미한다.
리눅스에서 바로전에 호출한 명령이 반환한 값을 확인하고 싶으면 $?를 입력해보면 된다.
del :
cd ./backup; rm -rf* 
위 와 같이 하면 하나의 쉘에서 연달아 명령을 수행할 수 있다고 한다. 하지만 이것도 우분투와 맥에서 ; 없이도 하나의 쉘에서 잘 동작한다.

모든 명령에 대해 에코기능을 끄려면 .SILENT : 타겟을 정의한다. 이 타겟에 등록한 디펜던시들의 명령어들은 모두 에코기능이 꺼지게 된다.

make는 명령어가 에러를 발생시키면 중단시킨다. 그런데 에러가 나도 중단시키기 않고 빌드를 계속 진행하고자 한다면 - 를 명령어 앞에 넣으면 된다.
cat :
-cat dummy.txt

Makefile전체에서 명령어 오류를 무시하고자 한다면 .IGNORE : 타겟을 사용한다. 여기에 적시한 디펜던시들은 명령어가 오류가 나도 무시하고 진행된다.


재귀적 make사용

소스가 많아지면 여러 폴더로 소스를 나누어서 빌드하면 관리가 아주 유용하다.
재귀적 make말고 VPATH를 이용하는 방법도 있으나 파일명에 종속되기 때문에 재귀적 make가 더 효율적이도 더 많이 사용된다.

아래와 같이 소스파일이 분포 시켰다.

leeyosam$ tree

.

├── Makefile

├── calendar

│   ├── Makefile

│   └── calendar.c

├── include

│   └── diary.h

├── main

│   ├── Makefile

│   └── main.c

└── memo

    ├── Makefile

    └── memo.c


여기서 calendar, main,  memo폴더 내의 Makefile은 모두 아래와 같이 정의된다.

OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c))
CFLAGS = -I../include

all : $(OBJECTS)
cp -f $^ ../
clean :
rm -rf *.o

$(patsubst %.c, %.o, $(wildcard *.c)) 구문은 함수에서 다시 설명하겠지만 해석하자면 확장자가 c인파일을 찾아서 o로 바꾸는 동작을 수행한다.

그리고 최상위 위치의 Makefile은 아래와 같다.

DIRS = memo calendar main
OBJECTS = memo.o calendar.o main.o
TARGET = diary

all : objs
$(CC) -o $(TARGET) $(OBJECTS)

objs :
@for dir in $(DIRS); do \
make -C $$dir || exit $?; \
done
clean :
@for dir in $(DIRS); do \
make -C $$dir clean; \
done
-rm -rf *.o $(TARGET)

@for구문을 사용해서 단순화하였는데 DIRS에 기술한 폴더들을 루프를 돌면서 make를 수행하고 exit $? 이전 경로로 돌아가고 있다.
위와 같이 구성하면 소스폴더가 생길때마다 폴더를 추가하는것으로 끝나게 된다.

서브 Makefile에 대한 매크로 전달

최상위 Makefile에서 정의한 매크로를 서브 Makefile에서도 사용하고자 하는 경우가 있을것이다. 이 때는 export키워드를 사용한다.

export CC    = gcc

조건부 수행

C의 if else에 해당하는 구문
ifeq ($(CC),gcc)        # 반대는 ifneq
@echo "GNU GCC compiler.."
else
@echo "$(CC) compiler"
endif


함수의 사용


make는 Makefile에서 사용할 수 있는 여러 함수들을 제공한다.

$(shell 쉘명령어) : 쉘명령어를 수행하고 결과를 리턴한다.

SRCS = $(shell ls *.c)

echo :

@echo $(SRCS)

$(subst 찾을문자열, 변경할문자열, 목표문자열)  : 목표문자열에서 찾을문자열을 찾아서 변경할문자열로 변경한다.

STR    = $(subst like, LIKE, I like you)

==>  I LIKE you 를 찍게 된다.

$(patsubst 패턴, 변경문자열, 목표문자열) : subst와 다른점은 패턴을 발견하면 변경문자열로 변경한다. %기호가 사용될 수 있다.

STR    = $(patsubst %.c, %.o, memo.c main.c ABCD)

==> memo.o main.o ABCD

$(매크로명: 패턴=치환할문자열) : 매크로에서 패턴을 발견하면 변경한다.

MACRO    = memo.c main.c ABCD

STR       = $(MACRO:%.c=%.o)

$(sort 문자열) : 문자열의 인자들을 정렬하며 중복된것은 하나로 인식한다.

MACRO    = bbb aaa ccc aaa ddd

STR    = $(sort $(MACRO))

==> aaa bbb ccc ddd

$(strip 문자열) : 인자로 오는 문자열의 앞뒤의 공백을 제거하고 문자열내의 공백은 한칸으로 줄인다. 가령 'I love you.      ' 같은 문자열은 'I love you.'로 변경된다.

$(filter 패턴, 문자열) : 패턴과 일치하는 문자열만 걸러준다. 소스와 헤더파일이 섞여 있는 경우 소스와 헤더파일을 각각 읽어올때 유용하다.

FILES    = memo.c head.h mein.c asm.S diary.h

SRCS    = $(filter %c %S, $(FILES))

HEADERS    = $(filter %h, $(FILES))

$(filter-out 패턴, 문자열) : filter 의 반대

$(findstring 찾을 문자열, 대상문자열)

$(words 문자열) : 문자열내에서 사용된 단어들의 개수를 리턴한다.

$(word [n], 문자열) : 문자열내의 n번째 문자열을 리턴한다.

$(wordlist 시작번호, 끝번호, 문자열) : 문자열의 시작번호와 끝번호사이의 문자열을 리턴한다.

$(firstword 문자열) : 문자열에서 첫번째 단어를 리턴한다.

$(join 문자열, 문자열) : 앞뒤의 문자열을 합친다.

$(dir 문자열) : 문자열에서 디렉토리부분만 리턴한다.

PATHS    = /bin/ls /sbin/ifconfig memo/memo.c Makefile

echo : 

@echo $(dir $(PATHS))

==> /bin/ /sbin/ memo/ ./  

$(notdir 문자열) : 문자열에서 디렉토리부분만 제외하고 리턴한다.

==> ls i fconfig memo.c Makefile

$(suffix 문자열) : 문자열에서 확장자만 리턴한다.

FILES    = src/memo.c asm/asm.S for.f ls /etc/resolv.conf

echo :

@echo $(suffix $(FILES))

==> .c .S .f .conf

$(basename 문자열) : 문자열에서 점과 확장자를 제외한 순수한 파일명만 리턴한다.

==> src/memo asm/asm for ls /etc/resolv

$(addsuffix 접미사, 문자열) : 문자열에 접미사를 추가한다.

$(addprefix 접두사, 문자열) : 문자열에 접두사를 추가한다.

$(wildcard 패턴) : wildcard 패턴결과를 리턴한다.

$(foreach 변수명, 대입문자열, 확장문자열) : 변수명에 대입문자열을 단어별로 대입하여 넣고 그 변수를 ㅎ ㅘㄱ장문자열에서 사용한다.

SRCS    = $(foreach str, a b c, $(str).c)

echo :

@echo $(SRCS)

==> a.c b.c c.c

주로 다음과 같이 반복확장시 유용하다.

SRCS    = $(foreach dir, . memo main calendar, $(wildcard $(dir)/*.c))

echo :

@echo $(SRCS)

Posted by 삼스
카테고리 없음2015. 4. 20. 12:46


안드로이드에서 비트맵을 아주 많이 빈번하게 사용해야 하는 앱을 작성해야 할 경우에 참고할만한 소스가 있다. 

첨부의 소스는 HoneyComb gallery에서 추출한 소스인데 이미지캐시 및 리사이즈, 네트워크다운로드까지 지원하도록 설계되어 있다.

클래스는 아래와 같은 구조로 되어 있으며 이미지를 캐시해야 할 경우에 참고할만한 아주 좋은 레퍼런스이다.


util.zip


Posted by 삼스
Android2015. 4. 15. 16:50



  • 객체 생성을 피하라. 객체를 생성하는 작업은 다른 작업들에 비해 로드가 많이 걸리는 작업이며 가비지컬렉션대상이 많아지는것도 성능에 장애요소이다.
  • Native method를 활용하라. 자바코드보다 동일작업시 10~100배 빠르다. 따라서 복잡하고 오래걸리는 연산작업은 native method를 사용하라.
  • 인터페이스 타입참조보다 클래스타입참조를 더 선호하라. 임베디드환경에서는 인터페이스타입참조를 통해 virtual method를 호출하는것이 본래 객체에 대한 class type을 참조하는것보다 2배 느리다.

Map m = new HashMap();  // 인터페이스로 참조하여 참조연산이 더 일어남

HashMap m = new HashMap();  // 명시적으로 참조하므로 추가연산발생안함

  • 가상연결보다 정적연결을 선호하라. 객체필드에 접근할 필요가 없다면 메소드를 static으로 하면 더 빠르다. 가상메서드테이블에 접근하지 않기 때문.
  • 내부에서 getter/setter의 사용은 피하라.
  • 필드참조들을 캐시하라. 멤버변수에 접근하는것이 지역변수에 접근하는것보다 훨씬 속도가 느림. 메소드내에서 멤버젼수에 접근을 많이 하는 경우 로컬변수로 가져와서 처리하면 개이득
  • 상수를 final로 선언하라. static 멤버변수가 초기값을 가지고 있고 값이 변경될 필요가 없다면 final키워드를 사용하면 값을 참조할때 field lookup을 사용하지 않으므로 더 빠르다.
  • Enhanced for loop를 사용하라. for(type n : array) {} 형식의 구문을 말하며 iterator를 사용하는것 보다 일반적으로 빠르다.
  • Enum자료형을 피하라. 코드가독성이나 잘못값을 할당하는 경우를 컴파일시 에러가 발생함으로써 매우 편리하나 코드 크기와 속도면에서는 손해다. 코드사이즈의 크기도 성능에 악영향을 미친다.
  • 내부클래스를 갖는 클래스의 경우 멤버변수를 private대신에 package 접근자를 사용하는것이 좋다. private으로 하면 내부적으로 컴파일러가 접근메서드를 생성하여 해당 메서드를 통해서 접근하기 때문에 접근시마다 함수호출이 발생하여 성능에 영향을 미친다. 

  • Float을 피하라. 일부 모바일 단말의 경우 부동소수점 연산의 지원이 미비할 수 있다.
  • 안드로이드에서 제공하는 개선된 성능의 데이터구조

LruCache

SparseArray

SparseBooleanArray

SparseIntArray

Pair

  • 메인스레드에서 많은 작업을 하지 않아야 한다.

키/터치 이벤트 처리

뷰그리기

라이브사이클 관리

  • 레이아웃 단순 화 및 지연초기화 이용
  • LinearLayout보다는 RelativeLayout이 더 단순한 처리를 하게 됨.
  • ViewStub를 사용해서 객체생성을 지연시킨다.
  • onCreate, onResume등 lifecycle관련 메소드에서는 최소한의 작업만 수행해야 ANR을 피할 수 있으며 사용자의 성능에 대한 민감도를 피해갈 수 있다. 100ms~200ms가 사용지연에 대한 임계치라는 조사가 있다.
  • StrictMode 활용

앱플리케이션 개발동안 StrictMode를 사용함으로써 느려지거나 느려질 가능성이 큰 코드를 검출할 수 있다.

Policy와 penaly를 랩에서 코드레벨로 설정하여 사용가능하다. Policy는 정책을 penalty는 위반시 표시방법을 나타낸다.

Policy는 ThreadPolicy와 VmPolicy 두가지를 설정할 수 있다.

ThreadPolicy: 특정 스레드에 file, network 접근 부분을 감지하여 위반되는 위치를 잡아준다.

VmPolicy : Memory leak의 위치를 감지해 Log, Dialog, DropBox, Splash, 강제종료와 같은 다양한 방식으로 알린다.

ThreadPolicy

detectNetwork() : 네트워크 사용

detectDiskRead() : disk read

detectDiskWrite() : disk write

detectAll() : all

detectCustomSlowCell() : api11이수 StrictMode.noteSlowCall()가 선언된 부분을 감지함.

VmPolicy

detectActiveLeaks() : Activity 서브클래스의 릭감지

detectLeakedClosableObjects(): File/Network IO관련 API사용시 close를 명시적으로 호출안하고 종료할 경우 나오는 리소스 leak감지

setClassInstanceLimit() : 특정클래스가 힘에 생성되는 최대 개 수를 설정하여 object leak현상을 모니터링 할 수 있다.

Panalty

penaltyLog() : logcat으로 표시

panaltyDropBox() : DropBoxManagerService에 로그가 저장되며 정보가 detail하여 추천된다. adb shell dumpsys dropbox data_app_strict_mode —print > strict.txt 로 추출 후 꺼내서 별것을 추천한다.

panaltyDialog() : dialog창으로 표시

penaltyFlashScreen() : 화면상으로 빨간색 테두리의 사각형 splash로 보여준다.

penaltyDeathOnNetwork() : Main스레드에서 network access가 진행되면 무조건 어플리케이션이 강제종료된다. detectNetwork()가 활성화되어 있어야 한다.

Posted by 삼스
Android2015. 4. 13. 18:04


http://developer.android.com/training/displaying-bitmaps/index.html 의 요약임을 밝힌다.

앱사용성에 이슈를 발생시키지 않으면서 메모리제한까지 피할 수 있는 방법에 대해 정리한다. 피하지 않으면 그 유명한 OOM(Out of memory)에 손도 못쓰고 당하게 될것이다.

모바일디바이스는 시스템리소스가 제한적인데 16MB정도의 적은 메모리만이 애플리케이션에 허용되는데 반해 비트맵은 많은 메모리를 사용하게 된다. 그리고 안드로이드 UI는 종종 여러개의 비트맵을 동시에 사용하는 경우가 있다. 이런것들이 어쩔 수 없이 메모리 이슈를 발생시킨다.


아주큰 비트맵을 효율적으로 로딩하기

애플리케이션의 메모리 제한에 문제없이 비트맵을 로딩하는 방법.

이미지를 표시할 UI콤포넌트는 대체적으로 이미지보다 작게 표시된다. 이 때는 이미지 원본사이즈가 아니라 표시될 콤포넌트의 크기에 맞게 로드하는것이 이득이다.

BitmapFactory클래스가 비트맵을 디코딩하기 위한 여러 메소드(decodeByteArray(), decodeFile(), decodeResource())들을 제공한다. 이 메소드들은 비트맵을 디코딩할때 메모리할당을 시도하기 때문에 OOM이 아주 쉽게... 간단하게 발생될수 있다. 이 메소드들에는 BitmapFactory.Options 인자를 받을 수 있는데 inJustDecodeBounds를 true를 주어서 디코딩시 메모리 할당을 피하면서 이미지의 가로/세로 사이즈를 알아낼 수 있다. 이를 이용해서 메모리 할당을 조절할 수 있다.


BitmapFactory.Options options = new BitmapFactory.Options();

options.inJustDecodeBounds = true;

BitmapFactory.decodeResource(getResources(), R.id.myimage, options);

int imageHeight = options.outHeight;

int imageWidth = options.outWidth;

String imageType = options.outMimeType;


인자 스케일다운된 버전을 메모리에 로드해보자

이미지의 사이즈를 알기 때문에 이미지을 풀사이즈를 사용할지 사이즈를 다운할지 결정할 수 있다. BitmapFactory.Options의 inSampleSize를 true로 하며 디코더가 더작은 샘플로 디코딩하도록 할 수 있다.가령 2048x1536사이즈의 이미지를 inSampleSize 4를 주면 512x384사이즈로 로딩한다. 이는 12MB의 메모리 사용량을 0.75MB로 줄이는 효과가 있다.

다음 코드는 inSampleSize를 정하는 코드 예이다.

public static int calculateInSampleSize (BitmapFactory.Options options, int reqWidth, int reqHeight) {

    // Raw height and width of image

    final int height = options.outHeight;

    final int width = options.outWidth;

    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;

        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both

        // height and width larger than the requested height and width.

        while ((halfHeight / inSampleSize) > reqHeight

                && (halfWidth / inSampleSize) > reqWidth) {

            inSampleSize *= 2;

        }

    }

    return inSampleSize;

}


다음은 스케일을 적용한 비트맵로딩 예이다. inJustDecodeBounds를 통해 원본의 크기를 얻은 후 필요한 사이즈에 맞도록 스케일하여 비트맵을 생성하여 반환한다.

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
       
int reqWidth, int reqHeight) {

   
// First decode with inJustDecodeBounds=true to check dimensions
   
final BitmapFactory.Options options = new BitmapFactory.Options();
    options
.inJustDecodeBounds = true;
   
BitmapFactory.decodeResource(res, resId, options);

   
// Calculate inSampleSize
    options
.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

   
// Decode bitmap with inSampleSize set
    options
.inJustDecodeBounds = false;
   
return BitmapFactory.decodeResource(res, resId, options);
}


다음 메소드 호출은 100, 100사이즈에 맞도록 비트맵 이미지를 스케일하여 적용하는 예이다.

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource
(getResources(), R.id.myimage, 100, 100));


비동기로 비트맵 로딩하기


BitmapFactory.decodeXXX 류의 메소드들은 main UI스레드에서 호출되면 안된다. 성능이슈를 발생시킨다. 버벅거리게 될거고 앱 사용자들은 모두 떠날것이다. 

AsyncTask로 백그라운드에서 UI스래드와 분리되어 로딩할 수 있으며 이 때 동시에 수행시 발생하는 이슈에 대해서도 살펴보겠다.

AsyncTask는 별도의 스레드로 어떤 작업을 하고 그 결과를 UI thread에 반환할 수 있도록 해준다. 이 클래스를 extends하고 메소드를 오버라이드하면 되는데 다음 에제를 보자.

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
   
private final WeakReference<ImageView> imageViewReference;
   
private int data = 0;

   
public BitmapWorkerTask(ImageView imageView) {
       
// Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference
= new WeakReference<ImageView>(imageView);
   
}

   
// Decode image in background.
   
@Override
   
protected Bitmap doInBackground(Integer... params) {
        data
= params[0];
       
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
   
}

   
// Once complete, see if ImageView is still around and set bitmap.
   
@Override
   
protected void onPostExecute(Bitmap bitmap) {
       
if (imageViewReference != null && bitmap != null) {
           
final ImageView imageView = imageViewReference.get();
           
if (imageView != null) {
                imageView
.setImageBitmap(bitmap);
           
}
       
}
   
}
}

WeakReference로  ImageView를 처리하는 이유는 해당 메모리릭을 염두에 둔것인데 일반참조를 사용하게 되면 imageView의 참조가 유지되어 GC대상에서 제외될수 있기 때문이다.

** WeakReference로 참조하는 객체는 언제든 메모리에서 해제될수 있기 때문에 사용할 때 get()메소드로 참조를 얻어온 후 null인지 체크한 후 사용해야 한다.

다음 예제는 아주 단순하게 비트맵을 비동기로 새로운 테스크를 만들어서 실행하는 예이다.


public void loadBitmap(int resId, ImageView imageView) {
   
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task
.execute(resId);
}


ListView나 GridView는 childview를 재사용하는 로직으로 구현되어 있다. 이런 경우 AsyncTask로 로딩하게 되면 로딩이 완료되었을 때 view가 사용자의 스크롤로 인해 다른 뷰가 될 수 있다.

이 문제를 해결하기 위해서 Worker task를 갖는 Drawable객체를 사용하게 된다. 여기서는 BitmapDrawable를 사용하여 task가 완료되면 imageView에 이미지를 표시하게 된다.


static class AsyncDrawable extends BitmapDrawable {
   
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

   
public AsyncDrawable(Resources res, Bitmap bitmap,
           
BitmapWorkerTask bitmapWorkerTask) {
       
super(res, bitmap);
        bitmapWorkerTaskReference
=
           
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
   
}

   
public BitmapWorkerTask getBitmapWorkerTask() {
       
return bitmapWorkerTaskReference.get();
   
}
}


AsyncDrawable은 WorkerTask를 WeakReference로 갖고 있고 getter를 노출하고 있다. BitmapWorkerTask를 실행하기 전에 AsyncDrawable를 생성하여 ImageView에 바인드한다.

public void loadBitmap(int resId, ImageView imageView) {
   
if (cancelPotentialWork(resId, imageView)) {
       
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
       
final AsyncDrawable asyncDrawable =
               
new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
        imageView
.setImageDrawable(asyncDrawable);
        task
.execute(resId);
   
}
}


cancelPotentialWork메소드는 다른 테스크가 이미  imageview에 연관되어 있다면 이전 task를 취소한다. 동일한 테스크로 시도하게 되면 아무것도 하지 않는다.


public static boolean cancelPotentialWork(int data, ImageView imageView) {
   
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

   
if (bitmapWorkerTask != null) {
       
final int bitmapData = bitmapWorkerTask.data;
       
// If bitmapData is not yet set or it differs from the new data
       
if (bitmapData == 0 || bitmapData != data) {
           
// Cancel previous task
            bitmapWorkerTask
.cancel(true);
       
} else {
           
// The same work is already in progress
           
return false;
       
}
   
}
   
// No task associated with the ImageView, or an existing task was cancelled
   
return true;
}


getBitmapWorkerTask는 ImageView에 연결되어 있는 WorkerTask를 반환한다.


private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
   
if (imageView != null) {
       
final Drawable drawable = imageView.getDrawable();
       
if (drawable instanceof AsyncDrawable) {
           
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
           
return asyncDrawable.getBitmapWorkerTask();
       
}
   
}
   
return null;
}


마지막 작업은 onPostExecute에서 task가 취소되었는지 확인하고 ImageView가 유효한지 확인 후 표시한다.


class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
   
...

   
@Override
   
protected void onPostExecute(Bitmap bitmap) {
       
if (isCancelled()) {
            bitmap
= null;
       
}


       
if (imageViewReference != null && bitmap != null) {
           
final ImageView imageView = imageViewReference.get();
           
final BitmapWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask
(imageView);

           
if (this == bitmapWorkerTask && imageView != null) {
                imageView
.setImageBitmap(bitmap);
           
}
       
}
   
}
}


ListView나 GridView에 아주 적합한 솔루션이다. 또는 child view를 재활용하는 뷰컴포넌트들에서 사용할 수 있는 패턴이다. GridView의 경우 getView()메소드가 테스크와 뷰를 연계해주는 어댑터역할을 한다.


비트맵 캐싱하여 사용하기

아주 많은 이미지를 로딩해야 하며 빈번하게 사용되는 경우라면 캐싱을 고려해야 한다. 메모리캐싱과 파일캐싱이 해당되겠다.

메모리 캐시

메모리를 좀더 사용되겠지만 속도면에서 많은 이득이 있다. LruCache가 비트맵을 캐시하는데 아주 적당하다. LruCache가 사용되기 전에는 SoftReference와 WeakReference를 이용해서 캐싱을 구현하였었는데 이는 Android 2.3의 gc에서 비효율을 야기한다고 한다. 머 무튼 LruCache를 사용하면 나중에 추가한 오브젝트는 LinkedHashMap으로 참조되고 최대개수가 넘어가면 오래된걸 제거하도록 되어 있다.

LruCache의 적절한 사이즈를 결졍하기 위해서 고려해야 할것들이 여러가지가 있겠으나 정해진 크기나 공식은 없다. 전적으로 당신의 분석에 따라서 알아서 결정해야한다. 캐시크기가 크면 메모리가 부족해질거고 캐시크기가 작으면 앱에 로드가 걸릴것이니 적당한 선에서 합의를 해야 함. 참고할만한 가이드는 아래 참고하고

  • How memory intensive is the rest of your activity and/or application?
  • How many images will be on-screen at once? How many need to be available ready to come on-screen?
  • What is the screen size and density of the device? An extra high density screen (xhdpi) device like Galaxy Nexus will need a larger cache to hold the same number of images in memory compared to a device likeNexus S (hdpi).
  • What dimensions and configuration are the bitmaps and therefore how much memory will each take up?
  • How frequently will the images be accessed? Will some be accessed more frequently than others? If so, perhaps you may want to keep certain items always in memory or even have multiple LruCache objects for different groups of bitmaps.
  • Can you balance quality against quantity? Sometimes it can be more useful to store a larger number of lower quality bitmaps, potentially loading a higher quality version in another background task.

다음은 LruCache로 구현한 샘플임


private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
   
...
   
// Get max available VM memory, exceeding this amount will throw an
   
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
   
// int in its constructor.
   
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

   
// Use 1/8th of the available memory for this memory cache.
   
final int cacheSize = maxMemory / 8;

    mMemoryCache
= new LruCache<String, Bitmap>(cacheSize) {
       
@Override
       
protected int sizeOf(String key, Bitmap bitmap) {
           
// The cache size will be measured in kilobytes rather than
           
// number of items.
           
return bitmap.getByteCount() / 1024;
       
}
   
};
   
...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
   
if (getBitmapFromMemCache(key) == null) {
        mMemoryCache
.put(key, bitmap);
   
}
}

public Bitmap getBitmapFromMemCache(String key) {
   
return mMemoryCache.get(key);
}


비트맵을 로드하기전에 캐시에 비트맵이 있는지 확인하고 로드한다.

public void loadBitmap(int resId, ImageView imageView) {
   
final String imageKey = String.valueOf(resId);

   
final Bitmap bitmap = getBitmapFromMemCache(imageKey);
   
if (bitmap != null) {
        mImageView
.setImageBitmap(bitmap);
   
} else {
        mImageView
.setImageResource(R.drawable.image_placeholder);
       
BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task
.execute(resId);
   
}
}


BitmapWorkerTask는 로드가 완료되면 메모리캐시에 추가한다.


class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
   
...
   
// Decode image in background.
   
@Override
   
protected Bitmap doInBackground(Integer... params) {
       
final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources
(), params[0], 100, 100));
        addBitmapToMemoryCache
(String.valueOf(params[0]), bitmap);
       
return bitmap;
   
}
   
...
}


디스크 캐시 이용하기

메모리캐시만으로는 부족하다. 너무 많이 사용하는것 이외에도 전화가 온다던가 하여 앱이 백그라운드상태가 되었을 때 앱은 종료가 될 수 있고 그럴경우 캐시도 제거될것이다. 앱이 다시 로드되면 또다시 이미지를 로드해야 할것이다. 이를 개선하기 위해 디스크에 캐시를 하고 디스크에 캐시가 있으면 디스크에서 읽어들이는 방법이 있다. 메모리캐시보다는 느리지만 네트워크보다는 빠르며 메모리캐시와 함께 사용해도 될것이다.

** 갤러리앱에서 관리하는 이미지들을 읽어들일때는 ContentProvider를 이용하면 더 빠르게 접근이 가능하다.

안드로이드 소스에서 사용중인 DiskLruCache를 사용하는 샘플코드이다.


private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";

@Override
protected void onCreate(Bundle savedInstanceState) {
   
...
   
// Initialize memory cache
   
...
   
// Initialize disk cache on background thread
   
File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
   
new InitDiskCacheTask().execute(cacheDir);
   
...
}

class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
   
@Override
   
protected Void doInBackground(File... params) {
       
synchronized (mDiskCacheLock) {
           
File cacheDir = params[0];
            mDiskLruCache
= DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
            mDiskCacheStarting
= false; // Finished initialization
            mDiskCacheLock
.notifyAll(); // Wake any waiting threads
       
}
       
return null;
   
}
}

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
   
...
   
// Decode image in background.
   
@Override
   
protected Bitmap doInBackground(Integer... params) {
       
final String imageKey = String.valueOf(params[0]);

       
// Check disk cache in background thread
       
Bitmap bitmap = getBitmapFromDiskCache(imageKey);

       
if (bitmap == null) { // Not found in disk cache
           
// Process as normal
           
final Bitmap bitmap = decodeSampledBitmapFromResource(
                    getResources
(), params[0], 100, 100));
       
}

       
// Add final bitmap to caches
        addBitmapToCache
(imageKey, bitmap);

       
return bitmap;
   
}
   
...
}

public void addBitmapToCache(String key, Bitmap bitmap) {
   
// Add to memory cache as before
   
if (getBitmapFromMemCache(key) == null) {
        mMemoryCache
.put(key, bitmap);
   
}

   
// Also add to disk cache
   
synchronized (mDiskCacheLock) {
       
if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
            mDiskLruCache
.put(key, bitmap);
       
}
   
}
}

public Bitmap getBitmapFromDiskCache(String key) {
   
synchronized (mDiskCacheLock) {
       
// Wait while disk cache is started from background thread
       
while (mDiskCacheStarting) {
           
try {
                mDiskCacheLock
.wait();
           
} catch (InterruptedException e) {}
       
}
       
if (mDiskLruCache != null) {
           
return mDiskLruCache.get(key);
       
}
   
}
   
return null;
}

// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
   
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
   
// otherwise use internal cache dir
   
final String cachePath =
           
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                   
!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                            context
.getCacheDir().getPath();

   
return new File(cachePath + File.separator + uniqueName);
}


** 위 예제에서 Lock 객체는 디스크 작업중에 캐시에 접근하는것을 막아준다.

메모리캐시가 UI thread에서 확인되는 반면 디스크캐시는 백그라운드에서 확인한다. 디스크작업은 절대 UI thread에서 구동되면 안된다. 이미지프로세싱이 끝나면 비트맵은 메모리와 디스크캐시 2군데 추가된다.


Configuration change에 대한 처리


런타입에 설정이 변경되게 되면 가령 스크린오리엔테이션 안드로이드는 액티비티를 제거하였다가 재시작한다. 이 때 이미지처리를 간결하게 하여 사용성을 해쳐서는 안될것이다.


Fragment를 사용하는 경우 Fragment에 setRetainInstance(true)를 호출함으로써 메모리캐시의 참조를 통해 캐시를 유지하고 참조할 수 있다.


private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
   
...
   
RetainFragment retainFragment =
           
RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache
= retainFragment.mRetainedCache;
   
if (mMemoryCache == null) {
        mMemoryCache
= new LruCache<String, Bitmap>(cacheSize) {
           
... // Initialize cache here as usual
       
}
        retainFragment
.mRetainedCache = mMemoryCache;
   
}
   
...
}

class RetainFragment extends Fragment {
   
private static final String TAG = "RetainFragment";
   
public LruCache<String, Bitmap> mRetainedCache;

   
public RetainFragment() {}

   
public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
       
RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
       
if (fragment == null) {
            fragment
= new RetainFragment();
            fm
.beginTransaction().add(fragment, TAG).commit();
       
}
       
return fragment;
   
}

   
@Override
   
public void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
       
setRetainInstance(true);
   
}
}





Posted by 삼스
iOS2015. 4. 3. 15:16



objective C에 비해 swift의 생소한것들 중에 optional value란것이 있다.

자바의 Nullable과 Nonnull과는 좀 마이 다르다.

어떤 변수(값이나 참조모두)에 대해 nil을 허용할것인가에 대한 옵션이 되겠는데 이게 무슨소용인가 싶을 것이다.

기존에 코딩시 습관적으로 if (someobject != nil) 을 삽입하던 코드를 좀더 간결하고 버그가능성을 줄일 수 있도록 많은 고민을 한듯 하다.


먼저 변수를 선언하는 방법은 그냥 선언하는것과 Optional로 선언하는 방법이 있겠다.

var normalValue: String = "samse"

var optionalValue: String?


optional의 의미는 nil을 허용하는가에 대한 것이다 따라서 아래 코드는 에러다.


normalValue = nil // Type 'String' does not conform to protocol 'NilLiteralConvertible'


아래코드는 정상코드이다.


optionalValue = nil


optional 변수는 Explicit optional 변수와 Implicit optional 변수가 있다.

?를 붙인것이 Explicit이고 !를 붙인것이 implicit이다.


var explicitValue : String?

var implicitValue : String!


Explicit변수는 참조 시 nil인지 확인을 하기 위해 Forced unwrapping을 해야 한다. 해당 변수에 !를 붙임으로서 forced unwrapping이 수행된다

.

var unwrappedExplicitValue = optionalValue!


반드시 forced unwrapper를 통해서 값을 추출해서 사용해야 에러가 발생하지 않는다.


if unwrapperExplicitValue != nil {

    println( unwrapperExplicitValue)

}

Implicit 변수는 참조시 unwrapper를 안해도 되나 런타임에 오류가 발생 가능하므로 항상 주의해야 한다.


if implicitValue != nil {

    println( implicitValue )

}

Optional binding을 통해서 코드를 좀더 간결하게 할 수 있다.


if let value = explicitValue { // explicitValue! 로 표기하지 않는다.

  println("Value = \(value)")

}


형식은 아래와 같다.


if let [새변수명] = [optional 변수 또는 optional변수를 반환하는 함수] { // 

  ..

}


위에서 [새변수명]은 scope이 if문 내 이다.

if문내에서 [새변수명]을 변경하고자 할 경우에는 let이 아니라 var를 사용한다.


if var value = explicitValue {   

    value = "changed to SAMSE"

    println("Value = \(value)")

}


값이 nil인 경우 디폴트값을 사용하고자 할 수 있는데 이 때 ?? 연산자를 제공한다.


var value = explicitValue ?? "SAMSE"

위 코든는 explicitValue가 평가한바 nil이면 value에 "SAMSE"를 대입한다.



클래스도 참조로서 당연히 optional로 선언가능하고 사용가능한데 class내의 optional 변수를 참조하는데 좀더 간결하게 접근하는 방법을 제공한다. 애플개발자들이 많은 고민을 하면서 어떻게든 코드양을 줄이고 편하게 코딩하고자 한 노력이 보이는 부분이다.

class Person {

    var contact: Contact?

    init() {}

}

class Contact {

    var address: String?

    var tel: String?

    var email: String?

    var extra: String = "admin@gmail.com"

    init() {}

}


위 와 같이 클래스를 정의하고 아래처럼 Persion객체를 생성하였다.


var p = Person()


클래스 선언부에서 변수선언시 초기화하지 않았기 때문에 초기값은 extra를 제외하고 optional인 변수들은 모두 nil이 된다.

따라서 아래 코드는 p의 contact가 nil이기 때문에 컴파일 에러가 발생한다.


var email = p.contact!.email


nil 체크를 반드시 해주어야 한다.


if let contact = p.contact {

    if let email = contact.email! {

        println(email)

    }

    else {

        println("nil email")

    }

}

else {

    priintln("nil contact")


이 때 위 코드를 간결하게 줄여주는 방법이 Optional Chaining 표기법이다. 이는 변수 뒤에 ?를 붙임으로서 가능하다. 

다음 코드는 정상이다.


var email2 = p.contact?.email?


이 코드는 p.contact가 nil이므로 검사를 통해 email2에 nil을 대입한다. optional binding과 함께하여 다음과 같이 코드 작성이 가능하다.


if let email = p.contact?.email? {

    println('email = \(email)")

} else {

    println("email is nil")

}


email is nil이 표시될것이다.


값을 대입할 때도 chaining을 통해 nil이 반환되면 해당 변수에 값이 대입되지 않는다.


p.contact?.email? = "someone@gmail.com"

if let email = p.contact?.email? {

    println("Email : \(email)")

} else {

    println("Email is nil yet")

}


마찬가지로 Email is nil yet이 표시될것이다.


아래와 같이 contact를 유효하게 만들어주면 대입이 될것이다.


p.contact = Contact()

p.contact?.email? = "someone@gmail.com"


if let email = p.contact?.email? {

    println("Email : \(email)")

else {

    println("Email is nil yet")

}

Email : someone@gmail.com 이 표시될것이다.


Contact클래스의 extra field는 optional 변수가 아니다.


class Contact {

    var address: String?

    var tel: String?

    var email: String?

    var extra: String = "admin@gmail.com"

    init() {}

}


이는 optional chaing으로 접근 시 타입은 String형식이더라도 옵셔널인 String?으로 받게됨을 알아야 한다. <-- 중요 중요!!!

let extra = p.contact?.extra! // p.contact?extra String? 반환함으로써 타입캐스팅 에러 발생


이는 다음과 같이 수정되어야 한다.


let extra = (p.contact?.extra)!

또는

if let extra = p.contact?.extra {

    // ...

}

애플개발자가 고민하여 만든것 같은데 nil에 대한 대비를 코딩레벨로 끌어내려서 런타임에러가 많이 줄어들것으로 예상된다. 실제 개발하면서 빠르게 익숙해져야 할듯 하다.





Posted by 삼스
Android2015. 4. 2. 16:25


Eclipse에서는 MAT를 플러그인으로 설치 및 사용이 가능하였지만 Android Studio로는 앱을 별도로 배포하는것을 받아서 사용할 수 있다.

아래 그 과정을 나열하였다.


1. MAT 다운로드

http://www.eclipse.org/downloads/download.php?file=/mat/1.4/rcp/MemoryAnalyzer-1.4.0.20140604-macosx.cocoa.x86_64.zip

2. Android Studio에서 DDMS실행

3. Update hprof

4. dump hprof

5. hprof-conv로 변환(SDK의 platform-tools에 있음)

6. 변환된 파일 MAT로 open(변환 안하면 안열림)


MAT에 대한 활용은 다음 포스트에...

Posted by 삼스
Android2015. 4. 2. 16:17


엄청 잘 설명되어 있음..

http://www.openeg.co.kr/289

Posted by 삼스
Android2015. 4. 2. 14:37



Map(HashMap)은 자바에서 굉장히 많이 사용되는 자료형이다. 이는 키로 값을 얻을 수 있는 것인데 HashMap은 키로 문자열등도 취할수 있기 때문에 사실상 무한대로 볼수 있다. 하지만 HashMap은 내부적으로 int범위의 bucket을 가지고 구현이 되기 때문에 계속 추가하다보면 충돌이 발생할 수 있다.

즉 "aa"와 "bb"키로 다른 값을 등록하였는데 읽을 때 동일한 값이 잃혀질 수 있다는 것이다.

이 로인 해 key.hashCode()를 적절히 구현하여 linked list를 활용하는 방안이 있다. 이른 해당 키의 값이 없으면 linked list로 추적하여 그 값을 읽어오는 방법이다. 이렇게 하면 충돌은 피할 수 있겠으나 결국 linked list와 별반 다르지 않다.

안드로이드에서는 이를 보완하기 위해 java.android.util.SparseArray를 제공한다.

이는 bucket인덱스 기준으로 0~100까지 저장되고 10000~10010까지 저장된다고 가정할 때 실제로는 110개만 bucket를 생성한다. 저장할 데이터가많으면 LongSparseArray를 사용하면 된다.

한가지 제약은 키값이 정수이어야 한다는 것이다. 이 부분만 뺴면 HashMap이나 linked list를 활용하는 것보다 빠르게 처리할 수 있다.

속도가 문제가 된다면 HashMap을 SparseArray로 변경하는것을 고민해볼때이다.


Posted by 삼스
카테고리 없음2015. 4. 2. 14:30


http://groups.google.com/group/calabash-android

Installation

필수

  • Ruby가 필수로 깔려 있어야 함. ruby -v를 터미널에서 실행해볼것. 윈도우에서는  RubyInstaller.org <- 여기에서 다운로드하여 설치
  • 안드로이드 SDK필수이며 ANDROID_HOME 환경변수에 압축이 풀린 SDK폴더위치를 가리키도록 해야 한다.
  • ANT도 필수로 설치되어야한다. https://ant.apache.org/bindownload.cgi 에서 설치할 수 있다.

설치

  • sudo gem install calabach-android


Ruby API

테스트 단계를 작성할 때 Ruby API로 애플리케이션과 연동함.

여기서는 high-level의 API에 대해서만 설명할것이며 더 자세한 내용을 보려면 https://github.com/calabash/calabash-android/blob/master/ruby-gem/lib/calabash-android 를 참조할것.

calabash android는 client-server 구조이다. Calabash Ruby API는 테스트서버와 HTTP로 통신하는 client side이며 서버는 앱과 함께 단말에서 동작한다. Calabash Android의 아키텍쳐가 궁금하면 다음 포스트를 참조해라.

AN OVERVIEW OF CALABASH ANDROID


일반적인 사항

start_test_server_in_background

테스트서버와 앱이 테스트에 진입(app under test:AUT)한다. 앱이 이미 실행중이면 restart한다.

reinstall_apps

테스트서버와 AUT를 새버전으로 설치한다. 


질의(Query)

query(uiquery, * args)

query는 그 결과를 array로 반환한다. 뷰나 다른 애븦리케이션 객체들을 찾아내고 유효성을 체크하거나 데이터를 추출해낼 수 있다. view객체의 경우 좌표, 클래스 그리고 컨텐츠디스크립션등을 반환한다.

irb(main):002:0> query("button index:1")

=> [{"id"=>"save", "enabled"=>true, "contentDescription"=>nil, "class"=>"android.widget.Button", "text"=>"Save", "rect"=>{"center_y"=>724.0, "center_x"=>645.5, "height"=>64, "y"=>692, "width"=>71, "x"=>610}, "description"=>"android.widget.Button{4267b4a0 VFED..C. ........ 497,243-568,307 #7f070023 app:id/save}"}] 

뷰는 hash로 표현되며 따라서 아래와 같은 결과를 볼수 있다.

irb(main):003:0> query("button index:1").first.keys 

=> ["id", "enabled", "contentDescription", "class", "text", "rect", "description"] 

*args 파라메터는 쿼리결과에 메소드를 수행할 수 있게 한다.

irb(main):005:0> query("button", "text")

=> ["Optional Settings", "Save", "Cancel", "Get a free blog at WordPress.com"]

이는 getter 메소드를 호출하게 되는데 text(), getText() 또는 isText()가 해당되며 뷰내의 버든들에 대해 수행된다. 연속으로 호출도 가능하다.

irb(main):007:0> query("button", "text", "length")

=> [17, 4, 6, 32]

irb(main):008:0> query("button", "text", "toLowerCase")

=> ["optional settings", "save", "cancel", "get a free blog at wordpress.com"]

인자가 필요한 경우 해시를 사용할 수 있다.

irb(main):033:0> query("edittext index:1", setText:"1234")

=> ["<VOID>"] 

Ruby 1.0에서는 key:val형식이 사용 불가하며 아래와 같이 사용해야 한다.

irb(main):033:0> query("edittext index:1", setText => "1234")

=> ["<VOID>"] 

쿼리 조건에 맞는 뮤들에 대해서 setTex 메소드가 "1234"인자와 함께 실행될것이다.

<VOID>는 자바 메소드가 void를 반환함을 의미한다.


Waiting

wait_for(options, &block)

특정 상태가 발생할때까지 대기한다. 옵션을 hash로 지정하면 블럭이 반복적으로 호출된다.

....







Posted by 삼스
카테고리 없음2015. 4. 2. 10:43


CALABASH 특징

  • 모바일앱에 대해 자동화된 테스트를 작성하고 실행할 수 있게 해준다.
  • 안드로이드와 iOS를 모두 지원하는 크로스플랫폼이다.
  • 오픈소스로 무료이며 Xamarin이라는 회사에서 개발중이다.
  • Xamarin Test Cloud 로 수백개의 단말에서 앱을 자동으로 테스트 할 수 있다.
  • native와 hybrid app과 연동하는 테스트코드를 작성할 수 있도록 해주는 라이브러리로 구성된다.
  • 연동액션은 다음과 같다.
  • Gestures : 터치나 제스쳐(탭, 스와이프, 회전등)
  • Assertions : 
  • Screenshots : 단말의 현재 뷰의 스크린덤프
  • Selenium WebDriver와 비교되는데 터치스크린을 갖는 네이티브앱과 연동한다는 것에서 많은 차이가 난다.

Android
  • https://github.com/calabash/calabash-android
iOS
  • https://github.com/calabash/calabash-ios


Posted by 삼스