LRU 알고리즘 캐 시 그림 사용 하기

UI 에 하나의 그림 을 표시 하 는 것 은 매우 간단 합 니 다.한 번 에 많은 그림 을 표시 해 야 한다 면 좀 복잡 합 니 다.많은 경우(예 를 들 어 ListView,GridView 또는 ViewPager 컨트롤 을 사용)화면 에 표 시 된 그림 과 화면 에 표 시 될 그림 의 수량 이 매우 크다(예 를 들 어 갤러리 에서 대량의 그림 을 탐색 하 는 것).
이 컨트롤 들 중 하위 컨트롤 이 표시 되 지 않 을 때 시스템 은 메모리 소 모 를 줄 이기 위해 이 컨트롤 을 다시 사용 합 니 다.또한 쓰레기 수 거 체 제 는 메모리 에 불 러 온 Bitmap 자원 도 방출 합 니 다.일반적으로 이것 은 모두 좋 은 것 이지 만 사용자 가 화면 을 왔다갔다 할 때 UI 의 유창 성과 그림 을 불 러 오 는 효율 을 확보 하기 위해 서 는 표시 할 그림 을 반복 적 으로 처리 하 는 것 을 피해 야 합 니 다.메모리 캐 시 와 디스크 캐 시 를 사용 하면 이 문 제 를 해결 할 수 있 습 니 다.캐 시 를 사용 하면 컨트롤 이 처 리 된 그림 을 빠르게 불 러 올 수 있 습 니 다.
이 절 은 UI 의 불 러 오기 입력 과 미끄럼 의 유창 성 을 높이 기 위해 캐 시 를 사용 하 는 방법 을 소개 합 니 다.
메모리 캐 시 사용
메모리 캐 시 는 그림 에 접근 하 는 속 도 를 높 였 지만 많은 메모 리 를 사용 해 야 합 니 다.LruCache 클래스(API 4 이전에 Support Library 의 클래스 를 사용 할 수 있 음)는 Bitmap 캐 시 에 특히 적합 하 며,최근 사용 한 Bitmap 대상 을 강 인용 으로 저장(LinkedHashMap 에 저장)하고,캐 시 수량 이 예 정 된 값 에 도달 하면 자주 사용 하지 않 는 이미 지 를 삭제 합 니 다.
메모:과거 에는 메모리 캐 시 를 실현 하 는 데 자주 사용 되 었 던 방법 은 SoftReference 나 WeakReference bitmap 캐 시 를 사 용 했 지만 이런 방식 을 사용 하 는 것 을 추천 하지 않 습 니 다.안 드 로 이 드 2.3(API Level 9)부터 쓰레기 수 거 를 시작 으로 soft/weak 인용 을 강제로 회수 하여 이 캐 시 들 이 효율 적 으로 향상 되 지 않 았 습 니 다.또 안 드 로 이 드 3.0(API Level 11)이전 에는 이 캐 시 된 비트 맵 데 이 터 를 바 텀 메모리(native memory)에 저장 하고,예 정 된 조건 에 도달 해도 이 대상 을 방출 하지 않 아 프로그램 이 메모리 제한 을 초과 해 붕괴 할 수 있다.
LruCache 를 사용 할 때 다음 과 같은 요 소 를 고려 하여 적당 한 캐 시 수량 인 자 를 선택해 야 합 니 다.
  • 프로그램 중 몇 개의 메모리 가
  • 을 사용 할 수 있 습 니까?
  • 동시에 화면 에 몇 개의 그림 을 표시 합 니까?보 이 는 화면 에 표시 할 그림 을 몇 개 캐 시 해 야 합 니까?
  • 장치 의 화면 크기 와 화면 밀 도 는 얼마 입 니까?높 은 화면 밀도(xhdpi,예 를 들 어 Galaxy Nexus)장치 가 같은 그림 을 표시 하 는 것 은 낮은 화면 밀도(hdpi,예 를 들 어 Nexus S)장치 보다 더 많은 메모리 가 필요 합 니 다.
  • 그림 의 크기 와 형식 은 모든 그림 이 얼마나 많은 메모 리 를 차지 해 야 하 는 지 결정 한다.
  • 사진 방문 빈 도 는 어 떻 습 니까?일부 그림 의 접근 빈 도 는 다른 그림 보다 훨씬 높 습 니까?그렇다면 자주 방문 하 는 그림 들 을 메모리 에 넣 어야 할 수도 있 습 니 다.
  • 은 품질 과 수량 에 있어 서 어떻게 균형 을 잡 습 니까?어떤 경우 에는 대량의 저 품질 그림 을 저장 하 는 것 이 매우 유용 하 며,필요 할 때 백 스테이지 스 레 드 를 사용 하여 고 품질 버 전의 그림 을 추가 합 니 다.

  • 모든 프로그램 에 적합 한 만능 레 시 피 가 없습니다.사용 상황 을 분석 하고 캐 시 정책 을 지정 해 야 합 니 다.너무 작은 캐 시 를 사용 하 는 것 은 효과 가 없 으 며,너무 큰 캐 시 를 사용 하면 더 많은 메모리 가 소모 되 어 자바.lang.OutOf Memory 가 이상 하거나 프로그램의 다른 기능 을 사용 할 수 있 는 메모리 가 적 게 남 을 수 있 습 니 다.
    다음은 LruCache 캐 시 를 사용 하 는 예제 입 니 다.
    private LruCache<string, bitmap=""> mMemoryCache;
                                                                  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Get memory class of this device, exceeding this amount will throw an
        // OutOfMemory exception.
        final int memClass = ((ActivityManager) context.getSystemService(
                Context.ACTIVITY_SERVICE)).getMemoryClass();
                                                                  
        // Use 1/8th of the available memory for this memory cache.
        final int cacheSize = 1024 * 1024 * memClass / 8;
                                                                  
        mMemoryCache = new LruCache<string, bitmap="">(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // The cache size will be measured in bytes rather than number of items.
                return bitmap.getByteCount();
            }
        };
        ...
    }
                                                                  
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }
                                                                  
    public Bitmap getBitmapFromMemCache(String key) {
        return mMemoryCache.get(key);
    }

    메모:이 예제 에서 이 프로그램의 8 분 의 1 메모리 가 캐 시 용 으로 사용 되 었 습 니 다.normal/hdpi 장치 에 서 는 최소 4MB(32/8)메모리 가 있 습 니 다.해상도 800×480 의 장치 에서 전체 화면 에 있 는 GridView 가 그림 을 모두 채 우 면 1.5MB(800*480*4 bytes)차이 가 나 지 않 는 메모 리 를 사용 하기 때문에 메모리 에 2.5 페이지 의 그림 을 캐 시 한 것 이 많 지 않다.
    ImageView 에 그림 이 표 시 될 때 는 LruCache 에 존재 하 는 지 확인 합 니 다.존재 하면 캐 시 된 그림 을 사용 합 니 다.존재 하지 않 으 면 배경 스 레 드 를 시작 하여 그림 을 불 러 오고 캐 시 합 니 다.
    public void loadBitmap(int resId, ImageView imageView) {
        final String imageKey = String.valueOf(resId);
                                                         
        final Bitmap bitmap = getBitmapFromMemCache(imageKey);
        if (bitmap != null) {
            mImageView.setImageBitmap(bitmap);
        } else {
            mImageView.setImageResource(R.drawable.image_placeholder);
            BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
            task.execute(resId);
        }
    }

    BitmapWorkerTask 는 새 그림 을 캐 시 에 추가 해 야 합 니 다:
    class BitmapWorkerTask extends AsyncTask<integer, void,="" bitmap=""> {
        ...
        // Decode image in background.
        @Override
        protected Bitmap doInBackground(Integer... params) {
            final Bitmap bitmap = decodeSampledBitmapFromResource(
                    getResources(), params[0], 100, 100));
            addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
            return bitmap;
        }
        ...
    }

    디스크 캐 시 사용
    최근 에 사용 한 그림 에 접근 할 때 메모리 캐 시 속도 가 빠 르 지만 캐 시 에 그림 이 있 는 지 확인 할 수 없습니다.GridView 와 같은 컨트롤 은 표시 해 야 할 그림 이 많 을 수 있 으 며,곧 그림 데이터 가 캐 시 용량 을 채 울 수 있 습 니 다.또한 프로그램 이 다른 작업 에 의 해 중 단 될 수도 있 습 니 다.예 를 들 어 전 화 를 걸 었 을 때 프로그램 이 배경 에 있 을 때 시스템 은 이 그림 캐 시 를 알 수 있 습 니 다.사용자 가 프로그램 을 다시 사용 하려 면 이 그림 들 을 다시 처리 해 야 합 니 다.
    이 경우 처리 한 그림 을 디스크 캐 시 로 저장 할 수 있 습 니 다.메모리 캐 시 에서 사용 할 수 없 을 때 디스크 캐 시 에서 불 러 와 그림 처리 과정 을 생략 할 수 있 습 니 다.물론 디스크 에서 그림 을 불 러 오 는 것 이 메모리 에서 읽 는 것 보다 훨씬 느 리 고 비 UI 스 레 드 에서 디스크 그림 을 불 러 와 야 합 니 다.
    메모:캐 시 된 그림 이 자주 사용 된다 면 ContentProvider 를 사용 하 는 것 을 고려 할 수 있 습 니 다.예 를 들 어 갤러리 프로그램 에서 이렇게 마 르 는 것 입 니 다.
    예제 코드 에 간단 한 DiskLruCache 가 있 습 니 다.그리고 Android 4.0 에는 더 신뢰 할 수 있 고 추천 할 수 있 는 DiskLruCache(libcore/luni/src/main/java/libcore/io/diskLruCache.java)가 포함 되 어 있 습 니 다.이것 을 4.0 이전 버 전에 쉽게 이식 할 수 있 습 니 다(href="http://www.google.com/search?q=disklrucache">구 글 은 다른 사람들 이 이미 이렇게 했 는 지 확인 해 보 세 요!)
    이것 은 업 데 이 트 된 버 전의 DiskLruCache 입 니 다.
    private DiskLruCache mDiskCache;
    private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
    private static final String DISK_CACHE_SUBDIR = "thumbnails";
                                   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Initialize memory cache
        ...
        File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR);
        mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE);
        ...
    }
                                   
    class BitmapWorkerTask extends AsyncTask<integer, void,="" bitmap=""> {
        ...
        // Decode image in background.
        @Override
        protected Bitmap doInBackground(Integer... params) {
            final String imageKey = String.valueOf(params[0]);
                                   
            // Check disk cache in background thread
            Bitmap bitmap = getBitmapFromDiskCache(imageKey);
                                   
            if (bitmap == null) { // Not found in disk cache
                // Process as normal
                final Bitmap bitmap = decodeSampledBitmapFromResource(
                        getResources(), params[0], 100, 100));
            }
                                   
            // Add final bitmap to caches
            addBitmapToCache(String.valueOf(imageKey, bitmap);
                                   
            return bitmap;
        }
        ...
    }
                                   
    public void addBitmapToCache(String key, Bitmap bitmap) {
        // Add to memory cache as before
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
                                   
        // Also add to disk cache
        if (!mDiskCache.containsKey(key)) {
            mDiskCache.put(key, bitmap);
        }
    }
                                   
    public Bitmap getBitmapFromDiskCache(String key) {
        return mDiskCache.get(key);
    }
                                   
    // Creates a unique subdirectory of the designated app cache directory. Tries to use external
    // but if not mounted, falls back on internal storage.
    public static File getCacheDir(Context context, String uniqueName) {
        // Check if media is mounted or storage is built-in, if so, try and use external cache dir
        // otherwise use internal cache dir
        final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
                || !Environment.isExternalStorageRemovable() ?
                        context.getExternalCacheDir().getPath() : context.getCacheDir().getPath();
                                   
        return new File(cachePath + File.separator + uniqueName);
    }

    UI 스 레 드 에서 메모리 캐 시 를 감지 하고 배경 스 레 드 에서 디스크 캐 시 를 감지 합 니 다.UI 스 레 드 에서 디스크 작업 이 이 루어 져 서 는 안 됩 니 다.그림 처리 가 끝나 면 나중에 사용 할 수 있 도록 메모리 캐 시 와 디스크 캐 시 에 최종 결 과 를 동시에 추가 합 니 다.
    설정 변경 이벤트 처리
    실행 중인 설정 변경-예 를 들 어 화면 방향 변경-실행 중인 Activity 를 Android 가 파괴 한 다음 새 설정 을 사용 하여 이 Activity 를 시작 합 니 다(자세 한 내용 은 여기 Handling Runtime Changes 참조).설정 이 바 뀌 었 을 때 모든 그림 을 다시 처리 하지 않도록 주의 하여 사용자 체험 을 향상 시 켜 야 합 니 다.
    다행히도 메모리 캐 시 부분 에 좋 은 그림 캐 시가 있 습 니 다.이 캐 시 는 Fragment(Fragment 는 setRetainInstance(true)함수 로 저 장 됩 니 다)를 통 해 새로운 Activity 에 전달 할 수 있 습 니 다.Activity 가 다시 시작 되면 Fragment 는 Activity 에 다시 추 가 됩 니 다.이 Fragment 를 통 해 캐 시 대상 을 가 져 올 수 있 습 니 다.
    다음은 Fragment 에 캐 시 를 저장 하 는 예제 입 니 다.
    private LruCache<string, bitmap=""> mMemoryCache;
                     
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        RetainFragment mRetainFragment =
                RetainFragment.findOrCreateRetainFragment(getFragmentManager());
        mMemoryCache = RetainFragment.mRetainedCache;
        if (mMemoryCache == null) {
            mMemoryCache = new LruCache<string, bitmap="">(cacheSize) {
                ... // Initialize cache here as usual
            }
            mRetainFragment.mRetainedCache = mMemoryCache;
        }
        ...
    }
                     
    class RetainFragment extends Fragment {
        private static final String TAG = "RetainFragment";
        public LruCache<string, bitmap=""> mRetainedCache;
                     
        public RetainFragment() {}
                     
        public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
            RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
            if (fragment == null) {
                fragment = new RetainFragment();
            }
            return fragment;
        }
                     
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            <strong>setRetainInstance(true);</strong>
        }
    }

    Fragment 를 사용 하지 않 고 장치 의 화면 방향 을 회전 시 켜 구체 적 인 그림 불 러 오 는 상황 을 볼 수 있 습 니 다.

    좋은 웹페이지 즐겨찾기