BattleShip DevDiary Vol.4

Retrofit2, etag, 캐시

REST API로 데이터를 가져와서 화면에 보여줄 때 네트웍 결과만 기다릴 수는 없으니 캐시를 사용하게 됩니다. 일단 캐시의 내용으로 화면에 보여주고, API 결과로 다시 보여주는 방식이죠. 그런데 retrofit2를 처음 사용해 보면서 이 부분에서 삽질을 좀 했습니다.

일단 캐시를 따로 만들지 않고 OkHttp 자체 캐시를 사용해 보려고 했습니다. 요청 헤더에 “Cache-Control”을 사용하면 서버에는 요청하지 않고 캐시만 읽을 수 있습니다만 실행해 보니 캐시를 못 읽네요. 원인은 정확히 모르겠지만, 우리쪽 서버 설정에서 etag를 사용하기 위해  max-age=1을 내려주기 때문에 캐시가 유효하지 않다고 판단한 것 같습니다.

그럼 캐시를 따로 만들려면, retrofit에서 URL을 뽑아 와야 하는데 이 부분에서도 좀 헤맸습니다. 실제로는 이런 식으로 얻을 수 있는데요,

T service = retrofit.create(serviceClazz);
Call<R> call = action.call(service);
String url = call.request().url().toString();

사실 방법은 간단한데 여기서 걱정했던건, action.call()을 하면서 서버 요청이 발생할 것 같다는 생각이 들어서 이 코드를 사용하지 못하고 이리저리 헤맸습니다. 사실 retrofit 사용법을 잘 보면 call.execute()를 해주도록 하고 있거든요, 서버 요청은 여기서 이루어지는 거죠. 그러므로 위의 코드에서처럼 url을 얻어 오고, 그걸로 캐시를 읽으면 됩니다.

그러면 서버 요청할 때 etag는 어떻게 넣느냐 하면, 아래와 같은 방법으로 합니다.

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(chain -> {
    Request original = chain.request();
    Request.Builder builder = original.newBuilder()
            .header("User-Agent", HandyHttpClientImpl.userAgent)
            .method(original.method(), original.body());

    if (etag != null) {
        builder.header("If-None-Match", etag);
    }
    return chain.proceed(builder.build());
});

Retrofit retrofit = new Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl(ServerTypeHelper.getApiServer())
        .client(httpClient.build())
        .build();

User-Agent를 넣는다거나 Gson 컨버터를 넣는다거나 하는 부분이 좀 더 들어가 있지만 중요한건 builder.header()입니다. 간단하게 처리해 주는 방법은 없고 직접 헤더를 만들면 된다..라는 단순한 결론이네요.

Retrofit으로 캐시를 직접 읽는 작업을 해보니, 이런 생각이 들더군요. 이거 retrofit을 안 쓰고 직접 Http Client를 만지는 것보다 좋은게 뭐지? 그리고 얻은 결론은, 이 라이브러리의 개발 의도는 Http Client를 간편하게 사용해 보자인데, 캐시를 직접 읽는 식의 복잡한 작업을 해버리는건 개발 의도와 맞지 않다. 오히려 더 거추장스러울 수도 있다.

생산성 향상을 위해 무조건 라이브러리를 도입하기 보다는 상황에 맞는 라이브러리를 도입해야겠다는 교훈을 얻었습니다.

Mockito ExceptionInInitializerError, multidex

mockito를 사용하는데 갑자기 예외가 발생합니다.. 다행히도 해결방법이 있네요 http://stackoverflow.com/questions/29290795/attempt-to-mockito-mock-any-class-generates-exceptionininitializererror 멀티덱스에서 추가로 필요한게 있었군요

어라 근데 이렇게 하면 dexmaker에서 예외가 발생합니다…

java.lang.NullPointerException: Attempt to invoke virtual method
'java.lang.Class java.lang.Object.getClass()' on a null object reference
at com.google.dexmaker.mockito.DexmakerMockMaker.getInvocationHandlerAdapter(DexmakerMockMaker.java:80)
at com.google.dexmaker.mockito.DexmakerMockMaker.getHandler(DexmakerMockMaker.java:75)
at org.mockito.internal.util.MockUtil.isMockitoMock(MockUtil.java:74)

으.. 다시 찾아 헤매입니다. 결국 어떤 상황이냐면 https://code.google.com/archive/p/dexmaker/issues/2 원래는 이쪽에서 관리되던건데 github로 프로젝트를 옮기면서 https://github.com/crittercism/dexmaker/pull/24에서 버그는 수정되었지만 릴리스되지는 않았습니다. 직접 코드를 다운로드 받아서 사용하는 방법밖에 없네요…

SDK Manager plugin, Error:Cause: com.android.sdklib.repository.FullRevision

다른 프로젝트의 gradle 파일을 보다 보니 생소한 플러그인이 있네요. 그러나 이것은 그 유명한 Jake Whaton옹이 만든 그레이들 플러그인입니다. 개발하면서 여러 프로젝트를 돌리다 보면 항상 빌드에 필요한 SDK나 서포트 라이브러리의 특정 버전을 다운로드 받아야 하는 번거로움이 있는데, 이 플러그인을 사용하면 빌드에 필요한 SDK 등을 다운로드해 줍니다.

그런데 막상 빌드를 해보면 에러가 발생합니다.. 여기에 대한 버그 수정은 됐는데https://github.com/JakeWharton/sdk-manager-plugin/pull/100, 이것도 역시 릴리스는 안 되어 있습니다.. 직접 코드를 받아서 빌드해야 하는 고민을 해보지만, 훌륭하신 분들이 이미 해결해 놨습니다.

repositories {
    maven { url 'https://jitpack.io' }
}

classpath 'com.github.JakeWharton:sdk-manager-plugin:220bf7a88a7072df3ed16dc8466fb144f2817070'

이렇게 하면 특정 커밋을 기준으로 라이브러리를 사용할 수 있네요. 바로 위의 dexmaker로 여기를 통하면 소스를 직접 가져오지 않아도 최신 버전을 사용할 수 있습니다. 만세~

APK의 원래 크기는 왜 설치된 후에 더 커질까

크게 신경 쓰지 않았던 부분인데, 빌드의 결과로 나온 apk를 단말에 설치해 보면 더 많은 공간을 차지하고 있습니다. 이유가 궁금해서 좀 찾아 보니 http://stackoverflow.com/questions/14409139/why-does-my-app-size-on-device-differ-than-the-apk-or-play-store-size. 앱을 설치하면 apk 파일은 그대로 두고 dex 파일을 다시 설치하는군요. 그래서 단말에서는 원래 apk 파일 크기의 두 배 정도를 차지하는걸로 나오네요

BattleShip DevDiary Vol.4

BattleShip DevDiary Vol.3

errorprone

구글에서 만든 자바용 정적 코드 분석 도구입니다(http://errorprone.info). 실행해보면 의외로 도움이 되는 부분도 있고, 쓸데 없는 내용도 많이 나옵니다 ^^; 저는 코드 분석 도구를 쓰고 있지 않지만, lint 등을 항상 사용하시는 분들은 도움이 될 것 같습니다.

안드로이드 스튜디오 2.0

1.5에서 2.0으로 올리면서 두 가지 문제가 있었는데요

1. 릴리스 모드에서 lint가 동작한다

로컬에서는 문제 없었는데 CI 서버에서 릴리스 빌드를 하니까 lint가 동작하면서 lint 에러 때문에 진행이 안 되네요. 옵션을 설정해 줄 수 있는데 아예 lint를 동작하지 않게 하는 방법은 이렇습니다

lintOptions {
checkReleaseBuilds false
}

2. Test Artifacts

테스트 모드를 전환하기 위해서는 Build Variants에서 Test Artifacts를 변경했는데, 이제는 그럴 필요 없습니다. 모든 테스트가 한 번에 빌드됩니다. 설정 – Build, Execution, Deployment – Build Tools – Gradle – Experimental에 Enable all test Artifacts… 를 체크하면 번거롭게 모드를 전환할 필요가 없습니다

안드로이드 스튜디오에서 Gradle 업그레이드

Project Structure 메뉴에서 Project를 보면 gradle 버전을 적는 부분이 있습니다.

com.google.gson.internal.LinkedTreeMap cannot be cast to

gson을 사용해서 파싱을 하는데 처음 보는 예외가 발생하네요.TypedValue로 파싱할 클래스를 명시했는데도 문제가 생깁니다. 원인은 반환값에 타입 파라미터를 사용했기 때문입니다. 보통은 이런 식으로 파싱을 할텐데

Type resultContainer = new TypeToken<ResultContainer<Detail>>() {
}.getType();

ResultContainer<Detail> result = gson.fromJson(json, resultContainer);

이걸 좀 더 일반화하기 위해 아래와 같이 타입 파라미터를 이용해서 수정했습니다.

Type resultContainer = new TypeToken<ResultContainer<R>>() {
}.getType();

ResultContainer<R> result = gson.fromJson(json, resultContainer);

그런데 제네릭은 컴파일 시간에만 의미가 있고, 실행 시간에는 지워지는 정보라 파싱을 하는 시점에는 ResultContainer 안에 무슨 타입이 들어가는지 gson은 알 수가 없습니다. 그래서 자체적으로 map을 만들어서 반환하면서 생기는 문제입니다.

반환값에 타입 파라미터를 사용하고 싶을 때는 아래와 같이 명시해서 사용해야 합니다. clazz는 T 타입의 클래스 변수입니다.

T response = gson.fromJson(json, clazz);

이 때 T는 ResultContainer<R>처럼 제네릭을 사용하면 안 됩니다. 당연하지만 그러면 클래스 변수를 가져올 수가 없기 때문입니다.

API23에서 Robolectric

compileSdkVersion을 23으로 올리니까 Robolectric이 동작하지 않습니다.. 3.1-SNAPSHOT을 사용하면 된다는데, 이후에도 메이븐으로 뭔가 설정을 해줘야 하네요. 일단 후퇴..

LeakCanary

https://github.com/square/leakcanary 안드로이드에서 메모리 누수를 잡아 주는 라이브러리입니다. 레퍼런스 때문에 activity가 메모리에서 해제되지 않는 문제는 MAT 돌리는 것보다 훨씬 편하게 위치를 찾을 수 있네요.

WebView에서 localStorage 사용하면서 NPE

웹뷰를 사용하는데 화면이 안 보여서 로그를 보니 자바스크립트쪽에서 NPE가 발생합니다. 위치를 보니까 local storage를 사용하는 곳입니다. 안드로이드에서는 WebSettings.setDomStorageEnabled(true)를 실행해 줘야 저장소를 사용할 수 있습니다.

Mac에서 JAVA 위치 조정

Retrolamda를 사용하려고 java8로 빌드를 하는데 맥에서 java8 위치를 못 찾습니다. 환경 변수 설정으로는 해결이 안 되고, gradle 설정으로 복잡하게 해결하는 방법도 있던데, 그냥 Project Structure 메뉴에 JDK 위치를 지정해 주는 부분이 있어서 그걸 수정했습니다.

Lombok 빌드 속도

프로젝트에 몇 가지 라이브러리를 추가했더니 빌드 속도가 엄청 느려졌습니다. 찾아 보니 lombok이 문제네요… lombok 없이 38초 걸리던 빌드가 lombok를 추가하면 48초가 됩니다…

Parceler에서 상속 구조의 객체를 필드로 저장할 때

안드로이드 Parcel 코드를 자동으로 생성해 주는 parceler를 도입해 봤는데, 군더더기 코드가 싹 없어 집니다. 물론 문제가 발생하는 부분도 있네요

class Outer {
    Parent s = new Child();
}

이런 Outer 클래스를 parceler로 처리해서 parcel에 넣었다가 빼면, s가 Child가 아닌 Parent로 인식됩니다. parceler에서 생성해 주는 코드가 상속 구조를 생각하지 않기 때문인데요. 이럴 때 ParcelPropertyConverter를 이용해서 다른 방법으로 read/write하도록 알려줘야 합니다.

class Outer {
    @ParcelPropertyConverter(Converter.class)
    Parent s = new Child();

    public static class Converter implements ParcelConverter<Parent> {
        @Override
        public void toParcel(Parent input, Parcel parcel) {
            parcel.writeParcelable(Parcels.wrap(input), 0);
        }

        @Override
        public Parent fromParcel(Parcel parcel) {
            return Parcels.unwrap(parcel.readParcelable(Parent.class.getClassLoader()));
        }
    }
}
BattleShip DevDiary Vol.3

BattleShip DevDiary vol.2

BottomSheet in Android Support Library 23.2

구글 맵 등에서 사용하는 BottomSheet라는 레이아웃이 있는데, 그 동안 직접 구현해서 사용해 왔습니다. 그런데 이번에 공개된 ASL 23.2에 해당 기능이 포함되어 있네요. 사용하기에도 간편한 것 같습니다. http://kunny.github.io/lecture/ui/2016/02/28/support_bottomsheet_behavior_basics/

Assertj를 안드로이드에서 사용하려면 1.x를

최신 버전의 Assertj를 빌드에 포함시키니까 오류가 발생해서 찾아 보니, 안드로이드에서는 1.x 를 사용해야 합니다. 내부적으로 뭘 사용하고 있길래… http://joel-costigliola.github.io/assertj/assertj-core.html

밑이 2인 Log를 계산하려면?

Math 패키지에는 밑이 10과 e에 대한 로그밖에 없습니다. 밑이 2인 로그는 간단한 수학으로 계산해야 하는군요.

log2(x) = log(x)/log(2)

사용하지 않는 리소스 제거

이전에는 AndroidUnusedResources.jar를 사용했는데, 이게 gradle에서는 제대로 동작 안 하네요. https://github.com/lsit81/android-unused-resources 에서 해결했다고 하지만(한국분인듯 ^^) 저는 실패…

간단하게 안드로이드 스튜디오에 있는 Inspect 기능을 사용하면 됩니다. 검사 내용 중에 unused resource가 포함되어 있습니다.

특정 코드의 문자가 Canvas.drawText()로 표시되지 않는 문제

다음과 같은 유니코드의 문자는 글자 크기를 256이 넘게 표시할 때, 즉 257부터는 Canvas.drawText()로 비트맵에 그릴 때 표시되지 않는 오류가 있습니다: \u2194 ~ \u2199

단순히 텍스트 뷰로 문자를 보여줄 때는 글자 크기에 상관없이 표시가 되고, drawText()로 256이하의 크기로 비트맵에 그릴 때는 잘 표시됩니다. 또한 해당 코드는 텍스트뷰든 비트맵에 그리든 색깔을 바꿀 수가 없습니다.

Glide에서 이미지 크기 조정

glide가 사용하기는 쉬웠는데 고급 기능을 사용하려고 하니까 쉽지 않네요. 옵션에 따라 이미지 크기가 어떻게 조절되는지 간단히 확인해 봤습니다. 조건은, 2560×1600 이미지를 1440×2560의 해상도에서 into()로 직접 이미지뷰에 뿌리는 방식입니다. 이미지 크기는 listener()의 onResourceReady()에서 출력했습니다.

  • 기본 : 1440×900
  • fitCenter() : 1440×900
  • override(720,720) : 720×450
  • override(2880,2880) : 2880×1800
  • override(720,720), fitCenter : 720×450
  • override(2880,2880), fitCenter : 2880×1800
  • getSampleSize() : 리스너에서는 크기가 바뀌지 않는데, 이미지 품질은 바뀐다
  • override, sampleSize : 곱해서 같은 크기라면 sampleSize에서 처리하는게 조금 빠르다.
  • 큰 이미지를 sampleSize=1로 놓고 읽으니 OOM 발생. 일단 sampleSize로 디코딩을 하고 override로 크기를 다시 맞추는듯

glide github에서 이슈를 조금 보다 보니, glide의 기본 원칙은 무난한 상황에서 최고의 효율을 내기 위함인 것 같습니다. 그래서 into()로 뷰를 지정할 경우 해당 뷰의 크기에 맞춰서 이미지를 스케일링하도록 한다네요. 화면의 1픽셀이 이미지의 1픽셀에 대응하도록 해서 이미지 품질도 유지하고 메모리 효율도 높이는 거죠. 그런데 그 과정에서 override()로 임의의 크기로 만들 수 있고, 그 전에 sampleSize()로 디코딩을 먼저 하는 것 같습니다.

동작이 더 궁금하기는 한데, 이렇게 옵션을 바꿔보면서 확인해 보기보다 차라리 소스를 분석해 보는게 더 도움이 될 것 같아서 시간 되는대로 도전해 보려고 합니다.

Jack으로 빌드하기

Jack이라는게 있다고는 들었는데 관심을 못 가지다가 이 소식을 접하게 되었습니다 : https://developer.android.com/intl/ko/preview/j8-jack.html. 자바8의 람다를 안드로이드에서 사용할 수 있게 되는데 그러려면 안드로이드 스튜디오 2.1과 Jack이 필요합니다.

2.0도 정식 버전이 안 나온 상태에서 2.1을 기대하는건 너무 이른 것 같고, Jack을 한 번 사용해 보고 싶어서 현재 작업 중인 프로젝트를 Jack으로 빌드해봤습니다. 컴파일 중에 에러가 나서 이상하다 했지만 apk를 만들길래 잘 되나 싶었는데, 단말에 설치하는 과정에서 INSTALL_FAIL_DEX_OPT 가 발생합니다. 로그캣에서도 오류 메시지가 나오는데 제조사별로 차이가 있습니다

  • LG : com.android.server.pm.PackageManagerException: scanPackageLI
  • 삼성 : DexFile_isDexOptNeeded failed to open oat file

너무 막막한 상황이였는데 다행히(?) 다음과 같은 오류 보고가 되어 있네요: https://code.google.com/p/android/issues/detail?id=82486. 윈도우즈에서 빌드가 안 된다는 내용인데요, 첫 보고 날짜가 좀 지나고 동일 증상에 대한 보고도 여러개인데 구글쪽 코멘트가 없네요.. Jack은 다시 깊게 묻어 둬야 할듯…

BattleShip DevDiary vol.2

BattleShip DevDiary vol.1

Korean articles is here: https://battleshippark.wordpress.com/2016/02/29/battleship-devdiary-vol-1/

Handling click event consecutively coming from view in Android

When developing UI, there are situations when click events are coming consecutively from view like button and we have to prevent it. For example, if I press camera shutter button and close button crash may occur. (I’m not sure I have to prevent, but) There are solutions like below.

1. android:splitMotionEvents=”false”

http://developer.android.com/reference/android/R.attr.html#splitMotionEvents

If I put splitMotionEvents=”false” as property in layout, it prevents click events occur simultaneously between child views. Precautions are:

  1. You have to apply to direct parent of views for handling. It does not apply to all of children even though you apply to the most upper layout.
  2. You have to specify it whenever you need although default value is false in document.

2. Ignoring events in a certain time period

It is so much complicated to handle all of views in screen like this. We can ignore other events in a certain period after a click event occurs. There are ways like implementing Handler or comparing timestamp all the time, but I tried to implement with RxJava as a study. I thought I need proper operator and I found out this:

throttleFirst(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread())

http://reactivex.io/documentation/operators/sample.html

Next-generation Graphics API – VULKAN

Graphics API following OpenGL was announced. It’s worth to have interest even although we can’t use in Android. https://developer.android.com/ndk/guides/graphics/index.html

Using MAT in Android Studio

It was very convenient to use MAT in Eclipse because it has MAT plugin. Although Android Studio(current 1.5.1) has heap analysis, it is inconvenient yet. Of course, you can download standalone application: http://www.eclipse.org/mat/downloads.php, but you can’t read heap dump with it after you get it from Android Studio. It seems version unmatch between them, and there is hprof-conv.exe in SDK-directory/platform-tools/. After you convert heap dump with this in command list, you can use MAT.

Checking Activity Stack

I was curious how activities are accumulated, and I found out like this:

adb shell dumpsys activity activities | grep -i run

There are more good functionalities in “adb shell dumpsys”.

Running external tools in Android Studio

Although this is helpful when you develop with NDK, you can use it in different situations, which is how to run external tools like javah. http://blog.burt.pe.kr/javah%EB%A5%BC-androidstudio%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0/

Building FFMPEG in Android

I introduce a well-organized document:  https://enoent.fr/blog/2014/06/20/compile-ffmpeg-for-android/

Using java.util.stream package of Java 8 in Android

When I use retrolambda, I can use lamda but can not use stream package. By the way, there is backport library here: https://sourceforge.net/projects/streamsupport/

YUV format and converting to NV21

I had to convert bitmap to NV21. I referred to here about format: http://blog.dasomoli.org/265, and here about converting:  http://stackoverflow.com/questions/5960247/convert-bitmap-array-to-yuv-ycbcr-nv21.

However, there is a bug that makes crash when image whose width or height is odd is given. I will share fixed code separately.

Rounding in Canvas.drawPath()

You can use Paint.setPathEffect(): http://stackoverflow.com/questions/7608362/how-to-draw-smooth-rounded-path

 

BattleShip DevDiary vol.1

BattleShip DevDiary vol.1

 

뷰에서 발생하는 클릭 이벤트가 연속으로 들어오는 경우의 처리

UI 개발을 하다 보면 버튼 같은 뷰에서 클릭 이벤트가 연속으로 들어오는 경우가 있고 이걸 막아야 하는 경우가 있습니다. 카메라 촬영 버튼과 종료 버튼을 동시에 누르면 로직이 꼬이면서 크래시가 발생하는데요, (굳이 이런 것을 막아야 하나 싶지만) 다음과 같은 해결 방법이 있습니다.

1. android:splitMotionEvents=”false”

http://developer.android.com/reference/android/R.attr.html#splitMotionEvents

레이아웃 속성으로 splitMotionEvents=”false”를 넣어 주면, 자식 뷰들간에 동시에 클릭 이벤트가 발생하지 못하게 막아줍니다. 주의점은

  1. 처리를 해야 하는 뷰의 바로 윗 부모에게 적용해야 합니다. 레이아웃 최상위에만 적용한다고 하위 모든 자식들에게 적용되지 않습니다.
  2. 문서에는 기본값이 false인데 필요할때 마다 지정해 줘야 합니다.

2. 일정 시간 간격을 두고 이벤트 무시

화면에 모든 뷰에 대해 이렇게 처리하려면 너무 복잡합니다. 그래서 일단 클릭 이벤트가 발생하면 일정 시간 내의 다른 클릭 이벤트는 무시하도록 하면 되는데요, 이걸 핸들러로 구현하거나 매번 타임스탬프를 비교하는 방법을 써도 되지만, 공부도 해 볼겸 RxJava로 구현해 보기로 했습니다. 적절한 연산자만 있으면 될 것 같았는데 아래와 같은 것이 있네요.

throttleFirst(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread())

http://reactivex.io/documentation/operators/sample.html

간단하게 해결!

차세대 그래픽스 API – Vulkan

OpenGL의 뒤를 잇는 그래픽스 API가 발표되었습니다. 아직 안드로이드에서 사용할 수는 없지만 관심 가져볼만 합니다.

https://developer.android.com/ndk/guides/graphics/index.html

안드로이드 스튜디오에서 MAT 사용

이클립스에서는 MAT 플러그인이 있어서 사용하기 좋았는데, 현재 안드로이드 스튜디오는(1.5.1 기준) 힙 분석 기능이 들어는 있지는 아직 많이 불편합니다.

독립 어플리케이션을 설치해서 사용하면 되는데 (http://www.eclipse.org/mat/downloads.php), 안드로이드 스튜디오에서 힙덤프를 받아도 읽어들이지를 못합니다.

양쪽의 버전이 맞지 않는 것 같은데, SDK 디렉토리를 보면 hprof-conv라는 프로그램이 있습니다. 이걸로 명령줄에서 힙덤프 파일을 변환시키면 MAT에서 사용할 수 있습니다.

안드로이드 액티비티 스택 확인

액티비티가 어떻게 쌓여 있는지 궁금해서 찾아보니 아래와 같은 방법이 있네요

adb shell dumpsys activity activities | grep -i run

adb shell dumpsys에 더 많은 기능이 있으니 찾아 보면 좋을 것 같습니다.

안드로이드 스튜디오에서 외부툴 연동

NDK 개발할 때 도움이 되는 내용이지만 다른 경우에도 활용할 수 있는 경우가 있을 것 같습니다. javah 같은 외부툴을 바로 실행할 수 있는 방법입니다. http://blog.burt.pe.kr/javah%EB%A5%BC-androidstudio%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0/

안드로이드에서 FFMPEG 빌드하기

잘 정리된 문서를 공유합니다. https://enoent.fr/blog/2014/06/20/compile-ffmpeg-for-android/

안드로이드에서 자바8의 java.util.stream 패키지 사용하기

retrolambda를 사용하면 lamda는 사용할 수 있지만 stream 패키지는 사용할 수 없습니다. 그런데 이에 대한 백포트 라이브러리가 있습니다(https://sourceforge.net/projects/streamsupport/). 시간 되는대로 사용해 보면서 안정성을 확인해 보겠습니다.

YUV 포맷과 NV21 변환

카메라 앱을 개발하다 보니 이런 포맷도 알게 되네요. 동영상쪽에서 잘 사용한다고 하던데, 비트맵을 NV21로 변환해야 해서 조사하게 되었습니다

포맷에 대해서는 http://blog.dasomoli.org/265 여기를 참조했고, NV21로 변환하는 코드는 http://stackoverflow.com/questions/5960247/convert-bitmap-array-to-yuv-ycbcr-nv21 여기를 참조했습니다.

그런데 저 변환 코드에 폭이나 높이가 홀수인 이미지를 입력하면 크래시가 발생합니다. 관련해서 수정한 코드는 따로 공유하겠습니다.

Canvas.drawPath()에서 라운딩 처리

Paint.setPathEffect()를 이용합니다. http://stackoverflow.com/questions/7608362/how-to-draw-smooth-rounded-path

 

 

BattleShip DevDiary vol.1

뽀모도로 시간 관리법

최근에 소프트스킬이라는 책을 읽었습니다. 평범한 개발자가 알려주는 자기개발 방법이라는 주제의 책인데, 괴물같은 사람들의 내용이 아니라 정말 주변에서 볼 수 있을 것 같은 사람의 내용이라 와닿는 부분이 많았습니다.

다양한 주제를 다루고 있는데요, 몇 가지 기억에 남는 내용이 있지만 그 중에 한 가지가 뽀모도로 시간 관리법입니다. 어떻게 하면 업무 시간에 최대한 집중을 할 수 있는가에 대한 것입니다. 제 경우에도 평소에 딴 생각을 굉장히 많이 하는 편이라 업무 시간에 집중을 하기가 어렵습니다. 낮에 최대한 집중해서 일을 마치고 정시에 퇴근하고 싶은 마음만 있고, 실제로는 스마트폰 열어 봤다가 인터넷 검색 했다가 화장실 갔다가 하면서 시간을 보내다 결국 야근을 하게 되는 경우가 많습니다.

뽀모도로는 굉장히 간단한 방법으로 집중력을 끌어 낼 수 있습니다. 깊게 파고들면야 더 많은 내용이 있겠지만, 책에서도 간단하게만 소개하고 있고, 또 제가 직접 해보니 기본적인 방법만으로도 충분히 효과를 볼 수 있습니다.

방법은,

  1. 타이머를 하나 준비한다.
  2. 20분에서 25분 정도 시간을 맞추고 그 시간 동안에는 다른 짓 하지 않고 업무에만 집중한다
  3. 5분 휴식
  4. 2~3 과정을 3회 정도 반복하면 20분 휴식
  5. 2~4 반복

간단하죠? 이게 무슨 도움이 되겠냐고 생각할 수  있지만 이 방법의 특징은 집중하는 시간과 쉬는 시간을 엄격히 구분한다는 겁니다. 즉 업무 시간과 쉬는 시간을 애매하게 섞어서 집중도를 떨어뜨리지 말고, 차라리 짧은 시간이라도 업무에만 집중하고, 쉬는 시간에는 확실하게 쉬는거죠. 일 보다가 잡생각이 들더라도 ‘그건 이따 쉴 때 해봐야겠다’라고 재껴 놓을 수 있습니다.

그러면 타이머가 필요한데, 실제 타이머를 구입할 수도 있지만 스마트폰용 뽀모도로 앱이 많이 있습니다. 몇 가지 설치해 보고 취향에 맞는걸 선택하면 됩니다. 어차피 복잡한 기능이 필요한게 아니니까요.

 

 

뽀모도로 시간 관리법

Error calling method on NPObject

안드로이드에서 웹뷰를 이용할 때 네이티브와 통신하기 위해 WebView.addJavascriptInterface()를 사용하면서 발생하는 에러이다.

검색해 보니 네이티브 쪽에 runOnUiThread()를 사용하라고 하는 글들이 있는데 내 경우에는 그게 원인은 아니였고, 메소드의 인자가 안 맞아서 발생했습니다.

자바스크립트에서 JSON이 필요한데 네이티브에서는 String만 써놓는다거나, 자바스크립트에서는 인자를 2개 사용했는데 네이티브에서는 한 개만 사용한다거나 하는 경우입니다.

Error calling method on NPObject