[go: up one dir, main page]

GDG Korea Android 의 공동 운영자이신 김기완(hl5pma 골뱅이 gmail.com) 님께서 안드로이드의 새로운 빌드시스템인 gradle 설정을 통해, 작년 가을 새롭게 릴리즈된 Java 7 컴파일러를 이용하여 안드로이드 어플리케이션을 개발하는 방법에 관한 내용을 정리해 주셨습니다. 개발자의 삶을 한층 더 편안하게 만들어줄 수 있는 Java 의 새로운 기능들을 함께 살펴보면 어떨까요? buildToolsVersion 19 부터 Java 7 로 앱을 빌드할 수 있게 됨에 따라 gradle 빌드 스크립트에 아래 내용을 추가해주면 Java 7 의 특성들을 이용하여 앱을 개발할 수 있게 되었습니다.
android {
  compileSdkVersion 19
  buildToolsVersion "19.0.0"

  defaultConfig {
    minSdkVersion 7
    targetSdkVersion 19
  }

  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_7
    targetCompatibility JavaVersion.VERSION_1_7
  }
}
안드로이드앱 개발시 이용할 수 있는 Java 7 의 특징들은 다음과 같습니다.
이진수 표현
숫자 앞에 "0b" 또는 "0B" 를 붙여 이진수 표현이 가능합니다.
int hex = 0x8;  // 16진수
int dec = 8;  // 10진수
int oct = 08; // 8진수
int bin = 0b101010; // 2진수
숫자 사이에 언더스코어(_)표시
숫자 사이에 언더스코어(_)를 넣을 수 있게 되어 가독성을 향상시킬 수 있게 되었습니다.
long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
float pi = 3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;
switch 구문에서 String 사용
그동안 switch 구문에 기본형과 열거형만 사용할 수 있었고 String 을 사용할때는 다수의 if else 문으로 제어문을 구성해야 했다면 Java 7 부터는 switch 구문에서 String 을 사용할 수 있게 되었습니다.
public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
  String typeOfDay;
  switch (dayOfWeekArg) {
    case "Monday":
      typeOfDay = "Start of work week";
      break;
    case "Tuesday":
      case "Wednesday":
      case "Thursday":
        typeOfDay = "Midweek";
        break;
      case "Friday":
        typeOfDay = "End of work week";
        break;
      case "Saturday":
      case "Sunday":
        typeOfDay = "Weekend";
        break;
      default:
        throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
  }
  return typeOfDay;
}
다이아몬드(<>)
제네릭 인스턴스를 생성할 때 그동안 인스턴스 선언/생성 양쪽에 모두 타입을 적어주었어야 했었습니다.
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
다이아몬드 기호(<>) 표시만으로 인스턴스 생성 부분에서의 타입을 생략할 수 있게 되었습니다.
Map<String, List<String>> myMap = new HashMap<>();
try-with-resources (Api level 19)
그동안 사용 후 close() 메서드를 호출하여 자원을 반납해야 했던 InputStream/OutputStream 등은 Java 7 에서 try 구문 시작시 간단한 선언으로 자동으로 자원반납이 가능하게 되었습니다.
static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
  BufferedReader br = new BufferedReader(new FileReader(path));
  try {
    return br.readLine();
  } finally {
    if (br != null) br.close();
  }
}
Java 7 이전까지는 이렇게 BufferedReader 를 사용하고 난 후 finlly 에서 close() 를 호출해 stream 을 닫아주어야 했었다면 아래 코드와 같이 try 구문이 시작할 때 BufferedReader 선언하면 try 구문이 끝날 때 자동으로 close() 를 호출하게 됩니다.
static String readFirstLineFromFile(String path) throws IOException {
  try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
  }
}
주의: AutoCloseable interface 에 의한 close() 호출이기 때문에 Api level 19(Kitkat) 버전부터 사용할 수 있습니다.
multi-catch Exception
그동안 예외를 처리할 때 서로 다른 예외간 처리방식은 동일함에도 불구하고 같은 내용의 코드를 중복 작성해야 하는 불편함이 있었습니다.
catch (IOException ex) {
    logger.log(ex);
    throw ex;
catch (SQLException ex) {
    logger.log(ex);
    throw ex;
}
Java 7 에서는 '|' 연산자로 여러개의 예외를 한번에 묶어 처리할 수 있게되었습니다.
catch (IOException|SQLException ex) {
  logger.log(ex);
  throw ex;
}
참고 사이트

안드로이드를 위한 훌륭한 사용자 UX 를 설계하는 것은 쉽지 않은 일 입니다. 디바이스의 종류는 다양하고 플레이 마켓에서 참고할 만한 앱들을 살펴보아도 서로 다른 부분을 쉽게 찾아볼 수 있습니다. 어떻게 UX 를 디자인해야 사용자가 편리하게 앱을 사용할 수 있을지 고민될 때는 안드로이드 디자인 가이드라인을 참고하시면 도움이 될 수 있습니다. 

디자인 가이드라인에는 마법 같고, 생활을 편리하게 만들어 주며, 사용자에게 놀라운(좋은쪽으로 말입니다) 경험을 선사해 주기 위해 주목할 원칙과 바로 적용할 수 있는 디자인 패턴들이 잘 정리되어 있습니다. 특히 “확인받기와 알려주기" 항목을 살펴보시면, 안드로이드 UX 의 고전적인 질문 - “언제 다이얼로그 창을 표시해야 할까?” 에 관한 참고할만할 내용을 살펴보실 수 있습니다. 

확인받기와 알려주기


확인받기 는 사용자에게 지금 막 수행되려는 작업을 정말로 수행할 것인지 다시 한번 물어보는 것 입니다. 앞으로 일어날 일을 보다 자세히 설명하고 주의할 점이 있는 경우 이를 경고 할 수 있습니다. 알려주기 는 해당 작업이 성공적으로 수행되었음을 텍스트로 표시해 주는 것 입니다. 시각적 혹은 그 외 다른 명확한 피드백이 없는 작업의 경우, 알려주기 패턴을 이용해 구체적인 피드백을 제공할 수 있고, 필요한 경우 진행된 작업을 취소할 수 있는 기능도 함께 제공할 수도 있습니다.

두 가지 방식을 잘 활용하면, 사용자가 되돌릴 수 없는 실수를 하는 것을 방지하고, 좀 더 명료하고 사용하기 편리한 경험을 제공할 수 있습니다. 물론, 앱 내의 
모든 작업에 대해 사용자의 확인을 받거나 알려주기 패턴을 적용해야하는 것은 아닙니다.아래의 플로우 차트를 참고해서 적용여부를 한번 고민해 보시면 좋을 것 같습니다.


이해를 돕기 위해, 실제 앱에서 확인받기와 살펴보기 패턴이 사용된 경우를 직접 살펴보도록 하겠습니다.

확인받기가 필요한 경우 - Play 북



플레이 라이브러리에서 책을 삭제하면, 사용자의 다른 어떤 디바이스에서도 해당 책의 내용을 확인 할 수 없게 됩니다. 이런 경우 되돌릴 수 없는 손실이 발생할 수 있기 때문에, 앱은 경고 창을 통해 사용자의 확인을 받아야 합니다. 경고 창을 디자인 할 때는 제목에 지금 일어날 작업 내용을 다시 한번 명시해서 사용자가 현재 어떤 작업이 이루어지는지 명확히 알 수 있도록 강조할 필요가 있습니다.

확인받기가 필요없는 경우 - 안드로이드 빔



안드로이드 빔을 통해 컨텐츠를 공유하고자 할 때는 ‘확인받기' 패턴을 사용할 필요가 없습니다. 컨텐츠 공유는 안드로이드 빔으로 두 디바이스가 연결된 후, 공유할 컨텐츠(사진)을 터치 해야만 이루어집니다. 만일 컨텐츠 공유를 취소하고 싶을 때는 그저 두 디바이스를 다시 멀찍히 떨어뜨려 놓으면 됩니다.

알려주기가 필요한 경우 - Gmail 임시 저장



사용자가 메일 작성 화면에서 백 혹은 업 네비게이션을 통해현재 화면에서 벗어나는 경우, 작성중이던 메일 내용이 자동으로 저장됩니다. 이 작업에 대한 명시적인 사용자 피드백이 없기 때문에 알려주기 패턴이 사용되며, Gmail 은 토스트를 통해 이 사실을 알려줍니다. 단, 취소하기 기능은 제공되지 않는데, 임시 저장은 앱에서 자동으로 이루어진 것이며 작성 중이던 메일 내용은 임시 보관함에서 쉽게 확인할 수 있기 때문입니다.

알려주기와 되돌리기가 필요한 경우 - Gmail 대화 삭제



사용자가 Gmail 에서 대화 목록 중 하나를 삭제한 경우에는, 앱은 ‘알려주기' 패턴을 이용해 사용자에게 이를 알리고, 동시에 되돌리기 버튼을 표시해 줍니다. 이 알림창은 사용자가 다른 액션 (대화 목록을 스크롤 하는 등의)을 수행하기 전까진 화면 상에 남아있게 됩니다.

확인받기나 알려주기가 필요 없는 경우


+1 하기

사용자 확인을 받을 필요가 없습니다. 사용자가 +1 을 눌렀다고 큰 사고가 발생하는 경우는 별로 없고,  실수인 경우 +1 버튼을 다시 한번 누르면 결과를 금방 되돌릴 수 있습니다. 알려주기 기능 또한 필요가 없습니다. +1 버튼을 누르면 멋진 애니매이션 피드백이 주어지고, 버튼의 색상이 붉게 변경됩니다. 추가적인 피드백은 불필요합니다.

홈스크린에서 앱 바로가기 삭제하기

사용자 확인을 다시 받을 필요가 없습니다. 홈 스크린에서 앱 바로가기 아이콘을 삭제하기 위해서는 앱 아이콘을 끌고 화면 상단의 정해진 목적지로 가져가야 합니다. 사용자의 의도가 분명히 들어난 셈이고 실수로 이런일이 벌어질 가능성은 거의 없습니다. 혹시, 실수로 일어난 일이라도 이를 되돌리는 데는 몇 초 시간이 걸리지 않습니다. 또, 홈 스크린에서 앱을 드래그 한 그 순간 이미 홈 스크린에서 앱 아이콘이 없어지기 때문에 알려주기 패턴을 사용할 필요도 없습니다.

사용자가 작업을 수행하려고 할 때, 상황에 맞추어 ‘확인받기' 와 ‘알려주기' 두 가지 패턴을 적절히 사용하면, 사용자의 혼란을 줄이고 좀 더 편안하게 앱을 사용할 수 있도록 도울 수 있습니다. 이 두 가지 패턴을 포함하여 안드로이드 디자인 가이드라인에서 제공된 다양한 디자인 패턴을 잘 활용하여, 마법같은 사용자 경험을 제공하는 멋진 안드로이드 앱을 만나보길 기대하고 있겠습니다.

안드로이드는 크게 세 가지 형태의 저장 공간을 갖고 있습니다.
  1. 내부 저장소 (Internal Storage)는 안드로이드 플랫폼, 시스템 도구, 앱, 그리고 앱에서 사용하는 데이터가 저장되는 공간입니다. 안드로이드 보안 모델에 따라 보호 받는 공간으로, 각각의 앱은 자신의 패키지 이름과 일치하는 디렉토리를 생성할 수 있으며, 오직 해당 디렉토리를 생성한 앱 만이 디렉토리 안의 파일을 읽고 쓸 수 있습니다.
  2. 모든 안드로이드 호환 디바이스는 하나 이상의 외부 저장소를 갖고 있습니다. 첫번째 외부 저장소 (Primary External Storage)는 앱간의 데이터를 공유할 수 있는 공개된 저장 공간입니다. READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE 권한을 갖고 있는 앱은 어떤 디렉토리에 저장된 데이터든 읽고 쓸 수 있습니다. 이 저장소는 크게 두 가지 방식으로 구현되는데, 2010년 출시된 넥서스 원 디바이스의 경우는 SD 카드 슬롯에 삽입된 메모리를 첫번째 외부 저장소로 활용하였고, 넥서스 5 를 비롯한 최근 디바이스들은 내장된 메모리 공간 중 일부를 나누어 활용하고 있습니다.
  3. 에뮬레이트된 첫번째 외부 저장소 외에 외장 메모리 슬롯을 지원하는 디바이스의 경우 추가적인 외부 저장소를 갖고 있을 수 있습니다. 다만 이 공간은 안드로이드 4.4 이전에는 안드로이드에서 공식적으로 지원하지 않고 있던 저장 공간입니다.
하나 이상의 외부 저장소가 존재하는 경우는 조금 혼란스럽습니다. 이 저장 공간은 4.4 이전의 경우 안드로이드에서 공식적으로 지원되는 공간은 아니였지만, 디바이스 제조사에 따라 조금씩 다른 방식으로 관리되었고 앱은 비공식적인 방법을 통해 해당 공간을 사용하고 있었습니다. 안드로이드 4.4 킷캣에서는 플랫폼 공식적으로 하나 이상의 저장 공간을 지원하며, 이와 함께 저장 공간 접근에 관련된 몇 가지 주요한 변경 점이 생겼습니다.
  • 모든 종류의 저장 공간에 대하여, 앱은 추가적인 시스템 권한 요청 없이 자신의 패키지명과 일치하는 디렉토리를 생성하고, 해당 디렉토리 내의 파일을 읽거나 쓸 수 있습니다. 안드로이드 플랫폼은 앱에 할당된 특정한 디렉토리에 접근할 수 있는 getFilesDir(), getExternalFilesDir() 과 같은 메서드를 제공하고 있습니다. 앱 개발자는 maxSdkVersion 속성을 이용하여, 오직 킷캣 이전 버전에서만 해당 권한을 요청하도록 앱을 수정할 수 있습니다.
  • 안드로이드 4.4 킷캣에서 getExternalFilesDirs() 메서드가 추가 되었습니다. 하나 이상의 외부 저장소를 지원하기 위한 메서드로, 메서드를 호출하면 파일 배열이 반환됩니다. 이 메서드를 통해 앱은 디바이스 상에 존재하는 모든 외부 저장소에 대하여, 자신에게 할당된 디렉토리 경로를 확인 할 수 있습니다. 개발자 분들은 하위 호환성을 위해 v4 호한 라이브러리에서 제공하는 ContextCompat.getExternalFilesDirs 메서드를 사용할 수 있습니다. 단, 킷캣 이전의 경우에는 오직 첫번째 외부 저장소에 대한 디렉토리 경로만이 반환됩니다.
  • 어플리케이션은 자신에게 할당된 디렉토리를 제외하고 첫번째 외부 저장소가 아닌 추가적인 외부 저장 공간에 데이터를 쓸 수 없습니다.
마지막 변경 사항에 관해 궁금하신 분들이 많을 것 같습니다. 조금더 자세히 설명드리자면, 지금까지 특정 앱에서 사용되는 파일이 외부 저장 공간 여기저기에 흩어져 저장되는 경우가 종종 있었습니다. 이런 경우 시스템은 외부 저장 공간에 저장된 파일이 어떤 앱과 연관되었는지 판단할 수 있는 방법이 없었고, 앱이 삭제되는 경우에도 이런 파일을 깔끔히 정리할 수가 없었습니다.

안드로이드 킷캣 부터는 어플리케이션에 특화된 디렉토리의 경우에는 권한 요청 없이 자유롭게 파일을 읽고 쓸 수 있도록 허용하는 동시에 그 외 다른 경로에는 파일을 쓸 수 없도록 제한되었습니다. 앱은 자신에게 할당된 디렉토리를 활용하여 데이터를 저장할 수 있고, 이와 동시에 시스템은 앱이 삭제될 때 앱과 연관있는 파일을 판단하고 이를 모두 삭제할 수 있게 되었습니다. 안드로이드 4.2 버전 부터 CDD(Compatibility Definition) 의 9.5 항목에 관련 내용이 추가되었으며, 해당 원문은 다음과 같습니다.

“Device implementations that include multiple external storage paths MUST NOT allow Android applications to write to the secondary external storage, except for their package-specific directories on the secondary external storage.”

만일, 앱이 자신의 디렉토리가 아닌 다른 공간에 파일을 생성하고 싶은 경우에는, 킷캣에서 새롭게 추가된 StorageAccessFramework(SAF) 를 활용할 수 있습니다. SAF 를 통해 사용자는 하나 이상의 외부 저장소를 비롯하여 안드로이드 플랫폼에서 접근 가능한 모든 문서, 이미지, 파일들을 일관적인 UI를 통해 접근하고 데이터를 관리할 수 있습니다. 또한, 개발자 분들은 ACTION_OPEN_DOCUMENT / CREATE_DOCUMENT 인텐트를 통해 손쉽게 새로운 기능을 활용할 수 있습니다.

이 변경 사항은 킷캣에 대응하는 모든 안드로이드 호환 디바이스에서 동일하게 적용되는 원칙임으로 첫번째 외부 저장소 외에 추가적인 외부 저장소를 활용하는 앱을 개발하는 개발자 분들은 꼭 내용을 확인하시길 바랍니다. 이외에 킷캣에서 변경된 부분에 관한 보다 자세한 내용은 안드로이드 개발자 사이트의 4.4 API 중요 변경 사항 항목을 참고해 보시기 바랍니다. 또는 관련된 내용을 한글로 정리해주신 전슬마로님의 블로그 포스트를 참고하셔도 큰 도움이 될 것 같습니다.

안드로이드 4.4 킷캣에서는 웹뷰가 크로미엄 프로젝트 기반으로 새롭게 구현되었습니다. 새로운 웹뷰는 HTML5, CSS3, 자바스크립트등 웹 표준 기술들을 지원하며, 특히 안드로이드용 크롬 브라우저 버전 30 이후에서 지원하던 최신 HTML5 기능 들을 대부분 지원하고 있습니다. 또한 V8 자바스크립트 엔진이 적용되어 있기 때문에 자바스크립트를 처리하는 속도가 훨씬 빨라졌습니다. 또, 개발자 분들이 더욱 편하게 웹페이지를 개발할 수 있도록 크롬 개발자 도구를 통한 리모트 디버깅 기능을 제공합니다. 예를 들어 여러분의 개발 머신 (데스크탑과 같은)을 통해 안드로이드 디바이스 상에서 동작하는 네이트브 앱의 웹뷰 컨텐츠의 내용을 확인하고 디버깅할 수 있습니다. 백문이 불여일견 한국어 버전의 DevBytes 동영상으로 관련 내용을 간단히 소개해드립니다.



동영상에서 설명드린 것 처럼, 이 새로운 웹뷰는 안드로이드 4.4 이상 버전에는 모두 기본으로 탑재되며, 기존 앱들도 별다른 수정 없이 새로운 웹뷰의 혜택을 바로 누릴 수 있습니다. 다만, 몇 가지 기존과 동작 방식이 변경된 부분이 있어 이를 다시 한번 정리해 보았습니다.
  • 웹뷰의 기본 USER_AGENT 값에 크롬 버전이 포함되도록 변경되었습니다. 예를 들어 다음과 같습니다.
    • Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16H) AppleWebKit/537.36(KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36
  • 다른 View 와 마찬가지로, UI 스레드가 아닌 곳에서 loadData 등의 메서드를 통해 웹뷰의 내용을 변경할 수 없습니다.
  • 비동기 자바 스크립트 함수 호출등을 이유로 UI 스레드를 블락해선 안됩니다. 대신, 자바스크립트 함수 호출의 결과를 받아 볼 수 있는 evaluateJavascript() 메서드가 추가되었습니다.
  • 사용자가 URL 을 클릭하는 이벤트를 오버라이드 하여 원하는 작업을 수행하고자 하는 경우, 사용자 정의 URL 은 RFC 3986 표준에 의거한 유효한 URL 을 사용해야 합니다. 그렇지 않은 경우 shouldOverrideUrlLoading() 메서드가 호출 되지 않거나, 의도하지 않은 형태의 URL 로 변경된 결과를 받게될 수 있습니다.
  • Viewport 메타태그에 다음과 같은 변화가 있습니다.
    • target-densitydpi 속성을 지원하지 않습니다.
    • 뷰포트의 크기를 디바이스 화면 크기보다 작게 설정하면, 해당 값이 화면 크기로 재설정되는 대신 화면크기에 맞도록 뷰포트가 확대됩니다.
    • 뷰포트 태그를 여러번 선언한 경우 가장 마지막에 사용된 태그만 적용됩니다.
  • CSS 스타일 관련되어 다음과 같은 변화가 있습니다.
    • 스타일 속성으로 background 을 지정하는 경우 background-size 속성 값이 오버라이드 됩니다. 따라서, 특정한 background-size 속성을 지정하기 위해서는 우선 background 속성 값을 지정한 다음에 background-size 속성을 설정해야 합니다.
    • window.outerWidth 와 window.outerHeight 같은 속성들은 실제 스크린 픽셀 값 대신 CSS 픽셀 값을 반환합니다.
    • NARROW_COLUMNS 와 SINGLE_COLUMN 값이 더이상 지원되지 않습니다. 이 값들은 targetSdkVersion을 18이나 그 이하로 설정할 경우에도 동작하지 않습니다.
  • 자바스크립트로 웹뷰 상의 터치 이벤트를 직접 처리할 때는, touchcancel 이벤트를 처리해야 합니다. touchcancel 이벤트는 특정 HTML 요소가 선택된 후 페이지 스크롤이 일어나거나, event.preventDefault() 가 호출되지 않은 경우 발생합니다.
보다 자세한 내용은 안드로이드 개발자 사이트의 웹뷰 마이그레이션에 관한 가이드라인 문서를 참고해 보시기 바랍니다. 혹은, 전슬마로님이 한글로 정리해주신 내용도 참고해 보시면 좋을 것 같습니다. (감사의 댓글도 잊지 마세요~)
https://medium.com/marojuns-android/407facd301c7

새롭게 구현된 킷캣 버전의 새로운 웹뷰와 편리한 개발자 도구를 이용하여 여러분의 웹컨텐츠를 안드로이드 위에서 더욱 멋지게 활용하시길 바랍니다.