Glide 이미지 라이브러리 커스터마이징

Glide의 커스터마이징은 공식 문서(영문)에 더 자세히 설명되어 있다. 내게 필요한 기능이 무엇인지 참고하고, 더 자세한 내용은 꼭 공식 문서를 보자.

Glide는 Fresco, Picasso, Universal Image Loader 등과 함께 안드로이드에서 손 꼽히는 이미지 라이브러리다. Glide를 리용하면 원격지(remote)/로컬(local) 이미지를 가져와 화면에 표시할 수 있다. 가져온 이미지는 캐싱을 통해 로딩 속도를 개선할 수 있으며, 필요하다면 이미지 모서리 깎기(rounding)같은 재처리와 애니메이티드 gif도 표시할 수 있다. 따로 HTTP 라이브러리를 쓰지 않고 동작하지만, Volley나 OkHttp 라이브러리와도 호환 동작할 수 있다. 이제는 사라진 범프(Bump) 앱에서 사용할 목적으로 개발/적용한 것이 이 라이브러리의 시작으로 알려져 있다.

Glide의 사용법은 쉬울 뿐 아니라 공식 문서(영문)가 예시와 함께 친절하게 작성되어 있다. 그러므로, 여기서는 기본적인 사용방법 보다는 Glide의 AppGlideModule를 통해 Glide의 동작을 커스터마이징을 할 수 있는 고급 옵션들에 대해 살펴보자.

Glide의 고급 설정(configuration)을 위해서는 준비가 필요하다.

  1. AppGlideModule 추상클래스의 구현체 추가
  2. @GlideModule 어노테이션을 구현체에 선언
  3. 구현체 컴파일을 위해 build.gradle에 Glide 어노테이션 프로세서 추가
  4. 프로가드 설정에 AppGlideModule 클래스의 -keep 선언 추가

준비가 끝나면, 아래와 같이 구현체의 applyOptions() 안에 고급 설정을 추가할 수 있다.

@GlideModule
public class YourGlideModule extends AppGlideModule
{
    @Override
    public void applyOptions( Context _context, GlideBuilder _builder )
    {
        // 고급 설정 추가
    }
}

메모리 캐시

GlideBuilder.setMemoryCache()를 통해, 메모리 캐시 크기를 정할 수 있다. 캐싱할 화면의 갯수를 정수로 지정하거나 직접 캐시 크기를 지정할 수 있다. 화면의 갯수로 지정할 때는  앱의 가장 긴 화면을 염두하여 지정하면 같은 이미지를 원격지에서 반복적으로 로딩하지 않고 캐싱할 수 있을 것이다.

비트맵 풀

GlideBuilder.setBitmapPoolScreens()를 통해, 비트맵 캐시 크기를 정할 수 있다. 정하는 방법은 메모리 캐시와 유사하다.

디스크 캐시

GlideBuilder.setDiskCache()를 통해, 디스크 캐시를 위한 폴더 이름과 디스크 캐시 폴더의 최대 용량를 정할 수 있다. 폴더의 기본 이름은 ‘glide’, 용량은 250mb이다.

리퀘스트 옵션 기본 값 변경

GlideBuilder.setDefaultRequestOptions()에 RequestOptions 객체를 전달하는 방식으로 변경할 수 있다. Glide.with() 메소드에서 반환하는 RequestManager.apply()를 통해 매 이미지 요청마다 설정할 수도 있지만, 여기서 설정하면 모든 요청의 기본 값으로 적용된다.

이미지 저장 방식(원본만, 재처리 후 사본 만, 모두)이나, 출처(원격지만, 로컬만, 모두), 이미지의 decode 포멧 등을 명시적으로 선언할 수 있다.

로그

GlideBuilder.setLogLevel()을 통해, 로그 레벨을 조절할 수 있다. 개발할 때는 Log.DEBUG, 공개할 때는 Log.ERROR 정도가 적당해 보인다.

모든 설정을 추가한다면, 아래와 같은 코드가 될 수 있다.

@GlideModule
public class YourGlideModule extends AppGlideModule 
{
    @Override
    public void applyOptions( Context _context, GlideBuilder _builder )
    {
        // 메모리 캐시
        MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context).setMemoryCacheScreens(2).build();
        _builder.setMemoryCache(new LruResourceCache(calculator.getMemoryCacheSize()));

        // 비트맵 풀
        int bitmapPoolSizeBytes = 1024 * 1024 * 30; // 30mb
        _builder.setBitmapPool(new LruBitmapPool(bitmapPoolSizeBytes));

        // 디스크 캐시
        int diskCacheSizeBytes = 1024 * 1024 * 100; // 100 MB
        _builder.setDiskCache(new InternalCacheDiskCacheFactory(context, "cacheFolderName", diskCacheSizeBytes));

        // 리퀘스트 옵션 기본 값
        _builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.RGB_565).disallowHardwareBitmaps());

        // 로그 레벨
        _builder.setLogLevel(Log.DEBUG);
    }
}

그 밖에

참조

안드로이드의 설정(Settings)

안드로이드는 단말에서 사용하는 설정(setting)에 관련된 정보를 Settings과 Settings 안의 클래스들을 통해 제공한다. 하지만, 안드로이드 셋팅 앱(com.android.settings)이나 시스템 앱이 아니라면 이 값을 참조하지 않거나 한 두개 정도 참조할 것이다.

Settings 클래스

Settings 클래스는 설정의 하부에 포함된 메뉴로 진입할 수 있는 액션(Action)을 상수로 선언하고 있다.  이 상수를 이용하면 접근성 메뉴, 계정 메뉴, 블루투스 설정, 네트워크 설정 등의 액티비티를 바로 호출할 수 있다.

아래 코드는 ‘설정 > 연결 > Wi-Fi'(N OS 기준)로 바로 진입할 수 있다.

startActivity( new Intent( Settings.ACTION_WIFI_SETTINGS ));

모든 메뉴들을 위한 액션들이 있는 것은 아니다. 예를 들면, Wi-Fi 설정이나 모바일 네트워크 설정으로 바로가는 액션은 있지만, 이 메뉴들의 진입점인 ‘설정 > 연결’ 액티비티를 호출하는 액션은 없다.

Settings의 내부 클래스

Settings 클래스에는 Global, Secure, System 등의 내부 클래스가 있다. 이 들은 단말의 설정 값들을 제공한다. Global 클래스는 사용자 계정(주인, 손님)에 관계 없이 적용되는 설정을, Secure는 시스템 보안관련 설정을 System은 그 밖의 나머지 설정 값을 제공한다. ContentResolver 객체와 함께 건네면 값을 반환해주는 get 메소드를 제공한다. 각 값의 타입은 안드로이드 API doc를 통해 확인할 수 있다.

String dataFormat = Settings.System.getString( getContentResolver(), System.TIME_12_24 );

위 예제는 단말의 시계 표기 방식을 리턴한다. 값이 없는 설정의 경우 스트링은 null을 리턴하고, 숫자를 반환하는 메소드는 기본 값을 설정할 수도 있다.

하지만, 설정에 따라 저장하고 있는 클래스가 다른데다 설정이 다른 클래스로 이동하기도 하기 때문에 사용자가 어떤 클래스에서 설정을 찾아야할 지 혼란스럽다. 그래서 System에서 설정을 읽어오려 했는데 그 설정이 Global나 Secure로 이동했다면, 값의 저장 위치가 변경되었다는 경고(warning) 로그 메시지를 출력한 후 변경된 클래스에서 값을 찾아 반환한다.

모든 설정 클래스들이 put 메소드들을 제공하지만, 설정의 종류의 따라 다르게 동작한다. Global과 Secure는 설정의 메뉴나 특정 API를 통해서는 수정할 수 있지만 어플리케이션은 읽을 수만 있다. System은 기본적으로 읽을 수 있고, 일부 쓸 수도 있다. get 메소드와 마찬가지로 권한이 없다면 경고(warning) 로그 메시지를 출력한다.

특정 설정 값이 변경되는지 옵저버(observer)를 달 수도 있다.  ContentResolver.registerContentObserver() 메소드를 통해 ContentObserver를 달면 된다. 위에서 확인해본, 날짜 표기 방법이 바뀌는지 옵저버를 달아보자.

...
getContentResolver().registerContentObserver(
        Settings.System.getUriFor( System.TIME_12_24 ),
        false,
        new ContentObserver( new Handler() )
        {
            @Override
            public void onChange( boolean selfChange )
            {
                onChange( selfChange, null );
            }

            @Override
            public void onChange( boolean selfChange, Uri uri )
            {
                String str = Settings.System.getString(
                        getContentResolver(),
                        System.TIME_12_24 );

                Toast.makeText( MainActivity.this,
                                "setting= " + str,
                                Toast.LENGTH_SHORT )
                     .show();
            }
        } );
...

이제, 시간 표기 법이 변경되면 ContentObserver.onChange() 메소드들이 호출된다.

주의할 점은, onChange( boolean, Uri) 메소드는 API16부터 추가되었기 때문에 그 이전 OS를 지원하려면 onChange( boolean)을 구현해야 한다. 사용한 ContentObserver는 꼭 ContentResolver.unregisterContentObserver()를 통해 해제해주어야 한다. 해제하지 않으면 메모리 릭을 유발할 수 있다.

참고