HangulParser – 한글 자소 조합과 분리

HangulParser는 Java와 Android 프로젝트에서 한글의 글자 하나를 자소(자음과 모음)로 분리하고, 자음과 모음을 하나의 글자로 조합 해주는 Java 라이브러리다. 간단한 안드로이드 앱을 개발하기 위해 찾아 보다가 한글 글자 하나는 유니코드 값을 가지고 초성, 중성, 종성으로 나눌 수 있는 문서를 읽고 만들게 되었다. 반대로 초성, 중성, 종성의 유니코드 값을 더하면 한 글자의 유니코드를 얻을 수 있다.

Reference App : 꽃별천지

Download

HangulParser Github

Java와 Android 프로젝트에서 빌드 툴로 Gradle을 사용하고 있다면 build.gradle 파일에 다음을 추가해서 빌드하면 쉽게 사용할 수 있다.

repositories {
  jcenter()
}

dependencies {
  compile 'com.github.kimkevin:hangulparser:1.0.0'
}

Usage

자소 분리 (Disassemble)

List disassemble(char hangul)

한 글자를 자소(초성, 중성, 종성)로 나누어 리스트로 반환한다.

Samples :

jasoList = HangulParser.getInstance().disassemble('한');
> [ㅎ, ㅏ, ㄴ]

예를 들어, ‘한’을 입력 값으로 넣어주면 결과는 [ㅎ,ㅏ,ㄴ]을 얻을 수 있다.

jasoList = HangulParser.getInstance().disassemble("한글");
> [ㅎ, ㅏ, ㄴ, ㄱ, ㅡ, ㄹ]

‘한글’을 입력할 경우에는 한 글자씩 분리해서 최종적으로는 ‘[ㅎ, ㅏ, ㄴ, ㄱ, ㅡ, ㄹ]’을 얻을 수 있다.

자소 결합 (assemble)

자소 리스트를 입력하면 조합해서 글자로 변환해준다.

public String assemble(List\<String\> jasoList)

Samples :

jasoList.add("ㅎ");
jasoList.add("ㅏ");
jasoList.add("ㄴ");
jasoList.add("ㄱ");
jasoList.add("ㅡ");
jasoList.add("ㄹ");

String hangul = HangulParser.getInstance().assemble(jasoList);
> 한글

‘[ㅎ, ㅏ, ㄴ, ㄱ, ㅡ, ㄹ]’을 입력값으로 넣어주면 결합해서 ‘한글’을 얻을 수 있다.

Proguard 난독화와 Crash Report Tool로 이슈 디버깅

프로젝트를 운영(유지보수)하면서 가장 중요하면서도 가장 기본이 되는 작업이 버그 리포트이다. 여기에선 Crash Report Tool로 Crashlytics를 사용했다. 그리고 회사 또는 팀의 정책, 작게는 개인에 따라 코드의 중요성이 높다면 난독화를 고민하게 되고 안드로이드에서는 무료인 Proguard 적용을 고려해보게 된다. (코드의 중요성이 낮더라도 한번 경험해보는 것도 좋다고 생각함) 하지만 난독화된 앱을 배포한 후 발생한 이슈를 디버깅하기 위해 Crash Report 콘솔에서 확인하면 이상한 문자를 접하게 된다.

Proguard 적용 전 이슈 발생했을 때

난독화가 되지 않은 앱을 디컴파일 해보면 소스 코드가 정상적으로 확인할 수 있다. 포함된 Android Support Library도 난독화가 되지 않은 상태이다.

그리고 앱에서 이슈를 발생시켜 보면 특정 함수에서 발생했으며 어떤 파일의 몇 번째 줄에서 발생했는데 ‘(MainActivity.java:21)을 통해서 알 수 있다.

public void forceCrash(View view) {
    throw new RuntimeException("This is a crash");
}

이슈는 다음과 같이 RuntimeException을 버튼 클릭 때 발생시키도록 만들었다.

Proguard 적용하기

프로젝트 생성 시 기본적으로 다음과 같이 build.gradle파일에 빌드 설정으로 다음과 같이 된다.

buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

그리고 배포 시 Proguard 적용하기 위해서는 minifyEnabled를 true로 변경하면 된다.

buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

ProGuard는 릴리스 모드(release)에서 빌드할 때는 적용되지만, 디버그 모드(debug)에서는 적용되지 않는다.

proguard를 적용해서 Signed APK파일을 만들고 디컴파일 했을 경우에는 위처럼 패키지부터 소스 파일까지 모두 특정 알파벳으로 난독화가 된 것을 확인할 수 있다.

앱에서 이슈를 발생시켜서 Crashlytics 콘솔에서 확인해보면 위에서 확인했던 이슈가 발생한 파일과 라인 수 부분이 Unknown Source로 변경되었다. 간단한 샘플 프로젝트이기 때문에 코드로 디버깅이 가능하지만 큰 규모의 프로젝트에서는 디버깅하는데 어려움을 겪을 수 있다.

그래서 proguard-rules.pro에 Proguard Rule을 추가하자.

-keepattributes SourceFile,LineNumberTable

다시 Proguard가 적용된 APK파일 생성하고 이슈를 발생 시켜보자.

Crashlytics를 확인하면 Unknown Source 대신에 이슈가 발생한 파일과 라인 수를 확인할 수 있다. 그리고 출력된 stack trace 오류에서 난독화된 패키지가 무엇인지 알고 싶다면 /outputs/mapping/release/mapping.txt 파일을 열어보자.

android.support.v4.app.Fragment -> android.support.v4.a.t:
    android.support.v4.util.SimpleArrayMap sClassMap -> aa
    java.lang.Object USE_DEFAULT_TRANSITION -> a
    int mState -> b
    android.view.View mAnimatingAway -> c
    int mStateAfterAnimating -> d
    android.os.Bundle mSavedFragmentState -> e
    android.util.SparseArray mSavedViewState -> f
    int mIndex -> g
    java.lang.String mWho -> h

Fragment가 속한 패키지는 android.support.v4.a.t로 변경된 것을 확인할 수 있으며 이에 속해 있는 변수나 함수들이 어떻게 변경되었는지 또한 확인할 수 있다. 난독화를 했다면 mapping.txt 파일은 보관해두고 만약 난독화된 코드의 stack trace를 디코딩하고 싶다면 mapping.txt를 이용해서 가능하기 때문이다.

retrace.bat -verbose mapping.txt stacktrace.txt > out.txt

out.txt파일에는 난독화된 코드의 stacktrace.txt가 해석된 stack trace가 출력된다.

ProGuard outputs the following files after it runs:

dump.txt
Describes the internal structure of all the class files in the .apk file
mapping.txt
Lists the mapping between the original and obfuscated class, method, and field names. This file is important when you receive a bug report from a release build, because it translates the obfuscated stack trace back to the original class, method, and member names. See Decoding Obfuscated Stack Traces for more information.
seeds.txt
Lists the classes and members that are not obfuscated
usage.txt
Lists the code that was stripped from the .apk

Proguard 사용하는 규칙

Proguard Manual – Proguard 사용 메뉴얼

-dontwarn [class_filter]
Specifies not to warn about unresolved references and other important problems at all. The optional filter is a regular expression; ProGuard doesn’t print warnings about classes with matching names. Ignoring warnings can be dangerous.
keep [,modifier,…] class_specification
Specifies classes and class members (fields and methods) to be preserved as entry points to your code.
-keepattributes [attribute_filter]
Specifies any optional attributes to be preserved. The attributes can be specified with one or more -keepattributes directives. The optional filter is a comma-separated list of attribute names that Java virtual machines and ProGuard support. Attribute names can contain ?, *, and ** wildcards, and they can be preceded by the ! negator.
-assumenosideeffects class_specification
Specifies methods that don’t have any side effects (other than maybe returning a value). In the optimization step, ProGuard will then remove calls to such methods, if it can determine that the return values aren’t used. With some care, you can also use the option to remove logging code.

Proguard를 적용하고 Signed Apk를 생성할 때, 가끔 발생하는 이슈들이 있는데, 그 중 하나가 Referenced Class를 찾을 수 없어서 IOException이 발생했기 때문이죠.

오류 메시지를 보면 Please correct the above warnings first로 발생한 Warning들을 고쳐달라고 합니다.

Proguard 적용 중에 왜 Warning이 발생했는지 궁금하시면 Problem while processing에서 확인할 수 있어요. 이때 위에 있는 규칙들 중에서 맞는 규칙을 찾아 proguard-rules.pro에 추가해준다.

-dontwarn okio.**

위의 이슈는 okio 패키지에 있는 파일들에 대해선 Warning을 뜨지 않도록 다음과 같이 설정을 해주면 된다.

-keep class com.example.classname
-keepattributes attribute

# examples
-keep class android.support.v4.app.** { *; }
-keep interface android.support.v4.app.** { *; }
-keep class android.support.v7.** { *; }
-keep interface android.support.v7.** { *; }

만약에 Class 변환 중에 이슈가 발생하거나 외부 라이브러리의 Proguard 적용을 원하지 않을 경우, 해당 패키지의 파일들을 Proguard가 적용되지 않도록 설정이 가능하다. 구글 라이브러리의 경우, Proguard가 불필요하기 때문에 Proguard적용에서 제외시켜주는게 좋다. 그리고 보통은 외부 라이브러리를 추가할 때, Proguard 적용시 주의사항으로 규칙들을 제공해주고 있으므로 잘 확인해서 추가만 해준다면 크게 문제가 발생하지 않는다.

decompile apk (APK 디컴파일하기)

가끔 진행하는 앱의 코드 난독화 확인을 위해서 기록해 두려고 한다. APK 디컴파일(Decompile)을 악용하지 말았으면 한다. 안드로이드 애플리케이션이 컴파일되면 .dex 파일이 생성된다. dex는 Dalvik Executable로 Dalvik에서 실행할 수 있는 파일이며 컴파일된 코드 파일을 의미한다.

Download dex2jar tool

1. dex파일을 jar(.class files)로 변환하기 위해서 dex2jar를 다운로드 한다.

2. Signed APK 파일의 확장명을 apk에서 zip으로 변경한다.

3. zip 압축을 풀면 classes.dex 파일을 다운받은 dex2jar 폴더로 복사한다.

$ ./d2j-dex2jar.sh classes.dex
sh: ./d2j-dex2jar.sh: Permission denied

4. 위처럼 dex2jar.sh 실행하면 권한 거부로 실행할 수 없게 된다.

$ chmod 764 *.sh
$ ./d2j-dex2jar.sh classes.dex
dex2jar classes.dex -> ./classes-dex2jar.jar

실행 권한을 변경한 후 다시 실행하면 classes-dex2jar.jar 파일이 생성된다.

Download JD_GUI

JD(Java Decompiler)-GUIjar파일 안에 있는 class파일들을 보기 위해서 다운로드 한다.

생성된 classes-dex2jar.jar를 열면 모든 class파일들을 볼 수 있다.

Slack Integration with Gitlab (Gitlab & Slack 연동하기)

It’s super easy way to enable Slack Integration, first you have to create new channel and then create an Incoming WebHooks on slack.

Create Webhook URL

Go into this following link with your teamdomain on Slack :

https://your_teamdomain.slack.com/services/new/incoming-webhook

Select your channel or create new channel that you want to send notifications and Click Add Incoming WebHooks integration.

You can get Webhook URL for sending data to Slack and also change curtomize name or icon.

These steps are all on Slack for integrating Gitlab. And now you have to use webhook url which gets from slack.

Enter the Webhook URL

Open your Gitlab and find Services right top of menu.

You can find Slack among the project services at the bottom.

Make Active checked and input webhook url from Slack and username, channel that you want. Save changes.

Finally, you will get this notification on your Slack chaneel.

failed to find target with hash string ‘android-XX’ in Jenkins CI

compileSdkVersion, minSdkVersion, and targetSdkVersion

First of all, read this ‘Picking your compileSdkVersion, minSdkVersion, and targetSdkVersion’ to know what these mean for building your project.

compileSdkVersion, minSdkVersion, and targetSdkVersion come in: they control what APIs are available, what the required API level is, and what compatiblity modes are applied, respectively. – Ian Lake’s blog

I’ve updated android latest compileSdkVersion, targetSdkVersion and buildToolsVersion on my Android project a couple of days ago, installing the latest version of the Android platform as android-24 and buildTools as 24.0.1.

android {
    compileSdkVersion 24
    buildToolsVersion '24.0.1'

    defaultConfig {
        targetSdkVersion 24
….

What went wrong :

But Jenkins gives this error through slack-plugin as below.

Jenkins Bot Message on Slack

Check Console Output on Jenkins

It failed to find android sdk 24 in android-sdk-linux directory because compileSdkVersion sets 23 to the newest API 24. So you can check list installed SDK package so far via command line.

$ cd /opt/android-sdk-linux/platforms
$ ls
android-10  android-15  android-16  android-17  android-18  android-19  android-20  android-21  android-22  android-23  android-8

You will understand it’s no android-24 folder, to compile app by API level 24 so let’s add android-24 to platforms and also Android SDK Build-tools, version 24.0.1. See the package list for Android SDK Tools, Android SDK Platform-Tools, Android SDK Build-tools, Google Play services, Google Admob and etc.

How To Update SDK

$ cd /opt/android-sdk-linux/tools
$ ./android list sdk --all
Packages available for installation or update: 156
   1- Android SDK Tools, revision 25.1.7
   2- Android SDK Tools, revision 25.2.1 rc1
   3- Android SDK Platform-tools, revision 24.0.1
   4- Android SDK Build-tools, revision 24.0.1
   5- Android SDK Build-tools, revision 24
   6- Android SDK Build-tools, revision 23.0.3
   7- Android SDK Build-tools, revision 23.0.2
   8- Android SDK Build-tools, revision 23.0.1

………

  29- SDK Platform Android 7.0, API 24, revision 2
  30- SDK Platform Android 6.0, API 23, revision 3
  31- SDK Platform Android 5.1.1, API 22, revision 2
  32- SDK Platform Android 5.0.1, API 21, revision 2
  33- SDK Platform Android 4.4W.2, API 20, revision 2
  34- SDK Platform Android 4.4.2, API 19, revision 4
  35- SDK Platform Android 4.3.1, API 18, revision 3

……….

 108- Google APIs, Android API 23, revision 1
 109- Google APIs, Android API 22, revision 1
 110- Google APIs, Android API 21, revision 1

Now we need to install No.29, ‘SDK Platform Android 7.0, API 24, revision 2’, to build in Jenkins successfully. So try to install by commend line as below.

$ sudo ./android update sdk -a -u -t 29

Try build it again in Jenkins. Finally, the problem was fixed and build was finished successfully.

CachePot으로 Activity 또는 Fragment 사이 간단한 데이터 통신

Java에서 흔히 객체로 데이터 통신 하기 위해 객체에 Serializable 인터페이스를 상속 받은 뒤 직렬화된 객체를 바이트 단위로 분해하여 전송한다. Android에서도 마찬가지로 Serializable을 사용하긴 하지만 보다 Parcelable을 많이 사용하고 있다. 하지만 Parcelable을 사용하기 위해서는 객체에 선언된 데이터가 추가 될 때마다 writeToParcelreadFromParcel을 통해 Parcel 객체에 읽고 쓰는 작업을 추가해야 데이터 전송이 가능하다. CachePot은 간단한 앱을 만들거나 Intent를 통한 다른 애플리케이션과 통신이 없다면 간단하게 데이터 캐시를 할 수 있는 인스턴스가 있으면 좋겠다고 생각되어 Generic을 사용해 간단하게 만든 안드로이드 라이브러리이다.

Download

현재 Gradle을 사용하는 중이라면 build.gradle에 아래를 추가하자.

repositories {
      jcenter()
}

dependencies {
      compile 'com.github.kimkevin:cachepot:1.0.0'
}

How To Use?

동기식 데이터 전달하기

  • Between Activity and A ctivity
  • Between Activity and Fragment
  • Between Fragment and Fragment

데이터가 메모리에 올라가기 때문에 결국은 사용자 인테페이스를 구성하는 Activity와 Fragment 등 아무대서나 동기적으로 데이터 전달이 가능하다.

1. Model 객체 전달

새로운 화면 생성 전 데이터 저장하기

KoreanFood foodItem = new KoreanFood(1, "Kimchi", "Traditional fermented Korean side dish made of vegetables");
CachePot.getInstance().push(foodItem);

새로운 화면 생성 후 데이터 가져오기

public class MainFragment extends Fragment{
    private KoreanFood foodItem;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        foodItem = CachePot.getInstance().pop(KoreanFood.class);
    }
}

2. Collection 또는 Map 객체 전달

새로운 화면 생성 전 데이터 저장하기

List<KoreanFood> foodItems = new ArrayList<>();
foodItems.add(new KoreanFood(1, "Kimchi", "Traditional fermented Korean side dish made of vegetables"));
foodItems.add(new KoreanFood(2, "Kkakdugi", "A variety of kimchi in Korean cuisine"));
CachePot.getInstance().push(foodItems);

새로운 화면 생성 후 데이터 가져오기

List<KoreanFood> foodItems = CachePot.getInstance().pop(ArrayList.class);

ArrayList나 HashMap, Stack, LinkedList 등과 같이 구현 클래스도 일반적인 모델 데이터를 전달하는 것과 같은 방식으로 데이터 전송이 가능하다.

비동기식 데이터 전달하기

ViewPager에서 position별로 Fragment에 객체 전달

요즘 앱에서 흔하게 ViewPagerFragmentStatePagerAdapter를 사용하고 있다. 그리고 Adapter에서는 getItem(int position)이 호출 될 때 Fragment를 생성하게 되는데 새로운 Fragment가 생성되고 CachePot 인스턴스에 저장된 데이터를 가져오기 전에 새로운 데이터를 캐시하게 되어 데이터를 가져올 때 오류가 발생하게 된다. 이를 방지하기 위해서 Fragment의 postiion을 함께 저장할 수 있는 기능을 제공한다.

Framgnet 생성 전에 position과 데이터 저장하기

private class PagerAdapter extends FragmentStatePagerAdapter {
    ...
    public Fragment getItem(int position) {
        CachePot.getInstance().push(position, foodItems.get(position));
        return FoodFragment.newInstance(position);
    }
}

생성된 Fragment에서 데이터 가져오기

public static FoodFragment newInstance(int position) {
    FoodFragment fragment = new FoodFragment();
    Bundle args = new Bundle();
    args.putInt(ARG_POSITION, position);
    fragment.setArguments(args);
    return fragment;
}
...
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
        final int position = getArguments().getInt(ARG_POSITION);
        koreanFoodItem = CachePot.getInstance().pop(position);
    }
}

‘Where is CEO?’ == ‘사장 어딨노?’ 사내 해커톤 후기

얼마전 플리토 사내에서 첫 번째 해커톤을 열었다. 보통 해커톤에 참여 할 경우에는 한 달 전부터 만들어 보고 싶은 것들을 꼭 팬으로 노트에 낙서하듯 적어본다. 딱히 이유는 없지만 팬으로 적었을 때에는 머리 속에서 더 오래 남아있는 것 같은 느낌이 들어서다.

그래서 이번은 회사 프로젝트는 아니지만 만들면 재밌을 것 같았던 ‘Where is Simon(CEO)?’로 결정했다. 플리토 청담 오피스는 현재 40명에 가까운 인원이 총 3개의 층을 사용하고 있고 사내 메신저로 가끔 누가 어디에 있는지 묻는 경우가 있어서 이를 확인할 수 있는 페이지가 있으면 좋겠다고 생각했었다. 가장 빈번히 찾는 대상은 어느 회사에서나 공공의 타겟 바로 대표님이다. 기본 아이디어는 ‘층마다 있는 무선 공유기(AP)에 접속한 직원들을 층별로 보여주자‘로 시작했다.

<img class=”aligncenter” src=”http://kimkevin.net/wp-content/uploads/2016/07/kk_160504_08.jpg” />

1. Mornitoring Agent – 층별 AP에 접속한 직원들 리스트 가져오기

가장 기본적으로 먼저 AP에 접속한 단말기의 정보를 가져와야 프로젝트가 시작될 수 있기 때문에 여러 방법들을 시도했다.

ipTIME 관리자 페이지로 리스트 가져오기

<img class=”aligncenter” src=”http://kimkevin.net/wp-content/uploads/2016/07/kk_160504_07.png” />

가장 먼저 iptime의 관리자 페이지에서 내부 네트워크의 접속 주소 정보를 리스트로 가져올 수 API가 있을까 했지만 실패다. 그래도 AP에 접속한 직원들의 MAC 주소 리스트를 쉽게 만들 수 있었다.

특정 AP에 접속한 기기정보 명령어로 가져오기

해커톤이 있는 전날 밤, 어떻게 직원들의 리스트를 가져올 수 있을지 찾아보다가 페이스북에 글을 남겼고 Bruce Lee님이 여러 링크를 댓글로 남겨주셨다.

<img class=”aligncenter” src=”http://kimkevin.net/wp-content/uploads/2016/07/kk_160504_01.png” />

그 중 하나가 AP에 연결된 클라이언트들의 리스트를 가져 올 수 있는 arp 명령어를 발견 했다.

$arp -a
? (192.168.2.1) at 90:9e:33:38:d3:98 on en0 ifscope [ethernet]
? (192.168.2.13) at (incomplete) on en0 ifscope [ethernet]
? (192.168.2.20) at c:8b:fc:b1:bd:ea on en0 ifscope [ethernet]
? (192.168.2.43) at d0:53:29:c7:8a:4b on en0 ifscope [ethernet]
? (192.168.2.54) at (incomplete) on en0 ifscope [ethernet]
? (192.168.2.63) at 44:12:10:b7:12:22 on en0 ifscope [ethernet]
? (192.168.2.71) at 28:5a:eb:a2:ed:94 on en0 ifscope [ethernet]
? (192.168.2.83) at d8:1c:72:d9:ce:e on en0 ifscope [ethernet]
? (192.168.0.112) at (incomplete) on en0 ifscope [ethernet]

첫 번째는 AP에서 할당한 내부 IP정보와 할당 받은 MAC 주소를 가져올 수 있다. PC뿐만 아니라 스마트폰, 태블릿이 AP에 접속하면 리스트에 포함이 된다. 여기에서 리스트의 가장 상단에 있는 192.168.7.1은 AP의 MAC 주소이다. 하지만 arp 명령어로 테스트를 해보면서 몇 가지의 이슈가 발생했다.

  1. AP의 2.4G, 5G는 MAC 주소가 다르다.
  2. MackBook은 얼마 후 ‘incomplete’상태로 변경되어 리스트에서 없어지고 Windows 노트북은 계속해서 유지된다.
  3. arp에 나오는 기기정보들은 내 MacBook이 AP에 접속한 후에 접속한 기기들만 포함된다.

해커톤은 24시간 내 개발을 해야하기 때문에 완벽함보다는 가능성이 있는 부분을 더욱 구체화 시키는 편이 더 좋다. 그래서 추후에 위의 3가지 이슈를 해결할 수 있는 다른 방법은 실제로 서비스할 수 있을 때 찾아서 수정하기로 한다.

AP에 접속한 단말 정보 주기적으로 가져오기
$arp -a | awk '{print $2, $4}' | sed -e 's/(/''/g' | sed -e 's/)/''/g'
192.168.2.1 90:7e:32:33:d2:x1
192.168.2.13 incomplete
192.168.2.20 c:8b:fc:b1:bd:ea
192.168.2.43 d0:53:29:c7:8a:4b
192.168.2.54 incomplete
192.168.2.63 44:12:10:b7:12:22
192.168.2.71 28:5a:eb:a2:ed:94
192.168.2.83 d8:1c:72:d9:ce:eg
192.168.0.112 incomplete

필요한 데이터를 추출한 다음에 incomplete된 Mac 주소와 AP의 Mac 주소를 제외하고 웹 서버에 층 정보와 Mac 주소 리스트를 등록한다. 그리고 새로운 기기가 Wifi가 연결될 때 자동으로 웹 서버에 등록할 수 없기 때문에 setInterval 로 10초당 한번씩 확인해서 갱신되었을 때마다 웹 서버에 등록을 시켜주면서 최신으로 업데이트하도록 구현하면 된다.(사실 깊게 생각 안 했고 cronJob으로도 가능할 듯)

Mornitoring Agent에서 층 구별하기

ap = {
"90:7e:32:33:d2:x1" : "6",
"90:8f:31:33:d1:93" : "7"
}

층 정보는 arp 명령어의 결과에서 받은 ‘192.168.2.1 90:7e:32:33:d2:x1’의 MAC 주소와 Agent에 등록된 AP MAC 주소를 비교해서 몇 층에 위치한 모니터링 서버인지 구별할 수 있다. 위를 비교해보면 결과물은 6층 AP임을 알 수 있다.

2. Express for Node.js – 웹 서버 만들기

이번 프로젝트는 간단한 Rest API와 웹 서버가 필요했기 때문에 빠르게 구현할 수 있는 Express를 사용했다.

직원들이 사용하는 단말의 MAC 주소 리스트 미리 등록하기
flittorians = {
"78:31:c0:c3:e4:13" : "김케빈",
"54:4d:80:94:7e:c1" : "김케빈 아이폰",
...
}

웹 서버에서는 직원들의 Mac 주소 리스트를 가지고 있으므로 층 별로 모니터링 하는 Agent에서 변화를 알려주면 층 별로 매칭되는 직원들의 최신 리스트를 가지고 있을 수 있게 된다. 그리고 이 정보가 필요할 땐 JSON으로 보내준다.

Slack Bot 연동하기

<img class=”aligncenter” src=”http://kimkevin.net/wp-content/uploads/2016/07/kk_160504_05.jpg” />

Bruce Lee님의 이번 주제는 ‘슬랙봇 만들기’이다. 새벽이 되었을 때 층 별 API를 요청을 하셨고, 간단한 작업이라 만들어 드렸더니 본인이 만들던 슬랙봇에 연결을 했다. 서로 말하고 시작한 프로젝트가 아니였는데 재밌다. 때로는 독립적인 작은 덩어리가 서로 연결되어 큰 덩어리가 된다. 이런게 바로 개발의 행복 그리고 소소한 재미를 느낄 수 있었다.

3. ejs & bootstrap 으로 웹 페이지 만들기

<img class=”aligncenter” src=”http://kimkevin.net/wp-content/uploads/2016/07/kk_160504_06.jpg” />

웹 프론트 작업은 새벽 4시정도 부터 시작할 수 있었는데 이번 해커톤은 혼자지만 생각보다 진행이 잘되어 발표시간(12시)까지는 8시간을 남겨놓은 상태라 여유로운 편이다. 웹페이지는 express 만들 때 탬플릿으로 ejs를 설정했기 때문에 간단하게 bootstrap을 사용하여 간단한 table로 구성했다. 처음에는 스타일 없이 작업을 하고 시간적 여유가 있다면 간단히 스타일을 주는건 결과물을 보는 청중에게 더욱 인정받는 결과물이 된다. 여기에서 table은 bootstap-table을 사용해서 간단히 스타일을 적용했다.

정리하면서..

그리고 국내에서든, 국외에서든 모든 해커톤에서는 시간 관리가 제일 중요하다. 아무리 뛰어난 아이디어라고 하더라도 발표 때 결과물이 없다면 또는 만들었지만 정상적으로 동작하지 않는다면 심사위원들에게 인정 받지 못하기 때문에 심사위원에게 무엇을 어떻게 어필할지 있을지 마지막까지 고민해야한다. 즉, 시간이 걸리는 작업은 되도록 나중에 하는 것이 낫다라는게 개인적인 생각이다. 새로운 팀빌딩으로 작업을 하기 때문에 밤새하지 않더라도 적어도 프로토타입을 보여줄 수 있을 정도면 충분한 것 같다

SlidingIconTabLayout 탭에 아이콘 넣기

구글에서는 현재 많은 샘플 코드들을 개발자가 전반적인 프로젝트 구조나 리소스, 코드 파일들을 볼 수 있도록 제공해주고 있다. 그 중에서 Material Design이 안드로이드 5.0에서 소개되면서 UI관련 소스들을 많이 추가되었고 많은 프로젝트에서 SlidingTabLayoutSlidingTabStrip를 사용한다.

프로젝트에서 ViewPager를 이용하게 되면 페이지의 이름을 보여주기 위한 레이아웃이 필요하게 되는데, 만약 탭의 이름을 텍스트로 보여주고 싶다면 구글에서 제공해주는 위의 두 파일로 충분히 구현할 수 있다. 하지만 텍스트를 사용할 경우, 다국어 지원을 하게 되면 여러가지 이슈가 발생하게 되는데 예를 들면, 한국어를 영어로 바꿀 경우에는 대부분 글자수가 많아진다. 이를 해결하기 위해서 흔히 탭의 이름을 아이콘으로 대체하고 ActionBar의 타이틀에 페이지의 이름을 노출하고 있다. 텍스트와 아이콘을 모두 지원하기 위해서 SlidingTabLayout에 아이콘도 추가할 수 있도록 간단한 인터페이스를 추가해서 오픈소스 SlidingIconTabLayout로 만들었다.

SlidingIconTabLayout

Download

repositories {
  jcenter()
}

dependencies {
  compile 'com.github.kimkevin:slidingicontablayout:1.0.0'
}

SlidingTabLayout.TabIconProvider 추가하기

SlidingIconTabLayout.TabIconProvider는 안드로이드 화면상에 노출되는 다수의 Fragment를 관리하기 위한 어답터이다. Fragment에 필요한 데이터나 리소스를 위치(Position)에 맞게 그려주기 위해서 FragmentPagerAdapter를 상속 받아야 하기 때문에 이때 아이콘을 텍스트 대신에 보여주기 위한 인터페이스가 필요하다.

public interface TabIconProvider {
    int getPageIconResId(int position);
}

SlidingTabLayout 수정

TabIconProvider를 상속받고 있다면 텍스트 대신 아이콘을 사용할 수 있다.

if (TextView.class.isInstance(tabTitleView)) {
    ((TextView) tabTitleView).setText(adapter.getPageTitle(i));
} else if (ImageView.class.isInstance(tabTitleView) && adapter instanceof TabIconProvider {
    TabIconProvider mTabIconProvider = (TabIconProvider) adapter;
    tabTitleView.setBackgroundResource(mTabIconProvider.getPageIconResId(i));
}

사용할 FragmentTabIconProvider를 추가해서 필요한 아이콘을 추가해주면 해당 Tab의 아이콘으로 노출된다.

모알보알 스쿠버다이빙, 삶의 균형을 위한 중성부력

새로운 도전! 스쿠버다이빙

scuba_diving_01

올해 8월 초, 5일간 필리핀 세부를 다녀왔다. 예전부터 꼭 하고 싶었지만, 2년 전 아일랜드 호핑으로 느꼈던 세부의 짠물에 대한 공포때문에 미뤄두고 있었던 스쿠버다이빙, 오픈워터에 도전하기로 마음 먹었다. 장소는 세부시티에서 가까운 막탄보다는 깨끗해서 투명한 바다, 많은 종류의 산호초와 사람만한 거북이, 화려한 색감의 열대어를 볼 수 있는 다이빙 포인트로 유명한 모알보알이다. 강사 말로는 예전에는 고래상어가 출몰해서 섬을 몇 바퀴 돌면서 머물다가 갔다고 했다.

막탄 공항에 도착하다

scuba_diving_02

막탄 공항을 빠져나와 택시타고 서부터미널로 향했다. 택시비는 약 500페소 정도면 갈 수 있다. 택시를 이용할 때는 잔돈이 있는게 좋다. 기사 아저씨들은 돈만 받으면 잔돈이 없다는 뻔한 말들을 하기 시작한다. 그래도 택시에 내리지 말고 버티다 보면 슬그머니 돈을 꺼내기 시작하면서 재미가 쏠쏠한 밀당이 시작된다. 터미널 안은 매표소가 별도로 없고 버스타고 출발하면 아저씨가 표를 주는데, 버스 이용요금은 116페소 한국돈으로 3300원 정도다. 만약 터미널이나 공항에서 벤이나 택시를 이용하게 되면 한국돈으로 6만원 ~ 10만원 든다고 한다. 여행하면서 로컬 버스를 타보는 것도 좋은 경험이 될 것 이라고 생각한다면 이것은 고생이다. 이동 시간이 4시간이라는 것만 빼고는… 세부시티로 돌아오는 길에는 약5시간 가까이 걸렸던 것 같다.

수심 30m에서 찾아온 나의 균형

scuba_diving_03

나에게 있어서 여행은 휴가, 휴식의 의미보다는 여행을 통한 배움을 위해서다. 그래서 꼭 1년에 한번 이상은 국내나 해외를 여행한다. 그럼 이번 오픈워터를 통해서는 무엇을 배웠을까? 이번 오픈워터 도전을 통해서 배운건 다른 여행보다 의미가 컸다. 스타트업 로켓에 탑승하기 전까지, 방세를 내며 여름 방학동안 공모전 공장을 운영해봤었고, 개발자에게는 좋은 근무환경을 제공하는 IT회사에서 근무도 해봤었다. 그리고 짧은 시간이었지만 미국에서 스타트업을 경험도 해보았다. 짧다면 짧고 길다면 긴 시간동안 많은 경험을 했던 만큼 나 자신에 대해 고민하고 생각해보면서 내놓은 결론이 있다. 삶에 있어서 가족, 친구, 지인, 회사, 꿈도 중요하지만 모든 곳에서 꼭 있어야 하는 무엇보다 중요한 바로 균형이라고 생각한다. 그것을 몸으로 느껴볼 수 있었던 경험이 바로 하루 3 ~ 5시간씩 물 속에서 있을 때였다.

두려움을 극복하라

scuba_diving_04

스쿠버다이빙에서 중성부력이라는 용어가 있다. 부력(위로 올라가려는 힘) 중력의 힘(아래로 내려가려는 힘)이 동일한 상태로, 물 속에서 뜨지도 가라 앉지도 않는 상태이며, 다이빙을 하면서 이 상태로 자신의 호흡만으로 즉, 폐 안의 공기를 이용해 수심을 유지하는 방법이다. 밸트에 납 때문에 중력으로 쉽게 밑으로 내려갈 수 있고, 부력조절기(BC)로 불리는 조끼같이 생긴 장비 때문에, BC에 공기를 넣어 중력의 반대의 힘 부력을 만들 수 있다.

스쿠버다이빙을 편하고 즐겁게 하기 위해서는 중성부력을 맞추지 못하면 다이빙하고 있는 수심이 낮아졌다 높아졌다하면서 이퀄라이징을 자주 해야하기 때문에 귀에 무리가 가고 귀가 아프기도 한다.

이런 이유보다 쉽게 중성부력을 잡지 못하는건 바로 내 마음속에 있는 두려움 때문이다. 두려움이 내 몸에 힘이 들어가게 만들고 힘이 들어가면 호흡이 가빠지다 보니 불안해지기 때문에 상황대처능력도 떨어지며 바로 앞에 있는 아름다운 바다를 보는 시야도 줄어들게 된다.

두려움 이겨내는 중성부력

scuba_diving_05

하루에 2 ~ 3시간 동안 바다 안에 있으면서 두려움을 잊기 위해서 많은 시도를 해봤지만 언제 어디서 나올지 모를 상어에 대한 걱정(강사도 실제로 큰 상어를 본적은 없다고 한다), 언제 마스크가 벗겨질지 모를 상황에 대한 걱정, 강사를 놓치진 않을까 하는 걱정, 아픈 귀에 대한 걱정, 공기통에 남은 공기의 양에 대한 걱정 등 물속에서 생각해야하는 것들이 많았다. 여러 환경적 요소들로 인해서 몸에는 힘이 잔득 들어가고 그로 인해 몸은 가라앉고, BC에 공기를 넣었다 빼는데 바빴던 것이다. 앞을 볼 시간은 줄어들었고 집중력은 떨어졌다. 즉 내가 보고 싶은 바다를 편안하게 보기 위해 필요했던건 바로 균형이었다.

그리고 문득 바다속에서 내가 처한 상황이 우리의 상황과 비슷하다는 생각이 들었다. 정신적으로 여유를 가지고 몸에는 힘을 뺀채로 넓은 시야로 눈 앞에 있는 문제들을 해결해야 하는데, 사소한 문제들로 인해서 균형이 깨져서 힘들어 하고 있진 않을까? 그게 금전적 문제, 직업, 직장이든 말이다.

직장인이라면 누구나 불안함을 지니고 산다. 하지만 생각해보면 그 불안감은 자기 자신이 만드는 것이다. 옆에서 상어가 오진 않을까 강사를 놓치진 않을까, 결국 나 자신이 만든 불안감으로 부터 시작되었다. 외부적 요인이 몸에 힘이 들어가게 했고 바다속으로 가라 앉도록 했던 것이다. 결국 부력(여유)이 중력(불안감)을 받쳐주지 못하기 때문이다. 자신이 여유를 생산하고 불안을 잘 컨트롤 할 수 있다면 그게 바로 균형일 것이다. 우리가 재미있고 행복하게 지내기 위해 가장 필요한 것은 균형을 맞추기 위한 중성부력이 아닐까 생각한다.

리얼포스 & 해피해킹 오사카 키보드 구매기

지난 주말, 해피해킹 키보드를 구매했다. 2년전 첫 번째 키보드는 기계식 레올포드 FC700R 중고를 구매하면서 부터다. 구매전에는 리얼포스, 해피해킹, 레오폴드, DECK등 많은 키보드들을 알아보았지만 처음에는 가장 무난한 FC700R의 적축으로 사용했다. 기계식 키보드에는 적축, 갈축, 청축, 흑축이 있는데, 타건을 해보면 축마다 특유의 차이점이 있다. 구매전 리더스키레오포들 사이트를 자주 들어가보면서 가격과 재고를 자주 확인하기도 했었고 만약 직접 타건을 해보고 싶으면 용산역 리더스키에 가서 키보드를 직접 사용해볼 수 있다.

FC700R

FC700R

첫 번째 키보드를 구매할 때 가장 좋고 비싼 해피해킹이나 리얼포스를 살까라고 생각했지만, 한국에서 구매하기엔 부담스러운 가격이라 FC700R 중고를 구매해서 사용해보기로 했다. 첫 번째 기계식 키보드 FC700R은 나에게 대만족이었다.

그리고 작년 9월, 미국에서 만난 일본 친구들을 보기 위해 오사카를 여행을 계획했고, 이왕 가는김에 키보드 구매를 결심했다. 검색으로 여기저기 돌아다니며 블로그 구매후기도 찾아 읽어보고가 덴덴타운에 대한 단서를 찾을 수 있었다. 20만원 정도에 구매를 했다는 후기를 보았던 것 같다.

리얼포스

Real Force

작년 오사카에서 리얼포스를 구매했다. 오사카 난바역에서 Nippombashi St을 찾아 걷다보면 오타쿠 천국이라고 불리는 거리를 볼 수 있게 된다. 길 양쪽으로는 게임, 피규어, 만화, 전자기기등 용산전자상가같은 느낌으로 길게 나열된 스토어들이 있다. 일본 만화나 피규어, 게임을 좋아한다면 하루도 충분히 보낼 수 있다. 지금까지 한번도 피규어를 사본적 적도 관심도 없던 나였지만 리얼포스를 사러 가던길에 피규어 샵에 들러서 구경하다 보니, 나도 모르게, 정말 나도 모르게 스토어를 나올 땐 내 손에는 두 개의 피규어가 있었다.

많은 스토어들을 찾아 들어가 리얼포스의 행방을 물었지만 87키가 없을 뿐더러 제품을 찾기는 어려웠다. 처음에는 1’s라는 가게를 모르고 모든 곳을 찾아다니면서 가격을 비교해봤고 가장 저렴하게 판매되던 곳이 1’s였던 것으로 기억한다. 덴덴타운에서는 리얼포스 파는 곳이 3~4곳은 있었던 것 같다.

1’s

1’s(one’s) PCワンズ Google Maps

가게에 도착해서 아래 계단으로 내려가면 다양한 키보드가 나열되어 있고, 올 해에는 작년에 보지 못한 레올폴드 FC660C도 봤는데 일본에서는 리얼포스보다 비싼 가격이었다. 리얼포스 가격은 작년 내가 구매한 20,980엔으로 변함이 없었다. 텍스프리를 작년 리얼포스 구매할 때 알았다면 이보다는 훨씬 싼가격으로 구매했을 텐데 사기전에 잘 알아보고 구매하는 것이 현명하다. 예상하기론 1’s에서도 텍스프리로 구매가 가능하겠지만, 만약 되지 않는다면 요도바시카메라나 다른 스토어에서 사는게 더 저렴할 수 있을 것 같다. 그리고 차등을 구매해서 물어보지 않아서 균등, 저소음 차등은 구매가 가능한지는 모르겠다.

Realforce Price

사용후기는 FC700R을 반년 정도 사용하다가 리얼포스로 바꾸니 처음에는 묵직한(?) 느낌으로 오히려 손가락에 부담이 되는거 같았지만, 일주일 정도 개발을 하다보니 어느새 리얼포스가 더 부드럽고 타건 재미에 빠졌다.

해피해킹 프로2 – 요도바시 카메라

Happy Hacking Pro2

지난주 일본 친구의 결혼식으로 다시 한번 오사카를 방문했다. 비행기 티켓 예약보다 먼저 구글 검색에 해피해킹 프로2 구매관련 키워드를 입력을 하고 있었다. 한 분의 구매기를 읽었더니 19,800엔 정도로 오사카에서 구매를 했다는 글을 읽고 구매를 결심했다. 작년에 리얼포스를 구매했던 곳(one’s)에서 구래를 할 수 있을 거라 생각하고 오사카 도착 후 일본친구와 함께 one’s를 갔지만 아쉽게도 해피해킹은 판매하지 않았다.

검색이 필요한 타이밍이다. 글 보다는 댓글에 초점을 맞추었다. 댓글 중에서 우메다 역에 있는 요도바시카메라에서 구매한 사람을 발견하고 교토를 가기전에 우메다역에 있는 요도바시카메라를 잠시 들러서 찾아보았다.

Happy Hacking Pro2

해피해킹 프로2 가격은 약 24,000엔. 분명 어제 보았던 글에서는 분명 19,800엔으로 봤던거 같은데 싸게 산 사람을 글을 읽고 났더니 구매의욕이 떨어졌다. 하지만 일본친구가 점원을 불러 이야기를 하더니 TAX FREE로 싸게 살 수 있다고 한다. 가격은 2개에 39,600엔, TAX FREE를 왜 몰랐을까라는 생각은 잠시 점원이 VISA카드로 결제하면 6% 할인을 더 해주겠다고 한다. 추가 할인이 가능하고 했을 경우 2개에 37,236엔, 하나에 18,600엔 정도 되는 가격으로 해피해킹 프로2를 구매할 수 있었다.

Happy Hacking Pro2 Price

아직까지 어떤 키보드를 살지 구매를 고민하고 있다면 일단 구매하고 사용해 보면 돈이 아까운 생각은 들지 않는다. 자신이 사고 싶은 키보드를 빨리 구매해서 그 시간만큼 열심히 개발하자.

Keyboard