반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 받은 트랙백

글 보관함

Android Network Connection

2016. 8. 10. 16:53 | Posted by 솔웅


오늘은 어제 Udacity 강좌에서 참고 자료로 사용했던 Android Network Connection 관련 글 두개를 공부 해 봤습니다.


From Android Developer Training Material


Connecting to the Network

이 과정에서는 네트워크에 연결하는 간단한 어플리케이션을 구현하는 방법을 보여 드릴 겁니다. 아주 간단한 network-connected app을 생성하기 위한 가장 좋은 방법을 설명합니다.

네트워크를 사용하려면 manifest 에 아래 두 permission이 설정 돼 있어야 합니다.

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


Choose an HTTP Client

대부분의 network-connected Android app 들은 데이터를 주고 받기 위해 HTTP를 사용합니다. 안드로이드 플랫폼에는 HTTPS, streaming uploads and downloads,configurable timeouts, IPv6 그리고 connection pooling을 지원하는 HttpURLConnection client 를 포함하고 있습니다.



Check the Network Connection

여러분의 앱이 네트워크에 접속하려고 하기 전에 네트워크 접속이 가능한지 여부에 대해서 getActiveNetworkInfo() 와 isConnected() 를 사용해서 체크해야 합니다. 해당 전화기가 네트워크가 안되는 상황일 수 있기 때문입니다. 이와 관련해서 자세한 사항은 Managing Network Usage 를 참조하세요.

public void myClickHandler(View view) {
    ...
    ConnectivityManager connMgr = (ConnectivityManager)
        getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isConnected()) {
        // fetch data
    } else {
        // display error
    }
    ...
}

Perform Network Operations on a Separate Thread

네트워크 작업은 예상치 못한 지연이 있을 수 있습니다. 이런 상황에서 사용자에게 불편을 주지 않기 위해 네트워크 작업은 UI를 다루는 쓰레드와는 별도의 쓰레드에서 이루어지도록 해야 합니다. AsyncTask 클래스는 UI 쓰레드와 별도의 쓰레드를 사용하도록 하는 간단한 방법을 제공합니다. 여기에 대한 자세한 설명은 Multithreading For Performance 를 참조하세요.

아래 소스코드는 myClickHandler() 메소드가 new DownloadWebpageTask().execute(stringUrl) 를 invoke 하는 것을 다루고 있습니다. DownloadWebpageTask 클래스는 AsyncTask 의 subclass 입니다. DownloadWebpageTask 클래스는 AsyncTask methods 를 implement 합니다.

    doInBackground() 는 downloadUrl() 메소드를 실행합니다. 파라미터로 웹페이지의 URL을 전달합니다. downloadUrl() 메소드는 웹페이지 connection을 수행합니다. 이 작업이 끝나면 결과를 String 형식으로 전달해 줍니다.

    onPostExecute() 는 return 된 string을 받아서 UI에 display 합니다.

public class HttpExampleActivity extends Activity {
    private static final String DEBUG_TAG = "HttpExample";
    private EditText urlText;
    private TextView textView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        urlText = (EditText) findViewById(R.id.myUrl);
        textView = (TextView) findViewById(R.id.myText);
    }

    // When user clicks button, calls AsyncTask.
    // Before attempting to fetch the URL, makes sure that there is a network connection.
    public void myClickHandler(View view) {
        // Gets the URL from the UI's text field.
        String stringUrl = urlText.getText().toString();
        ConnectivityManager connMgr = (ConnectivityManager)
            getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
        if (networkInfo != null && networkInfo.isConnected()) {
            new DownloadWebpageTask().execute(stringUrl);
        } else {
            textView.setText("No network connection available.");
        }
    }

     // Uses AsyncTask to create a task away from the main UI thread. This task takes a
     // URL string and uses it to create an HttpUrlConnection. Once the connection
     // has been established, the AsyncTask downloads the contents of the webpage as
     // an InputStream. Finally, the InputStream is converted into a string, which is
     // displayed in the UI by the AsyncTask's onPostExecute method.
     private class DownloadWebpageTask extends AsyncTask<String, Void, String> {
        @Override
        protected String doInBackground(String... urls) {

            // params comes from the execute() call: params[0] is the url.
            try {
                return downloadUrl(urls[0]);
            } catch (IOException e) {
                return "Unable to retrieve web page. URL may be invalid.";
            }
        }
        // onPostExecute displays the results of the AsyncTask.
        @Override
        protected void onPostExecute(String result) {
            textView.setText(result);
       }
    }
    ...
}

이 소스코드의 실행 순서는 아래와 같습니다.

    1. 사용자가 버튼을 클릭하면 myClickHandler() 가 invoke 됩니다. 사용자가 입력한 URL을 AsyncTask subclass 인 DownloadWebpageTask 로 전달합니다.
     2. AsyncTask doInBackground() 메소드는 downloadUrl() 메소드를 호출합니다.
    3. downloadUrl() 메소드는 파라미터로 URL string을 받고 URL 객체를 생성하는데 이를 사용합니다.
    4. URL 객체는 HttpURLConnection 를 만드는데 사용됩니다.
    5. connection이 이뤄지면 HttpURLConnection 객체는 InputStream으로 웹페이지의 내용을 받아서 담습니다.
    6. InputStream은 readIt() 메소드에 전달됩니다. 이 메소드는 stream을 string으로 변환합니다.

    7. 마지막으로 AsyncTask의 onPostExecute() 메소드가 main activity의 UI에 string을 display 합니다.



Connect and Download Data



네트워크 트랜잭션을 다루는 쓰레드에서 데이터를 GET 하고 다운로드하도록 하기 위해 HttpURLConnection 를 사용할 수 있습니다. connect()를 호출한 후 getInputStream()을 호출함으로서 데이터의 InputStream을 받을 수 있습니다.

다음 소스코드에서는 doInBackground() 메소드가 downloadUrl() 메소드를 호출합니다. 이 downloadUrl() 메소드는 주어진 URL을 받아서 HttpURLConnection을 거쳐 네트워크에 연결하는데 사용합니다.

// Given a URL, establishes an HttpUrlConnection and retrieves
// the web page content as a InputStream, which it returns as
// a string.
private String downloadUrl(String myurl) throws IOException {
    InputStream is = null;
    // Only display the first 500 characters of the retrieved
    // web page content.
    int len = 500;

    try {
        URL url = new URL(myurl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setReadTimeout(10000 /* milliseconds */);
        conn.setConnectTimeout(15000 /* milliseconds */);
        conn.setRequestMethod("GET");
        conn.setDoInput(true);
        // Starts the query
        conn.connect();
        int response = conn.getResponseCode();
        Log.d(DEBUG_TAG, "The response is: " + response);
        is = conn.getInputStream();

        // Convert the InputStream into a string
        String contentAsString = readIt(is, len);
        return contentAsString;

    // Makes sure that the InputStream is closed after the app is
    // finished using it.
    } finally {
        if (is != null) {
            is.close();
        }
    }
}

getResponseCode() 메소드가 connection의 status code를 return 하는 것을 기억해 두세요. connection에 대한 추가적인 정보를 얻는에 아주 유용한 방법입니다. status code 가 200 이면 성공한 겁니다.



Convert the InputStream to a String



InputStream은 byte로 이뤄져 있습니다. 이 InputStream을 받으면 일반적으로 이것을 특정 데이터 타입으로 decode 하거나 변환합니다. 예를 들어 이미지 데이터를 다운로드 한다면 이것을 아래와 같이 decode 해야 할 것입니다.

InputStream is = null;
...
Bitmap bitmap = BitmapFactory.decodeStream(is);
ImageView imageView = (ImageView) findViewById(R.id.image_view);
imageView.setImageBitmap(bitmap);

아래 예제에서는 InputStream에 웹페이지의 문자를 받은 경우 입니다. InputStream을 String으로 변환하는 방법이죠. 이렇게 한 후 UI에 display 하게 됩니다.

// Reads an InputStream and converts it to a String.
public String readIt(InputStream stream, int len) throws IOException, UnsupportedEncodingException {
    Reader reader = null;
    reader = new InputStreamReader(stream, "UTF-8");
    char[] buffer = new char[len];
    reader.read(buffer);
    return new String(buffer);
}




Android’s HTTP Clients
Jesse Wilson

[This post is by Jesse Wilson from the Dalvik team. —Tim Bray]


대부분의 network-connected Android app 들은 데이터를 주고 받기 위해 HTTP를 사용합니다. 안드로이드 플랫폼에는 HTTPS, streaming uploads and downloads,configurable timeouts, IPv6 그리고 connection pooling을 지원하는 HttpURLConnection client 를 포함하고 있습니다.



Apache HTTP Client



DefaultHttpClient 와 그 사촌인 AndroidHttpClient 는 웹브라우저에 적합한 확장 가능한 HTTP 클라이언트 입니다. 이들은 방대하고 유연한 API들을 가지고 있습니다. 이들을 구현하는 것은 유용하지만 몇개의 버그들도 있습니다.

하지만 방대한 규모의 이 API는 오히려 사용하는데 헛갈리게 만들기도 합니다. 안드로이드 팀은 Apache HTTP Client 와 관련해 적극적으로 대응해서 일하지 않는 것 같습니다.



HttpURLConnection



HttpURLConnection은 일반적이고 간단한 HTTP client에 대해 사용됩니다. 이 클래스는 작게 시작했지만 지속적으로 개선해서 아주 사용하기 편리하게 만들어 졌습니다.

Froyo 이전에는 HttpURLConnection 에 버그들이 좀 있었습니다. 특히 readable InputStream에서 close()를 호출하는 경우 connection pool에 문제가 발생했었습니다. 이것을 해결하는 방법은 connection pooling을 disabling 하는 것 이었습니다.

private void disableConnectionReuseIfNecessary() {
    // HTTP connection reuse which was buggy pre-froyo
    if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
        System.setProperty("http.keepAlive", "false");
    }
}


Gingerbread에서는 transparent response compression를 추가했습니다. HttpURLConnection 은 이 헤더를 outgoing request에 자동적으로 추가했습니다. 그리고 해당 response에서도 그에 상응하도록 처리합니다.



Accept-Encoding: gzip



이것의 장점은 웹 서버에서 response를 압축하도록 client에서 설정하도록 한다는 것입니다. 프로그램에서 response 압축을 하도록 한 경우 이것을 어떻게 disable 하게 할 것인가가 이 문서에 나와 있습니다.

HTTP의 Content-Length header가 압축된 사이즈를 return 하기 때문에 압축되지 않은 데이터의 buffer의 사이즈를 재는 getContentLength()를 이용하면 에러가 납니다. 이 대신에 InputStream.read() returns -1 일 경우 response 에 대해 byte를 읽어야 합니다.

Gingerbread 에서는 이 HTTPS에 대해 여러가지 개선을 했습니다. HttpsURLConnectionSNI (Server Name Indication) 에 연결하는 것을 시도합니다. 이것은 IP 주소를 공유하기 위한 여러개의 HTTPS를 가능하도록 하는 겁니다. 또한 압축과 세션 티켓을 가능하도록 했습니다. connection이 실패하면 자동적으로 이러한 features 없이 retire 합니다. 이것은 up-to-date sever들에 접속할 때 오래된 서버들과의 정합성을 해치지 않으면서 HttpsURLConnection 를 효율적으로 사용할 수 있도록 합니다.

Ice Cream Sandwich 에서는 response cashe를 추가했습니다. cashe 가 인스톨 되면 HTTP request는 3개중 하나에 속하게 됩니다.

    * Fully cached responses가 local storage 로부터 직접적으로 serve 된다. 왜냐하면 어떤 네트워크 connection도 이러한 response를 즉시 가능하게 할 필요가 없기 때문입니다.

    * 조건부로 cached responses들은 웹서버에 의해 검증된 최신 정보를 가지고 있어야 한다. 클라이언트는 “Give me /foo.png if it changed since yesterday” 같은 request를 보냅니다. 그러면 서버는 업데이트 된 내용이나 304 Not Modified status를 return 하게 됩니다. 만약 내용이 변동되지 않았다면 download 되지 않을 것입니다.

    Uncached responses들은 웹에서 serve 된다. 이러한 responses들은 나중에 response cache에 저장될 것입니다.

HTTP response가 이것을 지원하는 기기에 대해 cache 하도록 하기 위해서는 reflection을 사용합니다. 이 예제는 Ice Cream Sandwich에 대한 response cache를 turn on 할 것입니다.

private void enableHttpResponseCache() {
    try {
        long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
        File httpCacheDir = new File(getCacheDir(), "http");
        Class.forName("android.net.http.HttpResponseCache")
            .getMethod("install", File.class, long.class)
            .invoke(null, httpCacheDir, httpCacheSize);
    } catch (Exception httpResponseCacheNotAvailable) {
    }
}

여러분은 HTTP response에 대한 cache header를 세팅하기 위해 웹 서버를 configure 해야 합니다.


Which client is best?



Apache HTTP 클라이언트는 Eclair와 Froyo에서 비교적 버그가 적게 나타난다.

Gingerbread 이상에서는 HttpURLConnection이 최상이다. 이것은 간단한 API이자 적은 노력으로 큰효과를 보인다. Transparent compression과 response caching를 사용하면 네트워크 사용량을 줄일 수 있다. 또한 속도를 높이고 배터리를 절약할 수 있다. 새로운 어플리케이션은 HttpURLConnection을 사용해야 한다.


반응형

Comment