SlideShare uma empresa Scribd logo
1 de 48
Baixar para ler offline
battery
Android를 위한 쉬운 웹 API 호출
박준규
스포카
• 한솔넥스지 2009 - 2012
• StyleShare 2012
• 넥슨 2012 - 2015
• 스포카 2015 - 현재
• https://github.com/segfault87
• https://facebook.com/segfault87
발표자 소개
battery
batteries
included
• Java (Android)를 위한 Web API 클라이언트
• MIT 라이센스
• BETA™
• API가 바뀔 수 있습니다
• 문서화도 안 되어 있습니다
• 홈페이지도 아직 없습니다
• 하지만 돌아갑니다 프로덕션에서 사용중…
디자인 목표
• 보일러플레이트의 최소화
• 요청 객체와 응답 객체를 나누지 않는다
• 특정 구조를 강제하지 않는다
• 데이터 모델은 POJO + Annotation
• 노출될 필요가 없는 것은 최대한 숨긴다
• 속도보다는 유연함이 우선
https://github.com/spoqa/battery
사용법
• cd your-project

git clone https://github.com/spoqa/battery.git
• compile project(':battery')
• Jcenter, Maven central에는 첫 정식 릴리즈와 함께 올라갑니다.
• 그 땐 아마 이렇게 하시면 될듯
• compile ‘com.spoqa:battery:1.+’
사용법
@RpcObject(uri=“http://ip.jsontest.com”)
public class TestObject {
@Response public String ip;
}
public class TestActivity extends Activity {
…
private void test() {
AndroidRpcContext context = new AndroidRpcContext(this);
context.invokeAsync(new TestObject(), new OnResponse<TestObject>() {
@Override public void onResponse(TestObject responseBody) {
Log.d(TAG, “Your IP address: “ + responseBody.ip);
}
@Override public void onFailure(Throwable why) {
why.printStackTrace();
}
});
}
}
@RpcObject
• API 객체에 대한 메타데이터
• API 객체를 정의하기 위해서 별도의 상위 객체를 extend할 필요 없음
HTTP 요청
@RpcObject(
method=HttpRequest.Methods.GET,
uri=“/view_post/%1$s/%2$d”
)
public class ViewPostObject {
public ViewPostObject(String userId, long postId) {
this.userId = userId;
this.postId = postId;
}
@UriPath(1) public String userId;
@UriPath(2) public long postId;
}
REST 스타일
HTTP 요청
@RpcObject(
method=HttpRequest.Methods.GET,
uri=“/list”
)
public class ListObject {
public ListObject(int offset, int limit) {
this.offset = offset;
this.limit = limit;
}
@QueryString public int offset;
@QueryString(“count”) public int limit; // 명시적 이름 설정
}
쿼리 스트링
context.setDefaultUriPrefix(“http://foobar.local:5000”);
context.invokeAsync(new ListObject(0, 100), new OnResponse<ListObject>() { … });
http://foobar.local:5000/list?offset=0&count=100 생성
HTTP 요청
@RpcObject(uri=“/details”)
public class DetailsObject {
public ListObject(List<Integer> id) {
this.id = id;
}
@QueryString public List<Integer> id;
}
쿼리 스트링
http://foobar.local:5000/details?id=1&id=2&id=3
HTTP 요청
@RpcObject(
method=HttpRequest.Methods.POST,
uri=“/signin”
)
public class SignInObject {
public SignInObject(String id, String password) {
this.id = id;
this.password = password;
}
@RequestBody public String id;
@RequestBody public String password;
}
엔티티 바디
HTTP 요청
@RpcObject(
method=HttpRequest.Methods.POST,
uri=“/signin”,
requestSerializer=JsonCodec.class
)
public class SignInClass { … }
엔티티 바디
엔티티 serializer는 아래처럼 지정할 수 있다.
default는 UrlEncodedFormEncoder.class
HTTP 요청
엔티티 바디
Multipart entity는 추후 지원 예정
HTTP 응답
@RpcObject(…)
public class ProfileObject {
…
@Response public long id;
@Response public String user;
@Response(“display_name”) public String nickname;
@Response public List<Post> recentPosts;
}
@Response 어노테이션을 사용
HTTP 응답
@Response(required=true) public boolean result;
@Response
응답에 필수적인 필드는 required를 true로 설정
이 경우 누락시 예외 발생
@Response public int foo;
@Response public Integer bar;
Boxed primitive 사용 가능
HTTP 응답
{“user”: {“nickname”: “kyu”,
“email”: “kyu@spoqa.com”},
‘activities’: []}
@Response(“user.nickname”) public String nickname;
@Response(“user.email”) public String email;
@Response public List<UserActivity> activities;
@Response
Subobject 참조
HTTP 응답
@RpcObject(…)
public class FooObject {
private int id;
@Response public void setId(int id) { this.id = id; }
}
@Response
Setter methods
HTTP 응답
@Response
하위 응답 객체의 생성자
public static class SubObject {
public String foo;
}
public static class SubObject {
public SubObject(String foo) { this.foo = foo; }
public String foo;
}
public static class SubObject {
public SubObject() { this.foo = null; }
public SubObject(String foo) { this.foo = foo; }
public String foo;
}
O
O
X
HTTP 응답
@Response
• 그럼 모든 응답 필드에 대해 @Response를 붙여야 하나요?
• 아닙니다.
• 최상단 객체에만 붙여주면 됩니다.
• 하위 객체의 경우 필요하다면 (필드명 override, required=true) 붙
일 수 있습니다.
• 요청과 응답 필드들이 한 클래스 안에 섞이는 것이 싫은데요?
HTTP 응답
@ResponseObject
@RpcObject(…)
public class FooObject {
…
public static class FooResponse {
public int id;
public String name;
}
@ResponseObject public FooResponse response;
}
HTTP 응답
• 응답은 JSON만 지원하나요?
• 현재로선 그렇습니다.
• 다른 포맷도 추가할 계획이 있습니다. (XML, …)
• (일단은) 모듈러하게 구현되어 있습니다.
• ObjectBuilder.registerDeserializer(new JsonCodec());
• ObjectBuilder.registerDeserializer(new YourCodec());
필드 네이밍 자동 변환
{ “user_id”: 1,
“display_name”: “kyu”}
@RpcObject(
localName=CamelCaseTransformer.class,
remoteName=UnderscoreNameTransformer.class,
…)
public class BarObject {
@Response public long userId;
@Response public String displayName;
}
응답 뿐만 아니라 요청 필드에도 일률적으로 적용됨
커스텀 필드 타입
@Response public Date createdAt;
Date 자료형을 알아먹게 만들려면
RpcContext ctx = …;
ctx.registerTypeAdapter(new Rfc1123DateAdapter());
기본 포함된 Date adapter의 종류
• Iso8601DateAdapter
• Rfc1123DateAdapter
• TimestampDateAdapter
열거형 enumeration
public enum Currency {
KRW, USD, EUR, JPY
}
@RpcObject(…)
public class PurchaseObject {
@RequestBody public Currency currency;
@Response public List<Currency> acceptableCurrencies;
}
전역 설정 오버라이드
@RpcObject(
uri=“http://foobar.local:5000/qux”,
requestSerializer=UrlEncodedFormEncoder.class,
localName=CamelCaseTransformer.class,
remoteName=UnderscoreNameTransformer.class)
RpcContext ctx = …;
ctx.setDefaultUriPrefix(“http://foobar.local:5000”);
ctx.setRequestSerializer(new UrlEncodedFormEncoder());
ctx.setFieldNameTransformer(new CamelCaseTransformer.class,
new UnderscoreNameTransformer.class);
요청 전처리
RpcContext ctx = …;
ctx.setRequestPreprocessor(new RequestPreprocessor() {
@Override
public void validateContext(Object forWhat) throws ContextException {}
@Override
public void processHttpRequest(HttpRequest req) {
req.putHeader(“X-Application-Id”, BuildConfig.APP_ID);
req.putHeader(“X-Shared-Secret”, BuildConfig.SHARED_SECRET);
}
});
응답 검증 response validation
{ “result”: true,
“message”: “request successful”
“data”: { … }
}
API 레벨의 오류를 예외로 변환
위 응답을 POJO로 다음과 같이 정의할 수 있습니다.
public class CommonResponse {
public boolean result;
public String message;
}
public class SignInResponse extends CommonResponse {
public String accessToken;
}
public class ListResponse extends CommonResponse {
public List<Article> data;
}
public class SignOutResponse extends CommonResponse {
// No additional data
}
응답 검증 response validation
응답 검증 response validation
RpcContext ctx = …;
ctx.setResponseValidator(new ResponseValidator() {
@Override
public void validate(Object object) throws ResponseValidationException {
if (object instanceof CommonResponse) {
CommonResponse response = (CommonResponse) object;
if (!response.result)
throw new ResponseValidationException(response.message);
}
}
});
응답 검증 response validation
ctx.invokeAsync(new SignInObject(…), new OnResponse<SignInObject>() {
@Override
public void onResponse(SignInObject response) {
// Nothing wrong happened
}
@Override
public void onFailure(Throwable why) {
// ResponseValidator에서 예외 발생시 이 쪽으로 옴
why.printStackTrace();
}
});
에러 코드 일반화
{ “result”: 0,
“message”: “successful”,
“data”: { … } }
{ “result”: 100,
“message”: “’cause you used it so wrong” }
{ “result”: 101,
“message”: “sh*t happened” }
일반적으로 API 에러는 identifier를 가진다.
에러 코드 일반화
public @interface ErrorCode {
public String[] value();
}
일반적으로 API 에러는 identifier를 가진다.
에러 코드 일반화
에러 코드 일반화
• @ErrorCode 걸린 모든 예외 클래스들을 검색하여 map에
추가
• Java reflection의 도움으로
• 이렇게 하면 선언만으로 등록이 가능
• ResponseValidator에서 해당 응답의 에러 코드를
map에서 검색
• 발견한다면 예외 instantiate 후 throw
에러 코드 일반화
ctx.invokeAsync(new PayObject(…), new OnResponse<PayObject>() {
…
@Override
public void onFailure(Throwable why) {
if (why instanceof CreditCardExpired) {
…
} else if (why instanceof CreditCardStolen) {
…
} else if (why instanceof CreditCardWithdrawn) {
…
} else { … }
}
});
전역 예외 핸들링
• 호출과 관계없이 특정 종류의 예외 발생시 호출
• 예를 들어 세션 만료 에러가 발생시 SharedPreferences
에서 세션 정보 삭제 후 다시 로그인 페이지로 돌아가야 한
다거나…
• 핸들러 내에서 현재 UI 컨텍스트를 참조해야 한다면
invokeAsync()에 세번째 인자로 현재 Context를 전
달
• ctx.invokeAsync(req, onResponse, TestActivity.this);
전역 예외 핸들링
public class SessionExpiredHandler implements ExceptionHandler<Context> {
@Override
public boolean onException(Context context, Throwable error) {
SharedPreferences prefs = context.getSharedPreferences(…);
prefs.edit().remove(“session_key”).apply();
context.startActivity(new Intent(context, SignInActivity.class));
return true; // 참일 경우 예외를 더 이상 상위로 전달하지 않는다.
}
}
ctx.registerExceptionHandler(SessionExpiredException.class,
new SessionExpiredHandler());
전역 예외 핸들링
public class CredentialException extends Throwable { … }
public class WrongPasswordException extends CredentialException { … }
public class TooManyRetriesException extends WrongPasswordException { … }
ctx.registerExceptionHandler(Throwable.class, new FooHandler());
ctx.registerExceptionHandler(CredentialException.class, new BarHandler());
ctx.registerExceptionHandler(WrongPasswordException.class, new BazHandler());
ctx.registerExceptionHandler(TooManyRetriesException.class, new QuxHandler());
요청 결과로 TooManyRetriesException 발생 시 핸들러 평가 순서
QuxHandler → BazHandler → BarHandler → FooHandler → OnResponse.onFailure()
전역 예외 핸들링
if (BuildConfig.DEBUG) {
ctx.registerExceptionHandler(Throwable.class, new ExceptionHandler<Context>() {
@Override
public boolean onException(Context context, Throwable error) {
StringWriter sw = new StringWriter();
error.printStackTrace(new PrintWriter(sw));
new AlertDialog.Builder(context)
.setTitle(“오류 발생”)
.setMessage(sw.toString())
.setPositiveButton(“닫기”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
return false;
}
});
}
RxJava와 함께 쓰기
Observable<FooObject> ob = ctx.invokeObservable(this, new FooObject(…));
ob.subscribe(new Action1<FooObject>() {
@Override
public void call(FooObject fooObject) {
…
}
});
Kotlin에서 쓰기
• 객체 정의는 Java로 해야 됩니다.
• Java와 Kotlin은 reflection 구현이 달라서 호환이 안됨…
AndroidRpcContext(context).invokeAsync(
FooObject(),
object: OnResponse<FooObject> {
override fun onResponse(response: FooObject?) {
…
}
override fun onFailure(why: Throwable?) {
…
}
})
Proguard와 함께 쓰기
• Reflection에 의존하기 때문에 @RPCObject에는 사용 불
가능
• 필드명, 메소드명을 dynamically lookup하기 때문…
• -keep @com.spoqa.battery.annotations.RpcObject public class *
• 하위 객체가 있을 경우 안 됨
• 가장 좋은 방법은 객체를 특정 패키지에 몰아넣고 적용
• -keep public class com.your_app.objects.*
추후 개발 계획
• 성능개선
• field, method lookup은 고비용
• 현재는 cache를 둬서 반복되는 객체의 lookup을 최소화
하고 있음
• deterministic한 부분은 컴파일타임에서 최적화 가능
• annotation processor
추후 개발 계획
• Multipart encoder 구현
• 파일 업로드
• XML decoder 구현
• 문서화
• 테스트 스위트
• (정식) 릴리즈
버그 리포트, PR은 언제나 환영합니다!
https://github.com/spoqa/battery
개발자	 구합니다	 
kyu@spoqa.com

Mais conteúdo relacionado

Mais procurados

Mais procurados (17)

ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는
 
ECMAScript 6의 새로운 것들!
ECMAScript 6의 새로운 것들!ECMAScript 6의 새로운 것들!
ECMAScript 6의 새로운 것들!
 
비전공자의 자바스크립트 도전기
비전공자의 자바스크립트 도전기비전공자의 자바스크립트 도전기
비전공자의 자바스크립트 도전기
 
Startup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JSStartup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JS
 
골때리는 자바스크립트 발표자료
골때리는 자바스크립트 발표자료골때리는 자바스크립트 발표자료
골때리는 자바스크립트 발표자료
 
Javascript
JavascriptJavascript
Javascript
 
Javascript 조금 더 잘 알기
Javascript 조금 더 잘 알기Javascript 조금 더 잘 알기
Javascript 조금 더 잘 알기
 
Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)
 
MVP 패턴 소개
MVP 패턴 소개MVP 패턴 소개
MVP 패턴 소개
 
Dependency Injection 소개
Dependency Injection 소개Dependency Injection 소개
Dependency Injection 소개
 
Java advancd ed10
Java advancd ed10Java advancd ed10
Java advancd ed10
 
Angular2 router&http
Angular2 router&httpAngular2 router&http
Angular2 router&http
 
Java script 강의자료_ed13
Java script 강의자료_ed13Java script 강의자료_ed13
Java script 강의자료_ed13
 
GraphQL overview #2
GraphQL overview #2GraphQL overview #2
GraphQL overview #2
 
[1B4]안드로이드 동시성_프로그래밍
[1B4]안드로이드 동시성_프로그래밍[1B4]안드로이드 동시성_프로그래밍
[1B4]안드로이드 동시성_프로그래밍
 
모델링 연습 리뷰
모델링 연습 리뷰모델링 연습 리뷰
모델링 연습 리뷰
 
Javascript 실행 가능한 코드(Executable Code)와 실행 콘텍스트(Execution Context), Lexical En...
Javascript 실행 가능한 코드(Executable Code)와 실행 콘텍스트(Execution Context), Lexical En...Javascript 실행 가능한 코드(Executable Code)와 실행 콘텍스트(Execution Context), Lexical En...
Javascript 실행 가능한 코드(Executable Code)와 실행 콘텍스트(Execution Context), Lexical En...
 

Destaque

Java Micro Edition Platform & Android - Seminar on Small and Mobile Devices
Java Micro Edition Platform & Android - Seminar on Small and Mobile DevicesJava Micro Edition Platform & Android - Seminar on Small and Mobile Devices
Java Micro Edition Platform & Android - Seminar on Small and Mobile Devices
juricde
 

Destaque (20)

디자이너 없어도 괜찮아! (feat.Material Design Guide)
디자이너 없어도 괜찮아! (feat.Material Design Guide)디자이너 없어도 괜찮아! (feat.Material Design Guide)
디자이너 없어도 괜찮아! (feat.Material Design Guide)
 
RetroFit by Square - GDG Dallas 06/09/16
RetroFit by Square - GDG Dallas 06/09/16RetroFit by Square - GDG Dallas 06/09/16
RetroFit by Square - GDG Dallas 06/09/16
 
GKAC 2015 Apr. - Xamarin forms, mvvm and testing
GKAC 2015 Apr. - Xamarin forms, mvvm and testingGKAC 2015 Apr. - Xamarin forms, mvvm and testing
GKAC 2015 Apr. - Xamarin forms, mvvm and testing
 
Java Micro Edition Platform & Android - Seminar on Small and Mobile Devices
Java Micro Edition Platform & Android - Seminar on Small and Mobile DevicesJava Micro Edition Platform & Android - Seminar on Small and Mobile Devices
Java Micro Edition Platform & Android - Seminar on Small and Mobile Devices
 
Intro to Android : Making your first App!
Intro to Android : Making your first App!Intro to Android : Making your first App!
Intro to Android : Making your first App!
 
Async task, threads, pools, and executors oh my!
Async task, threads, pools, and executors oh my!Async task, threads, pools, and executors oh my!
Async task, threads, pools, and executors oh my!
 
같은 유저수, 다른 수익? 모바일 앱의 수익을 높이는 방법
같은 유저수, 다른 수익? 모바일 앱의 수익을 높이는 방법같은 유저수, 다른 수익? 모바일 앱의 수익을 높이는 방법
같은 유저수, 다른 수익? 모바일 앱의 수익을 높이는 방법
 
GKAC 2014 Nov. - Android Wear 개발, 할까요 말까요?
GKAC 2014 Nov. - Android Wear 개발, 할까요 말까요?GKAC 2014 Nov. - Android Wear 개발, 할까요 말까요?
GKAC 2014 Nov. - Android Wear 개발, 할까요 말까요?
 
GKAC 2015 Apr. - RxAndroid
GKAC 2015 Apr. - RxAndroidGKAC 2015 Apr. - RxAndroid
GKAC 2015 Apr. - RxAndroid
 
GKAC 2014 Nov. - RxJava를 활용한 Functional Reactive Programming
GKAC 2014 Nov. - RxJava를 활용한 Functional Reactive ProgrammingGKAC 2014 Nov. - RxJava를 활용한 Functional Reactive Programming
GKAC 2014 Nov. - RxJava를 활용한 Functional Reactive Programming
 
GKAC 2015 Apr. - 테스트 코드에서 코드 커버리지까지
GKAC 2015 Apr. - 테스트 코드에서 코드 커버리지까지GKAC 2015 Apr. - 테스트 코드에서 코드 커버리지까지
GKAC 2015 Apr. - 테스트 코드에서 코드 커버리지까지
 
Best Practices in Media Playback
Best Practices in Media PlaybackBest Practices in Media Playback
Best Practices in Media Playback
 
Reinfocement learning
Reinfocement learningReinfocement learning
Reinfocement learning
 
FIrebase를 이용한 호우호우 미니게임 만들기
FIrebase를 이용한 호우호우 미니게임 만들기FIrebase를 이용한 호우호우 미니게임 만들기
FIrebase를 이용한 호우호우 미니게임 만들기
 
안드로이드 데이터 바인딩
안드로이드 데이터 바인딩안드로이드 데이터 바인딩
안드로이드 데이터 바인딩
 
Introduce Android TV and new features from Google I/O 2016
Introduce Android TV and new features from Google I/O 2016Introduce Android TV and new features from Google I/O 2016
Introduce Android TV and new features from Google I/O 2016
 
Android - Preventing common memory leaks
Android - Preventing common memory leaksAndroid - Preventing common memory leaks
Android - Preventing common memory leaks
 
Android : How Do I Code Thee?
Android : How Do I Code Thee?Android : How Do I Code Thee?
Android : How Do I Code Thee?
 
Inside the Android application framework - Google I/O 2009
Inside the Android application framework - Google I/O 2009Inside the Android application framework - Google I/O 2009
Inside the Android application framework - Google I/O 2009
 
Nuclear Battery PPT
Nuclear Battery PPTNuclear Battery PPT
Nuclear Battery PPT
 

Semelhante a GKAC 2015 Apr. - Battery, 안드로이드를 위한 쉬운 웹 API 호출

Ksug 세미나 (윤성준) (20121208)
Ksug 세미나 (윤성준) (20121208)Ksug 세미나 (윤성준) (20121208)
Ksug 세미나 (윤성준) (20121208)
Sungjoon Yoon
 
Naver api for android
Naver api for androidNaver api for android
Naver api for android
Sangon Lee
 
Effective c++(chapter 5,6)
Effective c++(chapter 5,6)Effective c++(chapter 5,6)
Effective c++(chapter 5,6)
문익 장
 

Semelhante a GKAC 2015 Apr. - Battery, 안드로이드를 위한 쉬운 웹 API 호출 (20)

Spring boot actuator
Spring boot   actuatorSpring boot   actuator
Spring boot actuator
 
Ksug 세미나 (윤성준) (20121208)
Ksug 세미나 (윤성준) (20121208)Ksug 세미나 (윤성준) (20121208)
Ksug 세미나 (윤성준) (20121208)
 
20201121 코드 삼분지계
20201121 코드 삼분지계20201121 코드 삼분지계
20201121 코드 삼분지계
 
Nodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjsNodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjs
 
overview of spring4
overview of spring4overview of spring4
overview of spring4
 
Node.js and react
Node.js and reactNode.js and react
Node.js and react
 
[Codelab 2017] ReactJS 기초
[Codelab 2017] ReactJS 기초[Codelab 2017] ReactJS 기초
[Codelab 2017] ReactJS 기초
 
Java 8 & Beyond
Java 8 & BeyondJava 8 & Beyond
Java 8 & Beyond
 
Naver api for android
Naver api for androidNaver api for android
Naver api for android
 
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
 
반복적인 작업이 싫은 안드로이드 개발자에게
반복적인 작업이 싫은 안드로이드 개발자에게반복적인 작업이 싫은 안드로이드 개발자에게
반복적인 작업이 싫은 안드로이드 개발자에게
 
Do IoT Yourself! - 사물 간의 연결을 위한 Open API
Do IoT Yourself! - 사물 간의 연결을 위한 Open APIDo IoT Yourself! - 사물 간의 연결을 위한 Open API
Do IoT Yourself! - 사물 간의 연결을 위한 Open API
 
Effective c++(chapter 5,6)
Effective c++(chapter 5,6)Effective c++(chapter 5,6)
Effective c++(chapter 5,6)
 
MyBatis에서 JPA로
MyBatis에서 JPA로MyBatis에서 JPA로
MyBatis에서 JPA로
 
4-2. ajax
4-2. ajax4-2. ajax
4-2. ajax
 
처음배우는 자바스크립트, 제이쿼리 #4
처음배우는 자바스크립트, 제이쿼리 #4처음배우는 자바스크립트, 제이쿼리 #4
처음배우는 자바스크립트, 제이쿼리 #4
 
Spring boot 공작소(1-4장)
Spring boot 공작소(1-4장)Spring boot 공작소(1-4장)
Spring boot 공작소(1-4장)
 
Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)
Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)
Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)
 
4-3. jquery
4-3. jquery4-3. jquery
4-3. jquery
 
Boost라이브러리의내부구조 20151111 서진택
Boost라이브러리의내부구조 20151111 서진택Boost라이브러리의내부구조 20151111 서진택
Boost라이브러리의내부구조 20151111 서진택
 

Mais de GDG Korea (7)

접근성(Accessibility)과 안드로이드
접근성(Accessibility)과 안드로이드접근성(Accessibility)과 안드로이드
접근성(Accessibility)과 안드로이드
 
Tensorflow 101
Tensorflow 101Tensorflow 101
Tensorflow 101
 
Building Extraordinary Apps with Firebase Analytics
Building Extraordinary Apps with Firebase AnalyticsBuilding Extraordinary Apps with Firebase Analytics
Building Extraordinary Apps with Firebase Analytics
 
GKAC 2014 Nov. - 안드로이드 스튜디오로 생산성 올리기
GKAC 2014 Nov. - 안드로이드 스튜디오로 생산성 올리기GKAC 2014 Nov. - 안드로이드 스튜디오로 생산성 올리기
GKAC 2014 Nov. - 안드로이드 스튜디오로 생산성 올리기
 
GKAC 2014 Nov. - 그루비로 안드로이드 앱 개발하기
GKAC 2014 Nov. - 그루비로 안드로이드 앱 개발하기GKAC 2014 Nov. - 그루비로 안드로이드 앱 개발하기
GKAC 2014 Nov. - 그루비로 안드로이드 앱 개발하기
 
GKAC 2014 Nov. - The Beautiful Design Collection 살펴보기
GKAC 2014 Nov. - The Beautiful Design Collection 살펴보기GKAC 2014 Nov. - The Beautiful Design Collection 살펴보기
GKAC 2014 Nov. - The Beautiful Design Collection 살펴보기
 
GKAC 2014 Nov. - 안드로이드 5.0의 새로운 기능
GKAC 2014 Nov. - 안드로이드 5.0의 새로운 기능GKAC 2014 Nov. - 안드로이드 5.0의 새로운 기능
GKAC 2014 Nov. - 안드로이드 5.0의 새로운 기능
 

GKAC 2015 Apr. - Battery, 안드로이드를 위한 쉬운 웹 API 호출

  • 1. battery Android를 위한 쉬운 웹 API 호출 박준규 스포카
  • 2. • 한솔넥스지 2009 - 2012 • StyleShare 2012 • 넥슨 2012 - 2015 • 스포카 2015 - 현재 • https://github.com/segfault87 • https://facebook.com/segfault87 발표자 소개
  • 5. • Java (Android)를 위한 Web API 클라이언트 • MIT 라이센스 • BETA™ • API가 바뀔 수 있습니다 • 문서화도 안 되어 있습니다 • 홈페이지도 아직 없습니다 • 하지만 돌아갑니다 프로덕션에서 사용중…
  • 6. 디자인 목표 • 보일러플레이트의 최소화 • 요청 객체와 응답 객체를 나누지 않는다 • 특정 구조를 강제하지 않는다 • 데이터 모델은 POJO + Annotation • 노출될 필요가 없는 것은 최대한 숨긴다 • 속도보다는 유연함이 우선
  • 8. 사용법 • cd your-project
 git clone https://github.com/spoqa/battery.git • compile project(':battery') • Jcenter, Maven central에는 첫 정식 릴리즈와 함께 올라갑니다. • 그 땐 아마 이렇게 하시면 될듯 • compile ‘com.spoqa:battery:1.+’
  • 9. 사용법 @RpcObject(uri=“http://ip.jsontest.com”) public class TestObject { @Response public String ip; } public class TestActivity extends Activity { … private void test() { AndroidRpcContext context = new AndroidRpcContext(this); context.invokeAsync(new TestObject(), new OnResponse<TestObject>() { @Override public void onResponse(TestObject responseBody) { Log.d(TAG, “Your IP address: “ + responseBody.ip); } @Override public void onFailure(Throwable why) { why.printStackTrace(); } }); } }
  • 10. @RpcObject • API 객체에 대한 메타데이터 • API 객체를 정의하기 위해서 별도의 상위 객체를 extend할 필요 없음
  • 11. HTTP 요청 @RpcObject( method=HttpRequest.Methods.GET, uri=“/view_post/%1$s/%2$d” ) public class ViewPostObject { public ViewPostObject(String userId, long postId) { this.userId = userId; this.postId = postId; } @UriPath(1) public String userId; @UriPath(2) public long postId; } REST 스타일
  • 12. HTTP 요청 @RpcObject( method=HttpRequest.Methods.GET, uri=“/list” ) public class ListObject { public ListObject(int offset, int limit) { this.offset = offset; this.limit = limit; } @QueryString public int offset; @QueryString(“count”) public int limit; // 명시적 이름 설정 } 쿼리 스트링 context.setDefaultUriPrefix(“http://foobar.local:5000”); context.invokeAsync(new ListObject(0, 100), new OnResponse<ListObject>() { … }); http://foobar.local:5000/list?offset=0&count=100 생성
  • 13. HTTP 요청 @RpcObject(uri=“/details”) public class DetailsObject { public ListObject(List<Integer> id) { this.id = id; } @QueryString public List<Integer> id; } 쿼리 스트링 http://foobar.local:5000/details?id=1&id=2&id=3
  • 14. HTTP 요청 @RpcObject( method=HttpRequest.Methods.POST, uri=“/signin” ) public class SignInObject { public SignInObject(String id, String password) { this.id = id; this.password = password; } @RequestBody public String id; @RequestBody public String password; } 엔티티 바디
  • 15. HTTP 요청 @RpcObject( method=HttpRequest.Methods.POST, uri=“/signin”, requestSerializer=JsonCodec.class ) public class SignInClass { … } 엔티티 바디 엔티티 serializer는 아래처럼 지정할 수 있다. default는 UrlEncodedFormEncoder.class
  • 16. HTTP 요청 엔티티 바디 Multipart entity는 추후 지원 예정
  • 17. HTTP 응답 @RpcObject(…) public class ProfileObject { … @Response public long id; @Response public String user; @Response(“display_name”) public String nickname; @Response public List<Post> recentPosts; } @Response 어노테이션을 사용
  • 18. HTTP 응답 @Response(required=true) public boolean result; @Response 응답에 필수적인 필드는 required를 true로 설정 이 경우 누락시 예외 발생 @Response public int foo; @Response public Integer bar; Boxed primitive 사용 가능
  • 19. HTTP 응답 {“user”: {“nickname”: “kyu”, “email”: “kyu@spoqa.com”}, ‘activities’: []} @Response(“user.nickname”) public String nickname; @Response(“user.email”) public String email; @Response public List<UserActivity> activities; @Response Subobject 참조
  • 20. HTTP 응답 @RpcObject(…) public class FooObject { private int id; @Response public void setId(int id) { this.id = id; } } @Response Setter methods
  • 21. HTTP 응답 @Response 하위 응답 객체의 생성자 public static class SubObject { public String foo; } public static class SubObject { public SubObject(String foo) { this.foo = foo; } public String foo; } public static class SubObject { public SubObject() { this.foo = null; } public SubObject(String foo) { this.foo = foo; } public String foo; } O O X
  • 22. HTTP 응답 @Response • 그럼 모든 응답 필드에 대해 @Response를 붙여야 하나요? • 아닙니다. • 최상단 객체에만 붙여주면 됩니다. • 하위 객체의 경우 필요하다면 (필드명 override, required=true) 붙 일 수 있습니다. • 요청과 응답 필드들이 한 클래스 안에 섞이는 것이 싫은데요?
  • 23. HTTP 응답 @ResponseObject @RpcObject(…) public class FooObject { … public static class FooResponse { public int id; public String name; } @ResponseObject public FooResponse response; }
  • 24. HTTP 응답 • 응답은 JSON만 지원하나요? • 현재로선 그렇습니다. • 다른 포맷도 추가할 계획이 있습니다. (XML, …) • (일단은) 모듈러하게 구현되어 있습니다. • ObjectBuilder.registerDeserializer(new JsonCodec()); • ObjectBuilder.registerDeserializer(new YourCodec());
  • 25. 필드 네이밍 자동 변환 { “user_id”: 1, “display_name”: “kyu”} @RpcObject( localName=CamelCaseTransformer.class, remoteName=UnderscoreNameTransformer.class, …) public class BarObject { @Response public long userId; @Response public String displayName; } 응답 뿐만 아니라 요청 필드에도 일률적으로 적용됨
  • 26. 커스텀 필드 타입 @Response public Date createdAt; Date 자료형을 알아먹게 만들려면 RpcContext ctx = …; ctx.registerTypeAdapter(new Rfc1123DateAdapter()); 기본 포함된 Date adapter의 종류 • Iso8601DateAdapter • Rfc1123DateAdapter • TimestampDateAdapter
  • 27. 열거형 enumeration public enum Currency { KRW, USD, EUR, JPY } @RpcObject(…) public class PurchaseObject { @RequestBody public Currency currency; @Response public List<Currency> acceptableCurrencies; }
  • 28. 전역 설정 오버라이드 @RpcObject( uri=“http://foobar.local:5000/qux”, requestSerializer=UrlEncodedFormEncoder.class, localName=CamelCaseTransformer.class, remoteName=UnderscoreNameTransformer.class) RpcContext ctx = …; ctx.setDefaultUriPrefix(“http://foobar.local:5000”); ctx.setRequestSerializer(new UrlEncodedFormEncoder()); ctx.setFieldNameTransformer(new CamelCaseTransformer.class, new UnderscoreNameTransformer.class);
  • 29. 요청 전처리 RpcContext ctx = …; ctx.setRequestPreprocessor(new RequestPreprocessor() { @Override public void validateContext(Object forWhat) throws ContextException {} @Override public void processHttpRequest(HttpRequest req) { req.putHeader(“X-Application-Id”, BuildConfig.APP_ID); req.putHeader(“X-Shared-Secret”, BuildConfig.SHARED_SECRET); } });
  • 30. 응답 검증 response validation { “result”: true, “message”: “request successful” “data”: { … } } API 레벨의 오류를 예외로 변환 위 응답을 POJO로 다음과 같이 정의할 수 있습니다. public class CommonResponse { public boolean result; public String message; }
  • 31. public class SignInResponse extends CommonResponse { public String accessToken; } public class ListResponse extends CommonResponse { public List<Article> data; } public class SignOutResponse extends CommonResponse { // No additional data } 응답 검증 response validation
  • 32. 응답 검증 response validation RpcContext ctx = …; ctx.setResponseValidator(new ResponseValidator() { @Override public void validate(Object object) throws ResponseValidationException { if (object instanceof CommonResponse) { CommonResponse response = (CommonResponse) object; if (!response.result) throw new ResponseValidationException(response.message); } } });
  • 33. 응답 검증 response validation ctx.invokeAsync(new SignInObject(…), new OnResponse<SignInObject>() { @Override public void onResponse(SignInObject response) { // Nothing wrong happened } @Override public void onFailure(Throwable why) { // ResponseValidator에서 예외 발생시 이 쪽으로 옴 why.printStackTrace(); } });
  • 34. 에러 코드 일반화 { “result”: 0, “message”: “successful”, “data”: { … } } { “result”: 100, “message”: “’cause you used it so wrong” } { “result”: 101, “message”: “sh*t happened” } 일반적으로 API 에러는 identifier를 가진다.
  • 35. 에러 코드 일반화 public @interface ErrorCode { public String[] value(); } 일반적으로 API 에러는 identifier를 가진다.
  • 37. 에러 코드 일반화 • @ErrorCode 걸린 모든 예외 클래스들을 검색하여 map에 추가 • Java reflection의 도움으로 • 이렇게 하면 선언만으로 등록이 가능 • ResponseValidator에서 해당 응답의 에러 코드를 map에서 검색 • 발견한다면 예외 instantiate 후 throw
  • 38. 에러 코드 일반화 ctx.invokeAsync(new PayObject(…), new OnResponse<PayObject>() { … @Override public void onFailure(Throwable why) { if (why instanceof CreditCardExpired) { … } else if (why instanceof CreditCardStolen) { … } else if (why instanceof CreditCardWithdrawn) { … } else { … } } });
  • 39. 전역 예외 핸들링 • 호출과 관계없이 특정 종류의 예외 발생시 호출 • 예를 들어 세션 만료 에러가 발생시 SharedPreferences 에서 세션 정보 삭제 후 다시 로그인 페이지로 돌아가야 한 다거나… • 핸들러 내에서 현재 UI 컨텍스트를 참조해야 한다면 invokeAsync()에 세번째 인자로 현재 Context를 전 달 • ctx.invokeAsync(req, onResponse, TestActivity.this);
  • 40. 전역 예외 핸들링 public class SessionExpiredHandler implements ExceptionHandler<Context> { @Override public boolean onException(Context context, Throwable error) { SharedPreferences prefs = context.getSharedPreferences(…); prefs.edit().remove(“session_key”).apply(); context.startActivity(new Intent(context, SignInActivity.class)); return true; // 참일 경우 예외를 더 이상 상위로 전달하지 않는다. } } ctx.registerExceptionHandler(SessionExpiredException.class, new SessionExpiredHandler());
  • 41. 전역 예외 핸들링 public class CredentialException extends Throwable { … } public class WrongPasswordException extends CredentialException { … } public class TooManyRetriesException extends WrongPasswordException { … } ctx.registerExceptionHandler(Throwable.class, new FooHandler()); ctx.registerExceptionHandler(CredentialException.class, new BarHandler()); ctx.registerExceptionHandler(WrongPasswordException.class, new BazHandler()); ctx.registerExceptionHandler(TooManyRetriesException.class, new QuxHandler()); 요청 결과로 TooManyRetriesException 발생 시 핸들러 평가 순서 QuxHandler → BazHandler → BarHandler → FooHandler → OnResponse.onFailure()
  • 42. 전역 예외 핸들링 if (BuildConfig.DEBUG) { ctx.registerExceptionHandler(Throwable.class, new ExceptionHandler<Context>() { @Override public boolean onException(Context context, Throwable error) { StringWriter sw = new StringWriter(); error.printStackTrace(new PrintWriter(sw)); new AlertDialog.Builder(context) .setTitle(“오류 발생”) .setMessage(sw.toString()) .setPositiveButton(“닫기”, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .show(); return false; } }); }
  • 43. RxJava와 함께 쓰기 Observable<FooObject> ob = ctx.invokeObservable(this, new FooObject(…)); ob.subscribe(new Action1<FooObject>() { @Override public void call(FooObject fooObject) { … } });
  • 44. Kotlin에서 쓰기 • 객체 정의는 Java로 해야 됩니다. • Java와 Kotlin은 reflection 구현이 달라서 호환이 안됨… AndroidRpcContext(context).invokeAsync( FooObject(), object: OnResponse<FooObject> { override fun onResponse(response: FooObject?) { … } override fun onFailure(why: Throwable?) { … } })
  • 45. Proguard와 함께 쓰기 • Reflection에 의존하기 때문에 @RPCObject에는 사용 불 가능 • 필드명, 메소드명을 dynamically lookup하기 때문… • -keep @com.spoqa.battery.annotations.RpcObject public class * • 하위 객체가 있을 경우 안 됨 • 가장 좋은 방법은 객체를 특정 패키지에 몰아넣고 적용 • -keep public class com.your_app.objects.*
  • 46. 추후 개발 계획 • 성능개선 • field, method lookup은 고비용 • 현재는 cache를 둬서 반복되는 객체의 lookup을 최소화 하고 있음 • deterministic한 부분은 컴파일타임에서 최적화 가능 • annotation processor
  • 47. 추후 개발 계획 • Multipart encoder 구현 • 파일 업로드 • XML decoder 구현 • 문서화 • 테스트 스위트 • (정식) 릴리즈 버그 리포트, PR은 언제나 환영합니다! https://github.com/spoqa/battery