월별 글 목록: 2016년 7월월

실행시간을 로깅할 수 있는 TimingLogger 클래스 개선

앱을 개발하고, 테스트를 하다 보면 종종 특정 로직의 실행시간이 궁금한 경우가 많다. 그래서 메서드의 실행시간을 로깅할 수 있는 것을 찾아보니 안드로이드 API에서 TimingLogger라는 클래스를 제공하고 있다.
API 문서를 살펴보니, 사용방법은 다음과 같다.

TimingLogger timings = new TimingLogger(TAG, "methodA");
// ... do some work A ...
timings.addSplit("work A");
// ... do some work B ...
timings.addSplit("work B");
// ... do some work C ...
timings.addSplit("work C");
timings.dumpToLog();

그리고, 이 코드의 실행결과는 아래에서 보듯이 실행시간과 로깅 데이터를 보여준다.

D/TAG     ( 3459): methodA: begin
D/TAG     ( 3459): methodA:      9 ms, work A
D/TAG     ( 3459): methodA:      1 ms, work B
D/TAG     ( 3459): methodA:      6 ms, work C
D/TAG     ( 3459): methodA: end, 16 ms

API 문서만 봐서는 간단하게 메서드의 실행시간을 확인하는데 꽤 유용하게 사용할 수 있어 보인다. 그러나 TimingLogger 클래스를 자세히 보니, 로그 레벨이 Log.VERBOSE로 하드코딩되어 있어서 실제로는 사용하기 불편하다. 그래서, 간단하게 TimingLogger에 로깅레벨을 설정해서 사용할 수 있도록 TimingLogger 클래스를 개선해 봤다.

TimingLogger 클래스 소스 : https://github.com/android/platform_frameworks_base/blob/master/core/java/android/util/TimingLogger.java

이 클래스에 로깅레벨을 추가하고 추가한 로깅레벨을 기준으로 수정한 dumpToLog() 메서드는 다음과 같다.

/** LogLeve to check log or not */
private static int mLogLevel = Log.DEBUG;

public static void setLogLevel(int logLevel) {
    mLogLevel = logLevel;
}

public void dumpToLog() {
    if (mDisabled) return;

    final long first = mSplits.get(0);
    long now = first;
    for (int i = 1; i < mSplits.size(); i++) {
        now = mSplits.get(i);
        final String splitLabel = mSplitLabels.get(i);
        final long prev = mSplits.get(i - 1);

        Log.println(mLogLevel, mTag, mLabel + ":      " + (now - prev) + " ms, " + splitLabel);
    }
    Log.println(mLogLevel, mTag, mLabel + ": end, " + (now - first) + " ms");
}

이 TimingLogger 클래스를 수정한 클래스인 JTimingLogger 클래스는 개인 프로젝트인 AndoridUtil 라이브러리 프로젝트에서 볼 수 있다.

JTimingLogger 클래스 소스 : https://github.com/mcsong/AndoridUtil/blob/master/library/src/main/java/net/sjava/androidutil/JTimingLogger.java

다음으로 이 클래스를 사용하는 예제를 살펴보자.

import net.sjava.androidutil.JTimingLogger;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        display();
    }
    
    public void display() {
        Log.d("AA", "1111111111111111");

        JTimingLogger.setLogLevel(Log.INFO);
        JTimingLogger.setLogLevel(Log.VERBOSE);

        JTimingLogger logger = new JTimingLogger("AA", "VV");
        logger.addSplit("aaaaaaaaaaaaaaa");

        try {
            Thread.sleep(100);

            logger.addSplit("aaaaaaaaaaaaaaa");

            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        logger.addSplit("bbb");
        logger.dumpToLog();
    }
}

이 소스를 사용하는 방법으로, 사용하기 전에 JTimingLogger.setLogLevel() 메서드에 로그레벨을 설정하고 사용하면 된다. 그리고 이 소스를 실행한 화면은 다음과 같다.

07-27 20:47:39.713 12502-12502/net.sjava.androidutil.demo I/AA: VV:      0 ms, aaaaaaaaaaaaaaa
07-27 20:47:39.714 12502-12502/net.sjava.androidutil.demo I/AA: VV:      100 ms, aaaaaaaaaaaaaaa
07-27 20:47:39.715 12502-12502/net.sjava.androidutil.demo I/AA: VV:      101 ms, bbb
07-27 20:47:39.715 12502-12502/net.sjava.androidutil.demo I/AA: VV: end, 201 ms

이제 간단하게 확인하고 싶은 메서드의 실행시간을 쉽게 확인할 수 있다.

안드로이드 기기의 추가 SD CARD 마운트 위치

안드로이드 기기는 내부에서 사용하는 SD CARD와 더불어 추가로 SD CARD를 연결해서 사용할 수 있다. 물론 USB도 사용할 수 있고, USB 마운트 위치는 이곳에서 확인할 수 있다. 안드로이드 파일 관련한 작업을 개발하다 보면, 공식(API와 넥서스 기기)으로 지원하지 않는 형태(USB 마운트 및 제조사별의 마운트 위치로 인한)를 지원하다 보니, 안드로이드 기기의 파편화를 제대로 느낄 수 있다.

개발자들의 성지인 스택오버플로에서 검색으로 추가한 SD CARD를 확인하는 방법을 살펴보니, 정답은 없겠지만, 아래의 방법으로 추가 SD CARD를 확인하는 것이 좋을 듯했다.

System.getenv("SECONDARY_STORAGE")

위 설정 값을 사용해서 2개 이상의 SD CARD가 마운트된 위치를 가져오는 예제는 아래와 같다.

String[] externalArray;
String secondaryStorage = System.getenv("SECONDARY_STORAGE");
if (secondaryStorage != null) {
   externalArray = secondaryStorage.split(":");
} else {
   externalArray = new String[0];
}

이 예제로, SECONDARY_STORAGE는 콜론(:)을 딜리미터로 여러 개의 SD CARD 마운트 위치를 알려준다는 것을 알 수 있다. 이 예제는 안드로이드 프레임웍의 MediaStore 클래스에서 확인할 수 있는 코드이다. 즉, 제조사는 안드로이드 기기에 SECONDARY_STORAGE 속성을 사용해서 추가 SD CARD를 마운트해야 MediaStore가 제 역할을 한다는 것을 알 수 있다.

위 코드는 아래의 소스에서 확인할 수 있다.

https://github.com/android/platform_frameworks_base/blob/kitkat-release/core/java/android/provider/MediaStore.java

https://github.com/android/platform_frameworks_base/blob/lollipop-release/core/java/android/provider/MediaStore.java

https://github.com/android/platform_frameworks_base/blob/marshmallow-release/core/java/android/provider/MediaStore.java

하지만, 위에서 살펴본 예제는 젤리빈(JB)에서는 찾을 수 없다. 따라서, 위 코드는 킷캣 이후의 버전에서만 제대로 동작하리라는 것을 심증적으로나마 알 수 있다.

휴, 안드로이드 파편화 감당은 쉽지 않다.

안드로이드 기기의 USB 메모리 마운트 위치

안드로이드 기기를 제조하는 많은(공식 넥서스를 제외, 루팅 하면 가능) 제조사에서 기기에 USB를 사용할 수 있도록 제공하고 있다. 이것은 USB OTG라는 USB 확장지원을 안드로이드에서 지원하게 되었고, 이제 OTG 케이블을 안드로이드 기기에 연결하면 USB 메모리, 마우스 등을 사용할 수 있게 된다.

이게 편리하기는 하지만, 막상 USB를 외부 메모리로 사용하려고 하면, 연결한 USB가 어디에 마운트(mount)되어 있는지 명확하지가 않다. /mnt 나 /storage에 마운트 하는 것이 일반적이다. 그래서 USB를 연결한 안드로이드 기기별로 USB 마운트 위치를 찾아보니, 스택오버플로에 테스트해서 정리한 것이 있다. 정리된 내용은 아래와 같다.

/storage/UsbDriveA (all Samsung devices)
/storage/USBstorage1 (LG G4, V10, G3, G2, other LG devices)
/storage/usbdisk (Moto Maxx, Turbo 2, Moto X Pure, other Motorola devices)
/storage/usbotg (Sony Xperia devices, Lenovo Tabs)
/storage/UDiskA (Oppo devices)
/storage/usb-storage (Acer Iconia Tabs)
/storage/usbcard (Dell Venue — Vanilla Android 4.3 tablet)
/storage/usb (HTC One M7, and some Vanilla Android devices)

삼성과 LG를 확인해보니, 삼성은 UsbDriveA, B… F와 같은 형태로 마운트 위치를 제공하고, LG는 USBstorage1, 2… 의 형태로 여러 개의 USB를 대응하고 있다.

혹시, 더 자세한 정보를 가지고 있는 분이 있다면 알려주시면 매우 감사합니다.