2014년 8월 19일 화요일

[AnDrOiD] ListView 스크롤이 버벅거릴때 ImageDownloader.java 를 사용


ListView 에 웹에서 받아오는 이미지를 넣게 되는 경우 스크롤시 느려 지는 경우가 발생 한다.
이 문제 해결을 위해서 누군가가 만들어 놓은 ImageDownloader를 이용해본 결과 버벅거림이 만족 스러울 만큼 부드러워 졌다.


getView 의 소스는 아래와 같다.


private final imgDownloader imageDownloader = new ImageDownloader(); // ① 여기

 @Override
 public View getView(int position, View convertView, ViewGroup parent) {
  // TODO Auto-generated method stub
  
  if(convertView == null){
   convertView = inflater.inflate(R.layout.person_list, null);
   convertView.setTag(R.id.tvName, convertView.findViewById(R.id.tvName));
   convertView.setTag(R.id.tvAge, convertView.findViewById(R.id.tvAge));
   convertView.setTag(R.id.imgView, convertView.findViewById(R.id.imgView));
  }
  
  personInfo info = (personInfo) person.get(position);
  
  if(info != null){
   TextView tvName = (TextView)convertView.getTag(R.id.tvName);
   TextView tvAge = (TextView)convertView.getTag(R.id.tvAge);
   ImageView imgView = (ImageView)convertView.getTag(R.id.imgView);
   
   tvName.setText(info.getName());
   tvAge.setText(info.getAge());
   
   imgDownloader.download(info.getImage(),(ImageView)imgView); // ② 여기

  }
  return convertView;
 }

그리고 ImageDownloader.java 의 소스는 다음과 같다.

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.exam01;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.http.HttpEntity;
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.impl.client.DefaultHttpClient;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.http.AndroidHttpClient;
import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;

/**
 * This helper class download images from the Internet and binds those with the provided ImageView.
 *
 * It requires the INTERNET permission, which should be added to your application's manifest
 * file.

 *
 * A local cache of downloaded images is maintained internally to improve performance.
 */
public class ImageDownloader {
    private static final String LOG_TAG = "ImageDownloader";

    public enum Mode { NO_ASYNC_TASK, NO_DOWNLOADED_DRAWABLE, CORRECT }
    private Mode mode = Mode.NO_ASYNC_TASK;
    
    /**
     * Download the specified image from the Internet and binds it to the provided ImageView. The
     * binding is immediate if the image is found in the cache and will be done asynchronously
     * otherwise. A null bitmap will be associated to the ImageView if an error occurs.
     *
     * @param url The URL of the image to download.
     * @param imageView The ImageView to bind the downloaded image to.
     */
    public void download(String url, ImageView imageView) {
        resetPurgeTimer();
        Bitmap bitmap = getBitmapFromCache(url);

        if (bitmap == null) {
            forceDownload(url, imageView);
        } else {
            cancelPotentialDownload(url, imageView);
            imageView.setImageBitmap(bitmap);
        }
    }

    /*
     * Same as download but the image is always downloaded and the cache is not used.
     * Kept private at the moment as its interest is not clear.
       private void forceDownload(String url, ImageView view) {
          forceDownload(url, view, null);
       }
     */

    /**
     * Same as download but the image is always downloaded and the cache is not used.
     * Kept private at the moment as its interest is not clear.
     */
    private void forceDownload(String url, ImageView imageView) {
        // State sanity: url is guaranteed to never be null in DownloadedDrawable and cache keys.
        if (url == null) {
            imageView.setImageDrawable(null);
            return;
        }

        if (cancelPotentialDownload(url, imageView)) {
            switch (mode) {
                case NO_ASYNC_TASK:
                    Bitmap bitmap = downloadBitmap(url);
                    addBitmapToCache(url, bitmap);
                    imageView.setImageBitmap(bitmap);
                    break;

                case NO_DOWNLOADED_DRAWABLE:
                    imageView.setMinimumHeight(156);
                    BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
                    task.execute(url);
                    break;

                case CORRECT:
                    task = new BitmapDownloaderTask(imageView);
                    DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
                    imageView.setImageDrawable(downloadedDrawable);
                    imageView.setMinimumHeight(156);
                    task.execute(url);
                    break;
            }
        }
    }

    /**
     * Returns true if the current download has been canceled or if there was no download in
     * progress on this image view.
     * Returns false if the download in progress deals with the same url. The download is not
     * stopped in that case.
     */
    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;
    }

    /**
     * @param imageView Any imageView
     * @return Retrieve the currently active download task (if any) associated with this imageView.
     * null if there is no such task.
     */
    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;
    }

    Bitmap downloadBitmap(String url) {
        final int IO_BUFFER_SIZE = 4 * 1024;

        // AndroidHttpClient is not allowed to be used from the main thread
        final HttpClient client = (mode == Mode.NO_ASYNC_TASK) ? new DefaultHttpClient() :
            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();
                    return BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
                } finally {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    entity.consumeContent();
                }
            }
        } catch (IOException e) {
            getRequest.abort();
            Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
        } catch (IllegalStateException e) {
            getRequest.abort();
            Log.w(LOG_TAG, "Incorrect URL: " + url);
        } catch (Exception e) {
            getRequest.abort();
            Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e);
        } finally {
            if ((client instanceof AndroidHttpClient)) {
                ((AndroidHttpClient) client).close();
            }
        }
        return null;
    }
    
    /**
     * A patched InputSteam that tries harder to fully read the input stream.
     */
    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) break;
                totalBytesSkipped += bytesSkipped;
            }
            return totalBytesSkipped;
        }
    }

    /**
     * The actual AsyncTask that will asynchronously download the image.
     */
    class BitmapDownloaderTask extends AsyncTask {
        private String url;
        private final WeakReference imageViewReference;

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

        /**
         * Actual download method.
         */
        @Override
        protected Bitmap doInBackground(String... params) {
            url = params[0];
            return downloadBitmap(url);
        }

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

            addBitmapToCache(url, bitmap);

            if (imageViewReference != null) {
                ImageView imageView = imageViewReference.get();
                BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
                // Change bitmap only if this process is still associated with it
                // Or if we don't use any bitmap to task association (NO_DOWNLOADED_DRAWABLE mode)
                if ((this == bitmapDownloaderTask) || (mode != Mode.CORRECT)) {
                    imageView.setImageBitmap(bitmap);
                }
            }
        }
    }


    /**
     * A fake Drawable that will be attached to the imageView while the download is in progress.
     *
     * Contains a reference to the actual download task, so that a download task can be stopped
     * if a new binding is required, and makes sure that only the last started download process can
     * bind its result, independently of the download finish order.

     */
    static class DownloadedDrawable extends ColorDrawable {
        private final WeakReference bitmapDownloaderTaskReference;

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

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

    public void setMode(Mode mode) {
        this.mode = mode;
        clearCache();
    }

    
    /*
     * Cache-related fields and methods.
     * 
     * We use a hard and a soft cache. A soft reference cache is too aggressively cleared by the
     * Garbage Collector.
     */
    
    private static final int HARD_CACHE_CAPACITY = 50;
    private static final int DELAY_BEFORE_PURGE = 10 * 1000; // in milliseconds

    // Hard cache, with a fixed maximum capacity and a life duration
    private final HashMap sHardBitmapCache =
        new LinkedHashMap(HARD_CACHE_CAPACITY / 2, 0.75f, true) {
        @Override
        protected boolean removeEldestEntry(LinkedHashMap.Entry eldest) {
            if (size() > HARD_CACHE_CAPACITY) {
                // Entries push-out of hard reference cache are transferred to soft reference cache
                sSoftBitmapCache.put(eldest.getKey(), new SoftReference(eldest.getValue()));
                return true;
            } else
                return false;
        }
    };

    // Soft cache for bitmaps kicked out of hard cache
    private final static ConcurrentHashMap> sSoftBitmapCache =
        new ConcurrentHashMap>(HARD_CACHE_CAPACITY / 2);

    private final Handler purgeHandler = new Handler();

    private final Runnable purger = new Runnable() {
        public void run() {
            clearCache();
        }
    };

    /**
     * Adds this bitmap to the cache.
     * @param bitmap The newly downloaded bitmap.
     */
    private void addBitmapToCache(String url, Bitmap bitmap) {
        if (bitmap != null) {
            synchronized (sHardBitmapCache) {
                sHardBitmapCache.put(url, bitmap);
            }
        }
    }

    /**
     * @param url The URL of the image that will be retrieved from the cache.
     * @return The cached bitmap or null if it was not found.
     */
    private Bitmap getBitmapFromCache(String url) {
        // First try the hard reference cache
        synchronized (sHardBitmapCache) {
            final Bitmap bitmap = sHardBitmapCache.get(url);
            if (bitmap != null) {
                // Bitmap found in hard cache
                // Move element to first position, so that it is removed last
                sHardBitmapCache.remove(url);
                sHardBitmapCache.put(url, bitmap);
                return bitmap;
            }
        }

        // Then try the soft reference cache
        SoftReference bitmapReference = sSoftBitmapCache.get(url);
        if (bitmapReference != null) {
            final Bitmap bitmap = bitmapReference.get();
            if (bitmap != null) {
                // Bitmap found in soft cache
                return bitmap;
            } else {
                // Soft reference has been Garbage Collected
                sSoftBitmapCache.remove(url);
            }
        }

        return null;
    }
 
    /**
     * Clears the image cache used internally to improve performance. Note that for memory
     * efficiency reasons, the cache will automatically be cleared after a certain inactivity delay.
     */
    public void clearCache() {
        sHardBitmapCache.clear();
        sSoftBitmapCache.clear();
    }

    /**
     * Allow a new delay before the automatic cache clear is done.
     */
    private void resetPurgeTimer() {
        purgeHandler.removeCallbacks(purger);
        purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);
    }
}

[LiNuX] Debian 7 에 SAMBA 서버 구성



1. 패키지 설치

root@debian:~# apt-get install samba samba-common samba-common-bin


2. 삼바 데몬 정지

root@debian:~# /etc/init.d/samba status
[ ok ] nmbd is running.
[ ok ] smbd is running.
root@debian:~# /etc/init.d/samba stop
[ ok ] Stopping Samba daemons: nmbd smbd.


3. 삼바 디렉토리 생성

root@debian:~# mkdir -p /home/samba-shared
root@debian:~# chmod -R 777 /home/samba-shared/


4. 환경 설정 파일 수정

root@debian:~# vi /etc/samba/smb.conf

* 가장 아래  다음의 내용을 수정 하여 추가 한다.

[samba-shared]
comment = 공유 폴더
path = /home/samba-shared
read only = no
valid users = freecatz user1 user2
writable = yes
public = yes
create mask = 0777
directory mask = 0777


5. 삼바 서버 시작

root@debian:~# /etc/init.d/samba start
[ ok ] Starting Samba daemons: nmbd smbd.
root@debian:~# /etc/init.d/samba status
[ ok ] nmbd is running.
[ ok ] smbd is running.



6. 사용자 계정 추가


* 참고 : 우선 리눅스 시스템에 유저가 생성 되어 있어야 한다.
         시스템에 없는 계정을 등록 하려고 하는 경우 아래와 같은 에러 메세지가 나온다.

root@debian:~# smbpasswd -a tester
New SMB password:
Retype new SMB password:
Failed to add entry for user tester.

이러한 경우 useradd 명령어를 이용하여 사용자를 추가 하고 나서 smbpasswd 를 이용하여
삼바 계정을 생성 하도록 한다.


root@debian:~# smbpasswd -a freecatz
New SMB password:
Retype new SMB password:
Added user freecatz.

root@debian:~# smbpasswd -a user1
root@debian:~# smbpasswd -a user2
...


7. 공유디렉토리 접근 확인

\\192.168.0.10\samba-shared


8. 삼바 유저 확인

root@debian:~# pdbedit -w -L


9. 비밀번호 변경

root@debian:~# smbpasswd <계정>


10. 계정 삭제

root@debian:~# smbpasswd -x <계정>

2014년 8월 18일 월요일

[LiNuX] Debian 7 에 SVN 서버 구성



1. SVN 패키지 설치

root@debian:~# apt-get install subversion libapache2-svn apache2-mpm-worker


2. SVN 모듈 동작 확인

root@debian:~# a2enmod dav_svn
Considering dependency dav for dav_svn:
Module dav already enabled
Module dav_svn already enabled


3. SVN 디렉토리 생성 및 지정

root@debian:~# mkdir -p /home/svn/web
root@debian:~# svnadmin create /home/svn/web

root@debian:~# mkdir -p /home/svn/android
root@debian:~# svnadmin create /home/svn/android

* svnadmin 명령으로 디렉토리 생성이 안되는 경우 mkdir 명령을 사용하여 디렉토리를 만든 후 avnadmin 명령으로 디렉토리를 지정해 주도록 한다.



4. 디렉토리 권한 설정 

root@debian:~# chown www-data:www-data /home/svn/ -R


5. 환경설정 파일 수정

root@debian:~# vi /etc/apache2/mods-enabled/dav_svn.conf
<Location /svn/web>
   DAV svn
   SVNPath /home/svn/web
 
   AuthType Basic
   AuthName "Web Project Repository"
   AuthUserFile /etc/apache2/dav_svn.passwd
   Require valid-user
</Location>

<Location /svn/android>
   DAV svn
   SVNPath /home/svn/android
 
   AuthType Basic
   AuthName "Android Project Repository"
   AuthUserFile /etc/apache2/dav_svn.passwd
   Require valid-user
</Location>


6. SVN 사용자 계정 생성(최초 사용자 생성시 c 옵션을 적용)

root@debian:~# htpasswd -mc /etc/apache2/dav_svn.passwd <계정명>
root@debian:~# htpasswd -m /etc/apache2/dav_svn.passwd <계정명>


6-1. SVN 사용자 계정 삭제

root@debian:~# htpasswd -D /etc/apache2/dav_svn.passwd <계정명>


6-2. 비밀번호 변경 

root@debian:~# htpasswd /etc/apache2/dav_svn.passwd <계정명>

7. 아파치 서버 재시작

# /etc/init.d/apache2 restart


8. 브라우저를 이용하여 확인

http://192.168.0.10/svn/web
http://192.168.0.10/svn/android




2014년 8월 8일 금요일

[EtC] Play 2.2.4 Framework 개발 환경 구성


*참고 : play 명령 대신 activator 명령어를 사용 하시기 바랍니다.
          이 문서를 작성할 당시에는 play 명령어를 사용하여 작성 되었으나,
          activator 명령어가 play명령과 거의 동일한 옵션으로 실행 가능 합니다.



1. 플레이 프레임워크 다운로드

http://www.playframework.com/download 에서 다운로드 받아 적당한 디렉토리에 압축을 푼다. 이 문서에서는 play-2.2.4.zip 를 내려 받았다. 다른 버젼도 크게 다른 부분은 없을듯.


참고 : 플레이 프레임워크를 사용 하기 위해서는 jdk 가 설치 되어 있어야 한다.
          이 문서에서는 jdk 설치 와 환경 설정등은 다루지 않는다.


2. 압축 해제




적당한 디렉토리에 압축을 해제 한다.


3. play 명령어 사용을 위한 환경 변수 설정



어느 위치에서든 play.bat 명령어를 사용할 수 있도록 PATH 전역 변수에 등록 시켜 둔다.


4. play 명령어 실행 확인



위의 그림과 같이 play 명령을 실행해 본다. 명령이 실행 되지 않는다면, PATH 설정에 잘못된 부분이 있는지 확인 하도록 한다.


5. 프레임워크 구성 요소 내려 받기

프레임워크의 압축을 해제한 디렉토리로 이동 하여 위의 그림과 같이 builld.bat clean 을 실행 한다. 시간이 조금 오래 걸리니 기다리도록 한다. 

이 문서에서는 플레이 프레임워크를 D:\devel\play-2.2.4 에 압축 해제 하였으며, D:\devel\play-2.2.4\framework\ 로 이동 하여 명령어를 실행 하였다.


build.bat publish-local 명령을 실행 하도록 한다. 이 과정도 오래 걸리니 기다리도록 한다.


6. 테스트용 프로젝트 생성



프로젝트를 생성할 디렉토리로 이동 하여 위의 그림과 같이 play 명령어를 사용하여 프로젝트를 생성 한다.

d:\devel\workspace\play> play new helloworld

중간에 어떤 언어를 이용 할 것인지 묻는데, 프로젝트를 구성 하고자 하는 언어를 선택 후 엔터키를 입력 한다.


명령어를 실행 하고 나서 해당 디렉토리로 이동 하여 보면, 위의 그림과 같이 파일과 디렉토리가 자동으로 생성 되어 있을 것이다.



7. 생성된 프로젝트의 실행 및 확인

생성된 프로젝트 디렉토리로 이동 하여 play 명령을 사용 하면, play 명령 콘솔로 진입 하게 된다. 위의 그림과 같이 play 명령 콘솔로 진입 후, run 명령을 사용하여 프로젝트를 실행 시키도록 한다.

참고 : 처음에 프로젝트 디렉토리에서 play 명령을 사용하게 되면 어느 정도 시간이 걸리니 기다리도록 한다.



play 명령 콘솔에서 run 명령어 사용후, 위의 그림과 같이 "Server Started" 라는 메세지가 나오면 실행이 완료된 상태로, 브라우저를 통해서 확인 할 수 있다.
종료는 Ctrl + D 키를 누르면 play 명령 콘솔로 나올 수 있다.



위의 그림과 같이 브라우저에서 http://localhost:9000 의 주소로 접근하여 확인 할 수 있다.



8. 개발 하기 위한 Eclipse 환경 설정

생성한 프로젝트의 디렉토리로 이동 하여, 위의 그림과 같이 play eclipse 명령을 실행 한다. 조금 오래 걸릴수도 있으니 기다리도록 한다. 

이 과정에서는 이클립스에서 프로젝트를 import 할 수 있도록 프로젝트 디렉토리에 파일을 생성해 주는 단계. 명령이 완료 되면, 이제 이클리스를 실행 하도록 한다.


Project Explorer 윈도우 에서 마우스 오른쪽 버튼, 나타나는 메뉴에서 Import -> Import 를 선택 한다.



나타나는 팝업창에서 "EXisting Projects into Workspace" 를 선택 후 Next 버튼 클릭.



생성한 프로젝트의 디렉토리를 선택 후 "Finish" 버튼을 클릭 한다.



일단, 프로젝트의 import 는 되었으나 스칼라 언어의 자동완성이나 색상 표시를 해결 해야 한다.





9. 이클립스에 스칼라 언어 플러그인 설치




http://download.scala-ide.org/sdk/helium/e38/scala210/stable/site

플레이 프레임워크 2.4.4 에서는  스칼라 2.10 버젼대를 사용하고 있다. 



위의 그림과 같이 두개의 항목을 선택 후, "Next" 버튼을 클릭 한다.



"Next"  버튼을 클릭 하여 다음 단계로 진행 한다.



라이선스에 동의 선택 후, "Finish" 버튼을 클릭 하여 다음 단계로 진행 한다.



설치 하는 동안 잠시 기다리도록 한다.



이클립스를 다시 시작 한다.



스칼라 플러그인 업데이트를 감지 하였다고 한다. 필요한 경우 업데이트를 받도록 한다.



코드에 색상이 들어가 있다. 스칼라 코드를 살펴 보았으나, 아직은 이해 불가...


이후, 시간이 나면 다시 계속 작성할 예정...