Android에서 Bitmap 관련 작업을 할때 항상 사용하는것이 Bitmap 클래스와 BitmapFactory 클래스이다.

BitmapFactory 클래스는 decode 메서드를 사용하여 File, Stream 등을 입력받아 Bitmap으로 변환할 수 있다.
Bitmap 클래스는 Bitmap의 재가공, Bitmap의 구성을 변경한다던지, 크기를 변경하는 작업을 수행한다.

그런데 현재 Android 상에서 위 2개는 심각한 메모리 누수를 유발하고 있다.

단순 SD 카드에서 파일을 읽어 와 표시해주는 것이라면 관계가 없지만 

작업 1. MediaStore를 사용하여 이미지를 가지고 온 후 크기를 변경하고 이를 화면에 표시함과 동시에 서버에 업로드 한다.

위 작업이 한번이 아닌 여러번 수행 되어야 한다면..? 3번? 많게는 5번 이내로 오류나서 어플이 종료가 된다.;;

오류를 발생한 작업 

		Bitmap resized = null;
		Bitmap orgBitmap = null;
		File targetFile = null;
		int[] size = null;

		// 이미지 품질 옵션
		options.inTempStorage = new byte[16*1024];
		options.inSampleSize = 1;

		switch(requestCode){
			case CommonResource.SHOW_ALBUM:
				photoUri = data.getData();
				
				// 갤러리에서 선택했을때 실제 경로 가져오기 위해 커서
				String [] proj={MediaStore.Images.Media.DATA};  
			    Cursor cursor = managedQuery( photoUri,  
			            proj, // Which columns to return  
			            null,       // WHERE clause; which rows to return (all rows)  
			            null,       // WHERE clause selection arguments (none)  
			            null); // Order-by clause (ascending by name)  
			    int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);  
			    cursor.moveToFirst();  
			  
			    photoUri = Uri.parse(cursor.getString(column_index));
			    // 비트맵을 먼저 만들고
			    orgBitmap = BitmapFactory.decodeFile(photoUri.getPath(), options);
			    // 원본 사이즈에 따른 새로운 사이즈 계산
			    size = commonUtil.getBitmapSize(orgBitmap);
			    // 비트맵 생성
			    displayBitmap = Bitmap.createScaledBitmap(orgBitmap, size[0], size[1], true);
			    
			    // 디바이스에 맞게 비트맵 생성 후 업로드 하여야 하므로 임시 파일을 생성해준다.
			    // MediaStore에는 원본이, 아래는 복사본을 생성하여 업로드 하는 것.
			    try {
					File targetFolder = new File(targetPath);
					if(!targetFolder.exists()){
						targetFolder.mkdirs();
						targetFolder.createNewFile();
					}
					if(targetFile == null)
						targetFile = new File(targetFolder, UUID.randomUUID().toString());
					
					FileOutputStream out = new FileOutputStream(targetFile);
					displayBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); 
					out.flush();
					out.close();
					
					uploadImagePath = targetFile.getAbsolutePath();
					
				} catch (Exception e) { 
				       e.printStackTrace(); 
				} finally{
					targetFile = null;
					uploadImage.setImageBitmap(commonUtil.getRoundedCornerBitmap(this, displayBitmap, 10));
				}
			    break;
			}

위 소스에서 orgBitmap에 앨범에서 사진을 하나 불러온다. 이 사진은 사이즈가 허벌나다. (orgBitmap)
그리고 이 사진을 그대로 업로드 할 시 트래픽도 크고 시간도 오래 걸릴게 뻔하다 생각하여 
비율에 맞게 다시 크기를 조정하여 비트맵을 생성하게 했다. (displayBitmap)
그리고 업로드를 위한 임시파일을 생성하도록 코드가 이어지고
마지막으로 화면에 표시할때는 round 처리까지 한 후 보여지게 되어 있다.

그지같게도 비트맵을 생성한 후 메모리가 반환이 되지 않는다. 
절대... 쓸일 없는 Bitmap은 recycle()하면 된다지만 역시 일부다.

위 작업을 두번하니까 어플이 그냥 죽는다. 카메라로 촬영을 하든 앨범에서 가지고 오든 죽는다.

원인은 비트맵의 반복 생성. 
createScale을 사용하던 decode를 하던 한번 만드는 것도 메모리를 상당부분 사용하는데 이를 중복으로 사용하는게 문제라는 것.

그래서 아래와 같이 코드를 바꾸기로 했다.

		// 이미지 품질 옵션
		options.inJustDecodeBounds = true;
		
		switch(requestCode){
			case CommonResource.SHOW_ALBUM:
				photoUri = data.getData();
					
				// 갤러리에서 선택했을때 실제 경로 가져오기 위해 커서
				String [] proj={MediaStore.Images.Media.DATA};  
			    Cursor cursor = managedQuery( photoUri,  
			            proj, // Which columns to return  
			            null,       // WHERE clause; which rows to return (all rows)  
			            null,       // WHERE clause selection arguments (none)  
			            null); // Order-by clause (ascending by name)  
			    int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);  
			    cursor.moveToFirst();  
			  
			    photoUri = Uri.parse(cursor.getString(column_index));
			    uploadImagePath = photoUri.getEncodedPath();
			    displayBitmap = BitmapFactory.decodeFile(uploadImagePath, options);
			    
			    options = commonUtil.getBitmapSize(options);
			    displayBitmap = BitmapFactory.decodeFile(uploadImagePath, options);
				
			    uploadImage.setImageBitmap(displayBitmap);
				
			    break;
		}

// http://kfb-android.blogspot.com/2009/04/image-processing-in-android.html#viewSource
	public Options getBitmapSize(Options options){
		int targetWidth = 0;
		int targetHeight = 0;
		
		if(options.outWidth > options.outHeight){	
	    	targetWidth = (int)(600 * 1.3);
	    	targetHeight = 600;
	    }else{
	    	targetWidth = 600;
	    	targetHeight = (int)(600 * 1.3);
	    }

		Boolean scaleByHeight = Math.abs(options.outHeight - targetHeight) >= Math.abs(options.outWidth - targetWidth);
		if(options.outHeight * options.outWidth * 2 >= 16384){
		    double sampleSize = scaleByHeight
		        ? options.outHeight / targetHeight
		        : options.outWidth / targetWidth;
		    options.inSampleSize = (int) Math.pow(2d, Math.floor(Math.log(sampleSize)/Math.log(2d)));
		}
		options.inJustDecodeBounds = false;
		options.inTempStorage = new byte[16*1024];
		
		return options;
	}
Bitmap으로 생성하진 않고 먼저 decoding만 한 후 크기를 가져와 실제 맞추려는 크기와 비교하여 size 조절이 필요하면
사이즈를 조절하도록 option을 설정하면 된다.
구글링 하다 보니 누가 만들어 놓았더라. (출처는 소스에..)

아.. 맘 놓고 해결..
어제에 이어서 계속.....

먼저 이미지 캐싱을 하기전에 캐싱을 안하게되면 어떤 문제점이 발생되는지부터 알아야하기에 일반적인 GridView를 만들겠습니다.

새로운 프로젝트를 만듭니다.
(가급적이면 비슷하게 하면 더 쉽겠죠...)

API 8로 만드세요.. 잘 못 눌렀어요

프로젝트 생성후 AndroidManifest.xml을 아래와 같이 수정합니다.


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.yeory.gv"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" 
    			 android:label="@string/app_name"
    			 android:debuggable="true">
    			 
        <activity android:name=".ImageList"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-sdk android:minSdkVersion="8" />
</manifest> 
android:debuggable="true" -> Eclipse에서 디버그 모드를 가능하게 하는 명령줄입니다.android.permission.INTERNET -> App에서 Internet 접근을 허용하게 하는 권한 입니다.

대충 GridView의 초기 설정은 끝이 났습니다. 
Internet 권한을 준 이유는 당연한 것이겠지만 웹에서 이미지를 가져올 것이기에 그런 것이죠 - 
권한이 없다면 Permission Denied~~~ 에러 납니다.

res 폴더 내에 xml을 저장할 폴더를 만들고 파일을 만들겠습니다.
res 폴더는 App에서 사용할 자원(Resources)을 저장한다고 보시면 됩니다.


그리고 생성된 xml 폴더에 파일을 만듭니다. [ url.xml ] 
이 XML에는 웹에서 불러올 이미지 주소를 적어 줍니다.  

형식은 아래와 같습니다.

<images>
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image01.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image01.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image02.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image02.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image03.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image03.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image04.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image04.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image05.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image05.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image06.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image06.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image07.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image07.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image08.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image08.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image09.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image09.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image10.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image10.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image11.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image11.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image12.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image12.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image13.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image13.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image14.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image14.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image15.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image15.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image16.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image16.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image17.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image17.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image18.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image18.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image19.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image19.jpg" />
		   
	<image description="" uri="http://yeory.just4fun.co.kr/android/image/image20.jpg"
		   thumb="http://yeory.just4fun.co.kr/android/image/thumb/th_image20.jpg" />
</images>
20개 정도만 하겠습니다. 나중에 더 추가해야합니다.

이제 실제 GridView를 layout에 정의하겠습니다.

[ file : res/layout/main.xml]

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
	<GridView xmlns:android="http://schemas.android.com/apk/res/android" 
		android:id="@+id/gridView"
		android:layout_width="fill_parent"
		android:layout_height="400dp"
		android:numColumns="auto_fit"
		android:verticalSpacing="10dp"
		android:horizontalSpacing="10dp"
		android:columnWidth="90dp"
		android:fastScrollEnabled="true"
		android:smoothScrollbar="true"
		android:scrollingCache="true"
		android:stretchMode="columnWidth"
		android:gravity="center"
	/>
	<!-- 스크롤에 관련된 정보 보여줄 TextView -->
	<TextView xmlns:android="http://schemas.android.com/apk/res/android" 
		android:id="@+id/logText"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
	/>
</LinearLayout>

속성들은 대충 보셔도 알거라 생각하기에 그냥 넘깁니다. 
궁금한건 API 보시고 그래도 모르겠으면 구글링 하시고 그게 귀찮으면 댓글로...... ;;

이제 Activity에서 정의한 Layout을 불러와야 합니다.

[ file : ImageList.java ]
package com.yeory.gv;

import android.app.Activity;
import android.os.Bundle;
import android.widget.GridView;

/**
 * =============================================================================
/            프로젝트명 :   GridView_Example
/            화  일  명 :   ImageList.java
/            기      능 :   
/            인      수 :   
/            특이  사항 :
/-----------------------------------------------------------------------------
/                               변경 사항				                     
/-----------------------------------------------------------------------------
/    변경일자       	변경자(작성자)                 		변경 내역                 
/   ----------     	--------------------------       -------------------------
/   2010. 11. 10.       jYeory<jyeory@gmail.com>         	최 초 작 성                      
/==============================================================================
 */
public class ImageList extends Activity {    
	/** Called when the activity is first created. */    

	@Override    
	public void onCreate(Bundle savedInstanceState) {        
		super.onCreate(savedInstanceState);        
		setContentView(R.layout.main);                
		GridView gridView = (GridView) findViewById(R.id.gridView);    
	}
}
여기까지 한 것을 실행시켜 보도록 하죠..

먼저 왼쪽의 +를 눌러서 새로운 실행 환경을 설정해 주어야 합니다. 
눌러서 그림과 같이 적고 선택해 줍니다.

구글 가이드 라인은 휴대폰을 연결하여 작업하는 것을 권장합니다. 그러므로 Automatic이 아닌 Manual로 실행합시다.

실행 되겠죠?

XML을 읽어와 각 정보를 bean에 담아야 합니다.
그러므로 class를 하나 만들겠습니다.
<패키지 명 확인하세요>

[ file : Image.java ]
package com.yeory.gv.model;

/**
 * =============================================================================
/            프로젝트명 :   GridView_Example
/            화  일  명 :   Image.java
/            기      능 :   
/            인      수 :   
/            특이  사항 :
/-----------------------------------------------------------------------------
/                               변경 사항				                     
/-----------------------------------------------------------------------------
/    변경일자       	변경자(작성자)                 		변경 내역                 
/   ----------     	--------------------------       -------------------------
/   2010. 11. 10.      jYeory<jyeory@gmail.com>         		최 초 작 성                      
/==============================================================================
 */
public class Image {
	private String description;
	private String thumbNail;
	private String uri;
	
	public Image() {}
	
	public Image(String d, String u, String t) {
		this.description = d;
		this.uri = u;
		this.thumbNail = t;
	}
	
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	public String getUri() {
		return uri;
	}
	public void setUri(String uri) {
		this.uri = uri;
	}

	public void setThumbNail(String thumbNail) {
		this.thumbNail = thumbNail;
	}

	public String getThumbNail() {
		return thumbNail;
	}
}



이젠 xml로 정의한 내용(url)을 가져오도록 하겠습니다.
Activity가 실행되면 가장 먼저 해야할 작업입니다. 그리고 이를 가공하여 다음에 만들 Adapter에 넘겨줘야 하죠.

[ method : init() ]
	private ArrayList wholeImageList = new ArrayList();	// 전체 이미지 담을 것.
	private ArrayList imageList = new ArrayList();	// 부분 이미지 담을 것.
	private XmlPullParser xpp;
	final int size = 15;			// 한번에 보여줄 이미지 개수
	private int totalSize = 0;		// paging에 필요
	private int currentPage = 0;	// paging에 필요
	private int start = 0;			
	private int end = 0;
	
        /**
	 * XML을 읽어들여 전체 이미지를 ArrayList에 넣는다.
	 * 처음으로 호출 되기에 15개만 다른 list에 담아 캐싱. 
	 * @param page
	 * @param size
	 */
	private void init(final int page, final int size) {
		xpp = getResources().getXml(R.xml.url);
		try{
			while ( xpp.getEventType() != XmlPullParser.END_DOCUMENT ){  // 마지막 문서까지
				if ( xpp.getEventType() == XmlPullParser.START_TAG ){
					if ( xpp.getName().equals("image") ){
						wholeImageList.add(new Image(xpp.getAttributeValue(0), xpp.getAttributeValue(1), xpp.getAttributeValue(2)));
					}
				}
				xpp.next();
			}
			
			totalSize = wholeImageList.size();
			currentPage = page;

			start = (page - 1) * size; 
			end = (page*size);
			imageList.addAll(wholeImageList.subList(start, end));
			
		}catch(XmlPullParserException xppe){
			Log.i("error", "init()"+xppe);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
XML을 읽어 <image></image>의 내용을 wholeImageList에 add 합니다.
10개든 100개든 url만 저장하는거죠.
그리고 정해진 개수에 맞게 페이징 처리를 해서 실제 불러올 url만 따로 imageList에 저장하는 것이죠.
Adapter에 넘겨줄 list는 ImageList입니다.

이젠 Grid를 구성할 수 있게 ImageAdapter를 만들겠습니다.

GridView에서 작은 박스들로 그리드를 구성하여 리스팅하게 되는데 이때 사용하는 방법은 Adapter 방식을 사용합니다.
가이드 라인에서도 이미지 처리는 가능하다면 Adapter를 사용하라고 되어 있습니다. 
(참고로 지금 만들 ImageAdapter는 PHP의 Iterator class와 비슷합니다.)

패키지 명이 다릅니다. 확인하세요.


[ file : ImageAdapter.java ]
package com.yeory.gv.util;

import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
/**
 * =============================================================================
/            프로젝트명 :   GridView_Example
/            화  일  명 :   ImageAdapter.java
/            기      능 :   
/            인      수 :   
/            특이  사항 :
/-----------------------------------------------------------------------------
/                               변경 사항				                     
/-----------------------------------------------------------------------------
/    변경일자       	변경자(작성자)                 		변경 내역                 
/   ----------     	--------------------------       -------------------------
/   2010. 11. 10.      jYeory<jyeory@gmail.com>         		최 초 작 성                      
/==============================================================================
 */
public class ImageAdapter extends BaseAdapter{
	private final String LOG_TAG = "ImageAdapter";
	
	@Override
	public int getCount() {
		Log.i(LOG_TAG, "called getCount()");
		return 0;
	}

	@Override
	public Object getItem(int position) {
		Log.i(LOG_TAG, "called getItem()");
		return null;
	}

	@Override
	public long getItemId(int position) {
		Log.i(LOG_TAG, "called getItemId()");
		return 0;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		Log.i(LOG_TAG, "called getView()");
		return null;
	}

}
기본적인 ImageAdapter의 구조입니다. @Override 메서드는 모두 구현되어야만 합니다.
각 Log는 각 메서드의 호출 순서를 확인하기 위해서입니다만 본인은 다 까먹었습니다.. ㅋ

구글 데모에서는 Activity 내에 클래스를 만들어 사용을 했는데 전 그 방법을 좋아하지 않습니다.
가능하다면 위와 같이 외부 클래스로 작성 할것을 미리 알려드립니다.


기본적인 뼈를 만들었으니 살을 붙여야합니다.

[ file : ImageAdapter.java ]

package com.yeory.gv.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.DefaultHttpClient;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;

import com.yeory.gv.model.Image;
/**
 * =============================================================================
/            프로젝트명 :   GridView_Example
/            화  일  명 :   ImageAdapter.java
/            기      능 :   
/            인      수 :   
/            특이  사항 :
/-----------------------------------------------------------------------------
/                               변경 사항				                     
/-----------------------------------------------------------------------------
/    변경일자       	변경자(작성자)                 		변경 내역                 
/   ----------     	--------------------------       -------------------------
/   2010. 11. 10.      jYeory<jyeory@gmail.com>        		최 초 작 성                      
/==============================================================================
 */
public class ImageAdapter extends BaseAdapter{
	private final String LOG_TAG = "ImageAdapter";
	private Context mContext;
    private ImageView imageView;
    private ArrayList lists;
    private BitmapDrawable bd;
    
    public ImageAdapter(Context c) {
        mContext = c;
    }
    
    public ImageAdapter(Context c, ArrayList lists){
    	mContext = c;
    	this.lists = lists;
    }
	
	
	@Override
	public int getCount() {
		Log.i(LOG_TAG, "called getCount()");
		return lists.size();
	}

	@Override
	public Object getItem(int position) {
		Log.i(LOG_TAG, "called getItem()");
		return null;
	}

	@Override
	public long getItemId(int position) {
		Log.i(LOG_TAG, "called getItemId()");
		return 0;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		Log.i(LOG_TAG, "called getView()");
		if (convertView == null) {  // if it's not recycled, initialize some attributes.
    		imageView = new ImageView(mContext);
    		imageView.setLayoutParams(new GridView.LayoutParams(150, 150));
    		imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    		imageView.setPadding(4, 4, 4, 4);
    		
    		bd = downloadBitmap(lists.get(position).getThumbNail());
    		
            imageView.setImageBitmap(bd.getBitmap());
        } else {
            imageView = (ImageView) convertView;
        }
		
		return imageView;
	}
	
	private HttpClient client = new DefaultHttpClient();
	private HttpGet httpRequest = null; 
	private int statusCode;
	private HttpResponse response = null; 
	private BufferedHttpEntity bufHttpEntity = null; 
    private InputStream is = null;
	
	private BitmapDrawable downloadBitmap(String url) {
		Log.i("test", "url : "+url);
        // AndroidHttpClient is not allowed to be used from the main thread
        try {
        	httpRequest = new HttpGet(url); 
            response = client.execute(httpRequest);
            statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != HttpStatus.SC_OK) { 
                Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url); 
                return null;
            }
            
            bufHttpEntity = new BufferedHttpEntity(response.getEntity()); 
            if (bufHttpEntity != null) {
                try {
                	is = bufHttpEntity.getContent();
                    // return BitmapFactory.decodeStream(inputStream);
                    // Bug on slow connections, fixed in future release.
                    return new BitmapDrawable(BitmapFactory.decodeStream(is));
                } finally {
                    if (is != null) {
                    	is.close();
                    }
                    bufHttpEntity.consumeContent();
                }
            }
        } catch (IOException e) {
        	httpRequest.abort();
            Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
        } catch (IllegalStateException e) {
        	httpRequest.abort();
            Log.w(LOG_TAG, "Incorrect URL: " + url);
        } catch (Exception e) {
        	httpRequest.abort();
            Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
        }
        return null;
    }
}
ImageAdapter의 생성자를 통해 context와 url이 담겨져 있는 list를 받게 되어 있습니다.
이후 getView가 호출되면 순서에 해당하는 list의 url을 찾아 통신을 하게 되어 있죠.
이부분은 캐싱을 할 때 자세히 다루겠습니다...

글이 점점 길어집니다.. 하...

이렇게 만든 ImageAdpater를 Activity에 등록해주어야 합니다.

[ method : onCreate() ]

	private GridView gridView;
	private TextView textView;
	private ImageAdapter adapter; 
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        init(1, size);
        
        textView = (TextView) findViewById(R.id.logText);
        gridView = (GridView) findViewById(R.id.gridView);
        adapter = new ImageAdapter(this, imageList);
        
        gridView.setAdapter(adapter);
    }

이제 실행하여야하는데 와이파이를 이용하길 권장합니다.
실행시 그리드로 구성되고 스크롤도 가능합니다.

하지만 문제가 발생합니다.

1. 스크롤 시 보여지는 thumbNail이 변경됩니다.
GrdiView의 문제점이라고 할 수 있는데 ImageView가 Activity 밖으로 벗어나게 되면 해당 ImageView는 소멸(이라 쓰고 잠수라고 읽습니다..) 됩니다. (screen out)
그리고 다시 Activity안으로 ImageView가 다시 보여지게 된다면 재사용(recycled)이 가능해져 기존 사용한 ImageView를 불러오는데 이때 이미지가 변경된다고 짐작하고 있습니다...
(다 변경되는건 아니고 Activity 밖으로 스크롤된 이미지만!!)
2. 스크롤 시 다음 페이지를 가지고 와야한다.


여기까지 입니다.

풀 소스를 첨부합니다.

아... 힘들다.. 



 2011. 11. 02일 수정

이전에 적었던 1번 문제가 해결되었습니다.
아마 API가 업데이트 되면서 해결된거 같은데 기록을 뒤지자니 끝도 없어 포기;

현재 위 예제는 HTC 넥서스 원, HTC 레이더 LTE 기종에서 실행되는걸 확인 했습니다. 


  1. 이현태 2011.01.20 09:58 신고

    아..좋은내용 감사합니다!^^

    한참을 찾아헤매다 님의 블로그에서 찾게 되었네요.

    아직 실력이 부족해서..매일 구글링으로 자료를 찾아 다니고만 있네요.

    그래도 즐거운 마음으로 안드로이드 공부중입니다^^ 자료 감사합니다~

    캐싱부분도 기대하고 있겠습니다^^

  2. yap 2011.10.26 14:56 신고

    위에 써주신 문제점을 확인해보고 싶은데...
    다운로드 받아서 import한거 실행시켜보니까 프로그램이 죽어버리네요ㅜ.ㅜ
    와이파이도 연결 잘 돼있는데... 왜 그런지 아시나요?ㅎㅎ

- 편의상 편하게 쓸랍니다 -

안드로이드에서 제공하는 모든 이미지 관련 API는 기기내의 MediaStorage를 기반으로 한다. 

ThumbNail을 생성해주는 Images.Thumbnails.getThumbnail 메서드 또한 기기내의 파일을 위한 것이다.

구글에서 제공하는 Demo에서 사용되는 것 역시 기기(이하 로컬)내의 파일이다.

Activity에 GridView를 불러들이고 gridView에 ImageAdapter를 사용하여 그리드를 구현한다는 것인데 - 
(Adapater 사용은 구글에서 권고하는 가이드 라인에 포함되어 있는 코딩 방법이다)

Demo에 나와있는 아주 표준적인 ImageAdapter를 먼저 살펴보자...


public class ImageAdapter extends BaseAdapter {
    private Context mContext;

    public ImageAdapter(Context c) {
        mContext = c;
    }

    public int getCount() {
        return mThumbIds.length;
    }

    public Object getItem(int position) {
        return null;
    }

    public long getItemId(int position) {
        return 0;
    }

    // create a new ImageView for each item referenced by the Adapter
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView imageView;
        if (convertView == null) {  // if it's not recycled, initialize some attributes
            imageView = new ImageView(mContext);
            imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            imageView.setPadding(8, 8, 8, 8);
        } else {
            imageView = (ImageView) convertView;
        }

        imageView.setImageResource(mThumbIds[position]);
        return imageView;
    }

    // references to our images
    private Integer[] mThumbIds = {
            R.drawable.sample_2, R.drawable.sample_3,
            R.drawable.sample_4, R.drawable.sample_5,
            R.drawable.sample_6, R.drawable.sample_7,
            R.drawable.sample_0, R.drawable.sample_1,
            R.drawable.sample_2, R.drawable.sample_3,
            R.drawable.sample_4, R.drawable.sample_5,
            R.drawable.sample_6, R.drawable.sample_7,
            R.drawable.sample_0, R.drawable.sample_1,
            R.drawable.sample_2, R.drawable.sample_3,
            R.drawable.sample_4, R.drawable.sample_5,
            R.drawable.sample_6, R.drawable.sample_7
    };
}

간단한 구조로 되어있다. 또한 이미지를 res/drawable 폴더에 다 넣어서 직접 가져다 쓰고 있다.
이건 로컬의 이미지를 구현하기 위한 방법이므로 아주 적당하다고 할 수 있다. 
(하지만 100장이 넘는 이미지라면..? ID 다 적어줄 건가..? 당신은 어찌할 것인가??)

우선 그리드를 구성하는 하나의 이미지는 ImageView로 구성이 된다.
그리고 이 ImageView는 getView()에서 생성이 되는데 ImageView의 핵심인 이미지 파일의 정보 또한 getView()에서 처리되고 있다.

ImageAdapter를 사용하는 방법은 다음과 같다.

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    GridView gridview = (GridView) findViewById(R.id.gridview);
    gridview.setAdapter(new ImageAdapter(this));
}



기본적인 Demo에 관련된 자세한 사항은 이 글보다는 아래 글을 참고하길 바란다.


로컬의 이미지들을 처리할 것이라면 정말 이보다 좋은 방법은 있을 수 없다.
또한 이미 생성된 ImageView는 재사용(recycled)이 가능하기 때문에 자원(resource) 관리에도 도움이 된다.

로컬에서의 이미지 처리시 생각해야할 부분은 이미지 정보들을 어떻게 가져올 것인가에 대한 것이다.

가장 극단적인 예로 SD 카드의 이미지를 처리할려고 하면 정말 코드 몇줄이면 끝나지만 - 
자신이 만들고 있는 어플에서 쓸 이미지를 부를거라면? 
근데 그 이미지들이 1~2장이 아니라 100장이 된다면?  언제 R.id.xx 해서 100개 다 찍고 있을건가.?

즉 GridView를 이용하여 웹(또는 로컬)에서 이미지를 가져와 작업을 하려면 위 사항을 최소한으로 생각해보아야 한다.





계속 쭉쭉 이어 적을려고 했으나 퇴근시간이고 맥주타임이 기다리고 있기에 이까지 적고 - 
기본적인 웹에서 이미지 불러오는 것을 단계적으로 해결해 나가는 글을 차례로 적어 보렵니다... 

아마.. 다음글은  웹 이미지를 GridView로 불러와 보기 및 문제점 인식이 되겠네요.

Image Cache가 없이는 어떤 앱이든 만들 수가 없어보인다.

부하가 너무 심하다.

이미지 하나를 터치할때마다 request 하는데 이건 cache가 아니고선 정말 답이 없다.

오전 내내 삽질하다가 지금부터 cache를 만들어 볼까나....

+ Recent posts