자바 소켓 API
TCP, UDP
TCP, UDP상에서 바로 하기보다는 응용 프로토콜 라이브러리 사용
HTTP(S)를 사용하는 경우, 대부분 이 경우에 해당됨
자바 HTTP URL Connection
Retrofit 라이브러리 (OkHttp 기반)
Volley, Cronet 등도 있음
MQTT를 사용한다면
paho, HiveMQ 등 라이브러리 사용
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application ...
HTTP와 HTTPS
HTTPS는 메시지를 암호화하여 전송
HTTP는 메시지를 그대로 전송
안드로이드에서 HTTP Url Connection API 사용할 때, http는 기본적으로 금지
http를 사용하기 위해서 Manifest의 application 태그 속성에
android:usesCleartextTraffic="true"를 추가
<application
...
android:usesCeartextTraffic="true"
...
>
현재 연결된 네트워크 종류나 인터넷 연결 가능 여부를 판단
ConnectivityManager.allNetworks: 모든 네트워크 IF를 리턴
ConnectivityManager.activeNetwork: 현재 활성화된 네트워크 IF
private fun isNetworkAvailable(): Boolean {
val connectivityManager =
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val nw = connectivityManager.activeNetwork ?: return false // 현재 활성화된 네트워크
val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false
println("${actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)},
${actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)}, "
+ "${actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)}")
return actNw.hasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET)
// 인터넷 가능 여부
}
hasTransport: 네트워크의 특정 전송 방식
Wi-Fi인지, 모바일 데이터인지
hasCapabilities: 네트워크의 특정 기능
인터넷에 연결 가능한지
class NetworkRepository {
suspend fun fetchFromSocket(): String = withContext(Dispatchers.IO) {
val sock = SocketFactory.getDefault().createSocket("google.com", 80)
val istream = sock.getInputStream()
val ostream = sock.getOutputStream()
ostream.write("GET / \r\n".toByteArray())
ostream.flush()
val result = istream.readBytes().decodeToString()
sock.close()
result
}
}
withContext(코루틴 컨텍스트, 코루틴)
suspend
class MyViewModel : ViewModel() {
private val repository = NetworkRepository()
private val _response = MutableStateFlow("")
val response: StateFlow<String> = _response.asStateFlow()
private val _responseBy = MutableStateFlow("")
val responseBy: StateFlow<String> = _responseBy.asStateFlow()
fun refreshJavaSocket() {
viewModelScope.launch {
try {
val result = repository.fetchFromSocket()
_responseBy.value = "Java Socket : Succeeded to connect to the server"
_response.value = result
} catch (e: Exception) {
_responseBy.value = "Java Socket"
_responseBy.value = "Failed to connect to the server ${e.message}"
}
}
}
}
java에서 제공하는 HttpURLConnection, HttpsURLConnection
HTTP(s) 프로토콜을 손쉽게 사용할 수 있는 API
HTTP 사용 시 Manifest 파일의 application 태그에
android:usesCleartextTraffic="true"
class NetworkRepository {
suspend fun fetchBitmapHttps(url: String):Bitmap?
= withContext(Dispatcher.IO) {
val conn = URL(url).openConnection() as HttpsURLConnection
try {
val istream = conn.inputStream
BitmapFactory.decodeStream(istream)
} finally {
conn.disconnect()
}
}
}
class MyViewModel : ViewModel() {
private val repository = NetworkRepository()
private val _response = MutableStateFlow("")
val response: StateFlow<String> = _response.asStateFlow()
private val _responseBy = MutableStateFlow("")
val responseBy: StateFlow<String> = _responseBy.asStateFlow()
private val _responseImg = MutableStateFlow<Bitmap?>(null)
val responseImg: StateFlow<Bitmap?> = _responseImg.asStateFlow()
fun refreshBitmapByHttpsLib(url: String) {
viewModelScope.launch {
try {
val bitmap = repository.fetchBitmapHttps(url)
_responseBy.value = "HttpsURLConnection"
_response.value = "Succeeded to download the image"
_responseImg.value = bitmap
} catch(e: Exception) {
_responseBy.value = "HttpsURLConnection"
_responseBy.value = "Failed to download the image: ${e.message}"
}
}
}
}
ContentResolver를 이용하여 MediaStore에 이미지 저장private fun saveBitmap(bitmap: Bitmap, fileName : String) {
val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERAL)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
}
val destUri = contentResolver.insert(collection, contentValues) ?: return
contentResolver.openOutputStream(destUri)?.use {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
}
}
HTTP 연결을 통해 받은 JSON 등의 구조체 데이터를
알아서 클래스 객체로 만들어줌
HTTP body 내용이 JSON, 일반 문자열, XML 등에 따라
변환기를 선택 사용 가능
프로그래머는 필요한 인터페이스와 데이터 클래스만 정의하면
구현은 Retrofit이 알아서 함
build.gradle(모듈)
dependencies {
implementation("com.squareup.retrofit2:retrofit:3.0.0")
implementation("com.squareup.retrofit2:converter-gson:3.0.0")
// Json용 변환기
}
Retrofit에서 자동으로 만들어주는 REST API 정의
@GET(경로명) annotation 사용
경로명에 {이름}으로 된 것은 메소드의 인자로 치환됨
치환되는 인자의 앞에 @Path("이름") annotation 사용
리턴 값은
Call<타입>을 리턴받아 콜백으로 응답을 받거나,
suspend를 붙여서 해당 타입 객체를 바로 리턴 받는 방법이 가능
interface RestApi {
@GET("users/{user}/repos")
fun listReposCall(@Path("user") user: String): Call<List<Repos>>
@GET("users{user}/repos")
suspend fun listRepos(@Path("user")user: String): List<Repo>
}
data class Owner(val login: String)
data class Repo(val name: String, val owner: Owner, val url: String)
interface RestApi {
@GET("users/{user}/repos")
suspend fun listRepos(@Path("user")user: String): List<Repo>
}
class NetworkRepository {
private val baseURL = "https://api.github.com"
private val api: RestAi = with(Retrofit.Builder()) {
baseUrl(baseURL)
addConverterFactory(GsonConverterFactory.create()) // JSON 변환기 지정
build()
}.create(RestApi::class.java)
suspend fun fetchRepos(userName: String): List<Repo> {
return api.listRepos(userName)
}
}
class MyViewModel : ViewModel() {
private val repository = NetworkRepository()
private val _response = MutableStateFlow("")
val response: StateFlow<String> = _response.asStateFlow()
private val _responseBy = MutableStateFlow("")
val responseBy: StateFlow<String> = _responseBy.asStateFlow()
fun refreshRetrofit(userName: String) {
viewModelScope.launch {
try {
val repos = repository.fetchRepos(userName)
_responseBy.value = "retrofit : ${repos.size} repositories"
_response.value = StringBuilder().apply {
repos.forEach {
append(it.name)
append(" - ")
append(it.owner.login)
append("\n")
}
}.toString()
} catch (e: Exception)
}
}
}

DownloadManager.ACTION_DOWNLOAD_COMPLETE