안드로이드에서 서버와 통신하기 위한 AsyncTask 로 HttpURLConnection 을 이용하는 방법입니다.
저는 openweathermap 의 api 를 통해 날씨 정보를 표시해주는 앱을 만들어보았습니당 🌤
https://openweathermap.org/api
작성한 파일 목록 입니다.
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
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();
}
}
결과 화면
감사합니당~
'🤖 안드로이드 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 |