[Android] SNS앱 안드로이드 클라이언트 개발-1

나래·2024년 9월 9일

📖 SNS 앱을 만들면서 배우는 안드로이드 클라이언트 개발

이번 학기에는 여태까지 했던 서버도 없고 통신도 없는 앱제작에서 벗어나 API와 함께 동작하는 앱을 연습해보고 싶어 해당 교재를 통해 간단히 공부를 해보려고 한다.

01. 팀프로젝트를 맛보자 & 02. 안드로이드 개발을 준비하자

이 파트는 이미 어느정도 해본 부분이라 판단해 생략한다. 03.서버와 함께 Hello, World! 챕터부터 시작하겠다.


1. Flask 서버

먼저 내용을 시작하기 전에 책에서 제공하는 API를 서버를 통해 연결하는 과정을 가져야 한다.
서버는 Flask 서버를 사용했고 파이썬의 가상환경이 필요했다.

1) 가상환경 만들기 & 패키지 설치

  • 가상환경 만들기
C:\... > cd daily-q-server
C:\...\daily-q-server > python -m venv venv

파이썬에서는 내장된 venv를 사용해 가상환경을 만들 수 있다. daily-q-server 폴더에서 두번째 줄의 명령어를 실행하는데, 명령어에서 첫번째 venv는 명령을 실행하겠다는 것이고, 두 번째 venv는 가상환경의 이름이다.

가상환경은 사용할 때 활성화 시키고 사용이 끝나면 비활성화를 시켜야하는데 활성화는 생성된 venv 폴더의 스크립트를 사용해서 할 수 있다.

C:\...\daily-q-server> venv\Scripts\activate.bat
(venv) C:\...\daily-q-server>

활성화가 완료되고 나면 앞에 이렇게 (venv)라는 표식이 뜬다. 참고로 비활성화 명령어는 deactivate.bat을 입력하면 된다.

  • 패키지 설치

이 패키지 설치과정에서 특히나 기억에 남았던 건 .txt 파일에 필요한 패키지를 다 적어놓고 그 텍스트 파일을 읽어 패키지는 설치할 수 있다는 사실이 굉장히 인상깊었다. 여태까지 일일히 찾아서 했던 수고를 들일 필요가 없었다.

(venv) C:\...\daily-q-server > pip install -r requirements.txt

이렇게 설치한 패키지들은 모두 가상환경에 설치되어 venv\Lib\site-package에서 볼 수 있다.

2) 서버 실행

(venv) C:\...\daily-q-server > flask run

해당 명령어를 통해서 flask 서버를 실행시킬 수 있다. 해당 서버는 마이크로 웹 프레임워크인 Flask로, 가벼운 서버를 위주로 띄울 수 있다고 하는 것 같다. 추후 더 확인해봐야하는 내용.

이렇게 로컬 서버로 띄워지는 것을 볼 수 있는데, 이 서버의 API를 이제 가져다쓰고... 서버와 연결시키면 된다.


2. 기본 레이아웃 및 코드 만들기

1) 패키지 구성

패키지 구성은 이런식으로 진행되었고 MainActivity에서 다양한 Fragment로 접근하는 방식이다.
기타 다른 아이콘과 이미지는 github-링크 에 가면 다운받을 수 있다.

🚨 MainActivity에서 다른 Fragment와 연결하는 코드 등 다양하게 있지만 이 부분은 이미 내가 알고 있는 것들이기 때문에 이번 블로그 글에서는 그냥 클라이언트 서버 연결과 관련된 내용 위주로 작성하겠다.

2) HttpURLConnection으로 API 호출하기

안드로이드 스튜디오에서 REST API와 연동할 때 사용하는 가장 인기 있는 라이브러리는 Retrofit이다. Retrofit을 사용하면 비동기 요청, 캐싱, 응답 처리, 인증 등 다양한 기능을 쉽게 구현할 수 있다.

그러나 이를 사용하기 전 자바의 기본 패키지인 HttpURLConnection을 하용해 API를 호출하는 기능을 먼저 만들어보고, 그다음 Retrofit을 사용하여 추후 둘을 비교해보도록 하겠다.

3) API 호출

(1) AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

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

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"
        android:theme="@style/Theme.DailyQ"
        tools:targetApi="31">
        <activity
            android:name=".Ui.main.MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

안드로이드 앱과 네트워크를 연결하기 위해서 매니페스트에 android.permission.INTERNET 권한을 추가한다. 그리고 안드로이드 9(API 28)부터 보안 강화를 위해 암호화되지 않은 HTTP 프로토콜 사용이 제한된다. 따라서 지금 사용하고 있는 테스트 서버는 HTTP만 지원되기 때문에 매니페스트에 추가적으로 android:usesCleartextTraffic="true" 를 추가한다.

(2) TodayFragment.kt

class TodayFragment : BaseFragment() {

    var _binding : FragmentTodayBinding? = null
    val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentTodayBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        Thread{

            val url = URL("http://10.0.2.2:5000/v1/hello-world")

            val conn = url.openConnection() as HttpURLConnection
            conn.connectTimeout = 5000
            conn.readTimeout = 5000
            conn.requestMethod = "GET"

            conn.connect()

            val reader = BufferedReader(InputStreamReader(conn.inputStream))
            val body = reader.readText()
            reader.close()
            conn.disconnect()
            

            activity?.runOnUiThread {
                binding.question.text = body
            }
        }.start()

    }

    override fun onDestroy() {
        _binding = null
        super.onDestroy()
    }
}

① 메인 스레드(UI 스레드)에서 API 호출같이 오래 걸리는 IO작업을 하는 경우 사용자는 앱이 정지되었다고 느낄 수 있다. 따라서 이를 피하기 위해 안드로이드의 메인 스레드에서 API를 호출하면 android.os.NetworkOnMainThreadException 이 발생한다. 네트워크 작업은 항상 백그라운드 스레드에서 진행해야 한다.

HttpURLConnection은 생성자를 직접 호출할 수 없다. URL 클래스의 생성자에 서버 주소와 포트, 경로를 입력하고 openConnection을 호출하면 URLConnection의 객체를 넘겨준다. 여기서는 HTTP URL을 사용했기 때문에 URLConnection의 서브클래스인 HttpURLConnection이 넘어온다.

③ 'Hello, world!' API는 GET으로 호출하고 단순 문자열을 받아오기 때문에 requestMethod를 GET으로 설정한 후 결과를 inputStream에서 그대로 읽어온다.

④ 메인 스레드에서 네트워크 작업을 할 수 없는 것과 마찬가지로 백그라운드 스레드에서 UI를 변경할 수 없다. UI는 메인스레드에서만 변경할 수 있기 때문에 activity.runOnUiThread 메서드를 이용해 결과를 카드에 표시한다.

🌟결과 화면

Hello, world! 라는 문자열을 문제없이 가지고 온 것을 볼 수 있다.


3장은 여기서 끝난다. 다음은 4장의 실습과 정리에 관한 글을 쓸 예정!

0개의 댓글