SoftReference - GC - WeakReference 안드로이드 / 모바일

2010/07/28 22:12

복사 http://blog.naver.com/macyoo/100110064959

WeakReference
 용어그대로 약한 레퍼런스이다. 메모리관리를 위해 고안된것으로 레퍼런스를 WeakReference로 하면 gc에 해당 레퍼런스가 관여되지 않는다. 즉 어떤 객체에 대한 참조가 WeakReference 밖에 남아있지 않다면 그 객체는 gc(Garbage Collection) 의 대상이 된다.

private WeakReference<Launcher> mLauncher; // 변수선언
void setLauncher(Launcher launcher) {
    mLauncher = new WeakReference<Launcher>(launcher); // 참조를 WeakReference로 저장함.
}
if (mLauncher != null) {
    final Launcher launcher = mLauncher.get(); // 사용시에는 get을 호출하여 null을 반드시 체크하여 사용
        if (launcher != null) {
            launcher.loadWallpaper();
        }
    }



SoftReference
 WeakReference 보다는 약간 강한 참조이다. Soft reference 만 남아있는 객체는 메모리의 요청에 의한 gc 의 대상이 된다. 메모리의 남은 공간이 넉넉하다면 gc되지 않을 것이며, 메모리의 남은 공간이 얼마 없거나 하다면 gc 의 대상이 될 것이다. 
확실한건 vm 이 OutOfMemoryError 를 발생시키기 전에는 Soft reference 만 남아있는 객체가 gc의 대상이 된다는 점 정도고, 그 이외에는 vm 의 구현에 따라 다를 수 있다.

코딩을 하다보면 같은 버튼 클릭에 관한 것이지만 사람에 따라 스타일이 천차 만별인 것을 알 수 있습니다.

저도 처음 공부할때 너무 헷갈려서 정리를 하는데 한참 헤멨네요.

이번에는 가장 빈번하게 쓰이는 버튼 클릭에 대한 것을 정리해 보겠습니다.
마지막에 조금이라도 메모리를 줄일 수 있는 팁도 있으니 기대해 주세요.

먼저 아래와 같은 main.xml의 UI정의가 있습니다.
<?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">
<Button
  android:id="@+id/btnOK"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="OK" />
</LinearLayout>

여기서 이 버튼의 클릭시 함수에 대한 실행 방법은 크게 아래와 같이 3가지가 있겠습니다 .
1. 이름 무지정 클래스
2. 이름 지정 클래스
3. activity 실행

먼저 이름 무지정 클래스 실행 코드를 보겠습니다.
public class MainActivity extends Activity {

  private Button btnOK;

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

    btnOK = (Button)findViewById(R.id.btnOK);
    btnOK.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        Toast.makeText(MainActivity.this, "Hello!", Toast.LENGTH_LONG).show();
      }
    });
  }
}
웹 사이트에서 가장 흔하게 볼수 있는 스타일입니다.  개인적으로는 제일 싫어 합니다.
아무튼 이 방식의 장점은 아래와 같습니다.
 ● 버튼에 대한 구현을 개별적으로 할수 있다.
단점으로는
 ● 코드가 길어진다.
 ● Context를 취득하기 위해서 일일이 MainActivity.this를 입력해야 한다.



다음으로는 이름 지정 클래스를 보겠습니다.
public class MainActivity extends Activity {

  private Button btnOK;

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

    btnOK = (Button) findViewById(R.id.btnOK);
    btnOK.setOnClickListener(new BtnOKOnClick(this));
  }
}

class BtnOKOnClick implements OnClickListener {
  private Context mContext;

  public BtnOKOnClick(Context context) {
    mContext = context;
  }

  @Override
  public void onClick(View v) {
    Toast.makeText(mContext, "Hello!", Toast.LENGTH_LONG).show();
  }
}
요즘은 이와 같은 형식의 코드들도 인터넷에서 손 쉽게 볼수 있습니다.  제가 자주 쓰는 스타일 중 하나입니다.

이 스타일의 장점은 아래와 같습니다.
 ● 코드가 깔끔하다.
 ● 버튼에 대한 구현을 개별적으로 할수 있다.
반대로 단점은 아래와 같습니다.
 ● 클래스가 늘어 난다.
 ● 버튼 클릭에 대한 activity에 대한 수정을 할때 조금 귀찮음. 즉 이래저래 손이 간다.


마지막으로 Activity의 실행에 대하여 알아 보도록 하겠습니다.

public class MainActivity extends Activity implements OnClickListener {

  private Button btnOK;

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

    btnOK = (Button) findViewById(R.id.btnOK);
    btnOK.setOnClickListener(this);
  }

  @Override
  public void onClick(View v) {
    switch (R.id.btnOK) {
    case R.id.btnOK:
      Toast.makeText(this, "Hello!", Toast.LENGTH_LONG).show();
      break;
    }
  }
}

이 코딩 스타일도 요즘에는 많이 볼 수 있습니다.
장점으로는
 ● 자바의 코딩 스타일과 비슷하다.
 ● 깔끔해서 관리, 수정하기에 편하다.
단점으로는
 ● 코드가 길어지는 편이다.

간단하게 각 스타일 별로 정리를 해봤습니다.
어떤 스타일을 이용하시느냐는 개인 취향에 달렸지만 저는 마지막 방식을 추천하겠습니다.

왜냐하면 마지막 방식은 코드도 깔끔하거니와 클래스를 하나만 생성하고 있습니다.
의외로 신경 안쓰시는 분들이 많으신데 스마트 폰과 같이 하드웨어 스펙이 제한되어 있는 기기에서 메모리 관리는 매우 중요한 것입니다.  아래의 내용을 항상 기억하고 계시길 바랍니다.

안드로이드에서는 새로운 클래스를 생성할때마다 1KB의 메모리를 소비합니다.





Customizing Android ListView Colors

Since I struggled through getting a ListView to have custom colors, I am going to share exactly what is needed so others can avoid the struggle.

Below are the details for the resources required to accomplish this.  These resources will give your list items a white background with a highlighted color of blue and a clicked of green.

I've highlighted the key attributes in bold.  These are the attributes that differ from your default list.

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">
 <ListView
  android:id="@android:id/list"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:listSelector="@android:color/transparent"
  />
 <TextView
  android:id="@android:id/empty"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="@string/empty"
  />
</LinearLayout>
File: res/layout/item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent" 
 android:layout_height="fill_parent"
 android:orientation="horizontal" 
 android:padding="10sp"
 android:background="@drawable/item_selector">
 <CheckBox
  android:id="@+id/checkbox"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:focusable="false"
  />
 <TextView 
  android:id="@+id/title" 
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:ellipsize="end"
  android:singleLine="true" 
  android:textStyle="bold"
  android:padding="7dip"
  android:textSize="18sp"
  android:layout_toRightOf="@id/checkbox"
  android:textColor="@color/black"
  />
</RelativeLayout>


File: res/drawable/item_selector.xml



<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item 
     android:state_pressed="true" 
     android:drawable="@color/green" />
    <item 
     android:state_selected="true" 
     android:drawable="@color/blue" />
 <item 
     android:drawable="@color/white" />
</selector>


File: res/values/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources> 
 <color name="red">#ff00</color>
 <color name="green">#f0f0</color>
 <color name="blue">#f00f</color>
 <color name="white">#ffff</color>
 <color name="black">#f000</color>
</resources>

Android Multithreading For Performance

안드로이드 개발자 블로그에 또 한가지 흥미로운 포스트가 올라왔습니다. ListView 에 인터넷에 비동기적으로 이미지를 다운로드해서 표시하는 간단하지만 유용한 예제를 통해 안드로이드 어플리케이션에서 멀티 스레딩 작업을 수행할 때 고려해야할 여러가지 사항을 잘 정리해 주었습니다. 한번 찬찬히 살펴보시면 여러가지로 도움을 받을 수 있으실거 같네요. (개인적으로는 말미에 SoftReference 를 이용하여 Cache 를 만드는 부분이 가장 도움 되었습니다;;;)

[이 포스트는 Gilles Debunne 에 의해 작성되었습니다. 그는 멀티 태스킹을 사랑하는 안드로이드 엔지니어 입니다. — Tim Bray]


 빠르게 반응하는 어플리케이션을 만들기 위해서는 메인 UI 스레드가 가능한 최소한의 일만을 수행하도록 해야합니다. 수행하는데 오랜 시간이 걸릴 가능성이 있는 작업들은 반드시 메인 스레드가 아닌 다른 스레드에서 수행되어야합니다. 이러한 좋은 예가 바로 네트워크 작업입니다. 네트워크 작업은 어느정도의 시간이 걸릴지 예측하기 힘들기 때문에, 조심스럽게 처리하지 않으면 사용자들은 잠깐 잠깐씩 어플리케이션이 버벅거린다고 느끼거나, 어떤 경우에는 아예 멈추어버린 것 처럼 느낄 수도 있습니다.

 이번 포스트에서는 이러한 문제를 해결할 수 있는 방법을 살펴보기 위해, 이미지를 다운로드 하는 간단한 코드를 작성해볼 것 입니다. 이를 바탕으로, 인터넷에서 다운로드한 이미지 썸네일을 표시해주는 ListView 를 생성한 후, 어플리케이션은 정상적으로 빠르게 동작하는 가운데, 백그라운드에서 비동기적으로 이미지 다운로드 작업을 수행하도록 구현할 것입니다.

An Image Downloader

 안드로이드 프레임워크에서 제공하는 HTTP 관련 클래스를 사용하면, 웹에서 이미지를 다운로드하는 것은 꽤나 단순합니다. 여기 한 가지 예가 있습니다.

static Bitmap downloadBitmap(String url) {
    final
AndroidHttpClient client = AndroidHttpClient.newInstance("Android");
    final
HttpGet getRequest = new HttpGet(url);

    try {
        HttpResponse
response = client.execute(getRequest);
        final
int statusCode = response.getStatusLine().getStatusCode();
        if
(statusCode != HttpStatus.SC_OK) {
           
Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url);
           
return null;
        }
       
        final
HttpEntity entity = response.getEntity();
        if
(entity != null) {
            InputStream
inputStream = null;
            try {
                inputStream =
entity.getContent();
               
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                return bitmap;
            }
finally {
                if
(inputStream != null) {
                    inputStream.
close();  
                }
                entity.
consumeContent();
            }
        }
    } catch
(Exception e) {
        // Could provide a more explicit error message for IOException or IllegalStateException
        getRequest.abort();
        Log
.w("ImageDownloader", "Error while retrieving bitmap from " + url, e.toString());
    }
finally {
        if
(client != null) {
            client.
close();
        }
    }
    return null;
}

 HTTP 클라이언트와 Request 를 생성합니다. 만일 HTTP Request 가 성공한다면, HttpResoponse InputStream 에는 특정 이미지 정보가 담겨 있을 것이고, 해당 Stream 을 바로 디코딩하여 Bitmap 을 생성할 수 있습니다. 물론, 이러한 일이 가능하기 위해서는 우선적으로 어플리케이션이 INTERNET 퍼미션을 갖고 있어야 합니다.

Note: 이전 버전의 BitmapFactory.decodeStream 에는 네트워크 커넥션이 느린경우에는 정상적으로 작동하지 않는 버그가 있습니다. 따라서, 결과로 전달 받은 InputStream 을 new FlushedInputStream(inputStream) 으로 생성한 후에 이미지 디코딩을 수행하시는 편이 좋습니다. 이 클래스는 스트림이 끝나지 않은 한, skip() 메서드가 실재로 전달받은 바이트 수 만큼을 건너띄도록 구현되어있습니다. 
static class FlushedInputStream extends FilterInputStream {
    public
FlushedInputStream(InputStream inputStream) {
        super
(inputStream);
    }

    @Override
    public long
skip(long n) throws IOException {
        long
totalBytesSkipped = 0L;
        while
(totalBytesSkipped < n) {
            long
bytesSkipped = in.skip(n - totalBytesSkipped);
            if
(bytesSkipped == 0L) {
                  int
byte = read();
                  if
(byte < 0) {
                      break;  // we reached EOF
                  }
else {
                      bytesSkipped =
1; // we read one byte
                  }
           
}
            totalBytesSkipped +=
bytesSkipped;
       
}
        return totalBytesSkipped;
    }
}

 만일 여러분이 위에 구현된 이미지 다운로드 메서드를 ListAdapter 의 getView() 메서드에서 직접 호출한다면, 아마도 어플레케이션은 우울할정도로 버벅될 것 입니다. 새로운 View 를 그릴 때 마다 이미지가 다운로드 하기 위해 어플리케이션이 멈추고, 스크롤이 부드럽게 이루어지지 않습니다.

 더군다나, AndroidHttpClient 는 메인 스레드에서 사용될 수도 없습니다. 위의 코드를 실행하면, "This thread forbids HTTP request(이 스레드는 HTTP Request 를 허용하지 않습니다.)" 라는 에러가 발생합니다. 여러분이 반드시 메인 스레드 내에서 HTTP Request 를 요청하고자 한다면, DefaultHttpClient 클래스를 사용하셔야 합니다.

Introducing asynchronous tasks

 AsyncTask 클래스는 UI 스레드가 아닌 스레드에서 여러가지 작업을 수행할 때 사용할 수 있는 가장 간단한 해결책 중에 하나입니다. AsyncTask 클래스를 사용하는 ImageDownloader 클래스를 한번 만들어 봅시다. 이 클래스는 특정한 URL 경로에 위치한 이미지를 다운로드 한 후, 해당 이미지를 주어진 ImageView 에 바인드하는 download 메서드를 제공합니다. 

public class ImageDownloader {

    public
void download(String url, ImageView imageView) {
            BitmapDownloaderTask
task = new BitmapDownloaderTask(imageView);
            task.
execute(url);
        }
    }

   
/* class BitmapDownloaderTask, see below */
}

 BitmapDownloaderTask 는 ImageDownloader 클래스에서 생성하는, 이미지를 다운로드 작업을 실재로 수행하는 AsyncTask 클래스입니다. execute 메서드를 통해 다운로드가 시작되며, execute 메서드는 즉시 리턴됩니다. execute 메서드는 UI 스레드에서 호출되도록 구현되었기 때문에 그렇습니다. BitmapDownloaderTask 은 아래와 같이 구현됩니다.

class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
    private
String url;
    private
final WeakReference<ImageView> imageViewReference;

    public
BitmapDownloaderTask(ImageView imageView) {
        imageViewReference =
new WeakReference<ImageView>(imageView);
    }

    @Override
   
// Actual download method, run in the task thread
    protected Bitmap doInBackground
(String... params) {
         // params comes from the execute() call: params[0] is the url.
         return downloadBitmap
(params[0]);
    }

    @Override
   
// Once the image is downloaded, associates it to the imageView
    protected void onPostExecute
(Bitmap bitmap) {
        if
(isCancelled()) {
            bitmap = null;
        }

        if
(imageViewReference != null) {
            ImageView
imageView = imageViewReference.get();
            if
(imageView != null) {
                imageView.
setImageBitmap(bitmap);
            }
        }
   
}
}
 doInBackground 메서드는 메인 스레드와는 별개의 프로세스(주>아마 오타인 듯. 스레드가 맞을거 같네요)에서 수행됩니다. 위 코드에서 donInBackground 메서드는 단순히 앞서 구현한 downloadBitmap 메서드를 호출하도록 작성되었습니다.

 onPostExecute 메서드는 태스크가 종료된 시점에 UI 스레드에서 실행됩니다. 이 메서드는 인자로 넘겨받은 Bitmap 을 미리 지정된 ImageView 에 설정합니다. ImageView 가 WeakReference 로 저장되어 있는 것을 주의깊게 살펴보세요. 만일 BitmapDownloaderTask 가 ImageView 의 실재 참조를 갖고 있을경우, ImageView 와 연관된 Activity 가 종료된 후에도, GC 가 해당 ImageView 를 제거할 수 없습니다. WeakReference 를 사용했기 때문에 onPostExecute 메서드 내에서 ImageView 를 사용하기 전에 WeakReference 값이 null 인지, 그리고 실재 ImageView 가 null 인지 이중으로 확인해 보아야 합니다. 

 이 간단한 예제는 어떻게 AsyncTask 를 사용할 수 있는지에 관해 설명해주며, 만일 여러분이 이러한 기법을 실재로 시도해본다면, 단지 몇 줄의 코드만으로 ListView 가 부드럽게 스크롤 될 만큼 성능이 향상 됨을 확인 할 수 있을 것 입니다. AstncTask 에 관해 보다 자세히 알고 싶으시다면, Painless Threading 문서를 살펴보세요.

 그러나 ListView 와 관련되어, 현재의 구현 방식에는 한 가지 문제점이 있습니다. ListView 는 메모리를 효과적으로 활용하기 위해 한 번 사용된 View 를 재활용합니다. 사용자가 ListView 를 스크롤 하면, 한번 생성된 ImageView 가 여러번 사용되게 되지요. 또, 매번 ImageView 가 화면에 그려질 때면, 해당 ImageView 에 적합한 이미지를 다운로드 하기 위한 BitmapDownloaderTask 가 정확히 실행 되됩니다. 이런 경우 어떤 문제가 발생할까요? 대부분의 패러럴 어플리케이션 처럼, 가장 중요한 이슈는 바로 '순서' 에 있습니다. 우리의 경우, 이미지 다운로드 작업이 시작된 순서대로 종료된다는 보장이 없습니다. 결과적으로 특정 이미지를 다운로드 하는데 오랜 시간이 걸릴 경우, 훨씬 앞에 표시되어야할 이미지가 가장 마지막에 표시될 수도 있습니다. 만일 각각의 ImageView 에 이미지가 한 번씩만 설정된다면 별 문제 아닐 수가 있지만, 이는 일반적인 해결책이라고 할 수 없습니다. 이 문제를 한 번 해결해 봅시다.

Handling Concurrency

 이 문제를 해결하기 위해, 우리는 다운로드의 순서를 기억하고, 가장 마지막에 시작된 이미지만이 화면 상에 표시되도록 해야 합니다. 즉, 각각의 ImageView 가 마지막에 다운로드 된 이미지만을 기억하도록 구현하면 됩니다. 다운로드가 진행 중인 동안 임시로 ImageView 에 바인드 되는 커스텀한 Drawable 클래스를 이용하여 ImageView 에 부가 정보를 추가하겠습니다. 추가된 DownloadedDrawable 클래스의 코드는 다음과 같습니다.

static class DownloadedDrawable extends ColorDrawable {
    private
final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;

    public
DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
        super
(Color.BLACK);
        bitmapDownloaderTaskReference =
            new
WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
    }

    public
BitmapDownloaderTask getBitmapDownloaderTask() {
        return
bitmapDownloaderTaskReference.get();
    }
}

 DownloadedDrawable 은 다운로드 중인 이미지가 있는 경우, ImageView 에 검은 화면을 표시하기 위하여 ColorDrawable 을 상속받아 구현되었습니다. 물론 검은 화면 대신 "다운로드가 진행 중 입니다." 같은 이미지를 사용할 수도 있을 것 입니다. 또한, 오브젝트의 의존성을 제한하기 위하여 WeakReference 를 사용한 것을 다시 한번 유의깊게 살펴보시기 바랍니다.

 새로운 클래스를 사용하도록 기존의 코드를 수정해 봅시다. 먼저, download 메서드는 이 새로운 클래스의 인스턴스를 생성한 후 해당 인스턴스를 ImageView 에 설정합니다.

public void download(String url, ImageView imageView) {
     if
(cancelPotentialDownload(url, imageView)) {
         BitmapDownloaderTask
task = new BitmapDownloaderTask(imageView);
         DownloadedDrawable
downloadedDrawable = new DownloadedDrawable(task);
         imageView.
setImageDrawable(downloadedDrawable);
         task.
execute(url, cookie);
     }
}
 cancelPotentialDownload 메서드는 새로운 다운로드 작업이 시작됨으로, 현재 ImageView 와 연관된 이미지 다운로드 작업을 정지 시키는 역할을 수행합니다. 하지만, 이것만으로 가장 마지막에 다운로드를 시작한 이미지가 화면에 표시된다고 보장할 수는 없습니다. 왜냐하면 이전에 진행 중이던 다운로드 작업이 이미 완료되어 onPostExecute 메서드가 실행 중인 상황인 경우, 때에 따라서 onPostExecute  메서드가 새로운 다운로드 작업이 모두 완료되고 해당 다운로드의 onPostExecute 메서드가 호출된 이 후에 실행 될 수도 있기 때문입니다. 

private static boolean cancelPotentialDownload(String url, ImageView imageView) {
    BitmapDownloaderTask
bitmapDownloaderTask = getBitmapDownloaderTask(imageView);

    if
(bitmapDownloaderTask != null) {
        String
bitmapUrl = bitmapDownloaderTask.url;
        if
((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
            bitmapDownloaderTask.
cancel(true);
        }
else {
           
// The same URL is already being downloaded.
            return
false;
        }
    }
    return true;
}
 cancelPotentialDownload 메서드는 진행 중인 다운로드 작업을 정지하기 위해 AsyncTask 의 cancel 메서드를 사용합니다. 일반적으로 'true' 값을 리턴하며, 새로운 다운로드 작업이 시작됩니다. 다만, 이미 동일한 URL 에 대하여 다운로드가 진행 중인 경우에는 기존 다운로드 작업을 취소하지 않고 계속 진행되도록 구현되었습니다. 한가지 주의할 점이 있습니다. 현재의 구현대로라면 만일 ImageView 가 가비지 콜랙팅된 경우에도 이와 연관된 다운로드 작업이 중단되지 않습니다. 이를 처리하기 위해서는 RecyclerListener 를 사용할 수 있을거 같네요.

 위 메서드는 getBitmapDownloderTask 라는 함수를 사용하고 있습니다. 이 함수는 다음과 같이 간단하게 구현되어있습니다.

private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
    if
(imageView != null) {
        Drawable
drawable = imageView.getDrawable();
        if
(drawable instanceof DownloadedDrawable) {
            DownloadedDrawable
downloadedDrawable = (DownloadedDrawable)drawable;
            return
downloadedDrawable.getBitmapDownloaderTask();
        }
    }
    return null;
}
 마지막으로, onPostExecute 메서드는 오직 해당 ImageView 가 자기 자신과 연결되어 있는 경우에만 이미지를 설정하도록 수정되어야합니다.

if (imageViewReference != null) {
    ImageView
imageView = imageViewReference.get();
    BitmapDownloaderTask
bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
    // Change bitmap only if this process is still associated with it
    if
(this == bitmapDownloaderTask) {
        imageView.
setImageBitmap(bitmap);
    }
}
 이제 ImageDownloader 클래스는 우리가 기대하는 기본적인 서비스들을 제공하도록 수정되었습니다. 이 클래스 혹은 여기에 사용된 비동기 패턴을 여러분의 어플리케이션의 반응성을 향상시키는데 자유롭게 사용하시기 바랍니다.

Demo

 이 포스트에 나온 소스 코드는 online on Google Code 에서 확인하실 수 있습니다. 여러분은 이 포스트에서 서술한 세 가지 방법(AsyncTask 를 사용하지 않은 버전, Bitmap 설정과 관련된 추가 작업을 수행하지 않는 버전, 최종 버전)을 선택하여 각각의 구현을 비교해 볼 수 있습니다. 관련된 문제를 보다 잘 보여주기 위해 10개의 이미지 만을 캐쉬 하도록 제한되어 있습니다.


Future work

 이 코드는 패러럴한 작업에 관련된 부분에만 초점을 맞추도록 간략하게 작성되어 있으며, 여러가지 유용한 기능이 제외되어 있습니다. ImageDownloader 클래스는 동일한 이미지를 여러번 다운로드 받지 않도록 캐쉬를 이용하여 성능이 크게 개선될 수 있습니다. (ListView 와 함께 사용되는 경우라면 더욱 그렇습니다.) 이미지의 URL 을 키 값으로, Bitamp 의 SoftReference 를 밸류 값을 갖는 LinkedHashMap 을 이용하여 LRU(Least Recently Used) 방식의 캐쉬를 손쉽게 구현할 수 있습니다. 물론, 로컬 저장 장치에 이미지를 저장하는 방식으로 캐쉬 메카니즘을 구현할 수도 있으며, 이미지 리사이징이나 썸네일을 만드는 기능도 필요한 경우 추가될 수 있습니다.

 다운로드 오류와 네트워크 타임아웃 관련된 오류들은 현재 버전에서도 올바르게 처리되고 있습니다. Bitmap 대시 null 이 리턴됩니다. 이 경우 에러 이미지를 표시하도록 구현할 수도 있을 것 입니다.

 현재의 HTTP Request 는 꽤나 단순합니다. 특정한 웹사이트에서 요구되는 파라매터나 쿠키를 추가하도록 수정할 수도 있습니다.

 이 포스트에서 사용된 AsyncTask 는 UI 스레드가 아닌 다른 곳에서 특정한 작업을 수행할 수 있는 편리하고 쉬운 방법입니다. 여러분이 여러분이 수행하는 일을 보다 정교하게 제어하고자 하는 경우에는  (동시에 수행되는 다운로드 스레드의 수를 제한하는 등) Handler 클래스를 사용할 수 있습니다.
step 1 AndroidManifest.xml 에서 인터넷 권한을 설정합니다.

<uses-permission android:name="android.permission.INTERNET" />

step 2 URL 경로를 참조하여 이미지를 가져옵니다.

URL url = new URL(urlPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
BufferedInputStream bis = new BufferedInputStream(
        conn.getInputStream(), 95 * 110);
Bitmap bm = BitmapFactory.decodeStream(bis);
bis.close();

ImageView picture = (ImageView) v.findViewById(R.id.papular_picture);
picture.setImageBitmap(bm);

How do I pass the current AttributeSet to a custom View class? If I use a constructor that only has Context in the arguments, I lose all themes and the ability to use "style" tags in the xml for that custom View.

What I've done is create an activity that contains my custom view already in the xml file, and then programmatically create a new one and add it to the layout. What I find is the one that is made in the xml has the proper styling, and the one I create programmatically doesn't.

The difference between the two as far as I can tell is that the system uses theCustomLayout1(Context context, AttributeSet attrs) constructor. The problem is I can't figure out how to get the AttributeSet for the application to pass to this custom view when I create it programmatically.

Here's the Activity:

import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;
public class ThemeOne extends Activity {
 
@Override
 
public void onCreate(Bundle savedInstanceState) {
     
super.onCreate(savedInstanceState);
        setContentView
(R.layout.main);
       
LinearLayout layout = (LinearLayout) findViewById(R.id.mainlayout);
        layout
.addView(new CustomLayout1(getApplicationContext()));
 
}
}

Here's the main xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:orientation="vertical"
android:id="@+id/mainlayout"

android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.clearsync.test.theme1.CustomLayout1 android:id="@+id/maincustom"
 
android:layout_width="fill_parent"
 
android:layout_height="wrap_content" />
</LinearLayout>

The custom view class:

import com.clearsync.test.theme1.R;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
public class CustomLayout1 extends LinearLayout {
 
private Context context = null;
 
public CustomLayout1(Context context) {
 
super(context);
 
this.context = context;
  create
();
 
}
 
public CustomLayout1(Context context, AttributeSet attrs) {
 
super(context, attrs);
 
this.context = context;
  create
();
 
}
 
private void create(){
 
LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  layoutInflater
.inflate(R.layout.inflateme, this, true);
 
}
}

and finally, the custom view 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">
 
<TextView android:layout_width="wrap_content"
 
android:layout_height="wrap_content"
 
android:text="Oh, Hewroh..."

 
style="?textview_style1" />
</LinearLayout>

Gallery에 Item 들을 추가하게 되면 0번 인덱스나 마지막 인덱스로 아이템들을 스크롤 시키게되면 외쪽 또는 오른쪽으로 더이상 스크롤을 할 수 없게 된다.

여기서 구현 하고자 하는 내용은 0번 또는 마지막 인덱스로 스크롤 되더라도 큐와 같이 무한 스크롤 되도록 구현하는 방법.


핵심 포인트

Gallery Adapter에 실제 가지고 있는 Item의 개수의 3배를 가지고 있는 것처럼 보여 지게 하고

Gallery에 setOnItemSelectedListener를 추가하여 OnItemSelectedListener의 onItemSelected에서 가운데 범위를 넘어 서는 경우

Selected Item을 변경해 주면 무한 스크롤을 구현 할 수 있다.


Sample)

//-------------------------------------------------------------------------------------

//  GalleryAdapter Class

//-------------------------------------------------------------------------------------

package reno.gallerytest;

import android.content.*;
import android.view.*;
import android.widget.*;

public class GalleryAdapter extends BaseAdapter {
    private Context mContext;
    private String[] resString = {"1111", "2222", "3333", "4444", "5555"};
    public GalleryAdapter (Context c)
    {
        this.mContext = c;
    }
   
    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return resString.length*3;  // 총 Item 개수를 X 3으로 리턴한다. 이유는 Index의 맨 끝으로 이동시 좌,우에 Item이 연속되는 것 처럼 보기기 위함.
    }

    @Override
    public Object getItem(int arg0) {
        // TODO Auto-generated method stub
        return resString[arg0%resString.length];
    }

    @Override
    public long getItemId(int arg0) {
        // TODO Auto-generated method stub
        return arg0%resString.length;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        TextView txtView;
        position = position % resString.length;
       
        if(convertView == null)
        {
            txtView = new TextView(mContext);
        }else
        {
            txtView = (TextView)convertView;
        }
       
        txtView.setText(resString[position]);
        txtView.setLayoutParams(new Gallery.LayoutParams(75, 75));
        return txtView;       
    }

}


//------------------------------------------------------------------------------------------------

// GalleryTest Class

//------------------------------------------------------------------------------------------------

package reno.gallerytest;

import android.app.Activity;
import android.graphics.*;
import android.os.Bundle;
import android.widget.*;
import android.util.*;
import android.view.*;

public class GalleryTest extends Activity {
    /** Called when the activity is first created. */
    private RGallery unitGallery;
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        unitGallery = (RGallery)findViewById(R.id.galUit);       
        unitGallery.setAdapter(new GalleryAdapter(this));     
        unitGallery.setSpacing(0);       
        unitGallery.setSelection(6);
        unitGallery.setBackgroundColor(Color.GRAY);
        unitGallery.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener(){
            public void  onItemSelected  (AdapterView<?> parent, View view, int position, long id)
            {
                Log.d("GalleryTest", "onItemSelected index=" + String.valueOf(position));
                if(position < 5)
                    unitGallery.setSelection(position + 5);
                else if(position >= 10)
                    unitGallery.setSelection(position - 5);

                    // 이 부분이 무한 스크롤을 해주는 부분으로 가운데 영역을 벗어 나는 경우 setSelection으로 Position을 이동시켜 마치 무한 스크롤이 되게 보여 준다.

            }
           
            public void  onNothingSelected  (AdapterView<?> parent)
            {
               
            }
        });
    }
}

//----------------------------------------------------------------------------------------

//  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"
    >
<Gallery
android:id="@+id/galUit"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
</Gallery>
</LinearLayout>


 

<스플래쉬 액티비티>

 

스플래쉬 액티비티는 일반 프로그램에서 로딩화면쯤으로 생각하면 된다. 로딩화면의 역할은 프로그램에 필요한 자원을

불러들이는데 필요한 시간을 벌어주는 역할을 하는데 그것과 같게 생각하면 될 듯허다.

... 즉 , 프로그램을 구동전에 프로그램에 필요한 데이터를 읽어들일동안 보여주는 화면이다

 

<소스코드>

 

핸들러는 나중에 쓰레드(Thread)와 설명하기로 하고 일단 postDelayed라는 함수는 정해진 시간후에 Runnable 객체를 실행하는

함수이다.

 

밑에 splashandler라는 클래스는 Runnable인터페이스를 구현하는것을 볼 수 있는데 Runnable객체는 안드로이드에서 스케줄링을

할 수 있도록 한다. 즉, 정해진 일정시간후에 run함수를 실행시킬수 있다는 말이다.

 

즉 , 이 코드를 보면 5000밀리세컨드후에 run()함수를 실행한다. run()함수 내부에서는 새로운 인텐트(메인화면)을 실행한다.

그리고 현재 Splash액티비티는 종료된다.

(프로그램중에는 많은 액티비티를 이용할텐데 액티비티를 이동한다고 해서 이전 액티비티가

자동 종료되는 것이 아니다. 다만 사용자에게 보이지 않는 백그라운드(background)상태가 될 뿐이다. 물론 메모리가 부족하거나 하며

자동으로 우선순위에 따라서 액티비티는 종료되긴하지만...)

 

<xml 파일>

 

ImageView는 화면에 이미지를 보여주는 뷰이다. 안드로이드 내에 res->drawable폴더에 loading이라는 jpg파일을 삽입한 후

다음과 같은 코드를 통하여 이미지를 불러올 수 있다.

즉, 맨위에 그림과 같은 로딩시에 보여주고 싶은 화면을 설정하는 것이다.

Android Coverflow Widget

Here's an Android Coverflow like Widget that I created. It's based on the Android Gallery widget and is used in the same way, with a ViewAdapter. My main aim when coding this coverflow widget was to create an easily re-usable component using the standard Android 2D libraries.Yes, that's right I'm using the 2D libs, as they support perspective transformations and that is all that is needed for coverflow. I could have used Open GL, but I wanted to see what could be achieved without it. So here are the results.






The Video shows the Coverflow widget running on a HTC G1 (Dream). I can't remember the exact spec's of the phone but it's a fairly old one and is only running version 1.1 of Android. I'm moving the images using the track ball and then I also move them with screen touches. It is possible to drag, fling and touch on individual image to bring them to the centre. With a few tweaks I've managed to get the responsiveness and performance on this to be pretty good, it seems to be comparable to the standard gallery widget, and in my quick test the motion tracking was fluid and fast. I'm not sure how well it would run on newer versions of hardware and the Android OS, but I can only assume that things should be better. How much better I don't know, so if you try this on a different device or a newer OS I would love to hear how things worked.

참조사이트
http://www.inter-fuser.com/2010/01/android-coverflow-widget.html

소스
Here's the zip of the coverflow example: Coverflow.zip

이미지 효과로 이미지 크기의 절반을 반대 되는 이미지를 만들고,
그 반대된 이미지에 대해서는 반사 된 듯한 효과를 주는 소스 입니다.


package com.android.reflection2;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuffXfermode;
import android.graphics.Bitmap.Config;
import android.graphics.PorterDuff.Mode;
import android.graphics.Shader.TileMode;
import android.os.Bundle;
import android.widget.ImageView;

public class Reflection extends Activity {
	/** Called when the activity is first created. */

	ImageView view1;

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

		view1 = (ImageView) findViewById(R.id.ImageView01);

		Bitmap bitmapOriginal = BitmapFactory.decodeResource(getResources(),
				R.drawable.android);

		int nWidth = bitmapOriginal.getWidth();
		int nHeight = bitmapOriginal.getHeight();

		Matrix matrix = new Matrix();
		matrix.preScale(1, -1);

		Bitmap bitmapReflection = Bitmap.createBitmap(bitmapOriginal, 0,
				nHeight / 2, nWidth, nHeight / 2, matrix, false);

		Bitmap bitmapOrigianlAndReflection = Bitmap.createBitmap(nWidth,
				(nHeight + nHeight / 2), Config.ARGB_8888);

		Canvas canvas = new Canvas(bitmapOrigianlAndReflection);
		canvas.drawBitmap(bitmapOriginal, 0, 0, null);
		Paint deafaultPaint = new Paint();
		canvas.drawRect(0, nHeight, nWidth, nHeight + 5, deafaultPaint);
		canvas.drawBitmap(bitmapReflection, 0, nHeight + 5, null);

		Paint paint = new Paint();
		LinearGradient shader = new LinearGradient(0, bitmapOriginal
				.getHeight(), 0, bitmapOrigianlAndReflection.getHeight() + 5,
				0x70ffffff, 0x00ffffff, TileMode.CLAMP);
		paint.setShader(shader);
		paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
		canvas.drawRect(0, nHeight, nWidth, bitmapOrigianlAndReflection
				.getHeight() + 5, paint);

		view1.setImageBitmap(bitmapOrigianlAndReflection);
	}
}


결과 화면은 이런 식으로 나옵니다.

+ Recent posts