Categories
Development

Android SDK r16에서의 ant 빌드 이슈

Android SDK Tools r16에서 생겨난 이슈입니다.

요약하면 코드 한줄 고치고 ant 빌드했는데 apk 파일이 새로 만들어지지 않는다는 겁니다. 이런게 어떻게 릴리즈 됐고 아직도 패치가 안됐는지 참..

구글링 하다보면 r16 이 이상한거니 r15 쓰라는 말도 있고, 개발장비 성능이 좋은 개발자분들은 매번 ant clean debug 를 한다고 합니다.

com.android.ant.InputPath#ignores에서 boolean flag에 !를 실수로 붙여서 생긴 문제이므로 소스코드의 !를 빼주기만 하면 됩니다. 고치기 귀찮으시죠?

$ANDROID_SDK/tools/lib/anttasks.jar 를 덮어쓰시면 됩니다.

추가로 제가 수정한 이 anttasks.jar 파일에는 dx –no-optimize 옵션 지원을 넣은DexExecTask도 포함되어 있습니다. no-optimize는 말도 안되게 느린 안드로이드의 dex 파일 생성을 조금이라도 빨리 하기 위해 사용합니다. $ANDROID_SDK/tools/ant/build.xml 파일의 253 라인 dex element 에 nooptimize=”true”를 넣으면 30% 정도의 속도향상을 보이게 됩니다. 마켓에 배포할 때는 no-optimize 꺼주시는 것 잊지 말고요.

코드 한 바이트도 안고쳐진 외부 라이브러리들도 빌드때마다 매번 다시 처리하시는 android dx tool 에게 10초간 묵념합시다.

Categories
Development Personal

안드로이드앱에 광고 넣은지 어느덧 2개월

두달전인 지난 4월 24일, 개인적으로 만들어 쓰던 안드로이드용 MSN 클론인 MSN Talk에 광고를 달았다.

처음에는 AdMob으로 시작했다. 내가 사용하는 많은 무료앱들이 AdMob을 쓰고 있었기 때문이였다. 가입절차도 단순했고, 돈을 PayPal로 넣어주니 Google AdSense보다도 편리하다. (난 영국계좌가 있기 때문에 AdSense로부터 들어오는 금액이 EFT로 들어와서 3~4일안에 은행계좌로 받을 수 있긴 함)

블로그에 붙였던 광고는 돈이 전혀 안됐었고 (그래서 떼버렸다), 한글 Javadoc에 붙인 AdSense에서는 한달에 120~150 클릭밖에 안나왔었으니 모바일앱은 어떨까.. 하는 마음에 호기심으로 시작한거다.

그러나 유료앱이였던 MSN Talk을 무료앱으로 전환한지 6주만에 ‘갑자기’ 광고를 넣었기 때문에, 욕을 바가지로 먹었다. 소심한 나는 앱에 광고를 단지 5일만에 광고를 내렸고, 다시 3일정도 고민한 뒤 설정에 ‘Suppress Ads’ 라는 옵션을 넣어 업데이트를 했고 민심은 회복됐다.

여기서 한가지 재미있는 것은 Suppress Ads 옵션을 넣었음에도 불구하고 광고수익이 거의 떨어지지 않았다는 것이다. 민심은 회복하고 광고수익은 거의 떨어지지 않는다라.. 매력적이였다.

그렇게 또 한달이 지나갔다. 광고를 표시하지 않을 수 있는 옵션이 있었지만 5월 한달동안 Daily 광고수익은 3배가 됐다.

3배가 된 광고수익을 보고 있자니 돈 욕심이 생기기도 하고 호기심도 생겨서 Suppress Ads 옵션을 빼보기로 했다. 옵션을 빼는 것과 동시에 Google AdSense for Mobile Application 베타사용자에 선택되는 행운을 얻어 AdSense를 붙이고 Fill Rate 해결사인 AdWhirl를 이용해 AdMob/AdSense를 돌아가며 보여줄 수 있게 했다.

그리고 3일만에 일일 광고수익이 또 2배가 됐다. 한꺼번에 많은 변수를 넣어서 정확히 어떤 요소가 수익증가를 일으켰는지 알 수 없다. 한가지 알 수 없는 것은 Suppress Ads를 빼고 항상 광고가 노출될 수 있도록 Google AdSense와 AdWhirl을 넣은 6월 3일부터 광고 짜증난다는 댓글이 거의 올라오지 않는다는 것이다. Suppress Ads가 있었을 때도 해당 옵션을 찾지 못한 사람이 있었는지 2-3일에 한번씩은 Ads are annoying 댓글이 꼭 달렸었는데, 별점 1-2점짜리 댓글이 올라오는 경우는 광고와 무관하게 앱의 퀄리티가 떨어지거나 버그가 있을 때 뿐이다. “광고가 짱나긴 하지만, xxx가 좋네!” 라고 댓글을 남기는 사람들은 대부분 별점 4~5점을 주기 때문에 광고가 앱의 명예를 크게 훼손하는 일은 없는거라 생각한다. (MSN Talk은 여태까지 댓글이 1,800개 넘게 달렸고 Rating을 6,200개 넘게 받았다. 평점은 4.31.)

나 또한 시각정보 자체차단능력이 약한 사람이라 광고를 무지 싫어해서 Google Chrome에서 Ads Blocker를 쓰는 사람이고, Google의 Chief Economist인 Hal Varian이 말했듯 2%의 사용자가 광고를 클릭하고, 그 광고를 클릭한 사람들 중 sales를 일으키는 사람은 2%다. 그럼 0.04%. Advertiser들은 0.04%를 얻기 위해 광고를 내고, 나같은 Publisher들은 2% (내 클릭률은 2%보다 낮지만)를 위해 광고를 단다.

나머지 98% 유저는 광고에 관심도 없고, 읽지도 않고, 그러므로 클릭할 가능성도 없는 사람인데 앱 개발자의 수익을 위해 귀중한 2G/3G 트래픽을 광고를 다운로드 하느라 날려먹어야 하고 주의력을 소모해야하고, 안그래도 조막만한 모바일 화면의 일부를 광고를 위해 내주어야 하는 것이다. 허허.. 참.

그 2달동안에도 순위가 꾸준히 올라가서 어느덧 Top Free – Communication 13위를 랭크하고 있고 (전체순위는 150위 정도) 광고수익도 충분히 올라서, 설령 앞으로 아무런 업데이트를 하지 않고 탱자탱자 놀더라도 현재 성장률로 미루어볼 때, 머지않아 성장률이 마이너스를 기록하고 MSN 공식앱이 나오더라도 최소 3개월은 현재 수익을 유지할 수 있다. 게다가 경쟁자가 나타나도 아무 상관없는 아이템들을 5개 정도 더 가지고 있고, 마켓볼륨이 3배정도 큰 아이폰에는 아직 진출도 하지 않았으므로 참으로 매혹적인 옵션인 것이다. 물론 여태까지 MSN Talk 개발에 투입한 정신적/육체적 에너지들를 생각해보면 일년에 앱 4개 이상을 찍어내진 못할 것 같지만.

그저 (내가 보기엔) 거지같은 광고를 달아 클릭당 50원정도의 금액을 모아 모아 돈을 벌고 있으며, 이 수익은 2% 미만의 사용자들이 채워주고 있고, 나머지 98% 이상의 사용자들을 귀찮게 하면서 돈을 벌고 있다는 게 불명예스러운 것이다.

그렇다할지라도 나처럼 돈이 필요해서 앱에 광고를 달 생각을 하시는 분들을 위해..

  • AdMob은 아시아권 Fill Rate이 엄청나게 낮다. 아마 Advertiser가 거의 없어서 그런게 아닌가 싶다. 한국은 10~30% 정도다. 아예 광고가 노출조차되지 않는것이다. 재미로 해보는 게 아니라면 반드시 북미와 유럽을 노려라.
  • Google AdSense for Android 베타 지원에 성공했다면, Activity.onDestroy에서 꼭 AdSense 컴포넌트 속의 WebView를 찾아 destroy()를 불러줘라. 배터리 많이 먹는다고 욕먹기 싫으면.
  • 본문에도 잠시 언급했지만, Ad Fill이 실패했을때, 다른 업체 광고들을 자동으로 채워주는 AdWhirl을 적극 검토하라. AdWhirl for iPhone의 경우 AdMob, Google AdSense, iAd, inMobi, Jumptap, MdotM, Millenial Media, MobClix, Quattro, VideoEgg 등 수많은 광고회사들을 지원한다.
  • 일 수익이 $30가 넘어가기 시작하면, 매일/매시간마다 수익을 확인하고 오만가지 생각을 하며 시간죽이는 경험을 피할 수 없을 것이다. 대쉬보드를 하나 만들어 노동을 줄이는 것은 효과가 크지 않다. 소프트웨어 개발자의 정체성을 잃지 않도록 유의하라. 명상이나 반신욕을 추천한다.
  • 부정클릭을 유도하는 짓을 절대로 하지 마라. 정상 패턴을 벗어나는 행동을 하기 전에는 반드시 AdMob이나 Google에 이메일을 보내서 이런이런거 해보려고 하는데 이게 Policy에 위배되는 것이냐- 물어보고 진행해라. AdMob은 뻘 질문해도 2-3일안에 꼭 회신해준다. 매우 친절하게.
  • Google Checkout을 쓸 수 있는 국가가 몇 개 없다. 유료앱으로 돈을 벌려면 iPhone으로 가라. 안드로이드앱으로 돈을 벌려면 지금 시점에서는 광고다.
Categories
Development

안드로이드용 MSN 클론을 만들며 느끼는 것들

2001년에 오픈소스로 개발했었던 JMSN을 안드로이드로 새로 만들어 마켓에 올렸다.

지난달 중순에 UK £1.0 유료앱으로 올렸었고, 하루에 3~6개씩 3주에 걸쳐 총 100카피가 팔렸다. feature도 거의 없고 버그도 많았던 유료앱이 100개나 팔렸다는게 신기할 따름이다.

그렇지만 3주에 100카피는 너무 적다고 생각한다. 비록 유료앱 런칭했던 시점에는 블로그 운영도 안했고 (지금은 앱전용 영문 블로그를 운영하고 있음) 어떠한 형태의 홍보도 하지 않았지만 말이다.

그러던 일주일 전, 무료로 바꿔보면 어떤 일이 일어날지 궁금해졌다. 그래서 잠깐 무료로 바꿔봤다. 그리고나서 엄청난 사실을 깨달았다. 유료앱의 가격을 바꾸거나 유료앱을 무료앱으로 바꾸는건 승인없이 개발자 맘대로 할 수 있지만, 한번 무료앱으로 바뀐 앱은 유료앱으로 영영 바꿀 수 없다는 사실. 어찌보면 당연한 것인데, 그래도 Google android market 정도면.. javascript confirm 창으로 알림 정도는 줄 수 있는 거 아니였을까?

그래서 pro 버전을 만들어야겠다는 생각이 뒤따랐다. Free 버전에는 광고를 넣어야되나.. 이런저런 생각이 나를 괴롭힌다.

하지만 일단 제품이 좋아야하지 않겠나. 그래서 품질을 올리기 위해 지난 일주일동안 업데이트를 15번쯤 했다. 덕분에 안정성도 유료앱시절보다 훨씬 좋아졌고 영어, 프랑스어 등으로 날라온 고객의견메일들도 일일히 제품에 반영하여 회신하고.. (Google Translate 덕에 프랑스어로 온 메일도 프랑스어로 회신할 수 있게 됐다 ㅋ)

해서 3주동안 100개 팔렸던 앱을 무료로 바꾸고나서 1주일만에 5,000개가 나갔다. Google android market은 유료앱 구매후 24시간내 묻지마 환불정책이 있어서, 사람들이 일단 다운로드하고 맘에 안들면 바로 환불하겠지 생각했었는데.. 잘못된 생각이였다. 하루에 5개 다운로드되던 앱이 어찌 하루에 600개가…

기본 기능들에 대한 지원과 메신저 앱의 생명인 안정성 확보가 끝나고나니, i love this app! 하며 날 기쁘게 만들어주는 고객들이 생기기도 하지만, 화상채팅 지원해달라, 파일전송은 왜 안되냐, Nudge(창 흔들기)는 왜 안되냐, 자기 girl friend가 dark theme을 안좋아하는데 테마 지원 기능은 언제 들어가냐..  등 요구사항들도 계속 들어오고 있다. 괴테의 말대로, 서두르지 말고 그러나 쉬지 말고 전략만이 유일한 방법이다.

경쟁앱도 무시할 수 없다. 나같은 무료 MSN 앱이 이미 4개가 있고, 모두 다 나보다 시장에 먼저 들어간 앱들이라 다운로드수도 어마어마하다. 게다가 통합메신저로 유명한 Meebo와 eBuddy, IM+도 마켓을 지키고 있다. 하드코어 게임하는 기분으로 하나씩 하나씩 다 밟는 수 밖에 없다. 성공하려면.

획기적인 아이디어를 기반으로 새로운 마켓을 형성하는 것은 나같은 일개 개발자가 할 수 있는 일이 아니다.  이미 잘 형성된 마켓에 들어가 피터지게 싸워서 1등이 되는 수밖에 없다.  그러다 지치면 재미난 아이디어로 조그마한 앱 만들며 기분전환하고.. 하는게 개발자의 삶이 아닐까.

제품이 아직 맘에 안들어서 블로그에 홍보는 못하겠다.

Categories
Development

Acer의 Android Netbook이 도착했습니다.

Acer가 만든 10.1인치 안드로이드 넷북을 구입하였습니다.

Acer가 빌드한 안드로이드라 조금 커스터마이징이 되어있습니다. 박스를 뜯고 바로 전원을 넣으면 안드로이드가 뜨지 않고 윈도우즈 XP가 뜹니다. 여기서 Acer가 만든 Android Manager 프로그램을 띄워서 업데이트를 하는 등.. 의 작업을 하고 난 뒤에야 안드로이드로 부팅할 수 있습니다.

그럼 부팅! Print Screen 버튼을 눌러 찍은 스크린샷들을 보시지요. (스크린샷 이미지를 클릭하면 원본 크기의 이미지를 볼 수 있습니다)

빰빰. 1024×600의 안드로이드가 부팅되었습니다. 버전을 확인하기 위해 정보보는 화면을 찾아보았으나 Acer의 Android 빌드 넘버만 나오고 SDK 버전 몇이 탑재되어있는지 확인할 수가 없었습니다.

그래서 android.os.Builder.VERSION 클래스를 이용해 버전을 확인해보니, Android SDK 1.5를 지원하고 있네요.

다음에는 브라우저를 띄워 페이스북 게임을 실행해보았습니다. 안타깝게도 플래시 플레이어가 설치되어있지 않군요.

좌측 상단에 뭐라고 줄줄.. 나온 것은 You must download and install latest version of Adobe Flash Player 입니다. 안타깝네요. Acer 가 나쁜 놈이지요, 뭐.

다음엔 이메일 클라이언트를 띄워 보았습니다. 검은 바탕에 회색 글씨가 아주 기품있습니다.

이번엔 구글톡을 실행해보지요.

네 그렇습니다. U.S. shipping only 제품이지만, 한글 표시에 문제가 없습니다.

물론 아래 화면처럼 한글 입력에도 문제가 없습니다.

단, IME 완성도가 좋지 않습니다. 종성이 없는 글자를 제대로 인식하지 못합니다. 예를 들어 ‘안녕하세요 여러분’ 을 입력하면 ‘안녕핫ㅔ요 열ㅓ분’ 이 됩니다. 그래서 종성이 없는 글자를 입력할 때에는 글자 완성 후 공백을 하나 입력하고 백스페이스를 누르는 노가다를 해줘야합니다. 좋지 않아요..

카메라 화질, 좋지 않습니다.

입 삐죽 나온 거 보이시죠? 화질 안좋다는 표정입니다.

안드로이드 플랫폼에는 별도의 Shutdown 기능이 존재하지 않습니다. 하지만 Acer의 Android Netbook은 윈도우XP와 안드로이드 둘 사이의 듀얼부트를 지원하기 때문에, 안드로이드에서 윈도우로 복귀하는 메뉴가 필요하지요. 화면 좌측 상단을 누르면 아래와 같은 다이얼로그가 표시됩니다.

마지막으로 지난 포스팅에서 만들었던 안드로이드 계산기를 실행시켜보았습니다. 가로가 너무 넓은 바람에.. 버튼의 너비가 길쭉 길쭉하여 영 보기 좋지 않습니다.

버튼이 너무 넓어요.. 세로 보기를 지원하지 않아서 멋진 스크린샷을 찍을 수 없었습니다.

결정적으로 Acer 안드로이드 넷북의 큰 단점이 있다면, 안드로이드 마켓을 쓸 수 없다는 것입니다. 메뉴얼을 봐도, 리뷰를 봐도, 플랫폼을 샅샅이 뒤져봐도 안드로이드 마켓을 이용할 수 있게 해주는 부분이 없습니다. 자신이 만든 프로그램을 설치하려면 USB로 업로드하고 디버깅하는 환경을 누릴 수도 없습니다. 그저 브라우저를 띄우고 apk 파일의 URL을 직접 입력해서 설치하는 수 밖에 없습니다. 참 불편하지요.

윈도우로 부팅했을 때 Android Platform Update 메뉴가 있긴 한데.. 패치 3개 정도밖에 없네요. 세로보기 지원을 바라지는 않지만, SD 카드에서 어플리케이션을 설치한다거나, 안드로이드 마켓을 이용할 수 있게 해준다거나 (Free 앱이라도), Android SDK 1.6 이나 2.0을 사용할 수 있게 얼른 업데이트를 제공했으면 합니다.

지금은 영.. 부족합니다.

Acer 안드로이드 넷북의 초간단 리뷰였습니다.

Categories
Daily Development

Android 스터디: 계산기 만들기

저번 포스트에 이어 오늘은 Android로 계산기를 만들어보도록 하겠습니다.

Eclipse 에 ADT 설치까지 마치셨나요? 그럼 헬로월드 실행도 성공하셨을테니, 바로 계산기를 만들어보도록 하겠습니다.

먼저 UI 설계를 하지요. balsamiq을 사용하여 간단히 그려보았습니다.

설계가 끝났습니다. 초기화 버튼이 없는게 이상하지요? 숙제로 남겨두겠습니다. 적절히 넣을 자리를 찾아 배치하고 미리 만들어진 메서드와 연결시키면 됩니다.

그러면 위처럼 화면을 구현할 수 있도록 Layout을 만들어야 합니다. Android 1.5 까지는 iPhone처럼 오직 320×480 하나만 신경써도 되서 편했을텐데요, Android 1.6 부터는 여러 해상도에 대한 지원이  들어갔습니다. support-screen 엘리먼트를 이용하여 지원하고자하는 화면크기들을 각각 지정할 수 있고  각 해상도 (미리 정의된 small, normal, large)에 맞는 XML UI를 만들 수 있습니다.

그러나 저는 그렇게 할 생각이 없습니다. 화면 해상도와 Portrait/Landscape 에 맞춰 적절히 늘어나는 UI 코드를 작성할 계획입니다. 화면 크기별로 UI를 새로 만든다니.. 생각만해도 끔찍하지 않습니까? 그래서 Common Layout Object를 사용할 것입니다.

일단 완성된 Eclipse ADT가 만들어준 HelloActivity를 가지고 바로 시작해보겠습니다.

잠깐. 새 플랫폼 공부를 시작하려면 어떻게 디버그 로그를 출력하는지 아는 것이 중요합니다. Logcat을 통해 디버그 로그를 출력하는 방법을 알고 넘어갑시다. android.util.Log.i(“my log”, “중얼중얼”) 하면 됩니다. android.util.Log 클래스엔 e(rror), w(arning), i(nfo), d(ebug), v(erbose) 메서드가 있습니다. 취향것 쓰시면 되고요, error나 warning의 경우 오류객체(Throwable)만 집어던져도 됩니다. Log 클래스를 쓰게되면 Android Debug Bridge로 해당 로그 메시지를 보내주므로 Eclipse 에서 로그메시지를 편하게 확인할 수 있습니다.

Log.d(“hehehe”, “onCreate is executed”);

를 onCreate 메서드에 적절히 넣으시고, 디버그를 시작하시면 Log 창에 뭔가 잔뜩 출력되는 것을 볼 수 있습니다. “hehehe”만 보시려면 + 버튼을 눌러 로그필터를 만드세요. by Log Tag 부분에 hehehe를 입력하시면 내가 찍은 로그만 따로 볼 수 있습니다.

Activity, 하나만 쓰겠습니다. 이번 목표는 동작하는 계산기를 만들어서 안드로이드 플랫폼에 자신감을 얻는 것이기 때문입니다. 완성된 소스코드는 github에 올려뒀습니다.

먼저, UI를 구성하기 위해 어떠한 컴포넌트를 쓸지 결정하겠습니다. 입력중인 숫자들과 결과들이 출력될 컴포넌트는 TextView를 쓰고, 각 연산자들과 숫자들은 Button을 씁니다. 위부터 아래로 출력하기 위해 LinearLayout을 쓰겠습니다.

LinearLayout view = new LinearLayout(this);
TextView activeNumber = new TextView(this);
activeNumber.setLayoutParams(new LinearLayout.LayoutParams(FILL_PARENT, WRAP_CONTENT, 0.0f));

여기까지 입력하고나니.. 버튼을 어떻게 추가해야할지 고민이 됩니다. LinearLayout을 사용하였으니 앞으로 추가하는 버튼들은 모두 세로로 표시될 것입니다. 이것은 원하던 결과가 아닙니다. 그래서 ButtonGroup 이란 클래스를 만들어 버튼들을 묶고 각 버튼들에 액션을 연결해두는 GridView를 하나 만들도록 하겠습니다.

public class ButtonGroup extends GridView

{

final int gravity = Gravity.CENTER;

final float textSize = 36.0f;

private Button[] nums = new Button[10];

private Map<Operator, Button> operatorButtons = new HashMap<Operator, Button>();

private ButtonActions action;

public ButtonGroup(Context context, ButtonActions action)

{

super(context);

this.action = action;

setNumColumns(4);

setHorizontalSpacing(1);

setVerticalSpacing(1);    

setStretchMode(GridView.STRETCH_COLUMN_WIDTH);

createButtons();

initLayout();

}

private void createButtons()

{

for(int i=0; i<nums.length; i++)

{

final int number = i;

nums[i] = new Button(getContext());

nums[i].setText(String.valueOf(number));

nums[i].setGravity(gravity);

nums[i].setTextSize(textSize);

nums[i].setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {

action.processNumber(number);

}

});

}

for(final Operator op : Operator.values())

{     

Button button = new Button(getContext());

button.setText(op.text());

button.setGravity(gravity);

button.setTextSize(textSize);

button.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {

action.processOperator(op);

}    

});    

operatorButtons.put(op, button);

}

}

private void initLayout()

{

// 7, 8, 9, /

// 4, 5, 6, *

// 1, 2, 3, –

// ., 0, =, +     

final Map<Operator, Button> ops = operatorButtons;

setAdapter( new BaseAdapter() {

private View[] buttons = {

nums[7], nums[8], nums[9], ops.get(Operator.DIVIDE),

nums[4], nums[5], nums[6], ops.get(Operator.MULTIPLY),

nums[1], nums[2], nums[3], ops.get(Operator.MINUS),

ops.get(Operator.DOT), nums[0], ops.get(Operator.RESULT), ops.get(Operator.PLUS)

};

public int getCount() {

return nums.length + ops.size();

}

public Object getItem(int position) {

return null;

}

public long getItemId(int position) {

return 0;

}

public View getView(int position, View convertView, ViewGroup parent) {

View view = buttons[position];

view.setLayoutParams(

new GridView.LayoutParams(parent.getWidth()/4, (parent.getHeight()-40)/4));

return view;

}

});    

}

}

GridView는 말그대로 그리드 형태로 컴포넌트를 배치시켜주는 공용 레이아웃 클래스입니다. 소스코드 하단의 BaseAdapter 구현부분이 핵심이지요. 총 배치될 컴포넌트의 개수(getCount), n번째에 있는 컴포넌트를 리턴하는 getView 메서드가 핵심입니다.

여기까지 했으면, 이제 이 ButtonGroup을 LinearLayout에 넣어보지요.

View buttons = new ButtonGroup(this, actions);

buttons.setLayoutParams(

new LinearLayout.LayoutParams(FILL_PARENT, FILL_PARENT, 1.0f));

view.addView(buttons);

ButtonGroup의 세로 크기는 화면에 꽉 차게 하고 싶으니 LayoutParams에 WRAP_CONTENT가 아닌 FILL_PARENT를 사용했습니다.

이제 각 버튼을 눌렀을 때의 처리를 만들어줘야 합니다. ButtonGroup 생성자에서 ButtonActions 객체를 받았는데요, ButtonActions의 소스코드는 안드로이드 플랫폼과 무관하게 계산기 로직이 담긴 부분이므로 별도의 설명을 하지 않겠습니다.

그리고 ButtonActions에서는 입력중인 숫자와 결과값이 출력될 TextView의 레퍼런스를 넘겨서 각 버튼을 눌렀을 때의 결과를 출력할 수 있도록 했습니다.

끝났네요. 계산기가 만들어졌습니다.