Android 진급 연습 - 효율적인 Bitmap 표시(캐시 Bitmaps)

55732 단어 android
캐시 Bitmaps
단독 그림 한 장을 불러와서 표시하는 것은 매우 간단하지만, 한 번에 대량의 그림을 불러올 때, 일은 비교적 복잡해진다.
  ListView
GridView
 or  ViewPager 같은 구성 요소) 화면에 표시된 그림과 표시될 모든 그림을 더하면 곧 화면에서 무제한으로 스크롤하고 전환할 수 있습니다.
닮다ListView
GridView 이 구성 요소는 하위 항목이 보이지 않을 때, 프론트 데스크톱에서 하위 항목을 표시하기 위해 사용하는 메모리를 회수합니다.쓰레기 수거기도 이미 불러온 그림이 차지하는 메모리를 방출할 것이다. 만약 그것이 생명주기가 긴 대상이 아니라고 가정한다면.이러한 메모리의 효과적인 이용은 모두 좋지만, UI를 원활하게 실행하려면, 표시될 때마다 그림을 다시 불러오지 말아야 한다.이 때 메모리와 파일 캐시를 유지하는 것이 필요합니다. 이렇게 하면 그림을 신속하게 다시 불러올 수 있습니다.
메모리 캐시 사용
메모리 캐시는 응용 프로그램의 약간의 메모리를 미리 소모하여 데이터를 저장하여 응용 중의 구성 요소에 신속하게 데이터를 제공할 수 있도록 하는 전형적인 공간으로 시간을 바꾸는 전략이다.LruCache
클래스(Android v4 Support Library 클래스 라이브러리에서 사용 가능)는 그림 캐시 작업에 적합합니다.LinkedHashMap
가장 최근에 사용한 대상을 저장하는 데 사용되며, 저장된 대상이 사용한 메모리의 총계가 설계된 최대 메모리를 초과할 때, 자주 사용하지 않는 대상 구성원을 쓰레기 수거기에서 꺼내서 쓰레기수거하도록 합니다.
Note: 이전에 매우 유행했던 메모리 캐시의 실현은 사용SoftReferenceorWeakReference였지만, 이런 방법은 현재 추천하지 않습니다.안드로이드 2.3부터 스팸 수거기는 소프트 인용과 약한 인용의 대상을 더욱 적극적으로 회수하기 때문에 이런 방법은 상당히 무효다.또한 안드로이드 3.0 이전에 그림 데이터는 로컬 메모리에 저장되었는데 예견할 수 있는 방식으로 방출되지 않았기 때문에 응용 메모리의 소모량이 짧은 제한을 초과하여 응용 프로그램이 붕괴될 수 있다
    
위하다
  LruCache 적당한 메모리 크기를 설정하려면 다음과 같은 여러 가지 요소를 고려해야 한다.
          
           
1. 당신에게 줄 activity와/또는 응용 프로그램의 메모리가 얼마나 남았는지
2. 화면에 한꺼번에 몇 장의 그림을 표시해야 하는지, 얼마나 많은 그림이 화면에 표시되기를 기다려야 하는지
3. 휴대전화의 크기와 밀도가 얼마나 되는지, 초고밀도 스크린 장치(갤럭시 넥서스
) 더 큰 캐시가 필요한 경우가 많음
4. 그림의 크기와 배치가 얼마나 되는지 각 그림이 차지하는 메모리의 크기를 결정한다
5. 그림의 방문 빈도가 얼마나 되는지, 다른 방문보다 더 빈번한지 여부.만약 그렇다면 메모리에 그 몇 가지를 계속 저장해야 할지도 모르지만,
심지어 서로 다른 용도의 그림 그룹을 위해 다른 설정을 해야 한다LruCache
대상
6. 어떤 때는 그림의 질과 수량을 균형 있게 하고 대량의 저질의 그림을 저장해야 한다. 그리고 임시로 고품질의 그림 버전을 불러오는 것은 매우 유용하다.
     
여기에 모든 응용에 적용되는 고정된 크기나 공식이 없다. 응용 그림의 사용 상황을 상세하게 분석하여 적당한 해결 방법을 찾아야 한다.
하나의 설정
  LruCache의 작은 예
private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

Note: 이 작은 예시 프로그램에서 LruCache에 캐시 크기를 설정합니다. 응용 프로그램의 최대 메모리 사용량의 8분의 1입니다. 중밀도 또는 고밀도 장치에서 캐시 크기는 최소 4M(32/8)입니다. GridView의 경우 GridView가 화면에 그림을 가득 채우면 1.5M(800*480*4bytes) 메모리가 소모됩니다.그래서 우리는 적어도 2.5페이지의 그림 데이터를 캐시할 수 있다
ImageView에 그림을 불러올 때, 먼저 LruCache에서 캐시된 그림이 있는지 확인하고, 있으면 ImageView에 직접 업데이트합니다. 없으면 백엔드 라인이 터치되어 이 그림을 불러옵니다.
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);
    }
}

 
이미지가 로드된 Task에서는 로드된 이미지를 메모리 캐시에 추가해야 합니다.
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 메인 라인 밖의 라인에서 읽어야 한다.디스크 파일 캐시도 분명히 공간을 바꾸는 정책이다.
Note: 만약에 그림이 매우 빈번하게 사용된다면 ContentProvider 캐시 그림을 저장하는 데 더 적합할 수 있다. 예를 들어 그림갤러리 응용 프로그램 등이다.
다음은 디스크 파일 캐시를 사용하는 프로그램 세션입니다
private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
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
    ...
    // Initialize disk cache on background thread
    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
    new InitDiskCacheTask().execute(cacheDir);
    ...
}

class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
    @Override
    protected Void doInBackground(File... params) {
        synchronized (mDiskCacheLock) {
            File cacheDir = params[0];
            mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
            mDiskCacheStarting = false; // Finished initialization
            mDiskCacheLock.notifyAll(); // Wake any waiting threads
        }
        return null;
    }
}

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(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
    synchronized (mDiskCacheLock) {
        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
            mDiskLruCache.put(key, bitmap);
        }
    }
}

public Bitmap getBitmapFromDiskCache(String key) {
    synchronized (mDiskCacheLock) {
        // Wait while disk cache is started from background thread
        while (mDiskCacheStarting) {
            try {
                mDiskCacheLock.wait();
            } catch (InterruptedException e) {}
        }
        if (mDiskLruCache != null) {
            return mDiskLruCache.get(key);
        }
    }
    return null;
}

// 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 getDiskCacheDir(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.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                    !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                            context.getCacheDir().getPath();

    return new File(cachePath + File.separator + uniqueName);
}

Note: 디스크 캐시를 초기화하는 데도 디스크 작업이 필요하기 때문에 UI 메인 라인에서 이 작업을 할 수 없습니다. 이것은 디스크 캐시를 초기화하기 전에 접근할 수 있음을 의미합니다.이러한 상황을 피하기 위해서, 위의 프로그램 세션에서 잠금 대상은 디스크 캐시가 초기화되지 않았을 때, 디스크 캐시에 접근할 수 없도록 확보했다
 
메모리 캐시는 UI 스레드에서, 디스크 캐시는 UI 메인 스레드 밖의 스레드에서, 그림 처리가 끝난 후에 각각 메모리 캐시와 디스크 캐시에 저장됩니다.
장치 구성 변경 처리
실행할 때 장치의 설정 파라미터가 바뀔 수 있습니다. 예를 들어 장치의 방향이 바뀌면 안드로이드가 Activity를 없애고 새 설정에 따라 다시 시작할 수 있습니다. 이 경우 모든 그림을 다시 불러오는 것을 피하고 사용자가 원활한 체험을 할 수 있도록 할 수 있습니다.
다행히도, 지난 절에서 당신은 그림에 아주 좋은 메모리 캐시를 제공했다.Fragment를 사용하면 메모리 캐시 대상을 새로운 activity 실례에 전달할 수 있습니다. 호출setRetainInstance(true)
) 방법으로 Fragment 인스턴스를 유지합니다.activity가 다시 만들어진 후에 보존된 Fragment는 activity에 따라 존재합니다. Fragment를 통해 이미 존재하는 메모리 캐시 대상을 얻을 수 있습니다. 그러면 그림을 신속하게 얻고 ImageView에 설정하여 사용자에게 유창한 체험을 할 수 있습니다.
다음은 예시 프로그램 세션이다
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);
        //   Fragment Activity          
        setRetainInstance(true);
    }
}

장치의 방향을 바꾸어 위의 프로그램을 테스트할 수 있다. 기본적으로 지연이 없고, 그림이 곧 화면에 표시될 것이다.   
          

좋은 웹페이지 즐겨찾기