Android의 그림 처리(캐시, 크기, 최적화 등)

20377 단어
사용자 인터페이스에 비트맵을 불러오는 것은 간단하지만, 더 큰 그림을 바로 불러올 필요가 있다면 훨씬 복잡할 것이다.많은 경우(예를 들어 어떤 구성 요소들은ListView,GridView 및ViewPager 등) 화면에 나타나는 그림의 총량은 화면에 곧 스크롤될 수 있는 그림을 포함하는데 실제로는 무한하다.화면을 제거할 하위 보기의 구성 요소를 회수하여 메모리 사용을 보존할 수 있습니다.대상의 인용을 장기간 유지하지 않으면 쓰레기 수집기도 불러온 비트맵 메모리를 방출합니다.그러나 유창하고 빠르게 불러오기 위해, 화면에 나타날 때마다 이 그림을 처리하는 UI를 반복해서 불러오는 것을 피하는 것이 가장 좋다.메모리와 디스크의 캐시가 보통 이 문제를 해결하고 구성 요소가 그림을 신속하게 다시 처리할 수 있도록 합니다.
1, 메모리 캐시 사용(LruCache)
귀중한 응용 프로그램의 메모리를 차지하는 상황에서 메모리 버퍼는 비트맵에 빠르게 접근할 수 있도록 한다.LruCache 클래스, 캐시 비트맵에 사용되는 작업에 특히 적합합니다. 최근에 인용된 대상은 강력한 인용 링크드 HashMap에 저장되고, 캐시가 지정한 크기를 초과하기 전에 최근에 사용하지 않은 대상의 메모리를 방출합니다.
주의: 과거에는 자주 사용했던 메모리 캐시가 SoftReference나 WeakReference의 비트맵 캐시였지만 지금은 사용하지 않습니다.android2.3(API급 9)부터 쓰레기 수거기는 소프트/약한 인용을 회수하는 데 더욱 신경을 썼기 때문에 상기 인용을 사용하면 어느 정도 무효가 되었다.그 밖에 이전의android3.0(API 레벨 11) 비트맵의 백업 데이터는 예측 가능한 상황에서 방출되지 않은 로컬 메모리에 저장되어 응용 프로그램의 메모리 넘침과 붕괴를 초래할 수 있다.
LruCache의 원본을 간단하게 살펴보면 내부는 링크드 HashMap을 강력하게 인용하고 LRU(Least Recently Used)는 LruCache에서 get이 방문을 받을 때마다 이 Value 값을 팀 머리로 이동하고put할 때 Value를 팀 끝으로 이동하는 것을 나타낸다.한 가지 설명은 LruCache라는 종류는 라인이 안전하고 다중 라인도 동시에 사용할 수 있다는 것이다. 구체적으로 LruCache의 원본 코드를 참고할 수 있다.
private LruCache 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(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);
 }

2, 디스크 캐시 사용(DiskLruCache)
메모리 버퍼는 최근에 훑어본 비트맵에 빠르게 접근하는 데 유용하지만, 캐시에서 그림을 사용할 수 있도록 제한할 수는 없습니다.GridView와 같은 더 큰 데이터 집합을 가진 구성 요소는 모든 메모리 캐시를 쉽게 차지할 수 있다.응용 프로그램은 전화 등 다른 작업에 의해 끊기고, 백엔드에서 실행될 때 프로세스에 의해 죽고, 메모리 캐시가 회수됩니다.일단 사용자가 다시 열면, 응용 프로그램은 모든 그림을 다시 처리해야 한다.이 경우 디스크 캐시를 사용하여 비트맵을 지속적으로 처리할 수 있으며, 이미지가 메모리 캐시에서 더 이상 사용할 수 없을 때 불러오는 시간을 단축하는 데 도움이 된다.물론 디스크에서 그림을 가져오는 것은 메모리에서 불러오는 것보다 느리고 백엔드 라인에서 처리해야 한다. 왜냐하면 디스크가 읽는 시간은 예측할 수 없기 때문이다.이런 상황에서 DiskLruCache로 실현할 수 있다.DiskLruCache의 소스 코드는 Android 4.0 소스 코드(libcore/luni/src/main/java/libcore/io/DiskLruCache.java).주의: 만약 그것들이 더욱 빈번하게 방문된다면, 컨텐트 Provider는 캐시에 있는 그림을 저장하기에 더 적합한 곳일 수도 있습니다. 예를 들어 그림 라이브러리 프로그램에 있는 것입니다.
DiskLruCache의 사용은 제가 더 이상 말하지 않겠습니다. csdn 곽대협은 이미 상세하게 말했습니다. 제가 여기에 인용해 보겠습니다.
http://blog.csdn.net/guolin_blog/article/details/28863651
주의점: 제가 사용할 때 flush 방법을Activity의 onPause 방법에 넣었기 때문에 어떤 때는 파일을 다운로드했지만 조작 로그는 로그 파일에 저장되지 않았습니다. 이럴 때는 편집기의commit 방법을 직접 넣은 후에 개인의 사용 상황을 보고 결정할 수 있습니다.
간단하게 다음 코드를 보았습니다. Disk Lru Cache와 Lru Cache는 매우 비슷합니다. 내부는 모두 Linked Hash Map으로 이루어졌고 병렬 탱크도 사용되었습니다. 주로journal 파일을 쓰는 데 사용됩니다.
/**
     * This cache uses a single background thread to evict entries.
     */
    private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
            60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

lru의 구현은 위의 기본과 같습니다. 단지 이것은 Entry로 저장되어 있습니다. get은 매번 new의 새로운 대상이 필요합니다. 대기열에 다시 삽입해야 합니다. 설명해야 할 것은 Journal 파일이 최대 2000줄이 있다는 것입니다. 그러나 당신은 스스로 고칠 수 있습니다.
/**
     * We only rebuild the journal when it will halve the size of the journal
     * and eliminate at least 2000 ops.
     */
    private boolean journalRebuildRequired() {
        final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
        return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
                && redundantOpCount >= lruEntries.size();
    }

 
3. 동시 처리
AsyncTask 클래스는 백엔드 스레드에서 많은 작업을 수행하고 UI 스레드에 결과를 피드백하는 간단한 방법을 제공합니다.여기서는 AsynTask 사용에 대해 설명하지 않지만 AsynTask에서 약한 참조를 사용하여 ImageView를 저장하는 방법을 설명합니다.
class BitmapWorkerTask extends AsyncTask {
    private final WeakReference imageViewReference;
    private int data = 0;
 
    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference(imageView);
    }
 
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }
 
    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
 }

ImageView에 대해 Weak Reference는 AsyncTask가 ImageView와 인용이 쓰레기 수거 기간에 회수되는 것을 방해하지 않도록 합니다.ImageView가 작업이 끝난 후에도 존재할 수 없습니다. 따라서 인용을 onPostExecute () 방법에서 확인해야 합니다.ImageView가 더 이상 존재하지 않을 수도 있습니다. 예를 들어, 작업이 완료되기 전에 사용자가 활동을 종료했거나 구성이 변경되었을 수도 있습니다.
흔히 볼 수 있는 보기 구성 요소인 ListView와 GridView는 AsyncTask와 결합하여 사용할 때 또 다른 문제를 일으켰다.메모리를 최적화하기 위해서, 사용자가 스크롤할 때 이 구성 요소들은 하위 보기를 회수합니다.모든 하위 보기가 AsyncTask를 터치하면, 완성될 때 보장할 수 없고, 관련 보기가 회수되지 않았을 때 다른 하위 보기에 사용됩니다.그 밖에 비동기적인 임무가 시작되는 순서는 그들이 완성하는 순서를 보장할 수 없다.따라서 ImageView는 작업이 끝난 후에 다음에 검사될 수 있는 AsyncTask에 인용을 저장하는 해결 방안을 제공합니다.유사한 방법을 사용하면 위의 AsyncTask에서 유사한 패턴을 따르도록 확장할 수 있습니다.인용 백업을 작업 작업에 저장할 전용 Drawable 하위 클래스를 만듭니다.이 경우 작업이 완료된 후 자리 표시자 이미지가 ImageView에 표시되도록 BitmapDrawable가 사용됩니다.
class AsyncDrawable extends BitmapDrawable {
    private final WeakReference bitmapWorkerTaskReference;
 
    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =
            new WeakReference(bitmapWorkerTask);
    }
 
    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
 }

이렇게 되면 위의 BitmapWorkerTask 클래스의 onPostExecute 방법도 개선됩니다.
class BitmapWorkerTask extends AsyncTask {
    ...
 
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }
 
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
 }

코드가 좀 생략되어 있어서 구체적인 데모는 아래에 놓을게요.
4. 런타임 구성 변경을 처리합니다.
프로그램이 실행될 때 설정이 바뀐다. 예를 들어 화면의 방향이 바뀌어 시스템이 소각되고 새로운 설정으로 다시 실행된다. 사용자가 설정이 바뀔 때 안정적이고 빠른 체험을 할 수 있도록 모든 그림을 다시 처리하는 것을 피해야 한다.다행히도, 메모리 캐시 1절을 사용하면, 당신은 자신이 만든 비트맵 메모리 버퍼를 가지고 있다.캐시는 새로운 활동 실례를 통해 Fragment를 사용할 수 있습니다. 이 Fragment는 setRetainInstance (true) 를 호출하는 방법으로 보존됩니다.활동이 재구성된 후 보존된 부분을 다시 연결하고 기존의 캐시 대상에 접근할 수 있는 권한을 얻으면 그림을 신속하게 불러오고 ImageView 대상에 다시 채울 수 있습니다.구체적으로 저의 또 다른 블로그에는 항상 안드로이드 개발 실전-입문편이 소개되어 있습니다.
private LruCache mMemoryCache;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    RetainFragment mRetainFragment =
            RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache = RetainFragment.mRetainedCache;
    if (mMemoryCache == null) {
        mMemoryCache = new LruCache(cacheSize) {
            ... // Initialize cache here as usual
        }
        mRetainFragment.mRetainedCache = mMemoryCache;
    }
    ...
}
 
class RetainFragment extends Fragment {
    private static final String TAG = "RetainFragment";
    public LruCache 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);
        setRetainInstance(true);
    }
}

이를 테스트하기 위해 Fragment를 사용하지 않은 상태에서 장치를 회전하거나 장치를 회전하지 않는 것을 시도합니다.캐시를 보존하는 경우, 그림이 액티브에 채워졌을 때 메모리에서 거의 바로 불러오기 때문에, 정체된 느낌이 거의 없다는 것을 알아야 한다.모든 그림은 메모리 캐시에서 찾고 없으면 디스크 캐시에서 찾으며, 없으면 항상 그림을 가져오는 것처럼 처리됩니다.
5. 이미지 크기 처리
현재 그림의 크기는 이미 알고 있습니다. 전체 그림을 메모리에 불러올지, 축소된 버전으로 불러올지 결정할 수 있습니다.다음은 고려할 만한 요소들입니다. 완전한 이미지를 불러오는 데 필요한 메모리를 추정합니다.이 그림을 불러오는 데 필요한 공간이 프로그램에 필요한 다른 메모리 요구 사항을 약속합니다.이미지를 로드할 대상 ImageView 또는 UI 구성 요소 크기 준비현재 장치의 화면 크기와 밀도;예를 들어, 1024*768 픽셀의 이미지가 최종적으로 128*96 픽셀의 ImageView에 축소 표시되면 메모리에 로드할 가치가 없습니다.이 그림을 다시 샘플링해서 메모리에 더 작은 버전을 불러오라고 디코더에 알려 주십시오. 비트맵 팩토리에 있습니다.Option 객체에서 inSampleSize를 true로 설정합니다.예를 들어 해상도가 2048*1536인 이미지를 inSampleSize 값으로 4로 인코딩하면 약 512*384 크기의 비트맵이 생성됩니다.전체 12MB 크기의 이미지가 아닌 0.75MB 크기의 이미지를 메모리에 로드합니다(ARGB 8888 비트맵 구성을 사용하는 경우).여기에는 목표의 너비와 높이를 토대로 SampleSize의 값을 계산하는 방법이 있다.
public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth) {
            if (width > height) {
                inSampleSize = Math.round((float) height / (float) reqHeight);
            } else {
                inSampleSize = Math.round((float) width / (float) reqWidth);
            }
        }
        return  inSampleSize;
    }

NOTE: 2의 멱을 사용하여 inSampleSize의 값을 설정하면 디코더가 더욱 빠르고 효과적입니다.그러나 메모리나 하드디스크에 그림 조정 버전을 저장하려면 공간을 절약하기 위해 적당한 크기로 디코딩하는 것이 좋다.
이런 방법을 사용하려면 먼저 디코딩을 하고 inJust Decode Bounds를true로 설정하여 옵션을 전달한 다음 다시 디코딩하고 새로운 inSample Size 값을 사용하고 inJust Decode Bounds를false로 설정합니다.
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
                                                         int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);
        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

demo 다운로드 주소:https://github.com/xuwt/DisplayBitmap
참조:http://wiki.eoeandroid.com/Android_Training

좋은 웹페이지 즐겨찾기