🤖 안드로이드 Android

[안드로이드/Android] AsyncTask 로 HttpURLConnection 를 이용한 날씨 앱 만들기

핑크빛연어 2021. 8. 29. 15:46

 

안드로이드에서 서버와 통신하기 위한 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();
    }

}

 

 

결과 화면

 

 

 

감사합니당~

728x90
반응형