태그 보관물: Cache

안드로이드(Android) 앱에서 메모리 캐시 사이즈를 디바이스별로 동적으로 조절하기

안드로이드(Android)에서 많은 썸네일(Thumbnail)을 처리하기 위해서 많은 앱들이 1차 메모리, 2차 디스크 캐시를 사용하고 있다. 여기에서 2차 디스크는 용량이 충분하기 때문에 별 이슈가 없다. 하지만, 메모리 캐시의 경우에는 디바이스별로 다르기 때문에 보통은 작은 사이즈로 지정해서 사용하게 된다. 지금 출시되는 안드로이드 디바이스의 앱 메모리 설정을 보면, 이전보다 매우 크게 사용할 수 있는 것을 알수가 있다.

그래서, 메모리 캐시를 더 크게 사용해서, UX의 반응을 더 높이는 방법을 살펴보자. 기본적으로, http://www.sjava.net/332 를 보면,  안드로이드 OS의 설정정보를 읽어서, 디바이스의 정보를 알수가 있겠다.

앱마다 사용하는 썸네일 사이즈에 따라, 메모리를 점유하는 크기가 달라지기 때문에 이 내용은 앱에서 처리하는 썸네일의 크기에 따라 달라진다.

우선, 썸네일이 점유하는 메모리의 크기를 살펴보면..

100 x 100의 썸네일인 경우, 100x100x4의 크기이니 40,000 byte의 메모리를 점유한다고 보면 되겠다.  그래서, 대략적으로 캐시 사이즈에 따른 메모리 사용량을 가늠해 보면..

100×100 썸네일을 메모리에 100개 올리게 되면, 4M 정도의 메모리를 점유하게 될 것이고.. 이 정보를 기준으로 캐시 사이즈를 정하면 되겠다..

위의, 정보를 기준으로 해서 디바이스가 허용하는 앱의 힙 정보를 살펴보면..

* 갤럭시 넥서스

07-24 11:51:04.377: E/CacheUtil(27521): dalvik.vm.heapstartsize : 8m
07-24 11:51:04.377: E/CacheUtil(27521): dalvik.vm.heapgrowthlimit : 96m <– 최대 힙 사이즈를 기준으로 캐시의 사이즈를 조절한다.
07-24 11:51:04.377: E/CacheUtil(27521): dalvik.vm.heapsize : 256m <– 전체 힙 사이즈

* 넥서스 10

07-24 12:02:46.003: E/CacheUtil(12619): dalvik.vm.heapstartsize : 16m
07-24 12:02:46.008: E/CacheUtil(12619): dalvik.vm.heapgrowthlimit : 192m <– 최대 힙 사이즈를 기준으로 캐시의 사이즈를 조절한다.
07-24 12:02:46.008: E/CacheUtil(12619): dalvik.vm.heapsize : 512m <– 전체 힙 사이즈

위의 정보를 바탕으로 캐시클래스를 만들어 보면..

import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.graphics.Bitmap;

public class CacheUtil {
	static final String TAG = CacheUtil.class.getSimpleName();
	static int THUMBNAIL_CACHE_COUNT = 100;
	static boolean DEBUG = true;

	static{
		if(DEBUG) {
			Logger.e(TAG, "dalvik.vm.heapstartsize : "  + get("dalvik.vm.heapstartsize"));
			Logger.e(TAG, "dalvik.vm.heapgrowthlimit : "  + get("dalvik.vm.heapgrowthlimit"));
			Logger.e(TAG, "dalvik.vm.heapsize : "  + get("dalvik.vm.heapsize"));
			Logger.e(TAG, "cache_size : "  + getDynamicCacheSize());
		}
		
		THUMBNAIL_CACHE_COUNT = getDynamicCacheSize();
	}
		
	static LruCache<String, Bitmap> listCache = new LruCache<String, Bitmap>(THUMBNAIL_CACHE_COUNT);
	public static LruCache<String, Bitmap> listCache() {
		if(listCache == null)
			listCache = new LruCache<String, Bitmap>(THUMBNAIL_CACHE_COUNT);
		
		return listCache;
	}
	
	static LruCache<String, Bitmap> gridCache = new LruCache<String, Bitmap>(THUMBNAIL_CACHE_COUNT);
	public static LruCache<String, Bitmap> gridCache() {
		if(gridCache == null)
			gridCache = new LruCache<String, Bitmap>(THUMBNAIL_CACHE_COUNT);
		
		return gridCache;
	}

	
	static int getDynamicCacheSize() {
		String value = get("dalvik.vm.heapgrowthlimit");
		if(StringUtil.isEmpty(value))
			return THUMBNAIL_CACHE_COUNT;
		
		// M가 단위로 가져온다.. 
		int size = extractInt(value);
		if(size <= 64)
			return 100;
		
		if(size <= 128)
			return 200;
		
		return 300;
	}
	
	private static String get(String key) {
		try {
			Class clazz = Class.forName("android.os.SystemProperties");
			if (clazz == null)
				return "";

			Method method = clazz.getDeclaredMethod("get", String.class);
			if (method == null)
				return "";

			return (String) method.invoke(null, key);
		} catch (Exception e) {
			if (DEBUG)
				Logger.e(TAG, ExceptionUtil.exception(e));
		}

		return "";
	}
	
	private static int extractInt(String str) {
        Matcher matcher = Pattern.compile("\\d+").matcher(str);
        if (!matcher.find())
            throw new NumberFormatException("For input string [" + str + "]");

        return Integer.parseInt(matcher.group());
    }
}

위 클래스를 이용해서, 디바이스에 설정되어 있는 메모리 크기의 정보를 기준으로 메모리 캐시의 크기를 동적으로 설정할 수가 있겠다.

BufferedWriter를 이용한 가장 좋은 성능 방안에 대해서..

파일에 로그를 남긴다거나 데이타를 쓰기위해서 java.io.File 객체를 써도 되지만, 성능을 위해서 BufferedWriter를 많이 이용하게 됩니다. 특히 로그 라이브러리나 로깅 유틸리티 클래스에서 거의 대부분 사용할 것으로 생각이 드네요..
그래서, 생각한 방법이 BufferedWriter를 캐싱하는 것입니다.

아래 코드의 shutdown() 메쏘드는 ShudownHooking으로 등록된 쓰레드가 프로세스가 종료되면 호출을 해서 버퍼에 남아있는 내용을 기록하도록 되어 있습니다.

BufferedWriterCacheUtility.java

package net.sjava.io.test;

import java.util.Map;
import java.util.LinkedHashMap;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.util.Iterator;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BufferedWriterCacheUtility {
   
    /** max cache size */
    private static final int size = 10;
    /** lock */
    private static Lock lock = new ReentrantLock();
   
    /** initialized lru cache */
    private static Map<String, BufferedWriter> cache = new LinkedHashMap<String, BufferedWriter> (size, .75F, true) {
        private static final long serialVersionUID = 1L;
        protected boolean removeEldestEntry(Map.Entry<String, BufferedWriter> eldest) {
            if(size() > size) {
                try {
                    // flush and close before deleted
                    eldest.getValue().close();
                } catch(java.io.IOException e) {
                    // ignore
                }
            }
           
            return size() > size;  
        }
    };

   
    /**
     *
     * @param fileName
     * @return
     */
    public static BufferedWriter createBufferedWriter(String fileName) {
        lock.lock();
        try{
            if(cache.containsKey(fileName))
                return cache.get(fileName);
       
            return new BufferedWriter(new FileWriter(fileName, true), 1024);
        } catch(java.io.IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            lock.unlock();
        }
    }
   
    /**
     *
     * @param fileName
     * @param writer
     */
    public static void close(String fileName, BufferedWriter writer) {
        lock.lock();
        try {
            cache.put(fileName, writer);
        } finally {
            lock.unlock();
        }
    }
   
    /** shutdown hooking thread call */
    public static void shutdown() {
        // 아래의 코드를 통해서 종료가 되더라도 다 flush 하고 종료한다.     
        Iterator<BufferedWriter> iter = null;
        try {
            iter = cache.values().iterator();
            while (iter.hasNext()){
                iter.next().close();
                //iter.next().close();
            }
           
        }catch(java.io.IOException e) {
            e.printStackTrace();
        }
    }
}


아래 코드는 BufferedWriter를 사용하는 방법에 대한 3가지의 예입니다.
물론 2번째(bwriter02)를 사용하는 예가 가장 빠르겠지만, 3번째 예는 여러개의 fileName을 가지고 file write를 하기에 가장 좋은 방법으로 생각이 듭니다.

BufferedWriterTest.java

package net.sjava.io.test;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

public class BufferedWriterTest {

    private static SimpleDateFormat format = new SimpleDateFormat(“yyyy.MM.dd HH:mm:ss SSS”);

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //load();
               
        System.out.println(“s – ” + format.format(new Date()));
       
        BufferedWriter bwriter01 = null;
        try {
            for(int i=0; i < 100000; i++) {
                bwriter01 = new BufferedWriter(new FileWriter(“c:\\test01.txt”, true), 1024);
                bwriter01.write(“aaaaaaaaaaaaccccccccccccccccccaaa”);
                bwriter01.newLine();
                bwriter01.flush();
                bwriter01.close();
            }
        } catch(java.io.IOException e) {
            e.printStackTrace();
        }
        System.out.println(“e – ” + format.format(new Date()));
        
        System.out.println(“s – ” + format.format(new Date()));
        BufferedWriter bwriter02 = null;
        try {
            bwriter02 = new BufferedWriter(new FileWriter(“c:\\test02.txt”, true), 1024);
            for(int i=0; i < 100000; i++) {
                bwriter02.write(“aaaaaaaaaaaaccccccccccccccccccaaa”);
                bwriter02.newLine();
            }
            bwriter02.flush();
            bwriter02.close();
        } catch(java.io.IOException e) {
            e.printStackTrace();
        }
        System.out.println(“e – ” + format.format(new Date()));        
        
        // 날짜별 혹은 서비스별로 로깅을 해야 될경우
        System.out.println(“s – ” + format.format(new Date()));
        BufferedWriter bwriter03 = null;
        try {
            for(int i=0; i < 100000; i++) {
                bwriter03 = BufferedWriterCacheUtility.createBufferedWriter(“c:\\test03.txt”);   
                bwriter03.write(“aaaaaaaaaaaaccccccccccccccccccaaa”);
                bwriter03.newLine();
                BufferedWriterCacheUtility.close(“c:\\test03.txt”, bwriter03);
            }
        } catch(java.io.IOException e) {
            e.printStackTrace();
        }
       
        System.out.println(“e – ” + format.format(new Date()));
    }

}


여담이지만, java.io.BufferedWriter 소스를 보면, write시에 lock을 잡고 씁니다.
아래와 같은 코드를 많이 사용하지만요..

lock.lock();
try {
 bWriter.write(“000000000000000”);
}catch(java.io.IOException e) {
  e.printStackTrace();
} finally {
  lock.unlock();
}

위 코드보다는 아래코드를 사용하는게 맞겠습니다.

try {
 bWriter.write(“000000000000000”);
}catch(java.io.IOException e) {
  e.printStackTrace();
}