안드로이드에서 서버와 통신하기 위한 AsyncTask 로 HttpURLConnection 을 이용하는 방법입니다.
저는 openweathermap 의 api 를 통해 날씨 정보를 표시해주는 앱을 만들어보았습니당 🌤
https://openweathermap.org/api
Weather API - OpenWeatherMap
Please, sign up to use our fast and easy-to-work weather APIs for free. In case your requirements go beyond our freemium account conditions, you may check the entire list of our subscription plans. You can read the How to Start guide and enjoy using our po
openweathermap.org
작성한 파일 목록 입니다.
1. AndroidManifest.xml
2. build.gradle(:app)
3. strings.xml
4. WeatherModel.java
5. MainActivity.java
6. activity_main.xml
7. RequestHttpUrlConnection.java
1. AndroidManifest.xml
인터넷 권한 을 추가해야 합니다.
<uses-permission android:name="android.permission.INTERNET"/>
를 추가해주세요!
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eun.myapp">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
2. build.gradle(:app)
하단 dependencies {} 안에 gson, glide 에 대한 라이브러리를 사용하기 위해 추가해 줍니다.
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.eun.myapp"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:2.0.4'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:design:28.0.0'
implementation 'com.google.code.gson:gson:2.8.0'
implementation 'com.github.bumptech.glide:glide:4.9.0'
}
3. strings.xml
openweathermap.org 의 주소와 해당 사이트에서 발급받은 key값을 여기저기서 가져다 사용하기 위해 res/values/strings.xml 의 resources 에 지정해주었습니다.
<resources>
<string name="app_name">MyApp</string>
<string name="weather_url">http://api.openweathermap.org/</string>
<string name="weather_app_id">key값</string>
</resources>
4. WeatherModel.java
통신 시 받아올 데이터를 사용하기 쉽도록 응답받는 데이터에 맞추어 VO 를 구현한 Weather 관련 model class 입니당
package com.eun.myapp.data;
public class WeatherModel {
String name = ""; //도시이름
String icon = ""; //나라
String country = ""; //아이콘
double temp = 0.0; //온도
String main = ""; //날씨
String description = ""; //상세설명
double wind = 0.0; //바람
double clouds = 0.0; //구름
double humidity = 0.0; //습도
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public double getTemp() {
return temp;
}
public void setTemp(double temp) {
this.temp = temp;
}
public String getMain() {
return main;
}
public void setMain(String main) {
this.main = main;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public double getWind() {
return wind;
}
public void setWind(double wind) {
this.wind = wind;
}
public double getClouds() {
return clouds;
}
public void setClouds(double clouds) {
this.clouds = clouds;
}
public double getHumidity() {
return humidity;
}
public void setHumidity(double humidity) {
this.humidity = humidity;
}
}
5. MainActivity.java
화면을 구성하고 AsyncTask 를 사용하여 비동기로 HttpUrlConnection 을 통해 날씨 정보를 가지고 왔습니다.
AsyncTask 의 자세한 내용은 아래글을 참고해주세용!
https://eunoia3jy.tistory.com/124
[안드로이드/Android] AsyncTask 를 이용한 프로그레스바(ProgressBar) 표시하기
AsyncTask 안드로이드에서 UI 블록을 막기 위해 네트워크 통신 등의 기능을 구현할 때는 비동기 처리를 이용해야 한다. 비동기 처리를 하는 백그라운드 작업을 구현하게 될 때 사용하는 AsyncTask 라
eunoia3jy.tistory.com
package com.eun.myapp;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.eun.myapp.data.WeatherModel;
import com.eun.myapp.util.RequestHttpUrlConnection;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class MainActivity extends AppCompatActivity {
public static String TAG = "["+MainActivity.class.getSimpleName() +"] ";
Context context = MainActivity.this;
TextView tv_name, tv_country;
ImageView iv_weather;
TextView tv_temp, tv_main, tv_description;
TextView tv_wind, tv_cloud, tv_humidity;
String strUrl = ""; //통신할 URL
NetworkTask networkTask = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
strUrl = getString(R.string.weather_url)+"data/2.5/weather"; //Strings.xml 의 weather_url 로 통신할 URL 사용
initView();
requestNetwork();
}
/* view 를 설정하는 메소드 */
private void initView() {
tv_name = (TextView) findViewById(R.id.tv_name);
tv_country = (TextView) findViewById(R.id.tv_country);
iv_weather = (ImageView) findViewById(R.id.iv_weather);
tv_temp = (TextView) findViewById(R.id.tv_temp);
tv_main = (TextView) findViewById(R.id.tv_main);
tv_description = (TextView) findViewById(R.id.tv_description);
tv_wind = (TextView) findViewById(R.id.tv_wind);
tv_cloud = (TextView) findViewById(R.id.tv_cloud);
tv_humidity = (TextView) findViewById(R.id.tv_humidity);
}
/* NetworkTask 를 요청하기 위한 메소드 */
private void requestNetwork() {
ContentValues values = new ContentValues();
values.put("q", "seoul");
values.put("appid", getString(R.string.weather_app_id));
networkTask = new NetworkTask(context, strUrl, values);
networkTask.execute();
}
/* 비동기 처리를 위해 AsyncTask 상속한 NetworkTask 클래스 */
public class NetworkTask extends AsyncTask<Void, Void, String> {
Context context;
String url = "";
ContentValues values;
public NetworkTask(Context context, String url, ContentValues values) {
this.context = context;
this.url = url;
this.values = values;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected String doInBackground(Void... params) {
String result = "";
RequestHttpUrlConnection requestHttpUrlConnection = new RequestHttpUrlConnection();
result = requestHttpUrlConnection.request(url, values, "GET"); //HttpURLConnection 통신 요청
Log.d(TAG, "NetworkTask >> doInBackground() - result : " + result);
return result;
}
@Override
protected void onProgressUpdate(Void... values) {
}
@Override
protected void onPostExecute(String result) {
Log.d(TAG, "NetworkTask >> onPostExecute() - result : " + result);
if (result != null && !result.equals("")) {
JsonParser jp = new JsonParser();
JsonObject jsonObject = (JsonObject) jp.parse(result);
JsonObject jsonObjectSys = (JsonObject) jp.parse(jsonObject.get("sys").getAsJsonObject().toString());
JsonObject jsonObjectWeather = (JsonObject) jp.parse(jsonObject.get("weather").getAsJsonArray().get(0).toString());
JsonObject jsonObjectMain = (JsonObject) jp.parse(jsonObject.get("main").getAsJsonObject().toString());
JsonObject jsonObjectWind = (JsonObject) jp.parse(jsonObject.get("wind").getAsJsonObject().toString());
JsonObject jsonObjectClouds = (JsonObject) jp.parse(jsonObject.get("clouds").getAsJsonObject().toString());
WeatherModel model = new WeatherModel();
model.setName(jsonObject.get("name").toString().replaceAll("\"",""));
model.setCountry(jsonObjectSys.get("country").toString().replaceAll("\"",""));
model.setIcon(getString(R.string.weather_url)+"img/w/" + jsonObjectWeather.get("icon").toString().replaceAll("\"","") + ".png");
model.setTemp(jsonObjectMain.get("temp").getAsDouble() - 273.15);
model.setMain(jsonObjectWeather.get("main").toString().replaceAll("\"",""));
model.setDescription(jsonObjectWeather.get("description").toString().replaceAll("\"",""));
model.setWind(jsonObjectWind.get("speed").getAsDouble());
model.setClouds(jsonObjectClouds.get("all").getAsDouble());
model.setHumidity(jsonObjectMain.get("humidity").getAsDouble());
setWeatherData(model); //UI 업데이트
} else {
showFailPop();
}
}
@Override
protected void onCancelled() {
super.onCancelled();
}
} //NetworkTask End
/* 통신하여 받아온 날씨 데이터를 통해 UI 업데이트 메소드 */
private void setWeatherData(WeatherModel model) {
Log.d(TAG, "setWeatherData");
tv_name.setText(model.getName());
tv_country.setText(model.getCountry());
Glide.with(context).load(model.getIcon()) //Glide 라이브러리를 이용하여 ImageView 에 url 로 이미지 지정
.placeholder(R.drawable.icon_image)
.error(R.drawable.icon_image)
.into(iv_weather);
tv_temp.setText(doubleToStrFormat(2, model.getTemp()) + " 'C"); //소수점 2번째 자리까지 반올림하기
tv_main.setText(model.getMain());
tv_description.setText(model.getDescription());
tv_wind.setText(doubleToStrFormat(2, model.getWind()) + " m/s");
tv_cloud.setText(doubleToStrFormat(2, model.getClouds()) + " %");
tv_humidity.setText(doubleToStrFormat(2, model.getHumidity()) + " %");
}
/* 통신 실패시 AlertDialog 표시하는 메소드 */
private void showFailPop() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Title").setMessage("통신실패");
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
Toast.makeText(getApplicationContext(), "OK Click", Toast.LENGTH_SHORT).show();
}
});
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
Toast.makeText(getApplicationContext(), "Cancel Click", Toast.LENGTH_SHORT).show();
}
});
AlertDialog alertDialog = builder.create();
alertDialog.show();
}
/* 소수점 n번째 자리까지 반올림하기 */
private String doubleToStrFormat(int n, double value) {
return String.format("%."+n+"f", value);
}
}
6. activity_main.xml
MainActivity 에 대한 레이아웃입니다.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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:background="@color/white"
tools:context=".ConnectActivity" >
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/amber_200"
android:elevation="10dp"
android:gravity="center"
android:text="현재 날씨"
android:textColor="@color/black"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/ll_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/ll_name"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="50dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="20dp"
android:weightSum="100"
android:orientation="horizontal"
android:background="@drawable/bg_custom_title_name"
android:gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title" >
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="50"
android:layout_marginRight="2dp"
android:gravity="right|center_vertical"
android:text="Seoul"
android:textStyle="bold"
android:textColor="@color/white"
android:textSize="20dp" />
<TextView
android:id="@+id/tv_country"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="50"
android:layout_marginLeft="2dp"
android:gravity="left|center_vertical"
android:text="KR"
android:textStyle="bold"
android:textColor="@color/white"
android:textSize="20dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="30dp"
android:layout_marginHorizontal="20dp"
android:orientation="horizontal"
android:weightSum="100"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ll_name" >
<ImageView
android:id="@+id/iv_weather"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="30"
android:src="@drawable/icon_image"
android:background="@drawable/bg_solid" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="70"
android:gravity="center_vertical"
android:orientation="vertical"
android:weightSum="100"
app:layout_constraintLeft_toRightOf="@+id/iv_weather"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<TextView
android:id="@+id/tv_temp"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="35"
android:gravity="center_vertical"
android:paddingLeft="25dp"
android:text="12 C"
android:textColor="@color/blue_light"
android:textSize="25sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_main"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="35"
android:gravity="center_vertical"
android:paddingLeft="25dp"
android:text="Clear Sky"
android:textColor="@color/black"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_description"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="30"
android:gravity="center_vertical"
android:paddingLeft="25dp"
android:text="broken clouds"
android:textColor="@color/grey_500"
android:textSize="17sp" />
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/view_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="30dp"
android:layout_marginHorizontal="20dp"
android:background="@color/grey_200"
app:layout_constraintBottom_toTopOf="@+id/ll_detail"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ll_main" />
<LinearLayout
android:id="@+id/ll_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="30dp"
android:layout_marginHorizontal="20dp"
android:orientation="horizontal"
android:weightSum="100"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view_divider" >
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="33"
android:orientation="vertical"
android:weightSum="100" >
<ImageView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="45"
android:paddingTop="5dp"
android:src="@drawable/icon_wind" />
<TextView
android:id="@+id/nm_wind"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="15"
android:gravity="center"
android:text="바람 "
android:textColor="@color/grey_500"
android:textSize="13sp" />
<TextView
android:id="@+id/tv_wind"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="40"
android:gravity="center_horizontal"
android:text="4.6m/s"
android:textColor="@color/black"
android:textSize="17sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="33"
android:orientation="vertical"
android:weightSum="100" >
<ImageView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="45"
android:paddingTop="5dp"
android:src="@drawable/icon_cloud" />
<TextView
android:id="@+id/nm_cloud"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="15"
android:gravity="center"
android:text="구름 "
android:textColor="@color/grey_500"
android:textSize="13sp" />
<TextView
android:id="@+id/tv_cloud"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="40"
android:gravity="center_horizontal"
android:text="75%"
android:textColor="@color/black"
android:textSize="17sp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="33"
android:orientation="vertical"
android:weightSum="100" >
<ImageView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="45"
android:gravity="center"
android:paddingTop="5dp"
android:src="@drawable/icon_humidity" />
<TextView
android:id="@+id/nm_humidity"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="15"
android:gravity="center"
android:text="습도 "
android:textColor="@color/grey_500"
android:textSize="13sp" />
<TextView
android:id="@+id/tv_humidity"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="40"
android:gravity="center_horizontal"
android:text="59%"
android:textColor="@color/black"
android:textSize="17sp" />
</LinearLayout>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
7. RequestHttpUrlConnection.java
HttpURLConnection 기능을 수행합니다.
package com.eun.myapp.util;
import android.content.ContentValues;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
public class RequestHttpUrlConnection {
public String TAG = RequestHttpUrlConnection.class.getSimpleName();
public String request(String _url, ContentValues _params, String method) {
String result = "";
try{
Log.d(TAG, "----- request() - _url : "+_url);
Log.d(TAG, "----- request() - _params : "+_params.toString());
StringBuffer sbParams = new StringBuffer();
String data = "";
if (_params == null){
sbParams.append("");
} else { //파라미터가 있는 경우
//파라미터가 2개 이상이면 파라미터를 &로 연결할 변수 생성
boolean isAnd = false;
//파라미터 키와 값
String key;
String value;
for(Map.Entry<String, Object> parameter : _params.valueSet()){
key = parameter.getKey();
value = parameter.getValue().toString();
//파라미터가 두개 이상일때, 파라미터 사이에 &로 연결
if (isAnd){
sbParams.append("&");
}
sbParams.append(key).append("=").append(value);
if (!isAnd){
if (_params.size() >= 2){
isAnd = true;
}
}
}
}
data = sbParams.toString();
URL url = new URL(method == "POST" ? _url : _url+"?"+data); //URL 문자열을 이용해 URL 객체 생성
HttpURLConnection conn = (HttpURLConnection)url.openConnection(); //URL 객체를 이용해 HttpUrlConnection 객체 생성
conn.setConnectTimeout(3000);
conn.setReadTimeout(3000);
conn.setRequestProperty("Cache-Control", "no-cache");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Accept", "application/json");
conn.setDefaultUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestMethod(method);
// conn.setRequestMethod("POST");
// conn.setRequestMethod("GET");
Log.d(TAG, "----- request() - url+param : "+url+"?"+data);
// 서버로 값 전송
OutputStream outputStream = conn.getOutputStream();
outputStream.write(data.getBytes("UTF-8"));
outputStream.flush();
// Response 데이터 처리
int responseCode = conn.getResponseCode();
Log.d(TAG, "----- request() - responseCode : "+responseCode);
if(responseCode == HttpURLConnection.HTTP_OK) {
StringBuilder builder = new StringBuilder();
try {
InputStreamReader in = new InputStreamReader(conn.getInputStream(), "UTF-8");
BufferedReader reader = new BufferedReader(in); //응답 결과를 읽기 위한 스트림 객체 생성
String line = "";
while((line = reader.readLine()) != null) {
builder.append(line + "\n");
}
result = builder.toString();
} catch(IOException e) {
e.printStackTrace();
}
} else {
result = conn.getResponseMessage();
}
}catch(Exception e){
e.printStackTrace();
}
Log.d(TAG, "----- request() - result : "+result.trim());
return result.trim();
}
}
결과 화면
감사합니당~
![](https://t1.daumcdn.net/keditor/emoticon/face/large/062.png)
'🤖 안드로이드 Android' 카테고리의 다른 글
[안드로이드/Android] Context (Application Context vs. Activity Context) (0) | 2021.10.27 |
---|---|
[안드로이드/Android] 레트로핏 Retrofit 을 이용한 날씨 앱 만들기 (0) | 2021.08.29 |
[안드로이드/Android] AsyncTask 를 이용한 프로그레스바(ProgressBar) 표시하기 (0) | 2021.08.23 |
[안드로이드/Android] 안드로이드 스튜디오(Android Studio) 에서 벡터(Vector) 이미지/아이콘 추가 (1) | 2021.08.13 |
[안드로이드/Android] 안드로이드 스튜디오 (Android Studio) 플러그인 - 테마 변경 (0) | 2021.07.31 |