오늘은 HTTP 통신을 하기 위한 HttpURLConnection에 대해 포스팅하겠습니다. 아래 링크는 통신하는데 있어서 같이 사용할 API에 대해 포스팅한 글입니다. 참고해주시기 바랍니다.
데이터 처리 JSON Object : http://mailmail.tistory.com/11
비동기처리 AsyncTask : http://mailmail.tistory.com/12


1. 안드로이드의 서버 통신


안드로이드는 서버와 통신하기 위한 방법으로는 HTTP통신과 Soket통신이 있다. 오늘 다룰 내용은 HTTP통신으로 URL 접속을 통해 데이터를 읽어오는 방법이다. 우리는 주로 DB에 존재하는 데이터를 가져오기 위해 서버 통신을 한다. 하지만 안드로이드의 특성상 외부 DB에 직접 접근할 수 가 없도록 되어있어 중간 매체인 WEB을 활용해야한다. 이해를 위해 그림으로 쉽게 표현했다.  


[그림 1] 안드로이드와 WEB, 그리고 DB 통신 과정


안드로이드 통신은 보통 위와 같이 이뤄지며 본 포스팅에서는 'ANDROID-WEB'의 통신 방법에 대해 다룰 것이다. 우선 막연하게 WEB이라고만 하면 정확히 어떤 것을 말하는 것인지 헷갈릴 수 있다. 여기서 말하는 WEB은 HTML, JSP, PHP, SERVLET 등이 있다. 자신의 DB와 WEB에서 데이터를 가져오기 위해선 호스팅이나 개인서버를 구축한 후, 자신이 만든 WEB문서가 포함된 로컬호스트 주소에 접속해서 데이터를 얻어와야한다. 호스팅이나 서버구축에 관한 내용은 다음에 포스팅할 계획에 있으므로 지금 당장은 타 블로그의 포스팅을 참고하길 바란다.



2. Network Processing In Android

이전에 '비동기 처리를 위한 AsyncTask'라는 제목으로 포스팅을 했다. 안드로이드 어플리케이션 구현에 있어서 아주 중요한 부분인데 HttpURLConnection을 직접 구현하고 싶다면 저 포스팅을 보고 오길 강력 추천한다. 일단 간단히 설명하자면 안드로이드의 UI관련 처리는 메인쓰레드가 담당한다. 메인쓰레드는 UI쓰레드라고도 하며, 우리가 특정 명령으로 수행시키지 않아도 자동으로 수행된다. 조금 더 구체적으로 말하자면 액티비티를 띄우고 액티비티 내에서 일어나는 모든 처리는 메인쓰레드가 담당한다고 보면된다. 즉 onCreate()에서의 코드 수행도 메인쓰레드가 처리하며 혼자서 처리하다보니 동시 작업에 무리가 있다. 즉 현재 띄워진 액티비티 내에서 다운로드와 같은 오랜시간 동안 수행하는 작업을 메인쓰레드가 하게되면 다른 UI가 먹통이 된다. 모두 메인쓰레드가 처리하기 때문에 다운로드가 끝나기 전엔 다른 작업을 수행할 수 없는 것이다. 이를 해결하기 위해 비동기 처리를 해야하며 안드로이드 제공하는 AsyncTask라는 별도의 쓰레드를 이용한다. AsyncTask를 사용하면 메인쓰레드와 별개로 작업이 수행되며 수행 후 결과만 메인쓰레드에 넘겨주어 효율적인 작업이 이뤄질 수 있다. "아 귀찮아. 그냥 난 메인쓰레드에서 돌릴거야!"라고 생각하시는 분들도 계실텐데.. 실제로 돌려보면 Runtime Exception과 함께 다음과 같은 예외 상황이 발생한다.
android.os.NetworkOnMainThreadException !!
따라서 AsyncTask를 사용하길 적극 권장한다.
  '비동기 처리를 위한 AsyncTask' : http://mailmail.tistory.com/12



3. HttpURLConnection 구현과 소스 코드

안드로이드에서 HttpURLConnection를 통해 해당 URL에 출력되는 결과물을 얻어올 수 있다. 


다음은 구성할 파일목록입니다.
1. AndroidManifest.xml : 기존 매니페스트 파일 내용에 퍼미션만 추가
2. MainActivity.class : 위젯 처리 및 AsyncTask 구현
3. RequestHttpURLConnection.class : HttpURLConnection 수행
4. activity_main.xml : 레이아웃 설정



우선 첫번째로 'INTERNET' 대한 권한을 주어야한다.
in AndroidManifest.xml 
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="peace.httpurlconnection">

<!-- 네트워크 사용에 대한 퍼미션 -->
<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"

두번째로 별도의 클래스(RequestHttpURLConnection)를 만들어 HttpURLConnection을 수행하는 기능 구현한다.

in RequestHttpConnection.class 

import android.content.ContentValues;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;

public class RequestHttpURLConnection {

public String request(String _url, ContentValues _params){

// HttpURLConnection 참조 변수.
HttpURLConnection urlConn = null;
// URL 뒤에 붙여서 보낼 파라미터.
StringBuffer sbParams = new StringBuffer();

/**
* 1. StringBuffer에 파라미터 연결
* */
// 보낼 데이터가 없으면 파라미터를 비운다.
if (_params == null)
sbParams.append("");
// 보낼 데이터가 있으면 파라미터를 채운다.
else {
// 파라미터가 2개 이상이면 파라미터 연결에 &가 필요하므로 스위칭할 변수 생성.
boolean isAnd = false;
// 파라미터 키와 값.
String key;
String value;

for(Map.Entry<String, Object> parameter : _params.valueSet()){
key = parameter.getKey();
value = parameter.getValue().toString();

// 파라미터가 두개 이상일때, 파라미터 사이에 &를 붙인다.
if (isAnd)
sbParams.append("&");

sbParams.append(key).append("=").append(value);

// 파라미터가 2개 이상이면 isAnd를 true로 바꾸고 다음 루프부터 &를 붙인다.
if (!isAnd)
if (_params.size() >= 2)
isAnd = true;
}
}

/**
* 2. HttpURLConnection을 통해 web의 데이터를 가져온다.
* */
try{
URL url = new URL(_url);
urlConn = (HttpURLConnection) url.openConnection();

// [2-1]. urlConn 설정.
urlConn.setRequestMethod("POST"); // URL 요청에 대한 메소드 설정 : POST.
urlConn.setRequestProperty("Accept-Charset", "UTF-8"); // Accept-Charset 설정.
urlConn.setRequestProperty("Context_Type", "application/x-www-form-urlencoded;cahrset=UTF-8");

// [2-2]. parameter 전달 및 데이터 읽어오기.
String strParams = sbParams.toString(); //sbParams에 정리한 파라미터들을 스트링으로 저장. 예)id=id1&pw=123;
OutputStream os = urlConn.getOutputStream();
os.write(strParams.getBytes("UTF-8")); // 출력 스트림에 출력.
os.flush(); // 출력 스트림을 플러시(비운다)하고 버퍼링 된 모든 출력 바이트를 강제 실행.
os.close(); // 출력 스트림을 닫고 모든 시스템 자원을 해제.

// [2-3]. 연결 요청 확인.
// 실패 시 null을 리턴하고 메서드를 종료.
if (urlConn.getResponseCode() != HttpURLConnection.HTTP_OK)
return null;

// [2-4]. 읽어온 결과물 리턴.
// 요청한 URL의 출력물을 BufferedReader로 받는다.
BufferedReader reader = new BufferedReader(new InputStreamReader(urlConn.getInputStream(), "UTF-8"));

// 출력물의 라인과 그 합에 대한 변수.
String line;
String page = "";

// 라인을 받아와 합친다.
while ((line = reader.readLine()) != null){
page += line;
}

return page;

} catch (MalformedURLException e) { // for URL.
e.printStackTrace();
} catch (IOException e) { // for openConnection().
e.printStackTrace();
} finally {
if (urlConn != null)
urlConn.disconnect();
}

return null;

}

}


세번째로 MainActivity에서의 위젯을 처리하고 Network Processing에 대해서 비동기 처리하기 위해 AsyncTask를 구현한다. 

in MainActivity.class    

import android.content.ContentValues;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private TextView tv_outPut;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 위젯에 대한 참조.
tv_outPut = (TextView) findViewById(R.id.tv_outPut);

// URL 설정.
String url = "http:// .cafe24.com/LoadPat ";

// AsyncTask를 통해 HttpURLConnection 수행.
NetworkTask networkTask = new NetworkTask(url, null);
networkTask.execute();
}

public class NetworkTask extends AsyncTask<Void, Void, String> {

private String url;
private ContentValues values;

public NetworkTask(String url, ContentValues values) {

this.url = url;
this.values = values;
}

@Override
protected String doInBackground(Void... params) {

String result; // 요청 결과를 저장할 변수.
RequestHttpURLConnection requestHttpURLConnection = new RequestHttpURLConnection();
result = requestHttpURLConnection.request(url, values); // 해당 URL로 부터 결과물을 얻어온다.

return result;
}

@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);

//doInBackground()로 부터 리턴된 값이 onPostExecute()의 매개변수로 넘어오므로 s를 출력한다.
tv_outPut.setText(s);
}
}
}


다음은 MainActivity.class에 대한 레이아웃이다.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="peace.httpurlconnection.MainActivity">

<TextView
android:id="@+id/tv_outPut"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="출력 공간"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</RelativeLayout>




4. 구현 화면


필자는 호스팅된 주소에 접속해 출력 결과를 TextView에 출력했다.





#참고사항

안드로이드에서 HttpURLConnection를 사용하는데 많은 기능을 제공한다.

예를들어 타임 제한을 걸어둘 수 있는 메서드도 있다. 안드로이드 디벨로퍼를 참고하길 바란다.

https://developer.android.com/reference/java/net/HttpURLConnection.html