Twitter는 회원 인증과 OAuth 와 XAuth를 지원합니다. 
사실 XAuth로 인증 하는것이 Mobile에서 보편화된 방법인데, XAuth는 ID/Password를 3rd-Party Application에서 입력 받기에,  
개인적으로는 OAuth를 선호 합니다. 

Twitter에서 OAuth는 oauth_verifier를 Pin Code라는 개념으로, 유저에게 HTML 화면으로 노출시키고, 그것을 입력하도록 하고 있습니다. 
"휴대폰 인증번호" 쯤으로 생각하면 될듯합니다. 

사용자에게 입력하게 한다. 사용자는 이미 아이디와 비밀번호를 입력했는 데 또 입력하게 해야 한다니, 
별로 좋지 않군요. 

오늘의 목표는 저 Pin Code값을 자동으로 가져오는 것입니다 ! 

그래서 HTML 을 Parsing해서 가져와보자고 생각을 했습니다. 

Pin 값이 <div id="oauth_pin"> 으로 되어 있어서, HTML DOM Element Parsing으로 가져 올수 있을꺼 같다는 생각을 했습니다.

그러나..

Android WebView에 HTML 을 가져 오는게 존재 하지 않습니다 ;ㅂ;

그래서 WebView에 Javascript 를 집어 넣어서, Java Code를 호출 하여, dom의 값을 넣기로 했습니다. 


class MyJavaScriptInterface  

{  

    public void showHTML(String pin) throws TwitterException  

    {  

    if(pin.length() > 0 ) {

    Log.i("Javascript Interface","pin = " + pin);

    AccessToken accessToken = twitter.getOAuthAccessToken(requestToken, pin);

    Log.i("Javascript Interface", "AccessToken = " + accessToken);

    }

    else {

    Log.i("Javascript Interface","get pin failed...");

    }

    }  

}  

 webview.addJavascriptInterface(new MyJavaScriptInterface(), "HTMLOUT");


이런식으로 Java Code와 연결된, Javascript Interface를 WebView에 삽입 할수 있습니다. 

view.loadUrl("javascript:window.HTMLOUT.showHTML(document.getElementById('oauth_pin').innerHTML);");  

view.loadUrl("javascript:window.HTMLOUT.showHTML(document.getElementsByTagName('code')[0].innerHTML);");

WebViewClient의 onPageFinisehd() Method를 Override하여, 삽입한 Javascript를 실행해주면
Page가 로딩 될때마다, 호출되고,oauth_pin 값을 얻는 순간이 인증이 끝난 순간이됩니다. 

그것을 통해서 AccessToken을 가져오면 Twitter OAuth 인증은 끝난 셈이죠. 

Java Code내에서 끝나지 않고, Javascript 를 통한다는게 좀 마음에 안드는 방식이긴 합니다. 
더 좋은 방법을 알게 되면 후딱 후딱 넘어가야죠. 어쨌든 잘 동작합니다. 


iPhone 사용하시다 보면, 다들 이런 경험 있으시죠? ^^

iOS App Store에서 App.을 다운로드 받을때, 20 MB 이상인 녀석들은 3G 인터넷을 통해 다운로드 받을 수 없습니다. 
App Download에 제한이 걸리게 되면, App 판매량에도 분명 영향을 미칠것입니다.

자 그럼 어떻게 이 뚱뚱한 App 녀석을 다이어트 시켜야 할까요?

최근에 알게된 것중에 하나 인데, 소개 해보려고 합니다. 
Stretchable Image 즉, 확대 가능한 이미지를 사용함으로써, Image Size를 줄이고, 궁극적으로는 Application Size를 줄일 수 있습니다.
Android개발을 해보신 분들이라면, 9 patch Image라는 녀석을 아실텐데요, 그녀석과 흡사하지만 덜 파워풀한녀석입니다 ㅎ

 

실험 대상이 될 Image는 바로 요녀석인데요, 요녀석은 40x24 입니다. 
근데 저는 이녀석을 40x24로도 사용하고, 240x24 로도 사용하고 싶습니다.

가장 간단한 방법은 이녀석을 40x24 버전과 240x24버전을 따로 준비 하는것인데, 이러면서 App.이 뚱뚱해지기 시작합니다. 

그럼 어떻게 해야 하냐 ? 범인, 아니 정답은 여기 있습니다 !

- (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight;


이녀석을 이용하면, 일단 UIImage를 Stretchable하게 바꿔줍니다. 


위의 스킨 샷을 보시면, Stretchable한 Image를 이용하지 않고 그냥 UIImageView의 크기만 늘리게 되면, 
두번째 나오는 녀석처럼 쭈욱 늘어나서, 못생긴 녀석이 됩니다. 
그러나 UIImage를 Stretchable한 상태로 만들어 주면 짜잔 , 3번쨰 녀석 처럼 매끈하게 나옵니다. 


UIImage *img = [[UIImage imageNamed:@"image.png"] stretchableImageWithLeftCapWidth:10 topCapHeight:10];

[stretchableView setImage:img];


예제 코드는 위와 같은데요, 
leftCap/topCap 값을 줄수 있어서, 값에 따라 Image를 upscale할때, 특정 픽셀들은 남기고, 아래 값들만 upscale시키게 됩니다. 

애석하게도, 위의 방법은 Interface Builder에서는 할 수 있는 방법이 없기 때문에, 
UIimageView에 Image를 세팅하는것을 소스코드 레벨에서 따로 해줘야 합니다. 

Apple이녀석들은 이런 괜찮은걸 만들어 놨으면, InterfaceBuilder에서도 쓸 수 있게 해줘야지 ;ㅂ; 
친구 녀석 말따라, Apple 애들도 IB를 잘 안써서 그런가 봅니다.


Sample Project 첨부해 드리니, 살펴 보실 분들은 한번씩 살펴 보시기 바랍니다. 

어떻게, 당신의 App은 다이어트에 성공하셨나요? ^^




GDB 라는 엄청나게 강력한 도구를 C언어를 배울때 부터 알고 있었고, 몇번 쓰려고 했었지만, 
Makefile + vim 을 사용하던 시절에는 그냥 귀찮아서, printf로 작업한적이 많았고, 
GDB를 그냥 Core Dump의 Back Trace용도로만 사용했었습니다. 

이번에 작업을 하면서 BreakPoint를 찍어서 GDB를 활용하려고 다시 시도 해보려고 합니다.
어쩌면 다시 귀차니즘에 printf(iOS에선 NSLog)로 돌아 갈지도 모르지만, 말이죠 ㅎㅎ

XCode에서 BreakPoint를 찍는건 상당히 간단한데요,
Code Editor의 Line Number쪽을 클릭해주면 됩니다. 

그런후에, Build & Run이 아니라 Build & Debug를 실행시켜주면, GDB와 함께 App.이 실행 됩니다. 



Build & Debug를 한후에 GDB를 켜보면 이런식으로 화면이 나오는데요

특정 BreakPoint가 어떤 순서로 찍혀 져있는지, Application Cycle도 알수 있고,
구지 NSLog등으로 찍어 주지 않아도, 

변수, 주소 값 등등을 손쉽게 Navigation할 수 있습니다. 

별로 어려운것도, 아니지만, 상당히 강력하지만 또 번거롭기도 한 녀석인데요 
이번에는 좀더 전투적으로  사용해볼까 합니다 
요새, XCode용 Plugin 을 하나 개발하고 있는데요, 

XCode는 Plugin FrameWork은 있지만, API를 공개하지 않습니다. 

Objective-C의 특성을 통해서, 공개하지 않는 Header File을 긁어 낼수가 있는데요, 
그런 신통 방통한 일을 해주는 녀석이 바로 class-dump 입니다. 

위 페이지에 들어 가시면 다운 로드 받으실수 있고요, /usr/loca/bin 이나 PATH 설정 되어 있는곳에 설치 하시면 됩니다.


class-dump는 cli command이기 때문에 Terminal 에서 사용하실수 있습니다. 

그럼 한번 마법을 부려서 XCode의 Header를 스윽 긁어 볼까요


뭐 이런식으로 나오게 됩니다. Class들이 뭐 있는지만 살펴 보려면, 


요런 녀석들이 들어 있군요 ㅎ 할 수 있는 일에 내용이 많이 없죠? 
그 이유는 바로 XCode가 Plugin 위주로 개발이 되어 있기 때문입니다. SCM , 심지어 Compiler부분까지 plugin화 되어 있죠. 

왠지 Hacker가 된것 같은 기분이 들지만, 더 낳은 개발을 위해 오늘도 달려 봅시다 ㅎㅎ


특정날짜 시간의 Date Object를 구할일이 있어서 찾아 봤습니다.

java.util.Calendar를 이용해서 Date값을 구할 수 있습니다. 우선 예제 소스를 보시면

Calendar cal = Calendar.getInstance();

cal.set(Calendar.HOUR,hour);

cal.set(Calendar.MINUTE,min);

cal.set(Calendar.SECOND,0);

cal.set(Calendar.MILLISECOND, 0);

Date expectedDate = cal.getTime();


이런식입니다.

Calendar Class의 Object는 SingleTone으로만 접근이 가능합니다. 그래서 
"new Calendar()" 이런식으로 사용할 수 없고 "getInstance()" Method를 통해서 SingleTone Object를 가져 옵니다. 

그리고 Key-Value Mechanism을 통해서 년/월/일/시/분/초/마이크로초 이런식으로 세팅이 가능합니다. 

SingleTone으로 관리 되기 때문에, 사실 위와 같은 코딩은 조금 위험합니다. 
년/월/일을 다른데서 세팅 했을수 있기 때문이죠.
그래서 사용하기 전에, reset을 해주는게 좋은데요.

cal.setTime(new Date());


저 같은 경우는  위와 같이 현재 시간으로 reset해서 사용합니다. 



자 오늘 제가 하고 싶은 일은,  UITextField에 숫자를 입력할때, ","를 자동으로 집어 넣는것입니다.
1111->1,111 이런식으로 표기가 되는것이죠.

일단 값 변경이 일어 나는것을 Catch 하는 방법은 두가지가 있습니다.

UITextFieddDelegate의  "textField:shouldChangeCharactersInRage:replacementString:" 을 이용하는 방법이 하나이고, 

두번째로, UITextFieldTextDidChangeNotification을 이용하는 것입니다.

UIKIT_EXTERN NSString *const UITextFieldTextDidChangeNotification;


우선, Notification을 사용하는 방식은 선호 하지 않는 방법이기도 하고,  Notification을 등록하면, [textField setText:] Method가 불러 질때 에도 불러짐으로 사용하지 않고,  UITextFieldDelegate Method를 구현해서 사용하도록 하겠습니다.

TextField 의 값 Setting을 UITextField 내부에서 하지 않고, 우리 쪽 소스에서 하도록 해야 합니다.
결국 shouldChange... 샬라 샬라에서 NO 를 줘서, 변경이 일어 나지 않게 합니다.

아래 소스는 UITextField 내부 구현이 아닌, 우리의 구현으로 기본적이 동작이 가능하게 하는 코드 입니다. 

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

NSString *newText = [textField.text stringByReplacingCharactersInRange:range withString:string];

[textField setText:newText];

return NO;

}


여기에 newText를 우리가 원하는 포맷으로 변경해주는 Formatter를 삽입하여,  콤마를 자동입력하게 할수 있습니다.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

NSString *newText = [textField.text stringByReplacingCharactersInRange:range withString:string];

[textField setText:[DataFormatter moneyFormat:newText]];

return NO;

}


iOS에서 MoneyFormatting에 관한 내용은 http://krazie99.tistory.com/49 에서 참조 하시면 됩니다 :)
이를 응용해서, iOS에서 이런저런 Auto Format Completion을 비롯한 이런저런 자동완성기능을 할 수 있을꺼라 생각됩니다.

Sample소스 첨부하니, 보시고 궁금한건 언제든 문의 하세요 ~ 




Android File I/O를 살펴본 이유는 POJO 를 저장하기 위해서다. 

사실 SharedPreference를 통해서 POJO를 저장할 수 있길 기대 했지만, SharedPreference는 Java Object를 저장할 수 없게 되어 있다.

그래서 결국 POJO를 Serialize 해서, File로 떨구기로 결정했기에, Android File I/O에 대해서 살펴 보기로 하였다. 

Android에서 File I/O의 Target Directory는 두가지일수 있다. 
하나는 Internal Storage이고, 다른 하나는 External Stroage(SD Card)이다. 
POJO를 저장하는 건 보통 매우 작은 단위의 Data이기 때문에, Internal Storage로 결정했다.

File I/O 역시, Context Class를 통해서 해결한다. 
Context Class를 살펴 보면 openFileInput/openFileOutput이 있다. 

abstract FileInputStream openFileInput(String name)
Open a private file associated with this Context's application package for reading.
abstract FileOutputStream openFileOutput(String name, int mode)
Open a private file associated with this Context's application package for writing.

보통 Java Application 에서 FileInputStream을 직접 사용하는것과 다리 
Context의 openFileInput Method를 통해서 internal Storage에서 해당 App의 SandBox Directory로 부터 File Stream을 열수 있다. 

그외에 FileXXXXStream을 사용하는 방법은 일반 Java Programming과 동일 함으로 생략.

여기서 한가지 더 필요 했던게, 만든 파일을 어떻게 지울것인가 이다. 

지우는 Method도 Context Class에 있다. 

abstract boolean deleteFile(String name)
Delete the given private file associated with this Context's application package.
뭐 역시나 별로 어려운건 없다 ㅎ

Android는 Context단위로, 만위 움직이기때문에, API 사용시에 Context를 사용할 일이 많다. 
그래서 JUnit에서 Context의 접근은 꼭 필요 한일이다. 

결론부터 말하면 org.junit.TestCase대신에 android.test.AndroidTestCase 를 사용하라는 것이다. 


위에서 보는 바와 같이 AndroidTestCase에서는 이미 Mock Context Object를 제공하고 있음으로,
AndroidTestCase 를 상속받아서, TestCase Class를 정의하고, 
내부적으로 getContext() Method를 이용해서 바로 쓰면 된다. 


Android에서, 화면은 Activity단위로 움직인다.

특정 Context에서 또다른 Activity를 실행시키고, 현재의 Activity를 종료 시키는 방법에 대해서 알아 보려고 한다. 

Context Class를 찾아 보면 startActivity라는 녀석이 있다. Context Class에 정의 되어 있기 때문에, Context의 SubClass인, Activity는 물론 Service Object에서도 특정 Activity를 실행 시킬 수 있다. 

public abstract void startActivity (Intent intent)

Since: API Level 1

Launch a new activity. You will not receive any information about when the activity exits.

Note that if this method is being called from outside of an Activity Context, then the Intent must include the FLAG_ACTIVITY_NEW_TASK launch flag. This is because, without being started from an existing Activity, there is no existing task in which to place the new activity and thus it needs to be placed in its own separate task.

This method throws ActivityNotFoundException if there was no Activity found to run the given Intent.

Parameters
intentThe description of the activity to start.
Throws
ActivityNotFoundException

startActivity()를 통해서, activity를 실행시키게 되면, 현재의 Activity 는 Pause상태에 접어 들게 된다. 

특정 Activity()를 종료 시키기 위해서는 Activity Class내의 finishXXX() Method들을 사용하면 된다. 
가장 기초적인것만 살펴보면, finish()가 있다. 

public void finish ()

Since: API Level 1

Call this when your activity is done and should be closed. The ActivityResult is propagated back to whoever launched you via onActivityResult().


finish() 가 호출되면, 현재 Activity가 Destroy 상태가 되며, (OnDestroy() 호출됨 )  이전 Activity 가 다시 Active상태가 된다 ( onResume() 호출됨 )

이런식으로 대강 Activity의 흐름이 흘러간다. 

나중에 finishActivity(Intent, int ) 와 같은 함수들이 쓸일이 생기면 그때 걔네들을 좀 살펴봐야 겠다.



일단 Strings 에 정의된 String값들도, layout등과 같이 각자의 식별자를 가진다. 

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <string name="hello">Hello World</string>

</resources>


위와 같이 정의된 string파일이 있다면, "Hello World"라는 String은 "R.string.hello" 에 id가 정의된다. 

Java Source Code에서 string자원을 접근할때는,  Context Class의 getString() Method를 사용한다. 
 

위에서 보는 바와 같이 각종 Activity/Service등이 모두 Context의 SubClass임으로, 
Activity나 Service를 구현할때는 바로 getString을 사용할 수 있다.

아래 코드는 "R.string.hello"을 사용하는 예제 코드 이다 .

someTextView.setText(getString(R.string.hello));




+ Recent posts