2010년 10월 15일 금요일

안드로이드 에뮬레이터 네트워킹 설정 (Android Emulator Networking)

Network address space

실행되는 각 에뮬레이터 인스턴스는 개발머신의 네트웍 인터페이스나 설정과 분리된 가상 라우터/방화벽 뒤에서 실행된다. 에뮬레이트 되는 디바이스는 개발머신이나 다른 에뮬레이터 인스턴스를 볼 수가 없다. 이더넷을 통해 라우터/방화벽에 연결된걸로만 보이게 된다.

각 인스턴스를 위한 가상 라우터는 10.0.2/24 네트웍 주소 공간을 관리한다. - 라우터가 관리하는 모든 주소는 10.0.2.<xx>의 형태를 가지고 여기서 <xx>는 숫자이다. 이 공간내의 주소는 에뮬레이터/라우터에 의해 다음과 같이 미리 할당되어 있다.

Network Address      Description
-------------------  ------------
10.0.2.1             Router/gateway address
10.0.2.2             Special alias to your host loopback interface (i.e., 127.0.0.1 on your development machine)
10.0.2.3             First DNS server
10.0.2.4 / 10.0.2.5 / 10.0.2.6 Optional second, third and fourth DNS server (if any)
10.0.2.15            The emulated device's own network/ethernet interface
127.0.0.1            The emulated device's own loopback interface 

실행되는 모든 에뮬레이터 인스턴스는 동일한 주소 할당을 사용하고 있음을 주의해라. 즉 한 개발머신에 두개의 에뮬레이터 인스턴스가 동시에 실행되고 있을 때 각각의 에뮬레이터는 자신의 라우터를 가지고 그 라우터 뒤에서 동일한 IP 주소인 10.0.2.15를 가지게 된다. 에뮬레이터 인스턴스는 라우터로 분리되고 서로를 볼 수 없다.

또한 개발머신의 127.0.0.1은 에뮬레이터의 자체 루프백 인터페이스에 대응한다. 개발머신의 루프백 인터페이스(127.0.0.1)에서 실행되고 있는 서비스를 억세스 하고 싶으면 127.0.0.1 대신 특별 주소인 10.0.2.2를 사용해야 한다.

마지막으로 각 에뮬레이터 디바이스의 미리 할당된 주소들은 안드로이드 에뮬레이터에 한정된 것이고 실제 디바이스에서는 완전히 다를 수도 있음을 주의해야만 한다.


Local Networking Limitations

각 에뮬레이터 인스턴스는 가상 라우터 뒤에서 실행되지만 실제 라우터를 통해 인터넷에 연결되는 실제 디바이스와는 다르게 에뮬레이터 디바이스는 물리 네트웍을 억세스 할 수 없다. 그 대신 개발머신상의 일반적 어플리케이션의 일부분으로 실행된다. 즉 개발머신에서 실행되는 다른 어플리케이션같이 동일 네트웍 제한을 받게 된다.

   * 에뮬레이터 디바이스와의 통신은 개발머신에서 실행되는 방화벽 프로그램에 의해 막힐 수도 있다.
   * 에뮬레이터 디바이스와의 통신은 개발머신이 연결되어 있는 다른 (물리적) 방화벽/라우터에 의해 막힐 수도 있다.

에뮬레이터의 가상 라우터는 개발머신의 네트웍 환경이 허용해주면 모든 outbound TCP/UDP 커넥션/메시지를 에뮬레이터 디바이스의 대리로 처리할 수 있어야 한다. 호스트 OS와 네트웍에 의해 제한되지 않으면 포트번호나 범위에 대한 별다른 제한은 없다.

환경에 따라 에뮬레이터는 다른 프로토콜을 지원하지 않을 수 있다. 현재 에뮬레이터는 IGMP나 멀티캐스트를 지원하지 않는다


Using Network Redirections

가상 라우터 뒤에 있는 에뮬레이터 인스턴스와 통신하기 위해서는 가상 라우터의 네트웍 리다이렉션을 설정해 줘야 한다. 그러면 클라이언트는 라우터의 지정된 게스트 포트로 연결하고 라우터가 그 포트로 오가는 트래픽을 에뮬레이터 디바이스의 호스트 포트로 포워딩하게 된다.

네트웍 리다이렉션을 설정하려면 에뮬레이터 인스턴스에서 호스트/게스트 포트/주소를 매핑해야 한다. 네트웍 리다이렉션을 설정하는데 두가지 방법이 있다. 에뮬레이터 콘솔 명령어를 사용하거나 ADB 툴을 사용하면 된다.


Setting up Redirections through the Emulator Console

각 에뮬레이터 인스턴스는 그 인스턴스에 명령어를 실행할 수 있도록 연결할 수 있는 제어 콘솔을 제공한다. 리다이렉션을 설정하려면 redir 콘솔 명령어를 사용할 수 있다.

먼저 타겟 에뮬레이터 인스턴스의 콘솔 포트 번호를 알아야 한다. 예를 들어 첫번째 실행된 에뮬레이터 인스턴스를 위한 콘솔포트는 5554이다. 다음 명령어를 사용해서 타겟 에뮬레이터 인스턴스를 위한 콘솔에 연결한다.

% telnet localhost 5554

연결되면 redir 명령어를 사용해서 리다이렉션을 설정할 수 있다. 리다이렉션을 추가하려면 다음과 같다.

redir add <protocol>:<host-port>:<guest-port>

여기서 <protocol>은 tcp 또는 udp, <host-port>와 <guest-port>는 각각 개발머신과 에뮬레이터 시스템간의 매핑을 설정한다.

예를 들어 호스트(개발머신)의 127.0.0.1:5000으로 들어오는 모든 TCP 커넥션을 에뮬레이터 디바이스의 10.0.2.15:6000으로 리다이렉션 하도록 설정하려면 다음의 명령어를 사용하면 된다.

redir add tcp:5000:6000

리다이렉션을 삭제하려면 redir del 명령어를 사용하면 된다. 특정 에뮬레이터 인스턴스의 모든 리다이렉션 목록을 보고 싶으면 redir list명령어를 사용한다.

포트번호는 로컬 환경에 의해 제한된다는걸 주의해라. 즉 일반적으로 관리자 권한이 없으면 1024번 이하의 포트 번호는 사용할수가 없다. 또한 호스트의 다른 프로세스가 이미 사용하고 있는 호스트 포트를 리다이렉션 하려고 하면 redir은 에러메시지를 발생한다.


Setting Up Redirections through ADB

ADB 툴로 포트 포워딩을 설정할수도 있다.  또한 현재 ADB는 ADB서버를 kill하는것 이외에는 리다이렉션을 없앨 수 있는 방법을 제공하지 않는다. 상세한 내용은 ADB 문서의 Forwarding Ports를 참고하면 된다.


Configuring the Emulator's DNS Settings

처음 시작될 때 에뮬레이터는 호스트가 현재 사용하고 있는 DNS 서버 목록을 읽는다. 그래서 그 목록중에 최대 4개까지의 서버 주소를 기록하고 각각의 주소를 에뮬레이터의 10.0.2.3, 10.0.2.4, 10.0.2.5, 10.0.2.6으로 alias한다.

리눅스와 OS X에서 에뮬레이터는 /etc/resolv.conf 파일을 파싱해서 DNS 서버 주소를 가져온다. 윈도우에서는 GetNetworkParams() API를 호출해서 주소를 얻어온다. 즉 에뮬레이터는 hosts 파일(리눅스나 OS X에서는 /etc/hosts, 윈도우는 %WINDOWS%\system32\HOSTS)의 내용을 무시한다는 것이다.

명령어 라인에서 에뮬레이터를 시작할 때 -dns-server <serverList> 옵션을 사용해서 수동으로 사용할 DNS 서버 주소를 지정할수도 있다. <serverList>는 콤마로 구분된 서버 이름 또는 IP 주소 목록이다.


Using the Emulator with a Proxy

에뮬레이터가 프락시 서버를 통해 인터넷을 억세스 해야 하는 경우 에뮬레이터를 시작할 때 -http-proxy <proxy> 옵션을 사용해 적절하게 리다이렉션 하도록 설정할 수 있다. 이 경우 <proxy>의 프락시 정보는 다음중의 한가지 포맷을 사용할 수 있다.

http://<machineName>:<port> 또는 http://<username>:<password>@<machineName>:<port>

-http-proxy 옵션은 에뮬레이터가 모든 outgoing TCP 커넥션을 지정된 HTTP/HTTPS 프락시를 사용하도록 해 준다. UDP 리다이렉션은 현재 지원하지 않는다.

다른 방법으로 http_proxy 환경변수에 <proxy>의 값을 넣어 정의할수도 있다. 이 경우 -http-proxy 명령어에 <proxy> 값을 지정할 필요가 없다. 에뮬레이터가 시작될 때 http_proxy 환경변수의 값을 확인해서 변수값이 정의되어 있으면 자동으로 그 값을 사용한다.

프락시 커넥션 문제를 검사하려면 -debug-proxy 옵션을 사용할 수도 있다.


Interconnecting Emulator Instances

하나의 에뮬레이터 인스턴스가 다른 인스턴스와 통신을 할 수 있도록 하려면 다음과 같이 필요한 네트웍 리다이렉션을 설정해 줘야만 한다.

환경이 다음과 같다고 가정하겠다.

   * A는 개발머신(호스트)
   * B는 A에서 실행되는 첫번째 에뮬레이터 인스턴스
   * C는 A에서 실행되는 두번째 에뮬레이터 인스턴스

그리고 C가 연결할 서버는 B에서 실행한다고 가정한다.

  1. B에서 서버를 설정해서 10.0.2.15:<serverPort>를 listen하도록 한다.
  2. B의 콘솔에서 A:localhost:<localPort>에서 B:10.0.2.15:<serverPort>로 리다이렉션을 설정한다.
  3. C에서 클라이언트가 10.0.2.2:<localPort>로 접속한다.
예를 들면 HTTP 서버를 실행하기를 원하는데 <serverPort>는 80번으로 하고 <localPort>는 8080으로 하는 경우의 설정은 다음과 같다.

   * B는 10.0.2.15:80을 listen
   * B의 콘솔에서 redir add tcp:8080:80 을 실행
   * C가 10.0.2.2:8080으로 접속

2010년 10월 14일 목요일

CdS 광센서 사용하기 (Using CdS photoresistor)


CdS는 빛의 세기에 따라 저항값이 변하는 센서이다.

밝기에 따른 저항값의 변화는 다음과 같다. 참고로 y-축(저항값)은 로그 스케일이다.

아뒤노등의 마이크로 컨트롤러에 연결할 때는 다음과 같이 회로를 구성하고 V1을 마이크로컨트롤러의 Analog input에 연결해주면 된다.




위와 같은 회로일 때 출력 전압값을 구하는 공식은 다음과 같다.

여기서는 Vcc=5V, R1=10Kohm을 사용하고 있기 때문에 그 값을 위의 식에 대입해주면 된다.



즉 10 Lux정도의 밝기라 CdS의 저항값이 10Kohm인 경우 V1은 2.5V가 되고 매우 어두워서 CdS의 저항값이 200Kohm인 경우 V1은 4.76V가 된다.

2010년 10월 12일 화요일

Parcelable을 사용한 오브젝트 전달 (Object serialization using Parcelable)



앱을 만들다 보면 인텐트를 통해 단순히 String, int, boolean 같은 기본 타입 뿐 아니고 커스텀 클래스나 오브젝트를 다른 컴포넌트에 전달해 줘야 할 경우가 많다. 그 경우 단순히 그냥 인텐트에 putExtra() 로는  넣어줄 수가 없다.
안드로이드에서는 그런 경우를 위해 자바의 Serialization 개념과 유사한 Parcelable이라는 클래스가 있다.

먼저 이런것이 왜 필요한가 살펴보겠다. 예를 들어 다음과 같은 클래스가 있다고 하자.

public class BookData {
  int _id;
  String title;
  String author;
  String publisher;
  int price;
}

도서관리 앱에서 ListView로 화면에 표시하기 위해 ArrayList<BookData>에 책들의 정보를 넣어 인텐트로 넘겨주려고 하면 BookData 클래스를 그대로 사용할수는 없다.

오브젝트를 Parcelable 클래스로 만들어 주려면 android.os.Parcelable 인터페이스를 구현해야 한다. 그러므로 아래와 같이 클래스 정의를 변경한다.

public class BookData implements Parcelable {
  int _id;
  String title;
  String author;
  String publisher;
  int price;
}

그리고 android.os.Parcelable 인터페이스에 있는 2개의 메소드를 오버라이드 해 줘야만 한다.

describeContents() - Parcel 하려는 오브젝트의 종류를 정의한다.
writeToParcel(Parcel dest, int flags) - 실제 오브젝트 serialization/flattening을 하는 메소드. 오브젝트의 각 엘리먼트를 각각 parcel해줘야 한다.

public void writeToParcel(Parcel dest, int flags) {
  dest.writeInt(_id);
  dest.writeString(title);
  dest.writeString(author);
  dest.writeString(publisher);
  dest.writeInt(price);
}

다음으로 해야 할 일은 Parcel에서 데이터를 un-marshal/de-serialize하는 단계를 추가해줘야 한다. 그러기 위해서 Parcelable.Creator 타입의 CREATOR라는 변수를 정의해야 한다. 이 변수를 정의하지 않으면 안드로이드는 다음과 같은 익셉션을 발생한다.

Parcelable protocol requires a Parcelable.Creator object called CREATOR

아래는 위의 예제인 BookData 클래스를 위한 Parcelable.Creator<BookData>의 코드이다.

public class CustomCreator implements Parcelable.Creator<BookData> {
  public BookData createFromParcel(Parcel src) {
    return new BookData(src);
  }

  public BookData[] newArray(int size) {
    return new BookData[size];
  }
}

BookData.java에 모든 parcel된 데이터를 복구하는 생성자를 정의해 줘야만 한다.

  public BookData(Parcel src) {
    _id = src.readInt();
    title = src.readString();
    author = src.readString();
    publisher = src.readString();
    price = src.readInt();
  }

주의할것은 writeToParcel() 메소드에서 기록한 순서와 동일하게 복구해야만 한다.

전체 코드는 다음과 같다.

...
public class BookData implements Parcelable {
    private String title;
    private String author;
    private String publisher;
    private String isbn;
    private String description;
    private int price;
    private String photoUrl;
   
    public BookData() {
    }
   
    public BookData(Parcel in) {
       readFromParcel(in);
    }

    public BookData(String _title, String _author, String _pub, String _isbn, String _desc, int _price, String _photoUrl) {
         this.title = _title;
         this.author = _author;
         this.publisher = _pub;
         this.isbn = _isbn;
         this.description = _desc;
         this.price = _price;
         this.photoUrl = _photoUrl;
    }

// -------------------------------------------------------------------------
// Getters & Setters section - 각 필드에 대한 get/set 메소드들
// 여기서는 생략했음

// ....
// ....
// -------------------------------------------------------------------------
  

   public void writeToParcel(Parcel dest, int flags) {
           dest.writeString(title);
           dest.writeString(author);
           dest.writeString(publisher);
           dest.writeString(isbn);
           dest.writeString(description);
           dest.writeString(photoUrl);
           dest.writeInt(price);
   }

   private void readFromParcel(Parcel in){
           title = in.readString();
           author = in.readString();
           publisher = in.readString();
           isbn = in.readString();
           description = in.readString();
           photoUrl = in.readString();
           price = in.readInt();
   }
   
   public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        public BookData createFromParcel(Parcel in) {
             return new BookData(in);
       }

       public BookData[] newArray(int size) {
            return new BookData[size];
       }
   };
}

Parcelable 오브젝트를 인텐트로 보내는 경우는 다음과 같이 하면 된다.

BookData book = new BookData();
// 각 필드에 값을 넣어줌

Intent i = new Intent(this, ShowBook.class);
i.putExtra("bookInfo", book);
startActivity(i);

인텐트를 받을 ShowBook.java에서는 다음과 같이 Parcelable 오브젝트를 복구하면 된다.

Bundle bundle = getIntent().getExtras();
BookData book = bundle.getParcelable("bookInfo");

ArrayList<BookData>인 경우는 Intent를 만들어 보내는 쪽에서는 다음과 같이 하면 된다.

ArrayList<BookData> bookList = new ArrayList<BookData>();
...
// bookList.add() 메소드를 사용해서 bookList에 BookData 엔트리를 추가
...

Intent i = new Intent(this, BookList.class);
i.putParcelableArrayListExtra("myBooks", bookList);
startActivity(i);

BookList.java (인텐트에 의해 호출되는 액티비티)에서는 다음과 같이 오브젝트를 복구하면 된다.

ArrayList<BookData> bookList;
...
Intent i = getIntent();
bookList = i.getParcelableArrayListExtra("myBooks");