이번 학기에는 여태까지 했던 서버도 없고 통신도 없는 앱제작에서 벗어나 API와 함께 동작하는 앱을 연습해보고 싶어 해당 교재를 통해 간단히 공부를 해보려고 한다.
이 파트는 이미 어느정도 해본 부분이라 판단해 생략한다. 03.서버와 함께 Hello, World! 챕터부터 시작하겠다.
먼저 내용을 시작하기 전에 책에서 제공하는 API를 서버를 통해 연결하는 과정을 가져야 한다.
서버는 Flask 서버를 사용했고 파이썬의 가상환경이 필요했다.
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에서 볼 수 있다.
(venv) C:\...\daily-q-server > flask run

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

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

패키지 구성은 이런식으로 진행되었고 MainActivity에서 다양한 Fragment로 접근하는 방식이다.
기타 다른 아이콘과 이미지는 github-링크 에 가면 다운받을 수 있다.
안드로이드 스튜디오에서 REST API와 연동할 때 사용하는 가장 인기 있는 라이브러리는 Retrofit이다. Retrofit을 사용하면 비동기 요청, 캐싱, 응답 처리, 인증 등 다양한 기능을 쉽게 구현할 수 있다.
그러나 이를 사용하기 전 자바의 기본 패키지인 HttpURLConnection을 하용해 API를 호출하는 기능을 먼저 만들어보고, 그다음 Retrofit을 사용하여 추후 둘을 비교해보도록 하겠다.
<?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! 라는 문자열을 문제없이 가지고 온 것을 볼 수 있다.