2023.03.14 - 안드로이드 앱개발자 과정

CHA·2023년 3월 14일
0

Android



HTTP 통신

우리가 Web 에서 서버와 GET 방식과 POST 방식으로 통신했던것처럼, 안드로이드 에서 마찬가지로 서버와 통신하기 위해 GET 과 POST 방식으로 통신할 수 있습니다. 이번 테스트에서는 안드로이드에서의 HTTP 통신을 알아봅시다.

일단 테스트를 위해 화면 구성부터 해줍시다. 사용자의 입력을 받을 EditText 2개와, 서버로 정보를 GET 방식과 POST 방식으로 전송할 버튼 2개, 그리고 서버로 부터 응답을 받아와 화면에 표시해주는 TextView 하나를 먼저 설계해두었습니다.

<LinearLayout 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"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/et_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="이름을 입력하세요"
        android:inputType="text"/>
    <EditText
        android:id="@+id/et_message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="메세지를 입력하세요"
        android:inputType="textMultiLine"
        android:lines="5"
        android:gravity="top"/>

    <Button
        android:id="@+id/btn_get"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="GET METHOD"/>

    <Button
        android:id="@+id/btn_post"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="POST METHOD"/>

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="서버로부터 응답된 결과"
        android:textColor="@color/black"
        android:padding="8dp"/>
    
</LinearLayout>

GET 방식으로 서버와 통신하기

우리가 해야할 작업은 안드로이드 앱에서 입력받은 데이터를 서버로 보내는 작업 입니다. 서버의 주소는 HTTP 형식으로 되어있기 때문에 우리는 HTTP 통신을 이용하여 서버와의 통신을 주고받겠습니다. 또한 HTTP 의 요청 규약 중 GET 방식을 먼저 사용해보겠습니다.

GET METHOD 버튼을 누르면, clickGet() 메소드가 호출되며, 메소드의 내용은 다음과 같습니다. 코드를 보면서 GET 방식의 진행을 따라가 봅시다.

void clickGet(){
    new Thread(){
        @Override
        public void run() {
            String name = binding.etName.getText().toString();
            String msg = binding.etMessage.getText().toString();

            String serverAddress = "http://tjdrjs0803.dothome.co.kr/Android/getTest.php";

            try {
                name = URLEncoder.encode(name,"UTF-8");
                msg = URLEncoder.encode(msg,"UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
            String getAddress = serverAddress + "?name=" + name + "&msg=" + msg;

            try {
                URL url = new URL(getAddress);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET"); 
                connection.setDoInput(true); 
                connection.setUseCaches(false); 
                
                InputStream is = connection.getInputStream();
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader reader = new BufferedReader(isr);

                StringBuffer buffer = new StringBuffer();
                while(true){
                    String line = reader.readLine();
                    if(line == null) break;
                    buffer.append(line + "");
                }

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        binding.tv.setText(buffer.toString());
                    }
                });
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }.start();
}
  • String name = binding.etName.getText().toString();
    EditText 의 정보를 가져와 name 변수에 담아두는 작업입니다.

  • String serverAddress = "http://tjdrjs0803.dothome.co.kr/Android/getTest.php";
    아직 만들지는 않았지만, 우리가 만들어 두었던 서버안에 Android 폴더 안에 getTest.php 파일을 주소로 활용하여 데이터를 이 php 파일로 보낼 예정입니다.

  • name = URLEncoder.encode(name,"UTF-8");
    GET 방식의 경우 서버의 URL 뒤에 ?& 을 이용하여 정보를 덧붙여 주어야 합니다. 그래서 속도는 빠르지만, 보안에 취약하며, 특수문자와 글자수에 제한이 있습니다. 즉, 한글과 같은 데이터들은 URL 에 사용할수 없다는 단점이 있습니다. 그 단점을 보완하기 위해 인코딩 작업을 해주어야 합니다. EditText 로 받아온 name 변수를 인코딩 하여 다시 name 변수에 담아두었습니다. msg 변수 또한 마찬가지로 진행하면 됩니다. 추가로 인코딩 작업시에는 try-catch 문을 이용해야합니다.

  • String getAddress = serverAddress + "?name=" + name + "&msg=" + msg;
    앞서 말했듯, GET 방식의 경우 서버 URL 에 데이터를 ?& 를 이용하여 덧붙여 주어야 합니다. 위 코드가 데이터를 덧붙이는 코드입니다.

이제 서버의 주소와 데이터까지 모두 만들어졌으니, URL 객체를 이용하여 우리가 앞으로 만들 getTest.php 파일과 connection 을 만들어야 합니다. 그래야 데이터를 전송할테니 말이죠. 그리고 난 뒤에 getTest.php 파일에서는 데이터를 받고, echo 를 이용하여 다시 이쪽으로 데이터를 전송할 예정입니다. 그래서 미리 데이터를 받는 코드까지 작성해봅시다.

  • URL url = new URL(getAddress);
    getTest.php 와 연결하기 위해, 완성된 주소를 URL 객체에게 넘겨줍시다.

  • HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    URL 객체 만으로도 GET 방식으로 데이터를 전송할 수 있지만, 여러가지 장점들이 많은 내장 라이브러리인 HttpURLConnection 객체를 이용하여 데이터 전송을 하는게 좋습니다. URL 객체에게 openConnection() 을 요청하고 타입캐스팅을 통해 HttpURLConnection 객체를 생성할 수 있습니다.

  • connection.setRequestMethod("GET");
    HttpURLConnection 객체를 이용하여 몇가지 설정을 먼저 해주어야 합니다. 먼저, 위 코드를 작성하면 우리가 데이터를 보내는 방식을 설정을 할 수 있습니다. 우리는 GET 방식으로 보낼 예정이기 때문에 파라미터로 "GET" 을 전달해주면 됩니다.

  • connection.setDoInput(true);
    php 파일에서 echo 응답을 해주면 그 응답을 받아와야 하기 때문에 InputStream 을 이용한 데이터 전송이 필요합니다. 그렇기 때문에 파라미터로 true를 전달해주었습니다.

  • connection.setUseCaches(false);
    HTTP 의 경우 기본적으로 캐시 기능이 설정되어있습니다. 그래서 클라이언트 쪽에서 어떠한 정보를 요청했을 때, 최신 정보가 아닌 캐시 내부의 정보가 반환될 수 있습니다. 그래서 캐시를 사용하게 되면 이전의 정보가 반환될 수 있으므로, 최신 정보를 돌려주고자 할 때에는 캐시를 사용하지 않도록 설정해주는 작업이 필요하며, 이는 구글의 권장사항 입니다.

그 다음 코드부터는 예전부터 해왔던 파일 입출력에 관한 내용입니다. 단, InputStream 객체를 생성할 때, HttpURLConnection 객체가 생성해준다는 점만 다릅니다. 한번 읽고 넘어가도록 합시다.

자, 그러면 이제 getTest.php 파일을 작성해줍시다.

<?php
    header('Content-Type:text/plain; charset=utf-8');

    $name = $_GET['name'];
    $message = $_GET['msg'];

    echo "$name  : $message";
?>

안드로이드에서 GET 방식을 이용하여 전송해준 데이터를 $name$message 변수에 $_GET[] 을 이용하여 담아둡시다. 그리고 서버에 데이터가 잘 도착했는지 확인해보기 위해, echo 를 이용하여 다시 안드로이드로 넘겨줍시다. 그러면 안드로이드쪽에서는 echo 로 넘겨준 값을 InputStream 을 통해 데이터를 받아올 수 있습니다.

위 그림에서 우리가 EditText 로 데이터를 작성하고, GET METHOD 버튼을 누르면 php 파일로 데이터가 전송되며 php 파일에서는 echo 를 통해 전달된 데이터를 다시 안드로이드로 보냅니다. 그리고 안드로이드에서는 InputStream 을 이용해 데이터를 다시 받아올 수 있으며, 받아온 데이터를 아래쪽 TextView 에 뿌려주어 확인 작업을 완료했습니다.


POST 방식으로 서버와 통신하기

버튼을 누르면 clickPost() 를 호출합니다. 메서드의 내용은 다음과 같습니다.

void clickPost(){
    new Thread(){
        @Override
        public void run() {
            String name = binding.etName.getText().toString();
            String message = binding.etMessage.getText().toString();

            String serverAddress = "http://tjdrjs0803.dothome.co.kr/Android/postTest.php";

            try {
                URL url = new URL(serverAddress);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("POST");
                connection.setDoInput(true);
                connection.setDoOutput(true);
                connection.setUseCaches(false);

                String data = "name="+name+"&msg="+message;

                OutputStream os = connection.getOutputStream();
                OutputStreamWriter writer = new OutputStreamWriter(os);

                writer.write(data,0,data.length());
                writer.flush();
                writer.close();

                InputStream is = connection.getInputStream();
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader reader = new BufferedReader(isr);

                StringBuffer buffer = new StringBuffer();
                while(true){
                    String line = reader.readLine();
                    if(line == null) break;
                    buffer.append(line + "\n");
                }
                runOnUiThread(()->{
                    binding.tv.setText(buffer.toString());
                });
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }.start();
}

POST 방식 역시 GET 방식과 크게 다르지 않습니다. 우리가 앞으로 만들 postTest.php 파일에 커넥션을 연결하고, 데이터를 보내고, echo 를 이용하여 데이터가 잘 전송되었는지 잘 테스트만 해주면 됩니다. 단, POST 방식은 URL 에 직접적으로 데이터를 붙여서 전송하는 방식은 아니기 때문에 커넥션은 연결하되, 커넥션 객체에게 OutputStream 을 따로 만들어 이 Stream 으로 데이터를 보내주어야 합니다. GET 방식의 코드와 다른 부분들 위주로 코드를 살펴봅시다.

  • connection.setDoOutput(true);
    앞서 이야기 했듯, 우리는 데이터를 숨겨서 보내야 합니다. 그래서 URL 에 직접적으로 명시하지 않고, OutputStream 을 통해 보내야 하므로, true 를 설정해주었습니다.

  • String data = "name="+name+"&msg="+message;
    보낼 데이터를 준비해줍시다.

  • OutputStream os = connection.getOutputStream();
    커넥션 객체를 이용하여 OutputStream 의 객체를 생성해주었습니다. 이 Stream 을 이용하여 데이터를 보내줘야 합니다.

  • writer.write(data,0,data.length());
    데이터를 보내줄 때에는 너무 크기가 크다면 데이터를 분할해서 보내주는것이 권장됩니다. 보통 크기가 1024 바이트 정도로 분할되는것이 권장됩니다. 우리는 테스트 목적이므로, data.length() 를 이용해주었습니다.

그 뒤로는 이제 앞선 GET 방식과 동일하니 넘어갑시다. 그러면 이제 postTest.php 파일을 작성해봅시다. 앞선 테스트에서는 $_GET[] 을 이용하여 데이터를 관리했다면, 이번에는 $_POST[] 를 이용하여 데이터를 관리한다는 점 이외에는 다른 점이 없습니다. echo 를 이용하여 확인 작업을 마무리합시다.

<?php
    header('Content-Type:text/plain; charset=utf-8');

    $name = $_POST['name'];
    $message = $_POST['msg'];

    echo "$name : $message";
?>

Database 에 데이터 저장하기

우리의 궁극적인 목적은 한 사용자가 입력한 데이터를 다른 사용자가 볼 수 있도록 하는것입니다. 단, 입력받은 값을 바로 다른 사용자의 디바이스로 보내게 되면, 데이터의 손상이나 보안 측면에서 위험하기 때문에 서버를 이용하는것이 효율적입니다. 또한, 서버 내부의 데이터베이스에 데이터들을 저장시켜 많은 사용자로 하여금 데이터를 공유하는 방법이 일반적입니다. 그래서 이번에는 사용자로 부터 입력받은 데이터들을 서버로 보내고, 서버 내부의 데이터 베이스에 저장시키는 작업을 해봅시다.

앞선 POST 방식으로 서버에 데이터를 보낸 테스트를 기반으로 서버 내부에 데이터 베이스에 저장시켜 봅시다. 일단 POST 방식으로 데이터는 보내놓았으니, php 파일에서 데이터베이스에 저장시키는 작업만 해주면 됩니다. 또한 추가적으로 데이터 베이스 내부에 저장되는 시간을 추가로 저장해봅시다.

<?php
    header('Content-Type:text/plain; charset=utf-8');

    $name = $_POST['name'];
    $message = $_POST['msg'];

    echo "$name : $message";
    $now = date("Y-m-d H:i:s");
    
    $db = mysqli_connect('localhost',DB 사용자 이름,DB 암호,DB 이름);

    mysqli_query($db,"set names utf8");

    $sql = "INSERT INTO board2(name, msg, date) VALUES('$name','$message','$now')";
    $result = mysqli_query($db,$sql);

    if($result) echo "Insert Success";
    else echo "Insert Failed";

    mysqli_close($db);
?>
  • $now = date("Y-m-d H:i:s");
    date() 를 이용하면 함수가 호출되는 시점을 얻어올 수 있습니다. 파라미터로는 날짜와 시간의 형식을 지정할 수 있으며, 받아온 시점을 $now 변수에 담아두었습니다. 추후에 데이터베이스에 저장할 때 이 변수를 활용할 예정입니다.

  • $db = mysqli_connect('localhost',DB 사용자 이름,DB 암호,DB 이름);
    mysqli_connect() 을 이용하여 데이터베이스와 연결할 수 있습니다. 첫번째 파라미터로는 서버의 호스트 이름 또는 IP 주소를 전달할 수 있습니다. 이 예제에서는 데이터베이스가 동일한 서버에 있기 때문에 localhost 를 사용해주었습니다. 나머지 파라미터는 위 코드에 적힌 대로 전달해주면 됩니다.

  • mysqli_query($db,"set names utf8");
    한글 깨짐을 방지해주는 쿼리문을 하나 작성해주어야 합니다.

  • $sql = "INSERT INTO board2(name, msg, date) VALUES('$name','$message','$now')";
    우리는 안드로이드 앱으로 부터 받아온 데이터와, date() 로 부터 받아온 현재 시간을 DB 에 저장해주어야 하므로, INSERT 쿼리문을 활용하여 board2 테이블에 저장시켜 주었으며, sql문을 $sql 변수에 저장해주었습니다.

  • $result = mysqli_query($db,$sql);
    쿼리문을 $db 에 적용시켜주고, 그에 따른 결과값을 $result 에 받아두었습니다. 저장이 성공하면 true 를, 실패하면 false 를 반환합니다.

그래서 다음 코드부터는 저장의 성공 여부에 따라 echo 를 이용하여 안드로이드 앱에 문자열을 반환해주었습니다.

이렇게 해서 사용자로 부터 입력받은 데이터를 DB 에 저장시켜 주었습니다.


Database 에서 데이터 불러오기

다른 사용자들이 데이터를 볼 수 있게하기 위해 DB 에 성공적으로 저장을 시켜주었으니, 이제 DB 에서 데이터를 꺼내오는 작업을 해봅시다. 우리는 LoadBtn 버튼을 하나 누르면 데이터를 꺼내와서 리사이클러뷰에 뿌려주는 작업을 해보겠습니다. 그러기 위해서는 일단 Item 클래스를 하나 만들고, 이 Item 클래스를 제네릭 타입으로 가지는 ArrayList 를 만들어 이 ArrayList 에 DB 에서 가져온 값들을 저장시켜 줍니다. 그리고 어댑터에게 알려주면, 알아서 리사이클러뷰에 뿌려줄것입니다. 그럼 먼저 Item 클래스 부터 만들어 봅시다.

Item 클래스 만들기

현재 우리가 DB 에서 가져와야할 데이터는 인덱스값, 이름, 메시지, 현재 날짜 로 4가지 입니다. 그러면 클래스의 멤버변수를 4가지를 설정해야하며, 생성자를 이용하여 각각의 값들을 저장시켜 줄 수 있습니다. 다음은 Item 클래스 코드 입니다.

public class Item {

    int no;
    String name;
    String msg;
    String date;

    public Item(){

    }
    
    public Item(int no, String name, String msg, String date) {
        this.no = no;
        this.name = name;
        this.msg = msg;
        this.date = date;
    }
}

아이템 시안 준비하기

리사이클러뷰의 아이템들이 어떠한 화면구성을 가지는지 설계해봅시다.

<RelativeLayout ... 중략 >

    <TextView
        android:id="@+id/tv_no"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="1"
        android:textColor="@color/purple_700"
        android:textStyle="bold"
        android:textSize="20sp"
        android:padding="8dp"/>

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="name"
        android:textStyle="bold"
        android:textColor="@color/teal_700"
        android:textSize="20sp"
        android:padding="8dp"
        android:layout_toRightOf="@id/tv_no"/>

    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="message"
        android:textStyle="bold"
        android:textColor="@color/black"
        android:textSize="20sp"
        android:padding="8dp"
        android:layout_below="@id/tv_no"/>

    <TextView
        android:id="@+id/tv_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="2023-03-14 10:57:30"
        android:textStyle="bold"
        android:textColor="@color/teal_700"
        android:textSize="12sp"
        android:padding="8dp"
        android:layout_alignParentRight="true"
        android:layout_alignBaseline="@id/tv_no"/>

</RelativeLayout>

어댑터 준비하기

리사이클러뷰에 뿌려주기 위해서는 어댑터도 필요합니다. 후딱 만들어줍시다. 기존의 어댑터 생성방법과 차이가 없으니 따로 설명은 안하겠습니다.

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.VH> {

    Context context;
    ArrayList<Item> items;

    public MyAdapter(Context context, ArrayList<Item> items) {
        this.context = context;
        this.items = items;
    }

    @NonNull
    @Override
    public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new VH(LayoutInflater.from(context).inflate(R.layout.recylcer_itemview,parent,false));
    }

    @Override
    public void onBindViewHolder(@NonNull VH holder, int position) {
        Item item = items.get(position);

        holder.binding.tvNo.setText(item.no+"");
        holder.binding.tvName.setText(item.name);
        holder.binding.tvMsg.setText(item.msg);
        holder.binding.tvDate.setText(item.date);

    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    class VH extends RecyclerView.ViewHolder{

        RecylcerItemviewBinding binding;

        public VH(@NonNull View itemView) {
            super(itemView);
            binding = RecylcerItemviewBinding.bind(itemView);
        }
    }
}

데이터 가져오기

어댑터와 아이템 시안, Item 클래스까지 만들어주었으니 이제 데이터를 가져와서 ArrayList 에 저장시켜주겠습니다. 또한 우리는 데이터를 가져오는 작업을 onResume() 을 활용하여 액티비티가 화면에 모두 보여질 때 데이터를 가져오겠습니다. 그래야 다시 이 액티비티가 생성 될때 최신 데이터를 가져올 수 있기 때문입니다.

먼저, loadDB.php 부터 작성하여, 안드로이드 앱이 데이터를 가져갈 수 있도록 만들어줍시다.

<?php
    header('Content-Type:text/plain; charset=utf-8');

    $db = mysqli_connect('localhost','tjdrjs0803','dkssud109!','tjdrjs0803');
    mysqli_query($db,"set names utf9");

    $sql="SELECT * FROM board2";
    $result = mysqli_query($db,$sql);

    $rowNum= mysqli_num_rows($result);

    for($i = 0;$i<$rowNum;$i++){
        $row = mysqli_fetch_array($result,MYSQLI_ASSOC); 
        $no = $row['no'];
        $name = $row['name'];
        $msg = $row['msg'];
        $date = $row['date'];

        echo $no .",". $name .",". $msg .",". $date."&";
    }
    mysqli_close($db);
?>

DB 를 연결하고 한글 깨짐 방지 쿼리를 요청해주었습니다. 그리고 SELECT 쿼리문을 이용하여 board2 테이블에서 데이터를 받아오는 쿼리를 $sql 에 담아두었습니다.

  • $result = mysqli_query($db,$sql);
    SELECT 쿼리문을 담은 $sql$db 에 적용시켜주고, SELECT 이 적용된 테이블을 $result 에 담아두었습니다.

그리고 이제 우리는 뽑아온 테이블에서 데이터들을 하나씩 뽑아 echo 를 이용하여 안드로이드 앱으로 보내줄 예정입니다. 우리가 뽑아온 테이블의 데이터 개수는 하나가 아닐 수 있기 때문에 for 문을 이용해주어야 합니다.

  • $rowNum= mysqli_num_rows($result);
    우리는 for문을 데이터의 개수만큼 돌려야 하므로 mysqli_num_rows($result) 을 이용해주었습니다.

  • $row = mysqli_fetch_array($result,MYSQLI_ASSOC);
    그리고 우리는 반복문을 돌리는 동안에 반복문 내부에서는 데이터를 한줄씩 빼와서 각 데이터에 저장된 데이터들을 각각의 변수에 담아 echo 시켜주어야 합니다. 그래서 mysqli_fetch_array 을 이용하여 데이터 한줄을 빼와 $row 에 담아두었으며, 이는 배열로 이용할 수 있습니다. 또한 인덱스 값을 이용한 배열이 아닌 연관배열로 이용해주어야 하기 때문에 MYSQLI_ASSOC 속성을 지정해주었습니다.

그리고 반복문을 돌면서 $no = $row['no']; 와 같이 DB 의 열 이름으로 데이터를 빼와 변수에 넣어주었습니다. 그리고 echo 를 이용하여 안드로이드 앱으로 보내주었습니다. 단, 이번 테스트에서는 데이터를 CSV 로 받아 파싱해오는 테스트를 진행해볼 예정이기 때문에 php 파일에서 데이터를 보내줄 때, 콤마로 값을 구분해주어 보내주었습니다.

그러면 php 파일에서 DB 로부터 데이터를 받아와서 echo를 통해 안드로이드 앱으로 보내주었으므로, 이제 안드로이드 앱에서 데이터를 받아와 처리해봅시다.

public class BoardActivity extends AppCompatActivity {

	ActivityBoardBinding binding;
    ArrayList<Item> items = new ArrayList<>();
    MyAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        binding = ActivityBoardBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        adapter = new MyAdapter(this,items);
        binding.recyclerview.setAdapter(adapter);
    }

    @Override
    protected void onResume() {
        super.onResume();
        loadData();
    }

    void loadData(){
        new Thread(){
            @Override
            public void run() {
                String serverAddress = "http://tjdrjs0803.dothome.co.kr/Android/loadDB.php";
                try {
                    URL url = new URL(serverAddress);
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();

                    connection.setRequestMethod("GET");
                    connection.setDoInput(true);
                    connection.setUseCaches(false);

                    InputStream is = connection.getInputStream();
                    InputStreamReader isr = new InputStreamReader(is);
                    BufferedReader reader = new BufferedReader(isr);

                    StringBuffer buffer = new StringBuffer();
                    while(true){
                        String line = reader.readLine();
                        if(line == null) break;
                        buffer.append(line +"\n");
                    }
                    String[] rows = buffer.toString().split("&");
                    
                    for(String row : rows){
                        String[] datas = row.split(",");
                        if(datas.length != 4) continue;

                        int no = Integer.parseInt(datas[0]);
                        String name = datas[1];
                        String msg = datas[2];
                        String date = datas[3];

                        items.add(new Item(no,name,msg,date));
                    }
                    runOnUiThread(()->{
                        adapter.notifyDataSetChanged();
                    });

                } catch (MalformedURLException e) {
                    throw new RuntimeException(e);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }.start();
    }
}

우리가 주목해야할 코드는 loadData() 내부의 코드들 입니다. 이 내부에서 php 파일로 부터 데이터를 받아옵니다. 단, CSV 형식으로 받아왔기 때문에 CSV 파싱이 필요합니다. 파싱이 끝나면 어댑터에게 알려 리사이클러뷰에 뿌려주겠습니다.

  • InputStream is = connection.getInputStream();
    우리는 php 파일로 부터 데이터를 받아와야 하기 때문에 InputStream 을 사용하였습니다.

  • String[] rows = buffer.toString().split("&");
    이제 받아온 데이터들은 긴 문자열 하나로 이루어져 있기 때문에 우리가 사용하기 편한 형태로 구분을 해주어야 합니다. 첫번째 과정으로 & 을 기준으로 스프릿해주었습니다. 그리고 스플릿 된 문자열들을 문자열 배열 rows 에 담아두었습니다.

  • String[] datas = row.split(",");
    두번째 과정으로 , 를 기준으로 스플릿 해주었습니다. 그리고 스플릿 된 문자열들을 datas 문자열 배열에 담아두었습니다.

  • if(datas.length != 4) continue;
    우리가 가져온 데이터들은 DB 에서 가져온 데이터들 이므로 항상 4개의 데이터를 가지고 있습니다. 그래서 데이터가 4개가 아니라면 continue 해주고, for 문을 종료시킵니다.

그 뒤로는 어렵지 않습니다. datas 에 담겨있는 데이터들을 각각의 변수에 담아주고, 위에서 만들어 두었던 ArrayList<Item> 에 add 시켜주면 됩니다. 그리고 마지막으로는 어댑터에게 알려주기만 하면 데이터를 로드하여 리사이클러뷰에 뿌려주는 작업이 모두 종료됩니다.


JSON 파싱 해보기

우리가 이번에 해볼 작업은 JSON 파일을 파싱해오는 작업입니다. 앞서 우리가 CSV 를 파싱해온것, 예전에 XML 을 파싱했던것들과 마찬가지로 JSON 파일 또한 파싱해올 수 있습니다. 이번 테스트 에서는 간단한 구조의 JSON 파일을 파싱해오는 것부터 시작해봅시다.

assets 폴더

안드로이드 스튜디오에는 assets 라는 폴더를 생성할 수 있습니다. 이 안에는 텍스트 파일, 이미지 파일, 동영상 파일등 다양한 종류의 파일을 저장할 수 있습니다. res 폴더에 접근하기 위해 Resource 객체를 이용했던 것처럼, assets 폴더에 접근하기 위해서는 AssetManager 객체를 이용해야한다는 점을 기억합시다. 이번 테스트에서 사용할 JSON 파일 또한 이 폴더안에 저장시켜 놓겠습니다.

JSON 파일 형식

먼저 아래 aaa.json 파일을 보고, JSON 의 파일이 어떤 모습을 하고 있나 살펴봅시다.

{"name": "sam","msg": "Hello World", "age": 20}

JSON(JavaScript Object Notation) 은 key-value 쌍으로 이루어진 데이터 교환 형식입니다. 위 JSON 코드에서 name,msg,age 는 key 값 이며, sam,Hello World,20 은 Value 값 입니다. 위에 나온 중괄호는 객체를 표현하며, 대괄호를 사용하면 데이터의 배열을 표현할 수 있습니다.

JSON 파일 파싱 해오기

이제 JSON 파일을 파싱해오는 방법을 알아봅시다.

void clickBtn(){
    AssetManager assetManager = getAssets();
    try {
        InputStream is = assetManager.open("aaa.json");
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader reader = new BufferedReader(isr);

        StringBuffer buffer = new StringBuffer();
        while(true){
            String line = reader.readLine();
            if(line == null) break;
            buffer.append(line + "\n");
        }
        String jsonStr = buffer.toString();

        JSONObject jo = new JSONObject(jsonStr);
        String name = jo.getString("name");
        String message = jo.getString("msg");
        int age = jo.getInt("age");
        binding.tv.setText(name + "\n" + message + "\n" + age);
    } catch (IOException e) {
        throw new RuntimeException(e);
    } catch (JSONException e) {
        throw new RuntimeException(e);
    }
}
  • AssetManager assetManager = getAssets();
    MainActivity 가 가지고 있는 getAssets() 를 호출하여 AssetManager 객체를 생성합시다. 이 매니저 객체가 JSON 파일을 가져다줄 객체 입니다.

  • InputStream is = assetManager.open("aaa.json");
    매니저 객체는 InputStream 객체를 반환하는 open() 을 호출할 수 있으며, 파라미터로 JSON 파일을 넘겨주면 됩니다. 열린 Stream 을 이용하여 JSON 파일에 적힌 내용을 한줄씩 받아와 StringBuffer 객체에 넣어둡시다.

  • JSONObject jo = new JSONObject(jsonStr);
    버퍼에 저장되어있는 JSON 파일의 내용을 파라미터로 넘겨주면서 JSONObject 객체를 하나 만들어둡시다. 이 객체를 이용하면 String name = jo.getString("name"); 처럼, 데이터를 키값을 기반으로 꺼내올 수 있습니다.

이렇게 JSON 을 파싱해보았습니다. CSV 나 XML 보다는 더 간단하게 파싱이 가능합니다.

중첩 JSON

여러 JSON 파일들의 구조를 보면 보이겠지만, 다음과 같이 중첩으로 구성된 JSON 파일도 있습니다.

{"name": "sam","msg": "Hello World", "age": 20, "address": {"nation": "korea","city": "seoul"}}

앞선 aaa.json 파일에 키 값이 address 인 데이터 하나를 추가했으며, 이 데이터는 중첩구조를 가집니다. 그래서 address 데이터 안쪽에 nation 키와 city 키가 들어있으며, value 값으로 koreaseoul 이 명시되어 있습니다.

그러면 이러한 중첩 JSON 을 파싱해오는 방법을 간단하게 살펴봅시다. 사실 앞선 JSON 파싱 방법과 크게 다르지 않습니다. 앞서서는 JSONObject 객체에 getString() 등을 호출하여 파라미터로 키값을 전달하고 그에 맞는 데이터를 달라고 요청했습니다. 이번에는 객체에게 address 객체를 달라고 해야합니다. 그리고 address 객체에게 키값에 맞는 데이터를 달라고 해야하는거죠. 코드 구성도 중첩적으로 구성해주면 됩니다. 코드 구성은 데이터를 추출하는 부분만 따로 봅시다.

JSONObject jo = new JSONObject(jsonStr);
String nation = jo.getJSONObject("address").getString("nation");
String city = jo.getJSONObject("address").getString("city");

JSONArray 파싱법

앞서 JSON 파일의 중괄호는 객체를 표현하며, 대괄호는 배열을 표현한다고 하였습니다. 그리고 중괄호 내부의 데이터를 얻어오기 위해 JSONObject 객체를 생성했었죠. 마찬가지로 대괄호 내부의 객체를 얻어오기 위해 JSONArray 를 사용해주어야 합니다. 그러면 코드를 통해 JSONArray 를 사용한 파싱 방법을 알아봅시다.

ArrayList<Item> items = new ArrayList<>();

void clickBtn2(){
    AssetManager assetManager = getAssets();
    try {
        InputStream is = assetManager.open("bbb.json");
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader reader = new BufferedReader(isr);

        StringBuffer buffer = new StringBuffer();
        while(true){
            String line = reader.readLine();
            if(line == null) break;
            buffer.append(line+"\n");
        }
        String jsonStr = buffer.toString();
        JSONArray ja = new JSONArray(jsonStr);
        for(int i=0;i<ja.length();i++){
            JSONObject jo = ja.getJSONObject(i);

            int no = jo.getInt("no");
            String name = jo.getString("name");
            String msg = jo.getString("msg");

            items.add(new Item(no,name,msg));
        }
        binding.tv.setText(items.size()+"");

    } catch (IOException e) {
        throw new RuntimeException(e);
    } catch (JSONException e) {
        throw new RuntimeException(e);
    }
}
  • JSONArray ja = new JSONArray(jsonStr);
    InputStream 으로 가져온 JSON 파일의 내용들을 문자열 변수에 담아 JSONArray 객체의 파라미터로 전달해주면서 객체를 생성해주었습니다. 이 배열 객체의 getJSONObject() 를 이용하면 중괄호로 표현된 객체를 가져올 수 있습니다. 반복문을 활용하여 각각의 객체를 얻어오고, 각각의 객체들이 가지고 있는 데이터들을 뽑아 낼 수 있습니다. 위 테스트에서는 ArrayList 에 각각의 데이터들을 추가해보았습니다.

GSON 라이브러리

GSON 라이브러리는 JSON 을 훨씬 간편하게 사용할 수 있도록 도와주는 기능입니다. 외부 라이브러리 이므로, 추가해서 사용해봅시다. 사용하면서 앞서 JSON 을 직접 사용했을 때와 어떠한 차이점이 있는지 느껴봅시다. 다만, 테스트의 편의성을 위해 따로 JSON 파일을 만들진 않고 String 변수에 JSON 의 내용을 담아 테스트 하겠습니다. 일단, JSON 에서 가져온 데이터를 저장할 클래스 하나만 만들고 시작해봅시다.

public class Person {
    String name;
    int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

JSON -> Object

GSON 라이브러리를 사용하여 JSON 데이터를 객체로 만들어봅시다. 가져올 데이터는 이름과 나이를 가져옵시다. 그리고 가져온 데이터는 Person 클래스에 저장시켜놓겠습니다.

void clickBtn(){
    String jsonStr = "{'name':'sam','age':20}";

    Gson gson = new Gson();
    Person person = gson.fromJson(jsonStr, Person.class);
    binding.tv.setText(person.name + " : " + person.age);
}
  • Person person = gson.fromJson(jsonStr, Person.class);
    Gson 객체를 이용하면, JSON 파일의 내용들을 기반으로 Person 객체를 만들어줍니다. fromJson() 을 이용하여, 첫번째 파라미터로는 JSON 코드의 내용을, 두번째 파라미터로는 저장할 클래스 정보를 전달해주면 됩니다. 이 코드 한줄이면 Person 객체가 만들어지게 됩니다. 그리고 이 Person 객체를 이용하면 데이터를 추출해올 수 있게됩니다. 앞선 테스트에 비해 정말 많이 쉬워진게 느껴지시나요?

Object -> JSON

void clickBtn2(){
    Person person = new Person("robin",25);

    Gson gson = new Gson();
    String jsonStr = gson.toJson(person);
    binding.tv.setText(jsonStr);
}

이번에는 생성된 Person 객체를 JSON 파일로 만드는 방법입니다. robin25 의 데이터가 담긴 Person 객체를 하나 생성해주었으며, Gson 객체의 toJson() 을 이용하여 JSON 파일의 내용을 생성할 수 있습니다.

JSONArray -> Object Array

void clickBtn3(){
    String jsonStr = "[{'name':'sam','age':20},{'name':'robin','age':25}]";
    Gson gson = new Gson();
    Person[] people = gson.fromJson(jsonStr,Person[].class);
    binding.tv.setText("객체 수 : "+people.length);
}

Gson 객체를 이용하면 대괄호로 표현한 JSONArray 를 Person[] 로 바꿔줄 수도 있습니다. 방식은 앞선 테스트들과 동일합니다.

profile
Developer

0개의 댓글