티스토리 뷰

안드로이드

안드로이드 - 서버와 http통신하기

쉬엄쉬엄하자 2020. 2. 3. 14:36
728x90

이번에는 안드로이드 앱에서 서버와 http통신하는 방법에 대해 알아보겠습니다.

앱과 서버간의 연결을 유지해야 하는 서비스가 아니라면 굳이 개발과 유지보수가 어려운 소켓통신을 구현할 필요가 없죠. 가장 기본적인 예제로 간단하게 서버와 통신하는 방법에 대해 알아보고 최근 안드로이드 버전에서 발생하는 http통신 오류를 해결하는 방법에 대해서도 알아보겠습니다.

 

가장 먼저 http통신을 하기 위한 클래스를 생성합니다. 액티비티 클래스 안에 inner 클래스로 생성하여도 되지만 앱 전체에서 반복적으로 http통신을 원활하게 하기 위해선 따로 하나의 클래스로 생성하고 필요할때마다 인스턴스화 해서 사용하여야 합니다.

 

package guitar.academyservice;
import android.content.ContentValues;
import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.String;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;

public class HttpClient    {
    public String request(String _url, ContentValues params){
        HttpURLConnection urlConn = null;
        StringBuffer sbParams = new StringBuffer();

        if(params == null) sbParams.append("");
        else{
            boolean isAnd = false;
            String key;
            String value;

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

                if(isAnd == true) sbParams.append("&");

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

                if(isAnd == false && params.size() > 1) isAnd = true;
            }
        }

        try{
            String strParams = sbParams.toString();
            URL url = new URL(_url + strParams);
            Log.d("http_test", "url = " + url.toString());
            urlConn = (HttpURLConnection) url.openConnection();

            urlConn.setConnectTimeout(2000);
            urlConn.setRequestMethod("POST");
            urlConn.setDoInput(true);
            urlConn.setRequestProperty("Accept-Charset", "UTF-8");
            urlConn.setRequestProperty("Context_Type", "application/x-www-form-urlencoded;charset=UTF-8");

            Log.d("http_test", "params = " + strParams);

            if(urlConn.getResponseCode() != HttpURLConnection.HTTP_OK){
                Log.d("http_test", "getResponseFail");
                return null;
            }
            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){
            e.printStackTrace();
        }
        catch(IOException e){
            e.printStackTrace();
        }
        finally {
            if(urlConn != null)
                urlConn.disconnect();
        }

        return null;
    }
}

ContentValues 객체는 http통신할 때 파라미터를 생성하는 용으로 많이 쓰입니다.

사용법은 인스턴스 하나를 생성한뒤에 put(String keyName, String data) 메서드를 호출합니다.

put메서드는 두번째 매개변수로 각 자료형마다 오버로딩 되어있기 때문에 String이 아닌 데이터도 사용가능합니다.

 

ContentValues contentValues = new ContentValues();
contentValues.put("key", data);

그래서 http클라이언트 인스턴스의  request메소드에 contentvalues를 넘겨주면 먼저 데이터가 2개이상인지 여부에 따라 파라미터 사이에 &를 붙여 서버에 보낼 url을 완성시킵니다.

이 과정을 통해 완성된 url은 http://yourURL?param1&param2 ... 형식으로 완성되는것이죠.

 

url을 완성시킨 뒤에는 HttpUrlConnection 인스턴스를 생성하고 완성한 url로 요청을 보내 통신합니다.

 

if(urlConn.getResponseCode() != HttpURLConnection.HTTP_OK){
                Log.d("http_test", "getResponseFail");
                return null;
            }
            BufferedReader reader = new BufferedReader(new InputStreamReader(urlConn.getInputStream(), "UTF-8"));

이 부분에서 정상적으로 연결이 되어있는지 판단하고 서버에서 오는 응답 데이터를 버퍼에 저장합니다.

읽어들인 내용을 반복문을 통해 한줄씩 파싱하고 해당 내용을 리턴하여 서버와의 통신을 종료합니다.

 

이제 액티비티에서 이 Http클래스의 인스턴스를 생성해서 사용해보도록 하겠습니다.

먼저 서버통신을 하기 위해선 앱에서 권한을 부여해야하는데요. 이를 위해 메니페스트 파일에 아래 코드를 추가합니다.

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

그리고 네트워크 작업은 반드시 메인스레드가 아닌 서브스레드에서 진행을 해야합니다. 만약 메인스레드에서 http인스턴스를 생성하고 네트워킹을 시도할 경우 에러가 발생합니다. 그래서 서브스레드에서 네트워킹을 하는 방법으로 AsyncTask클래스를 사용합니다. 

 

네트워킹 작업이 필요한 액티비티에 inner클래스를 하나 생성합니다. 이 클래스는 AsyncTask클래스를 상속받아야합니다. 그리고 생성자로 url주소와, contentvalues를 받습니다. AsyncTask클래스를 사용할때는 보통 3개의 메서드를 오버라이딩 하는데요. onPreExcute(), doInBackground(), onPostExecute()입니다. 이 메서드를 쉽게 오버라이딩 하는 방법은 안드로이드 스튜디오에서 단축키 ctrl + O 를 눌러서 진행할 수 있습니다.

 

각 메서드의 설명을 간략히 드리면

onPreExcute()는 서브스레드에서 네트워킹 작업을 하기 전 호출 되는 메서드입니다. 보통 progressbar를 생성하고 초기화하는 작업을 합니다.

doInBackground()는 서브스레드에서 작업할 내용을 작성합니다. 우리는 여기서 본격적인 네트워킹을 한다고 보시면 됩니다.

onPostExcute()는 서브스레드에서 작업이 끝난 후 호출 되는 메서드입니다. 여기서 doInBackground()의 리턴값을 매개변수로 받습니다. 우리는 여기서 통신 후 결과값을 파싱하거나 생성했던 progressBar를 없애는 등의 작업을 진행합니다.

 

    public class NetworkTask extends AsyncTask {
        private String url;
        private ContentValues values;

        public NetworkTask(String url, ContentValues values) {
            this.url = url;
            this.values = values;
        }

        @Override
        protected void onPreExecute() {
            progressBar = findViewById(R.id.loading);
            progressBar.setVisibility(View.VISIBLE);
        }

        @Override
        protected Object doInBackground(Object[] objects) {
            String result;
            HttpClient httpClient = new HttpClient();
            result = httpClient.request(url, values);
            if (result == "" || result == null) {
                result = "main activity networking test result";
            }
            return result;
        }

        @Override
        protected void onPostExecute(Object o) {
            super.onPostExecute(o);
            progressBar.setVisibility(View.GONE);
            parseCourseList(o.toString());
            Log.d("main_test", "selected academy's courselist size = " + selectedCourse.pointList.size());
            Intent intent = new Intent(MainActivity.this, CourseActivity.class);
            intent.putExtra("course", selectedCourse);
            startActivity(intent);

            //Todo after httpNetworking.
            //ex)Intent, terminate progress, courselist setting
        }

제가 진행하고 있는 프로젝트의 코드 일부를 가져왔습니다. doInBackground메서드에서 위에서 생성한 HttpClient 인스턴스를 생성하고 request를 호출하여 result에 그 결과를 저장하였습니다. 그 뒤 doInBackground에서 result를 리턴하면 

result가 onPostExecute()메서드의 매개변수로 넘어갑니다. 이 메서드에서 결과값을 파싱하고 progressbar를 없앤뒤 다음 액티비티로 인텐트 해주었습니다. 만약 통신 오류로 제대로된 데이터를 받지 못했다면 데이터를 파싱하는 메서드 내부에서 적절한 예외처리를 해줍시다.

 

여기까지 기본적인 http통신하는 법에 대해 알아보았는데요. 최근 안드로이드 최신버전으로 업데이트 되면서 https통신이 아닌 통신작업은 기본적으로 차단되어있습니다. 

Cleartext HTTP traffic to ~~~~ not permitted 

만약 다음과 같은 오류가 로그캣에서 발견된다면 지금부터 설명하는대로 조치하시면 해결할 수 있습니다.

 

안드로이드에서 http 통신을 차단한것이기 때문에 수동으로 http통신을 가능하게 수정해주기만 하면 됩니다.

이를 위해서 먼저 res -> new -> android resources directory를 눌러 xml 폴더를 만듭니다. 이미 xml폴더가 있다면 바로 xml폴더에 network-security-config.xml 파일을 생성합니다. 그후 해당 파일에 다음과 같이 작성합니다.

<?xml version ="1.0" encoding ="utf-8"?><!--  Learn More about how to use App Actions: https://developer.android.com/guide/actions/index.html -->
<network-security-config>
    <base-config cleartextTrafficPermitted="true"/>
</network-security-config>

그리고 메니페스트 파일 application태그 내부에 xml설정을 적용시킵니다.

android:networkSecurityConfig="@xml/network_security-config"

이 두가지를 해주시면 최신 안드로이드 버전에서도 http통신이 가능해집니다.

 

이상 안드로이드에서 기본적인 http통신 하는법 이었습니다. 

안되시는분은 댓글 남겨주시고 한분한분 정성껏 답변해드리겠습니다 감사합니다.

댓글