하이브리드 앱 강좌 #4. 전자책형 앱

2013년 vs 2018년

5년 전에 이 블로그에서 하이브리드 앱 강좌를 시작했습니다.
개인적인 사정으로 중단했다가 이번에 기회가 되어 하이브리드 앱 강좌를 다시 시작하게 되었습니다.

5년 사이에 몇 가지 점이 달라졌습니다.
가장 대표적인 것은 안드로이드 버전의 변화입니다.
예전에는 젤리빈이 가장 최신 버전이었으나 지금은 오레오입니다.
또한 안드로이드 앱 개발도구도 달라졌습니다.
예전에는 이클립스가 많이 사용되었으나 지금은 안드로이드 스튜디오가 대세입니다.

이번 강좌부터는 안드로이드 스튜디오를 이용하여 내용을 작성하겠습니다.
JDK 10.0.1, Android Studio 3.1.2 를 기반으로 하였습니다.

시작하기 전에

전자책형 하이브리드 앱의 개념은 앞의 강좌 에서 설명한 것처럼, 앱 설치 파일 내부에 htm 파일을 포함시키는 것입니다.
웹페이지들을 제작하고 이것을 프로젝트의 assets 폴더에 넣은 뒤 .apk 파일에 포함시켜 배포합니다.
따라서 전자책형 앱은 다음과 같은 특징을 가지고 있습니다.

  • 별도의 서버를 운영할 필요가 없습니다.
  • 인터넷 접속을 필요로 하지 않습니다.
  • 페이지 전환 속도가 빠릅니다.
  • 파일 한 개가 변경되어도 매번 이를 플레이스토어에 업로드하여야 합니다.
  • apk 파일의 압축을 해제하면 assets 폴더의 html, javascript 코드가 그대로 노출됩니다.
    따라서 전자책형이라는 이름과 달리 전자책을 이런 식으로 개발해서는 안 됩니다.
  • 플레이스토어에 업로드할 수 있는 apk 파일크기는 100MB 용량 제한이 있습니다.
  • 반응형 웹사이트를 이용할 수 있습니다.

전자책형 앱은 다음과 같은 순서로 제작하겠습니다.

  1. 기본 준비
  2. 웹페이지 준비
  3. 웹페이지를 assets 폴더에 넣기

#1. 기본 준비

안드로이드 스튜디오에서 새로운 프로젝트를 생성합니다.
form factors and minimum SDK 에서는 Phone and Tablet 과 API 15: Android 4.0.3 (ICS) 을 선택합니다.
Add an Activity to Mobile 에서는 Empty Activity 를 선택합니다.
기본으로 들어있는 TextView를 지우고 WebView를 추가합니다.
추가한 WebView의 ID를 webViewMain으로 지정합니다.
다음 코드를 작성하여 초기화합니다.

public class MainActivity extends Activity {

    WebView webViewMain;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        webViewMain = (WebView) findViewById(R.id.webViewMain);
        WebSettings webSettings = webViewMain.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setBuiltInZoomControls(true);
        webSettings.setDisplayZoomControls(false);
        webViewMain.setWebViewClient(new WebViewClient() {

        });
        webViewMain.setWebChromeClient(new WebChromeClient() {

        });
    }

    @Override
    public void onBackPressed() {
        if (webViewMain.canGoBack()) {
            webViewMain.goBack();
            return;
        }
        super.onBackPressed();
    }
}

 

#2. 웹페이지 준비

불러올 웹페이지를 제작합니다.
여기서는 빠른 진행을 위해 부트스트랩 홈페이지에 있는 starter template를 이용하도록 하겠습니다.

https://getbootstrap.com/docs/4.0/examples/starter-template/

인터넷 익스플로러를 이용하여 위의 링크에 접속해서 Ctrl+S 버튼을 눌러 index.htm으로 저장합니다.
(크롬에도 같은 기능이 있지만 버그가 있어서 제대로 작동하지 않습니다.)

그러면 index.htm 파일 및 index_files 폴더가 생성되는데 index.htm 파일을 열어 끝부분을 다음과 같이 수정해 줍니다.

<!DOCTYPE HTML>
<!-- saved from url=(0061)https://getbootstrap.com/docs/4.0/examples/starter-template/ -->
<!DOCTYPE html PUBLIC "" ""><HTML lang="en"><HEAD><META content="IE=11.0000" http-equiv="X-UA-Compatible">

...

<!-- Bootstrap core JavaScript
================================================== --> 
<!-- Placed at the end of the document so the pages load faster -->     
<SCRIPT src="index_files/jquery-3.2.1.slim.min.js"></SCRIPT>
<SCRIPT src="index_files/popper.min.js"></SCRIPT>
<SCRIPT src="index_files/bootstrap.min.js"></SCRIPT>
</BODY></HTML>

여기까지 하면 웹페이지 파일 준비가 끝난 것입니다.

#3. 웹페이지를 assets 폴더에 넣기

다음으로는 안드로이드 애셋 폴더를 만들어야 합니다.
안드로이드 스튜디오에서는 애셋 폴더를 기본적으로 생성해 주지 않으므로 직접 추가해야 합니다.
프로젝트 뷰 – app 우클릭 – New – Folder – Assets Folder를 클릭합니다.

기본값인 main을 그대로 두고 finish 버튼을 클릭하면 프로젝트 뷰에 assets 항목이 생긴 것을 확인할 수 있습니다.
윈도우 탐색기에서 앞에서 저장한 index.htm 파일 및 index_files 폴더를 한꺼번에 선택하고 Ctrl+C 를 눌러 클립보드에 복사해 둡니다.
프로젝트 뷰의 assets 항목을 선택하고 Ctrl+V 를 눌러 붙여넣기 합니다.
경로를 확인하는 창이 나오는데 그대로 두고 OK 버튼을 클릭합니다.

여기까지 되었다면 onCreate 메서드의 마지막 부분에 다음 코드를 추가하여 애셋에 저장된 페이지를 웹뷰로 불러옵니다.

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    webViewMain.loadUrl("file:///android_asset/index.htm");
}

실행하면 다음과 같은 화면을 볼 수 있습니다.

이상으로 전자책형 하이브리드 앱 개발에 대해 예제를 통해 간단히 다루어 보았습니다.
다음 강좌에서는 하이브리드형 하이브리드 앱 개발 방법을 다루겠습니다.

참고 자료

관련 포스트

이비인후과 전문의입니다

9월 29일

원서 접수에 6개월 이내에 촬영한 여권용 사진이 필요하다 하여 근처 사진관을 방문하여 사진을 촬영하고, 사진 파일도 받았다.

10월 12일

이비인후과학회 홈페이지 (http://www.korl.or.kr/) 에 2018년 제61차 전문의자격시험 시행 안내라는 글이 올라왔다.

요약하면 다음과 같은 순서로 진행하게 된다는 것이었다.

  1. 전문의자격시험 홈페이지에 응시자 정보 입력
  2. 원서 구매
  3. 원서 및 서류 제출
  4. 1차 시험
  5. 2차 시험

전문의자격시험 홈페이지에 접속하여 회원가입을 하고  내 정보를 입력하고 사진, 의사 면허증, 수련과정 이수(예정)증명서를 업로드했다.

그런 뒤 원서비 (20만원) 을 결제했다.

이비인후과 원서 접수시에 필요한 서류는 원서와 전공의 기록부였다.
원서 작성에는 어려움이 없었으나, 전공의 기록부에 채워야 할 내용이 많았다.
전공의 기록부를 미리 작성해두지 않은 사람들은 밀린 방학숙제 하는 느낌으로 며칠에 걸쳐 기록부를 작성해야 했다.

10월 24일

전공의 연회비 (30만원), 이비인후과 평생회비 (80만원), 응시료 (70만원) 을 송금했다.
원서와 전공의 기록부를 들고 이촌역 근처에 있는 이비인후과학회 사무국을 찾았다.

전공의 기록부를 빈틈없이 작성했다고 생각했으나, 원내 집담회 (컨퍼런스) 제목이 누락된 것이 확인되어 병원에 다시 돌아와서 해당 부분을 보충했다.
그런 뒤 다시 방문하였을 때에는 문제 없어 무사히 서류 제출을 끝냈다.

본격적인 시험 공부를 시작했다.

12월 25일

서울대병원 전공의협의회에서 전문의 시험날 차량 지원을 하여 메일로 신청했다.

1월 11일

전날 수험표를 출력해 두고 병원에서 밤까지 공부하다가 당직실에서 잤다.

아침에 일어나 전공의협의회에서 마련한 버스를 타러갔다.
버스 앞에서 출석체크를 하면서 맥모닝과 생수를 주었다.

버스는 고사장인 삼육대학교를 향했고, 8시가 조금 넘은 시각에 도착했다.
버스에서 내리니 의사협회에서 핫팩, 생수, 초콜릿바 등이 담긴 종이백을 나누어 주면서 응원해 주었다.

고사장까지 찾아온 부모님께서 응원해주고 가셨다.
내가 시험 치를 128 고사장은 에스라관 2층이었고, 이비인후과와 정형외과가 모여 시험을 치게 되었다.

8시 반까지 입실을 완료한 후, 9시에 시험이 시작되었다.
이전에 1교시 105문제, 2교시 35문제 (R형 20문제) 으로 공지되었던 것과 달리, 실제로는 1교시 100문제, 2교시 40문제였다.
대부분의 문제가 최근 기출문제와 유사하게 출제되었으나, 일부 문제는 오래된 기출문제나 인트레이닝 문제에서 출제되었다.
또한 새로 출제된 문제들 중에는 어렵거나 정답을 고르기 애매한 것들이 있었다.

11시에 1교시 시험이 끝난 뒤 30분간의 쉬는 시간이 있었다.
쉬는 시간에 전자기기를 사용하는 것은 금지되었기 때문에 준비해간 책을 보며 공부했다.

2교시는 11시 30분부터 12시 30분까지 1시간 동안 치러졌다.
앞의 20문제 (A형) 는 쉬운 편이었으나 뒤의 20문제 (R형) 는 어려웠다.

시험이 끝난 뒤 교수님들께 인사드리기 위해 병원으로 향했다.
병원으로 오는 길에 몇몇 낯설거나 어려운 문제들의 답을 확인해 보았다.
생각보다 많이 맞아서 기뻤다.

병원에 도착한 뒤에는 교수님들을 직접 찾아뵙거나 전화를 드렸다.

2차 시험 기출문제집을 선배들에게 받았으나, 1차 시험 합격 발표 나기 전까지는 공부가 손에 잡히지 않았다.

1월 15일

오후 2시에 전문의 자격시험 홈페이지에서 합격자 명단이 공개되었으나 내 이름을 찾을 수 없었다.
당황하여 다른 동기들의 이름을 찾아보았으나 전부 없었다.
알고보니 착오로 작년 합격자 명단을 올린 것이었다.
얼마 뒤 새로 합격자 명단이 공개되었고 거기에는 내 이름이 있었다.
5시에는 대한의학회에서 합격 통지와 함께 사과 문자가 왔다.

본격적인 2차 시험 준비를 시작했다.

1월 16일

동기 레지던트들끼리 각자 흩어져 공부하다가 시간을 맞추어 공부방으로 모였다.
알레르기내과에서 빌린 알레르기 피부반응검사 기구를 가지고 실습해 보기 위해서였다.
검사방법을 동영상으로 많이 보았으나 실물을 보기는 처음이었다.

그런 뒤에는 후각검사실에 방문하여 비강통기도검사, 음향비강통기도검사, 후각검사 (CCSIT) 을 각각 실습해 보았다.

이런 검사들을 실제로 연습해 보는 것이 많은 도움이 되었다.

1월 17일

동기 레지던트들이 모여 평형검사실을 방문하였으나, 나는 다른 공부를 하느라 가지 못했다.

1월 18일

다들 청력검사실에 모여 순음청력검사 (기도, 골도), 언어청력검사, 임피던스 검사, 청성뇌간유발반응역치검사 등을 실습해 보았다.

1월 19일

그동안 컴퓨터를 통해 기출문제 공부를 했으나, 시험날 대기실에서는 전자기기를 사용할 수 없기 때문에 인쇄된 공부자료가 필요했다.
시험을 하루 앞두고 최근 10개년의  2차시험 기출문제를 분과별로 모아 제본했다.

제본한 기출문제를 한 번 다 본뒤 서울아산병원 근처의 모텔로 가서 잠을 청했다

1월 20일

서울아산병원에서 2차 시험을 쳤다.

8시까지 입실하여 9시부터 시험이 시작되었다.
두 그룹으로 나누어 (A, B) 가나다순으로 시험을 치렀다.
나는 B그룹 3번째 조에 속해서 시험을 치게 되었다.

귀, 코, 목 각각 3방씩 총 9방을 돌면서 문제를 풀었다.
각 방에서의 제한시간은 5분이었고, 끝나기 1분 전에 안내방송이 나왔다.

나는 1개의 방에서만 시간이 모자랐고 나머지 8개의 방은 문제를 푼 뒤 시간이 남아서 가만히 앉아있어야 했다.
9개의 방의 시험을 다 돌고 난 뒤에는 합격을 예감할 수 있었다.

내 시험이 끝난 뒤에는 강당에 모여 영화 2편을 감상하면서 다른 사람들의 시험이 끝날 때까지 기다렸다.
오후 4시가 되어 모든 사람들의 시험이 끝난 뒤 귀가했다.

2월 2일

1차 때와 마찬가지로 오후 2시에 홈페이지를 통해 합격자 발표가 나왔다.

내 이름도 합격자 명단에 포함되어 있었다.

지난 11년 (예과 2년, 본과 4년, 인턴 1년, 레지던트 4년) 간의 노력이 결실을 맺었다.
훌륭한 이비인후과 전문의가 되겠다.

관련 포스트

커스텀 페이지 템플릿을 플러그인으로 제작

플러그인을 이용한 커스텀 페이지 템플릿 제작이 필요한 이유

앞선 강좌에서 자식 테마를 이용한 커스텀 페이지 템플릿 제작을 다루었습니다.
(#3. 테마 적용, 자식 테마 제작, #4. 페이지 제작, 커스텀 테마 적용)

이러한 방식은 간편하다는 장점이 있으나 부모 테마에 의존하므로, 테마 종류를 변경하기가 어렵다는 단점이 있습니다.
테마를 바꾸더라도 커스텀 페이지 템플릿은 바뀌지 않도록 하려면 플러그인을 사용해야 합니다.

워드프레스 플러그인 검색에는 나오지 않아, 구글링을 통해 해당 플러그인을 찾았습니다.

플러그인 소스 링크

GitHub

플러그인 소스

아래 소스를 wp-content/plugins/(플러그인명)/(플러그인명).php 파일에 작성합니다.

<?php
/*
Plugin Name: Page Template Plugin : 'Good To Be Bad'
Plugin URI: http://www.wpexplorer.com/wordpress-page-templates-plugin/
Version: 1.1.0
Author: WPExplorer
Author URI: http://www.wpexplorer.com/
*/
class PageTemplater {
 /**
 * A reference to an instance of this class.
 */
 private static $instance;
 /**
 * The array of templates that this plugin tracks.
 */
 protected $templates;
 /**
 * Returns an instance of this class. 
 */
 public static function get_instance() {
 if ( null == self::$instance ) {
 self::$instance = new PageTemplater();
 } 
 return self::$instance;
 } 
 /**
 * Initializes the plugin by setting filters and administration functions.
 */
 private function __construct() {
 $this->templates = array();
 // Add a filter to the attributes metabox to inject template into the cache.
 if ( version_compare( floatval( get_bloginfo( 'version' ) ), '4.7', '<' ) ) {
 // 4.6 and older
 add_filter(
 'page_attributes_dropdown_pages_args',
 array( $this, 'register_project_templates' )
 );
 } else {
 // Add a filter to the wp 4.7 version attributes metabox
 add_filter(
 'theme_page_templates', array( $this, 'add_new_template' )
 );
 }
 // Add a filter to the save post to inject out template into the page cache
 add_filter(
 'wp_insert_post_data', 
 array( $this, 'register_project_templates' ) 
 );
 // Add a filter to the template include to determine if the page has our 
 // template assigned and return it's path
 add_filter(
 'template_include', 
 array( $this, 'view_project_template') 
 );
 // Add your templates to this array.
 $this->templates = array(
 'goodtobebad-template.php' => 'It\'s Good to Be Bad',
 );
 
 } 
 /**
 * Adds our template to the page dropdown for v4.7+
 *
 */
 public function add_new_template( $posts_templates ) {
 $posts_templates = array_merge( $posts_templates, $this->templates );
 return $posts_templates;
 }
 /**
 * Adds our template to the pages cache in order to trick WordPress
 * into thinking the template file exists where it doens't really exist.
 */
 public function register_project_templates( $atts ) {
 // Create the key used for the themes cache
 $cache_key = 'page_templates-' . md5( get_theme_root() . '/' . get_stylesheet() );
 // Retrieve the cache list. 
 // If it doesn't exist, or it's empty prepare an array
 $templates = wp_get_theme()->get_page_templates();
 if ( empty( $templates ) ) {
 $templates = array();
 } 
 // New cache, therefore remove the old one
 wp_cache_delete( $cache_key , 'themes');
 // Now add our template to the list of templates by merging our templates
 // with the existing templates array from the cache.
 $templates = array_merge( $templates, $this->templates );
 // Add the modified cache to allow WordPress to pick it up for listing
 // available templates
 wp_cache_add( $cache_key, $templates, 'themes', 1800 );
 return $atts;
 } 
 /**
 * Checks if the template is assigned to the page
 */
 public function view_project_template( $template ) {
 // Return the search template if we're searching (instead of the template for the first result)
 if ( is_search() ) {
 return $template;
 }
 
 // Get global post
 global $post;
 // Return template if post is empty
 if ( ! $post ) {
 return $template;
 }
 // Return default template if we don't have a custom one defined
 if ( ! isset( $this->templates[get_post_meta( 
 $post->ID, '_wp_page_template', true 
 )] ) ) {
 return $template;
 } 
 $file = plugin_dir_path( __FILE__ ). get_post_meta( 
 $post->ID, '_wp_page_template', true
 );
 // Just to be safe, we check if the file exist first
 if ( file_exists( $file ) ) {
 return $file;
 } else {
 echo $file;
 }
 // Return template
 return $template;
 }
} 
add_action( 'plugins_loaded', array( 'PageTemplater', 'get_instance' ) );
?>

위 소스의 58번 행을 적절히 수정하고, 커스텀 페이지 템플릿 파일을 wp-content/plugins/(플러그인명)/ 디렉토리 안에 생성합니다.

관리자 페이지 – 플러그인 – Page Template Plugin : ‘Good To Be Bad’ – 활성화를 합니다.
그러면 페이지 편집 화면의 페이지 속성 – 템플릿에서 커스텀 템플릿을 볼 수 있습니다.

관련 포스트

2014년도 전공의 자율평가고사 (인트레이닝) 후기

7월 18일

대한이비인후과학회 홈페이지 공지사항에 2014년도 전공의 자율평가고사 시행 안내라는 제목의 글이 올라왔다.
시험을 위해 각 파견병원별로 일괄접수를 했다.

9월 1일

대한이비인후과학회 홈페이지 공지사항에 2014년도 자율평가고사 수험장 배정 안내라는 제목의 글이 올라왔다.
자율평가고사에 응시한 연차별 113~124명의 이비인후과 전공의들의 명단과 각각에게 배정된 수험장이 함께 공지되었다.
나는 서울대병원에서 시험을 치게 되었다.

시험일

시험 전날부터 기출문제 공부를 시작했으나 당직과 겹쳐 밤 사이에 별로 공부를 하지 못했다.
시험 날 오전에서야 2012년 기출문제만 한 번 풀어볼 수 있었다.
오후 2시가 되자 고려대, 중앙대, 연세대, 서울대, 중앙보훈, 부산대, 경북대, 충남대, 전남대, 전북대병원 등 전국 10개 지정장소에서 2014년도 전공의 자율평가고사가 시작되었다.
각 고사장에 모인 전국 이비인후과 전공의들은 3시간동안 총 100개의 문제를 풀게 되었다.
나에게는 이과, 두경부외과에 비해 비과 문제가 어렵게 느껴졌다.
아는 문제는 답이 보여 금방 풀었고, 모르는 문제는 고민하더라도 답이 나오지 않을 것이므로 금방 찍었다.
알든 모르든 빠르게 답을 골랐기 때문에 퇴실이 가능한 4시가 되자 곧바로 답안지를 제출하고 퇴실할 수 있었다.
시험을 마치고 나가면서 답안지를 받을 수 있었고, 곧바로 내 점수를 알 수 있었다.
1년차는 50점만 넘어도 전국 순위권이라는 얘기를 들었기에 반타작만 하자는 생각으로 채점을 했다.
그러나 흐린 기억을 더듬어 찍었던 답들이 의외로 많이 맞아서 생각보다 높은 점수를 받아서 기뻤다.

9월 24일

외래 치프 선생님이 전체 카톡창에 자율평가고사 관련 공문을 스캔해서 올려주셨다.
공문의 내용은 지난 번 시험에서 정답이 바뀐 문제가 3개 있다는 것이었다.
확인해 보니 채점 당시에 답이 이상하다고 생각했던 문제들이었고, 정답이 정정된 결과 내 점수는 2점이 더 올랐다.

10월 1일

서울대병원은 전공의 자율평가고사 시험 성적에 신경쓰지 않는 분위기이기 때문에 나도 시험에 대해서는 잊고 지내고 있었다.
오후 회진을 준비하며 병동에 앉아 있는데 이비인후과 비서분이 나를 찾아오더니 축하한다면서 나에게 아래와 같은 공문을 전해주었다.


종이에 적힌 내용이 무엇인지 깨닫고 나서 처음에는 실감이 안 났다가 뒤늦게야 기쁜 마음이 들었다.
한편으로는 내 실력에 비해 과분한 성적을 받은 것 같아 부담스럽기도 했다.
시상식 날짜가 당직날과 겹쳐서 당직을 바꾸다 보니 그날이 이비인후과 가을학회 첫날이고, 시상식이 개회식의 일부로 진행된다는 것을 알게 되었다.

10월 10일

포스터, 숙소 체크인 등을 위해 선발대로 대전을 찾아 전날 밤을 대전에 있는 레지던스에서 묵고 아침 일찍 학회장을 찾았다.
아침에 학회장에서 하기로 했던 일들을 마무리한 뒤 후발대와 합류하여 개회식장을 찾았다.
시상식의 차례가 되어 다른 수상자들과 함께 나도 호명되었다.
대한이비인후과학회 이사장님께 상장을 받았고 기념촬영도 했다.
이후에 학회장에서 만난 많은 사람들께 축하와 격려를 받아서 감사하고 기뻤다.


학회가 마친 뒤 병원에 돌아와 지난 한달 동안 겪은 일을 돌이켜보면 벌써부터 꿈만 같다.
내년 인트레이닝에서는 시험 점수는 오르겠지만 올해와 같은 등수를 받기는 어려울 것이다.
그러나 시험 등수가 중요한 것이 아니라는 것을 알기 때문에 개의치 않는다.

SAMSUNG CSC

나에게는 훌륭한 이비인후과 의사가 되는 것이 중요한 목표이다.
앞만 보고 달려온 지난 수련기간을 이번 시험을 통해 돌아볼 수 있게 된 것이 큰 의미가 있다.
밥먹듯 끼니와 잠을 거르며 허우적거리기에 바빴던 지난 기간들이 헛되지 않았고, 내가 올바른 길을 걷고 있다는 것을 확인한 것이 이번 시험의 진정한 성과라고 생각한다.
앞으로 더 잘하기 위해 열심히 노력해야겠다.

관련 포스트

이비인후과 0년차입니다

10월 8일

인턴 하계수련회에서 공지한 바대로, 올해부터 이비인후과도 사전면접(arrange)을 하기로 했다.
사전면접에 앞서, 이비인후과 설명회가 이날 저녁에 열렸다.
나는 그날 아침에 상을 당해서 지방으로 내려가 있는 상태였기 때문에 설명회에 참석할 수 없었다.
설명회에서는 저녁식사를 하면서 지원동기를 포함하여 자기소개를 했고, 모임이 끝날 무렵에는 다음 주 중 면접에 참석 가능한 날짜를 조사했다고 한다.
나처럼 사정상 설명회에 참석하지 못한 지원자들에게는 면접에 참석 가능한 날짜를 알려달라는 문자 메시지가 왔다.

10월 12일

사전면접 날짜가 14일로 결정되었다고 통보받았다.
더불어 면접일 아침까지 주어진 양식에 맞추어 자기소개서를 작성하여 보내라는 연락도 받았다.

10월 14일

새벽에 일어나 2시간만에 자기소개서를 작성하여 마감 시한을 10분 남기고 제출했다.
분당서울대병원 소아과에서의 오전 근무를 마치고 면접 시간에 맞추어 서울대병원으로 갔다.
면접 대기실에는 남자 지원자 7명, 여자 지원자 4명이 있었다.
현역 원내턴이 아닌 사람들이 많이 지원했기 때문에 지난 주의 설명회에 참석하지 못한 나에게는 대부분이 초면이었다.
면접은 남자 먼저, 나이 많은 순서대로 1명씩 치렀다.
남자 중에서는 내가 2번째로 어렸기 때문에 전체 중 6번째로 면접을 보러 들어갔다.
들어가서 인사드리자마자 면접관 중 한 교수님께서 “우리가 왜 자네를 뽑아야 하는지 설명해 봐”라고 하셨다.
첫 질문이 워낙 강렬해서 이후의 다양한 면접 내용은 기억에 남지 않았지만, 면접 분위기는 나쁘지 않았다.
면접을 보고 나온 지원자들은 각자 하던 일을 하러 갔다.
나는 당직을 서기 위해서 다시 분당서울대병원으로 돌아왔다.

10월 17일

사흘 전에 보았던 면접 결과가 나왔고, 합격 여부를 듣기 위해서는 다시 서울대병원으로 찾아오라는 연락을 받았다.
함께 소아과에서 근무하는 다른 인턴에게 양해를 구하고 다시 본원으로 갔다.
교수님 연구실을 찾아 개별 면담 형식으로 합격 여부를 통보받았다.
사정상 자세하게 적을 수는 없지만 긍정적인 소식을 들어서 기뻤다.

11월 15일

의대생 커뮤니티 사이트에 ‘2014_레지던트_티오’라는 제목의 글이 올라왔다.
다시 말해 2014년 각 병원별 레지던트 1년차 정원이 확정되었다는 의미였다.
나의 관심사는 내년의 서울대병원 이비인후과 레지던트 정원 숫자가 기존의 7명으로부터 변동이 있는지 여부였다.
정원이 증가하는 것은 나의 당락에 영향을 미치지 않지만, 정원이 감소하는 경우에는 기존 합격자 중 불합격하는 경우가 생기기 때문이다.

다행히 본원 4명, 분당 2명, 보라매 1명의 이비인후과 레지던트 정원에는 변화가 없었다.

11월 18일

원서 접수를 1주일 앞두고 레지던트 모집계획 공고가 서울대병원 채용정보 게시판에 올라왔다.

원서 이외에도 몇 가지 서류가 필요했으나 자기소개서나 추천서 등과 같이 준비하기 까다로운 서류는 없었다.
다만 원서에 부착할 사진이나 토익 성적표는 부모님 집에 있어서 부모님께 가져다 달라고 부탁드렸다.

11월 21일

인턴 성적이 발표되었다(인턴 성적이 나왔다 참조).
만족할 만한 성적을 받았다.

11월 25일

비가 내리는 날씨임에도 불구하고 원서접수 첫날에 접수하기로 했다.

작성했던 원서, 토익 성적표, 입금증 등의 서류를 가지고 예전에 인턴 원서 접수를 했던 곳과 동일하게 교육연구부 행정팀을 찾아 원서를 제출했다.

앞의 3자리(011)는 병원 코드(서울대병원), 중간 2자리(16)는 지원과목 코드(이비인후과), 마지막 3자리(017)는 병원별 접수순서를 의미한다고 했다.

현장 접수와는 별개로 온라인 원서접수도 해야 했는데, 분당서울대병원으로 돌아온 뒤에 잠시 시간을 내어 접수했다.
이후로는 응급실에서 근무하는 도중에 가끔씩 시간 날 때마다 레지던트 지원 현황을 새로고침하면서 시간을 보냈다.

11월 27일

오후 5시에 원서접수가 마감되었다.
원서 접수 이튿날 오전부터 1:1로 유지되던 이비인후과 경쟁률이 변함 없이 마감되었는지에 주로 관심이 있었다.

다행해도 원서 접수가 마감될 때까지 추가 지원자는 없었다.

12월 8일

전날 당직이었지만 다행히도 잠은 충분히 잘 수 있었다.
팁스 책 2권 중 내과 파트를 챙겨서 병원을 나섰다.

병원 정문 로비에서는 잠실고로 전공의 시험을 치러 갈 인턴들을 위한 버스가 마련되어 있었다.

8시에 본원에서 출발해서 아침식사로 제공된 샌드위치를 먹으면서 공부하다 자다를 반복하다 보니 금세 잠실고에 도착했다.

나는 수험표를 챙겨오지 않았기 때문에 잠실고 신관 1층에 위치한 시험본부에 찾아가서 신분증을 제시하고 수험표를 재교부받았다.

내가 배정받은 고사장에 가 보니 수험번호 순서대로 자리가 배정되어 있었다.

그제서야 사전면접에서 합격한 다른 이비인후과 지원자가 누구인지 알 수 있었다.

공부를 조금 하다가 시험 시작 시간이 되었다.
문제에서 묻는 게 무엇인지가 보이는 게 대부분이어서 객관적인 문제 난이도가 쉽다고는 느꼈다.
그러나 사전면접에서 합격한 뒤로 도무지 공부가 손에 잡히지 않던 나에게는 답을 고르기가 힘든 문제들이 많았다.
시험이 끝난 이후에는 아침에 탔고 왔던 버스를 다시 타고 본원으로 돌아와서 안과 당직을 섰다.

12월 9일

전날 치른 전공의 시험 성적 통보를 하루 종일 기다렸다.
결국 오후 6시가 조금 지나서 각 과목별 점수, 총점, 지원자의 평균 점수가 문자로 발송되었다.
내 성적은 이비인후과 지원자 중에서는 평균에 약간 못 미치는 정도였고, 득점 백분율로 따지면 60%를 간신히 넘기는 수준이었다.
점수가 그다지 높지는 않았지만, 공부한 만큼 점수를 받았다고 생각했기 때문에 아쉬움은 없었다.

12월 11일

면접을 앞두고 6일 연속으로 당직을 서다 보니 마지막날 당직 때에는 체력이 바닥났다.
자정에 하려고 했던 일을 하지 못하고 잠들어 버려 새벽 4시부터 당직일을 시작했다.
일을 마무리한 뒤 씻고 나서 면접과 실기시험 준비를 시작한 것은 6시 반이었다.
6시 50분까지 면접 장소로 모여야 했기 때문에 나에게 주어진 시간은 많지 않았다.
그나마 면접 문제와 실기 시험의 기출문제는 알고 있었고, 아침부터 눈이 많이 내려 면접관 중 몇몇 교수님께서 늦으시면서 면접 시간도 늦춰졌다는 것은 나에게 다행이었다.
면접은 접수한 순서대로 한명씩 보았고, 이비인후과 지원자 중 가장 먼저 접수했던 나부터 면접을 보게 되었다.
면접 내내 분위기는 좋았고, 면접장을 나설 때에는 합격을 예감할 수 있었다.
7번째 지원자의 면접이 끝난 뒤에는 다같이 모여 실기시험을 치렀다.
기출문제의 유형은 알고 있었지만, 문제 유형을 알더라도 풀기 쉽지 않은 문제였다.
시험이 모두 마친 뒤에는 지원자들끼리 간단히 인사를 하고 연락처를 교환한 뒤에 흩어졌다.

12월 13일

면접날 저녁부터 난무했던 합격, 불합격에 관한 소식은 이날 오후 4시에 병원 홈페이지 채용정보 게시판을 통해서 합격자 명단이 발표될 때 사실 여부가 판명될 예정이었다.

실제로는 오후 3시가 약간 지나자 합격자 명단이 공개되었다.

이비인후과의 지원자 7명 모두 합격했다.
나중에 확인해 본 결과 사전면접을 치르지 않은 과들 중 일부는 경쟁률이 1대1이었음에도 불구하고 정원보다 적게 뽑기도 했다고 한다.
2007년에 같이 입학했던 의예과 동기 70여명 중 서울대병원 레지던트에 합격한 사람은 40명 정도라는 사실도 듣게 되었다.
경쟁을 뚫고 합격한 몇몇 동기들에게 합격을 축하하는 연락을 주고받았다.

나는 이비인후과 레지던트 0년차가 되었다.
아직은 인턴이지만 내년에는 레지던트 1년차가 될 예정이라는 의미이다.
두 달 전부터 이렇게 되리라는 것을 알고는 있었지만, 막상 공식적인 합격 통보를 받으니 느낌이 달랐다.
불확실하기만 했던 내 미래 중에서 7년(레지던트 4년, 군대 3년)은 윤곽이 잡혔기 때문이다.
내가 원했던 과에 합격해서 수련받게 되어 기쁘지만, 한편으로는 몇 년간 밤낮없이 고생할 생각을 하니 걱정도 된다.
수련과정을 잘 마쳐서 훌륭한 이비인후과 의사가 되고 싶다.

관련 포스트

인턴 성적이 나왔다

인턴 성적

의대생들은 성적이라는 단어에 예민하다.
여느 대한민국 학생들보다 이 단어에 더욱 더 많은 관심을 가져 왔기에 그들이 의대생이 될 수 있었을 것이다.
의대에 다니는 동안에도 성적에 대한 집착은 유지되며, 아무리 못하더라도 재시나 유급만큼은 피하기 위해 공부하다 보면 어느 새 졸업을 맞는다.

막상 인턴이 되면 시험의 압박감에서는 다소 해방되지만 대신에 근무 성적이라는 새로운 성적이 기다리고 있다.
매 달마다 과를 옮겨가면서 수련을 받고, 해당 과에서는 인턴의 근무 성적을 평가한다.
3월부터 10월까지 집계된 근무 성적은 다른 몇 가지 항목 점수들과 합산하여 인턴 성적이 되고, 이 성적은 대개 11월 말경에 개별적으로 통보된다.
결국 의대생이 졸업하고 인턴이 되어서도 성적으로부터 완전히 자유로울 수는 없는 것이다.
인턴이 근무 성적으로부터 자유롭게 되는 시기는 11월부터이고, 이 시기의 인턴을 흔히들 말턴(말년 인턴)이라고 부르기도 한다.

11월 13일

휴가차 부산에 와 있던 중 인턴 카페에 새 글이 올라왔다는 알림이 떴다.
네이버 카페에 접속해 보니 교육연구부에서 인턴 근무 성적 공지에 관한 안내문을 올린 것을 확인할 수 있었다.

뒷 부분은 중략했으나 한 마디로 요약하면 ‘인턴 성적은 다음 주말쯤에나 알랴줌ㅋ’ 이라는 것이었다.
레지던트 선발에서 인턴 성적이 차지하는 비중이 작지 않기 때문에 10월 말부터 교육연구부에 전화가 빗발쳤으리라는 것은 충분히 짐작하고 남았다.
또한 인턴 성적을 산출하는 데에 필요한 다른 점수들은 대개 집계가 끝난 상황이었기 때문에, 11월 중순에 이러한 공지를 올리는 것은 일부 과에서 교육연구부에 10월 인턴 성적을 통보하지 않았기 때문이라고 생각되었다.
나도 다른 인턴들과 마찬가지로 내 인턴 성적이 궁금했지만, 성적이 그렇게까지 중요한 상황은 아니었기 때문에 느긋하게 기다리기로 했다.

11월 21일

전날 퇴근하면서도 같은 당직실 쓰는 사람들끼리 인턴 성적이 21일에 발표될 것인지 22일에 발표될 것인지에 대해 이야기를 나눈 터였다.
보다 최신 소식에 따르면 목요일에 발표될 것이라고 했기 때문에 출근하고 나서도 문자가 오기만 하면 혹시나 인턴 성적이 아닐까 싶어서 화들짝 놀라기도 했다.
그러나 근무 시간이 다 지나갈 때까지 인턴 성적은 통보되지 않은 채였다.
오후 7시 퇴근을 30분 정도 남기고서 분당 응급실 단체 카톡창에 ‘근무평가등수옴!!’ 이라는 메시지가 올라왔다.
다른 사람들은 아직 문자를 받지 못한 것으로 보아, 성적 통보에 개인별 시간차가 있는 듯했다.
5분 뒤에 나에게 다음과 같은 문자가 도착했다.

저녁식사를 하는 중에 교육연구부로부터 문자 발송에 오류가 있었다고 하는 공지를 받고 잠시 뒤숭숭하기도 했다.
그러나 결국 등수 하나가 밀리는 정도에서 마무리되어 마음이 놓였다.

내 인턴 근무 성적이 누군가에게 자랑할 만큼 아주 뛰어난 것은 아니지만, 그래도 부끄럽지 않은 정도여서 만족스러웠다.
나의 지난 8개월이 단지 몇 자리 숫자로 표현된다는 게 허무하기도 했다.
그동안 항상 환자를 우선적으로 생각하면서 일해왔고, 다같이 힘들고 피곤한 상황에서 나의 짜증이 남에게 투사되지 않도록 했고, 같이 일하는 사람들을 존중하고, 그들과 함께 즐겁게 일하기 위해 노력했던 매 순간들이 떠올랐다.
과정이 결과보다 중요하다고 생각하지만, 그동안 손해를 감수하면서도 보이지 않게 노력한 과정이 다른 사람에게 인정받아 좋은 결과로 이어졌다고 생각하니 기뻤다.

앞으로 남은 인턴 기간에도 지금껏 해온 것처럼 최선을 다했으면 좋겠다.

관련 포스트

하이브리드 앱 강좌 #3. 웹브라우저형 앱

하이브리드 앱의 유형

하이브리드 앱에 대한 개념이 모호한 이유 중 하나는 하이브리드 앱이라 부를 수 있는 앱의 종류가 워낙 다양하기 때문입니다.
웹 앱을 웹뷰를 통해 네이티브 앱 속에 포장한다는 개념은 공통적이지만, 포장할 웹 앱을 가져오는 방법에 따라서 하이브리드 앱을 크게 3가지로 나누어 볼 수 있습니다.
(아래에 쓰인 용어들는 편의상 제가 자의적으로 붙인 이름입니다.)

  1. 웹브라우저형 앱
  2. 전자책형 앱
  3. 하이브리드형 앱

웹브라우저형 앱

웹브라우저형 앱은 웹브라우저처럼 매번 특정 URL에 접속해서 웹 앱(웹페이지)을 불러옵니다.
웹 앱은 자신이 개발한 웹사이트를 참조할 수도 있고, 타 웹사이트를 참조할 수도 있습니다.
네이버 모바일 페이지를 참조하는 네이버 앱이 전자에 해당합니다.

네이버 모바일

DC인사이드를 참조하는 여러 브라우저 앱들은 후자에 해당합니다(예: 하이브리드 DC).

하이브리드 DC

이런 종류의 앱은 오프라인 상태에서는 아무런 기능을 할 수 없습니다.

오프라인

심지어 제대로 코딩되지 않은 경우에는 모바일 앱이 접속하려는 웹 앱의 URL이 그대로 노출되기도 합니다.

URL 노출

애플 앱스토어 검수에서는 웹브라우저형 앱이 단지 모바일 페이지를 불러오는 것에 그칠 경우, 앱으로 만들지 말고 웹사이트의 북마크를 추가하라고 합니다.
따라서 이런 앱은 네이티브 앱에서만 사용 가능한 기능들을(푸쉬 알림, 카메라 등) 추가해야 합니다.

전자책형 앱

전자책형 앱은 앱 설치 파일 내부에 이미 htm 파일들이 포함되어 있습니다.
웹뷰를 통해 로컬 htm 파일을 불러오므로 웹 서버에 접속할 필요가 없습니다.
따라서 서버의 트래픽을 소모하지 않고, 오프라인 상태에서도 작동하고, 각 페이지를 로딩하는 시간이 짧다는 장점이 있습니다.
컨텐츠 제공을 주된 목적으로 하는 몇몇 앱들이 해당됩니다(예: jQuery Reference)

jQuery Reference

단점으로는 하나의 페이지를 업데이트 하더라도 어플을 새로이 마켓에 업로드해야 한다는 점입니다.
안드로이드 앱의 경우에는 수 시간 안에 업데이트되지만, iOS 앱의 경우에는 또 다시 검수 과정을 거쳐야 하므로 하이브리드 앱의 장점 중 하나를 잃게 됩니다.

애플 앱스토어 검수에서는 이런 앱이 단지 정해진 컨텐츠를 불러오는 것에 그칠 경우, 앱으로 만들지 말고 전자책을 만들라고 합니다.
따라서 이런 앱은 사용자와 상호작용하는 기능을 추가해야 합니다.
인터넷을 사용하지 않는 순수한 전자책형 앱은 찾기 어렵고, 대개는 상호작용 기능을 추가하기 위해 웹뷰를 통해 게시판을 보여주는 방식으로 제작됩니다.

하이브리드형 앱

하이브리드형 앱은 웹브라우저형 앱과 전자책형 앱의 장점을 결합하고 단점은 보완한 것입니다.
일부 기능들은 전자책형 앱으로 구현하고 나머지 기능들은 웹브라우저형 앱으로 구현하는 경우가 대표적입니다.
공통 자바스크립트, 이미지 파일 등은 앱 내부에서 불러오고, 나머지 컨텐츠는 웹을 통해서 가져오기도 합니다.
웹 앱에 처음 접속할 때에는 인터넷을 사용하지만, 다음부터는 캐시된 페이지를 보여주기도 합니다.

개요

이번 강좌에서는 하이브리드 앱의 유형 중 가장 단순한 웹브라우저형 앱을 제작하겠습니다.
앱의 이름은 `DC 도우미`이며, 이름대로 타 웹사이트(DC인사이드)를 참조하므로 따로 HTML, javascript 코딩은 하지 않습니다.
안드로이드 웹뷰 활용 강좌로 보아도 됩니다.

준비

앞 강좌에서 다룬 대로 이클립스에서 새 프로젝트를 생성합니다.
LinearLayout, WebView, Button을 배치하고 텍스트를 지정합니다.

레이아웃 배치

버튼 클릭 이벤트를 처리하기 위해 WebView와 Button에 id를 부여합니다.

id지정

기본 작업을 위한 소스는 다음과 같습니다.

public class MainActivity extends Activity {

	WebView webViewMain;
	Button buttonTitle, button1, button2, button3;
	OnClickListener cListener;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		webViewMain = (WebView) findViewById(R.id.webViewMain);
		buttonTitle = (Button) findViewById(R.id.buttonTitle);
		button1 = (Button) findViewById(R.id.button1);
		button2 = (Button) findViewById(R.id.button2);
		button3 = (Button) findViewById(R.id.button3);
		cListener = new OnClickListener() {

			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				switch (v.getId()) {
				case R.id.buttonTitle:
					buttonTitleClick();
					break;
				case R.id.button1:
					button1Click();
					break;
				case R.id.button2:
					button2Click();
					break;
				case R.id.button3:
					button3Click();
					break;
				default:
					break;
				}
			}
		};
		buttonTitle.setOnClickListener(cListener);
		button1.setOnClickListener(cListener);
		button2.setOnClickListener(cListener);
		button3.setOnClickListener(cListener);
		
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) { ... }

	void buttonTitleClick() { }

	void button1Click() { }

	void button2Click() { }

	void button3Click() { }
}

인터넷 퍼미션 추가

웹에 접속해야 하므로 `인터넷` 퍼미션을 사용해야 합니다.
이를 추가하기 위해서는 Project Explorer에서 AndroidManifest.xml을 더블클릭합니다.

퍼미션

Manifest 창 아래에서 Permission 탭을 선택합니다.

퍼미션

Add 버튼을 클릭합니다.

퍼미션

이 중에서 Uses Permission을 선택하고 OK를 클릭합니다.

퍼미션

Name 옆의 셀렉트 박스에서 android.permission.INTERNET 을 선택합니다.
Ctrl+S를 눌러 저장하면 반영된 것을 볼 수 있습니다.

이제 이 어플을 설치하려 하면 `네트워크 통신 – 완전한 네트워크 액세스`라는 권한이 필요하다는 메시지를 볼 수 있게 됩니다.
이러한 단계를 거치지 않은 경우, 인터넷이 되는 상황에서도 웹뷰에서는 웹페이지를 표시할 수 없다는 메시지만 나옵니다.

URL 불러오기

지금 상태에서 어플을 실행시킬 경우 웹뷰에서는 빈 화면만 볼 수 있습니다.
어플 실행이 마친 뒤에는 웹뷰에 홈페이지를 불러와야 합니다.
onCreate 메서드에 다음과 같은 코드를 추가하기만 하면 됩니다.

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		...
		
		webViewMain.loadUrl("http://http://m.dcinside.com/list.php?id=programming");
	}

WebSettings

첫실행

이제 어플을 실행하여 작동해 보면 몇 가지 이상한 점들이 눈에 뜨입니다.
우선 자바스크립트가 작동하지 않습니다.
또한 페이지 줌인/줌아웃이 작동하지 않습니다.
이러한 문제점들을 해결하기 위해서는 WebSettings를 이용해야 합니다.

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		...
		
		WebSettings webSettings = webViewMain.getSettings();
		webSettings.setJavaScriptEnabled(true);
		webSettings.setBuiltInZoomControls(true);
		webViewMain.loadUrl("http://http://m.dcinside.com/list.php?id=programming");
	}

setJavaScriptEnabled 메서드를 통해 javascript 실행을 허용할 수 있습니다.
setBuiltInZoomControls 메서드를 통해 화면 확대/축소를 허용할 수 있습니다.
그 이외에도 확대 축소를 할 때에 웹뷰 오른쪽 아래에 나타나는 돋보기 아이콘을 없애기 위해서는 setDisplayZoomControls(false)를 추가하면 됩니다.

WebViewClient 지정

현재 상태의 또 다른 문제점 중 하나는 링크를 클릭하면 웹뷰 안에서 열리지 않고 `작업을 수행할 때 사용하는 애플리케이션`을 선택하라고 하는 것입니다.

링크클릭

이러한 문제점은 WebView에 WebViewClient가 지정되어 있지 않기 때문입니다.
WebViewClient는 페이지 로딩 시작과 끝, 키입력 등의 여러 이벤트를 처리할 수 있도록 도와줍니다.
따라서 다음과 같은 코드를 추가하여 WebViewClient를 지정하면 WebView가 링크 클릭을 처리할 수 있게 됩니다.

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		...
		
		WebSettings webSettings = webViewMain.getSettings();
		webSettings.setJavaScriptEnabled(true);
		webSettings.setBuiltInZoomControls(true);
		
		webViewMain.setWebViewClient(new WebViewClient() {
			
		});
		
		webViewMain.loadUrl("http://m.dcinside.com/list.php?id=programming");
	}

WebChromeClient 지정

또 다른 문제점은 My 갤러리 메뉴를 클릭하면 `로그인 후 이용가능합니다. 로그인하시겠습니까?` 라는 대화상자가 표시되어야 하지만 아무런 반응이 없다는 것입니다.
이러한 문제점은 WebView에 WebChromeClient가 지정되어 있지 않기 때문입니다.
WebChromeClient는 dialog, favicon, title, progress를 다루도록 도와줍니다.
따라서 다음과 같은 코드를 추가하여 WebChromeClient를 지정하면 WebView가 대화상자를 출력할 수 있게 됩니다.

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		...
		
		WebSettings webSettings = webViewMain.getSettings();
		webSettings.setJavaScriptEnabled(true);
		webSettings.setBuiltInZoomControls(true);
		
		webViewMain.setWebViewClient(new WebViewClient() {
			
		});
		
		webViewMain.setWebChromeClient(new WebChromeClient() {
			
		});
		
		webViewMain.loadUrl("http://m.dcinside.com/list.php?id=programming");
	}

Back 버튼 눌러서 뒤로가기

또 다른 문제점은 Back 버튼을 누르면 곧바로 어플이 종료된다는 점입니다.
onBackPressed 메서드를 재정의하여 webViewMain에서 뒤로가기가 가능할 경우 어플을 종료하지 않고 웹페이지 뒤로가기 기능을 실행하도록 하겠습니다.
메서드를 재정의할 때에는 직접 타이핑하는 것이 아니라 적절한 위치에 커서를 위치하고 Alt+Shift+S를 누르면 다음과 같은 컨텍스트 메뉴가 나옵니다.

자동완성 메뉴

여기에서 Override/Implement Methods 메뉴를 선택하면 다음과 같은 창이 나옵니다.

메서드 재정의 또는 구현

목록에서 onBackPressed를 선택하고 OK 버튼을 클릭하면 커서가 있던 위치에 다음과 같은 소스가 자동삽입됩니다.

public class MainActivity extends Activity {
	...

	@Override
	public void onBackPressed() {
		// TODO Auto-generated method stub
		super.onBackPressed();
	}

	...
}

앞에서 말한 기능을 구현하기 위해서는 onBackPressed 메서드 안에 다음과 같이 코드를 수정/추가하면 됩니다.

public class MainActivity extends Activity {
	...

	@Override
	public void onBackPressed() {
		// TODO Auto-generated method stub
		//super.onBackPressed();
		if (webViewMain.canGoBack()) {
			webViewMain.goBack();
		} else {
			finish();
		}
	}

	...
}

액션바 숨기기

또 다른 불편한 점은 불필요한 액션바가 큰 공간을 차지하고 있다는 것입니다.
이것은 어플의 테마를 수정하여 해결할 수 있습니다.
AndroidManifest.xml 파일을 연 뒤, Application 탭을 선택합니다.

테마 설정

Theme 옆의 Browse 버튼을 클릭합니다.

테마 설정

Resource Chooser 창에서 System Resources를 선택하고, 텍스트칸에 theme.light.not 정도를 입력하면 `Theme.Light.NoTitleBar`가 보입니다.
선택하고 OK 버튼을 누른 뒤 Ctrl+S를 눌러 저장하고 실행하면 다음과 같은 결과를 얻을 수 있습니다.

테마 설정

로딩 관련 처리

링크를 클릭하여 웹뷰에서 로딩이 시작되면 상단 버튼의 텍스트를 `로딩 중…`으로 바꾸고, 로딩이 완료되면 해당 웹페이지의 타이틀을 상단 버튼의 텍스트로 지정합니다.
이를 위해서는 앞서 웹뷰에 지정한 WebViewClient의 소스를 수정해야 합니다.
onCreate 메서드에서 다음과 같이 되어 있었던 원래의 소스에서,

		webViewMain.setWebViewClient(new WebViewClient() {

		});

자동완성 기능을 이용하여 onPageStarted, onPageFinished 메서드를 추가해서 다음과 같이 수정합니다.

		webViewMain.setWebViewClient(new WebViewClient() {

			@Override
			public void onPageStarted(WebView view, String url, Bitmap favicon) {
				// TODO Auto-generated method stub
				super.onPageStarted(view, url, favicon);
			}
			
			@Override
			public void onPageFinished(WebView view, String url) {
				// TODO Auto-generated method stub
				super.onPageFinished(view, url);
			}
			
		});

onPageStarted 메서드에서는 상단 버튼의 텍스트를 `로딩 중…`으로 변경합니다.

			@Override
			public void onPageStarted(WebView view, String url, Bitmap favicon) {
				// TODO Auto-generated method stub
				super.onPageStarted(view, url, favicon);
				buttonTitle.setText("로딩 중...");
			}

onPageFinished 메서드에서는 상단 버튼의 텍스트를 웹뷰에 로딩된 페이지의 타이틀로 변경합니다.

			@Override
			public void onPageFinished(WebView view, String url) {
				// TODO Auto-generated method stub
				super.onPageFinished(view, url);
				buttonTitle.setText(webViewMain.getTitle());
			}

버튼 클릭 이벤트 처리

글쓰기 버튼은 button2이고, 앞에서 이를 클릭하면 button2Click 메서드가 실행되도록 코딩했습니다.
이 버튼을 클릭하면 글쓰기 링크로 연결되도록 합니다.
방법은 onCreate에서 홈페이지를 불러올 때와 동일합니다.

	void button2Click() {
		webViewMain.loadUrl("http://m.dcinside.com/write.php?id=programming&mode=write");
	}

새로고침 버튼은 button3이고, 앞에서 이를 클릭하면 button3Click 메서드가 실행되도록 코딩했습니다.
이 버튼을 클릭하면 자바스크립트 함수를 호출해야 합니다.
loadUrl 메서드를 사용한다는 점은 동일하지만, 앞에 `javascript:`를 붙여야 한다는 점이 다릅니다.

	void button3Click() {
		webViewMain.loadUrl("javascript:location.reload();");
	}

이를 응용하면 메시지 창을 띄우거나, 특정 element의 스타일을 변경하는 작업이 가능합니다.
예를 들어 button1Click 메서드를 다음과 같이 코딩하고 실행한 뒤 button1을 클릭하면 다음과 같이 특정 div를 감출 수 있습니다.

	void button1Click() {
		webViewMain.loadUrl("javascript:document.getElementsByTagName('div')[2].style.display='none';");
	}

실행 후 버튼을 클릭한 결과는 다음과 같습니다.

javascript 실행

이런 방법은 무궁무진하게 활용될 수 있지만, 이를 자세히 다루는 것은 본 강좌의 범위를 벗어납니다.
보다 자세한 내용을 알기 위해서는 HTML5, CSS, jQuery 등에 관한 좋은 강좌들을 참고하세요.

이런 과정으로 작성된 어플은 다음 링크에서 설치할 수 있습니다(구글 플레이 스토어).

DC 도우미

이상으로 웹브라우저형 하이브리드 앱 개발에 대해 예제를 통해 간단히 다루어 보았습니다.
다음 강좌에서는 전자책형 하이브리드 앱 개발 방법을 다루겠습니다.

관련 포스트

2013년 인턴 하계수련회 후기

하계수련회

모든 서울대 병원 인턴은 일 년에 2번 한 자리에 모인다.
첫 번째는 인턴 합격 직후 일을 시작하기 전의 신입 인턴 오리엔테이션이다.
당시에 일산에 위치한 동양인재개발원에 모여 3박 4일 동안 다양한 교육을 받았다.

두 번째는 인턴 일을 시작한지 6개월 가량 지난 이후에 열리는 하계 수련회이다.
이번 수련회는 수원에 위치한 LIG 인재니움 연수원에서 당일치기로 진행되었다.

각 파견 병원에서 수련회 장소까지 오가는 시간을 포함하여 모든 인턴이 약 12시간 동안 병원을 비운다.
원래 인턴이 하는 일은 인턴 중 누군가가 하게 되어 있지만, 인턴이 병원에 없으므로 그 동안에 인턴이 해야 하는 일 중 급한 것은 주치의(레지던트 1년차)가 하고, 급하지 않은 것은 인턴이 수련회에 가기 전에 끝내놓거나 수련회에 다녀와서 마무리하게 된다.

오전

분당서울대병원에서 수원까지는 가까웠기 때문에 아침 8시 반에 버스가 출발해도 수련회 시작 시각인 9시 반까지 도착하는 데에는 아무런 지장이 없었다.
LIG 인재니움 연수원 지하 1층에 위치한 대강당에 모인 뒤에 박중신 교육연구부장님의 인사말씀으로 수련회 일정이 시작되었다.

인사말씀이 끝난 뒤에는 2014년 레지던트 선발일정을 안내받았다.
레지던트 선발은 전기모집과 추가모집으로 나뉜다.

전기모집 공고는 11월 18일
원서접수는 11월 25일~27일
필기시험은 12월 8일 10시~12시
면접 및 실기시험은 12월 11일
합격자발표는 12월 13일에 할 예정이다.

인사말씀이 끝난 뒤 이경훈 교수님의 항암치료 특강을 들었고, 이어서 신상훈 교수님의 “유머가 이긴다”라는 강의를 들었다.
강의에서 기억에 남는 것은 같은 교실에서 같은 선생님에게 같은 수업을 들어도 학생들 사이에 성적의 차이가 나는 것을 비유로 들어, 사람들을 깔때기와 빨대로 나눌 수 있다고 한 내용이었다.

오후

오전 강의가 끝나고 단체사진 촬영과 점심식사를 한 뒤에는 진료과 소개가 이어졌다.
아침에 강의실 입구에서 나누어 준 책자에 각 과들의 소개 자료가 들어 있어, 이를 참고하며 소개를 들었다.
최근에 레지던트 지원률이 저조하거나, 올해 처음으로 전공의 모집을 시작하여 인지도가 부족한 과들은 특별히 전체 인턴을 대상으로 진료과를 홍보하는 시간을 가졌다.
흉부외과, 산부인과, 비뇨기과, 신경외과, 외과, 분당 영상의학과 등에서 과 소개를 하였다.
잘 준비된 프레젠테이션은 물론이고, 공을 들여서 제작한 것이 느껴지는 홍보 영상도 제공되었다.

그런 뒤에는 10여 개의 부스로 나뉘어 30분씩 한 세션으로 각 과별 소개시간이 있었다.
전체 인턴을 대상으로 한 것이 아니라, 각 인턴들이 관심이 있는 부스를 찾아가는 것이었다.
관심이 있는 과의 부스에 찾아가서 30분간 소개를 듣고 10분간 다른 부스로 이동하는 방식이었다.
나는 임상약리학과, 가정의학과, 안과, 이비인후과 부스를 차례대로 돌아다니면서 소개를 듣고, 나머지 시간에는 휴식을 취했다.

오후 6시까지 과별 소개시간이 있었고, 다음으로는 저녁식사 시간이 되었다.
각 진료과별로 테이블을 배정해 주었고, 사전에 관심이 있는 과를 조사하여 테이블을 배정해 주었다.
나는 이비인후과 테이블에 앉아 이비인후과 레지던트 선생님들과 이비인후과에 관심이 있는 인턴들과 함께 식사했다.
가족같은 분위기를 정말 잘 느낄 수 있었다.

저녁 식사가 마친 뒤에는 다시 버스를 타고 각 수련병원으로 복귀했다.
전날 당직을 서고 하루종일 수련회에 다녀온 뒤, 밀린 정규 일들을 하느라 몸은 무거웠다.
그러나 이번 수련회를 계기로 나의 진로에 대해 확실히 결정할 수 있게 되어 마음은 가벼웠다.

관련 포스트

관상(2013) 엔딩 크레딧 없습니다

영화 ‘관상’ 보너스 영상 없습니다

관상

9월 11일에 개봉했습니다.
저는 대개는 전문가 평점에 따라 볼 영화를 선택하지만, 소재가 참신하거나 예고편이 훌륭한 경우에는 전문가 평점이 나오기 전에도 영화를 봅니다.
이번 영화 ‘관상’도 참신한 소재를 다루었기 때문에 보게 되었습니다.

좋았던 점

줄거리는 개연성 있었고 앞뒤가 잘 들어맞도록 짜여졌습니다.
계유정란이라는 역사적인 사실과 관상이라는 참신한 소재를 조합한 것은 좋은 선택이었습니다.
배우들은 각 역할에 잘 어울렸고, 연기도 좋았습니다.

아쉬운 점

김혜수씨가 맡은 기생 캐릭터는 영화에 잘 녹아들지 않았는데, 영화에 약간의 선정성을 더한 것 이외에는 별다른 역할을 하지 못했습니다.
관상이 사람의 과거를 보여주고 미래까지 예측할 수 있다는 이야기를 전달하기 위해서 다양한 복선과 에피소드를 과할 정도로 그려냅니다.
다양한 이야기를 적절히 압축하거나 풀어서 보여주는 솜씨가 부족하여, 긴 상영시간이 지루하게 느껴지기도 합니다.

관련 포스트

2013 슈퍼앱 코리아 개발자 예선 참가 후기

2013 슈퍼앱 코리아(2013 Super App Korea)

슈퍼 앱 코리아

공식 페이스북에 있는 소개글에 따르면 슈퍼앱 코리아는 국내외 시장을 타켓으로 경쟁력있는 앱서비스 개발과 스타트업을 지원하고자 하는 대회입니다.
특정 기간 동안 학생, 또는 일반인들로 구성된 기획자, 개발자, 디자이너가 모여 팀을 이루어 앱을 개발합니다.
개발 기간이 끝난 뒤에는 앱을 심사하여 우수한 앱을 개발한 팀에게 시상하게 됩니다.

이 대회는 2011년 1차 대회를 시작으로 2012년에 2차, 3차 대회가 열렸고, 올해에는 슈퍼앱 코리아 4차 대회가 열리게 되었습니다.
공식 명칭은 2013 슈퍼앱 코리아입니다.
이번 대회에서 특이한 점은 이전과 달리 별도의 기획자 모집이 없고, 개발자와 디자이너만 각각 모집한다는 점입니다.

예선을 통과한 개발자와 디자이너들은 9월 13일과 14일에 열리는 본선 기간에 팀을 결성합니다.
팀 결성 후 약 6주 동안 앱을 개발합니다.
심사는 10월 24일 ~ 11월 8일에 이루어질 예정입니다.
시상은 11월 12일에 할 예정입니다.
총 23팀이 수상하게 되고, 특별히 우승팀에게는 미래창조과학부 장관상 및 상금이 수여됩니다.

보다 자세한 내용은 아래의 링크들을 참조하세요.

AppCenter | (사)앱센터
슈퍼앱 코리아 공식 페이스북

8월 12일

SNS에서 우연히 슈퍼앱 코리아에 대해 알게 되었습니다.
개발자 참가 신청 기간이 7월 29일부터 8월 29일까지였기 때문에 당장 참가 신청이 가능했습니다.
개발자와 디자이너를 각각 모집하고 있었는데, 저는 개발자로 참가 신청을 했습니다.
앱센터에서 제공하는 구글 양식에 연락처, 앱개발 경험, 참가동기 등의 다양한 정보를 입력한 뒤에 신청서를 제출했습니다.
신청이 완료된 이후에는 다음과 같은 확인 메일을 받았습니다.

신청 접수

8월 30일

참가 신청 기간이 마감되자 개발자 예선의 시작을 알리는 이메일이 도착했습니다.

예선 안내

8월 31일에서 9월 3일까지의 기간 중 언제든 2시간 동안 예선에 참가할 수 있다는 것을 알 수 있었습니다.
마음 같아서는 예선 기간이 시작하자마자 당장이라도 참가하고 싶었지만, 당장은 바빴기 때문에 시간이 날 때까지 미루어 두었습니다.

9월 1일

시간의 여유가 생겨 이전에 받은 메일을 확인하여 codility.com의 링크에 접속했습니다.
총 3문제가 주어졌고, 주어진 문제의 답을 구하는 함수를 작성하는 것이었습니다.
두 문제는 난이도 낮음에 해당하고, 나머지 한 문제는 난이도 중간에 해당한다고 하였습니다.
C, C++, C#, Java, Python, PHP 등 다양한 언어를 선택할 수 있었는데, 저는 Python을 선택했습니다.

1시간 동안에 3문제를 다 풀고 제출하였습니다.

코딩 테스트

각각의 문제마다 답을 한 번씩만 제출할 수 있습니다.
한 번 답을 제출한 이후에는 수정할 수 없습니다.

코딩 테스트

제출한 답을 검증합니다.

코딩 테스트

답 검증이 끝난 뒤에는 남아 있는 다른 문제들을 풀 수 있습니다.

코딩 테스트

3문제를 모두 풀고 난 뒤에는 세션이 종료됩니다.
그러면 codility에서 제공하는 설문조사에 참여할 수 있습니다.

코딩 테스트

평가가 적절한지, 문제를 제대로 이해했는지, 시간이 충분했는지, codility 환경이 쓰기 편리했는지를 1점에서 10점 사이로 평가할 수 있습니다.

코딩 테스트

설문조사까지 끝나면 모든 과정이 종료됩니다.

코딩 테스트

9월 9일

오후에 낯선 번호에서 전화가 걸려왔습니다.
발신자는 슈퍼앱코리아였고, 제가 개발자 예선을 통과했다고 안내해 주었고, 이번 주말에 열리는 본선에 참석 가능한지를 물었습니다.
대회는 금요일 오후 2시부터 토요일 오후 2시까지 경기도 이천에서 진행될 예정이라고 하였습니다.
여기에 참석하지 않을 경우 중도 포기로 간주된다고 하였습니다.
저는 사정상 그 시간대에 참석할 수 없었기에 아쉬운 마음으로 대회 참가를 포기하겠다고 답했습니다.

대회를 통해서 다른 사람들과 함께 팀을 이루어 앱 개발을 해 볼 수 있는 기회였지만 사정상 참가하지 못하게 되어 아쉽습니다.
다음에는 슈퍼 앱 코리아 대회 본선에 참여할 여건이 생겼으면 좋겠습니다.

관련 포스트