본문 바로가기

안드로이드

안드로이드 : 웹 서버 연결

AndroidManifest.xml 파일에 설정 

INTERNET 권한 설정

http 인 경우 android:useClearTextTrafic 을 true 로 설정

 

이미지 다운로드

이미지를 ImageView 에 바로 출력하기

이미지를 매번 다시 출력

 

이미지를 다운로드 받아서 파일로 만들고 출력

처음 한 번 만 다운로드 받고 다음부터는 다운로드 받지않고 로컬에서 이미지를 가져와서 출력 가능

 

Data parsing

parsing : 데이터를 분석해서 자신에게 필요한 데이터를 원하는 포맷으로 만드는 것

서버에서 제공하는 데이터 포맷 :

csv - 변하지 않는 데이터 - 과거에 만들어져서 제공되는 데이터, 공백이나 쉼표로 구분된 데이터)

xml - 태그 형태로 표현하는 데이터 포맷 - 실시간으로 변경되는 데이터, 인간이 알아보기 쉽게 기계가 사용하기는 무거운 포맷, RSS 라고도 합니다.

json - 자바 스크립트 데이터 포맷으로 데이터 표현, XML 보다는 가벼워서 많이 사용 읽기가 어렵습니다.

 

html 은 데이터 포맷이 아니고 브라우저가 해석해서 랜더링하기 위한 포맷

open api 형태로 제공되지는 않고 웹화면으로만 제공되는 데이터의 경우는 html 파싱을 수행해서 필요한 데이터를 추출하는 경우가 있습니다.

=> html 파싱을 하고자 하는 경우에는 tag, class, id, xpath 의 개념을 알고 있어야 합니다.

=> java 에서는 jsoup 라는 라이브러리를 이용해서 html 파싱을 합니다.

=> android studio 는 gradle 방식의 빌드를 합니다.

build.gradle 파일에 의존성을 설정 

=> java는 mvnrepository.com 에서 외부 라이브러리를 검색

 

 

html 크롤링이나 자동로그인 같은 작업을 만들고자 하시면 selenium 을 이용합니다.

 

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="kr.co.tjoeun.webserveruse">

    <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/Theme.WebServerUse"
        android:usesCleartextTraffic="true">
        <activity android:name=".HanActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".HaniActivity" />
        <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>

build.gradle(:app)

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "kr.co.tjoeun.webserveruse"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.3'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    // https://mvnrepository.com/artifact/org.jsoup/jsoup
    implementation group: 'org.jsoup', name: 'jsoup', version: '1.13.1'

}

MainActivity.kt

package kr.co.tjoeun.webserveruse

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.*
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import android.widget.Toast
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File


class MainActivity : AppCompatActivity() {
    inner class TextDownloadThread : Thread(){
        override fun run() {
            // 다운로드 받을 URL 을 생성
            val url = URL("https://www.google.com")
            // Connection 생성
            val con = url.openConnection() as HttpURLConnection
            // 옵션 설정
            con.setConnectTimeout(30000) // 최대 접속 시간
            con.setUseCaches(false)

            // post 방식이나 header 설정을 수행
            // 문자열 다운로드 받는 스트림을 생성
            val br = BufferedReader(InputStreamReader(con.inputStream))

            // 다운로드 받은 문자열을 저장할 변수
            var result = ""
            // 줄 단위로 문자열 읽기
            while (true){
                val line = br.readLine() // 한 줄 읽기

                // 읽은 데이터가 없으면 종료
                if(line == null) {
                    break
                }
                // 읽은 데이터를 result 에 추가
                result = result + line + "\n"
            }

            // 정리 작업
            br.close()
            con.disconnect()

            // 메인 스레드가 아닌 곳에서는 UI 갱신 작업을 하면 안됩니다.
            //resulttxt.text = result

            // 필요한 데이터를 Message 에 저장하고 핸들러를 호출
            val message = Message()
            message.obj = result

            handler.sendMessage(message)
        }
    }
    val handler = object: Handler(Looper.getMainLooper()){
        override fun handleMessage(msg: Message){
            // 전송된 데이터를 읽습니다.
            val str = msg.obj as String
            resulttxt.text = str
        }
    }
    inner class ImageThread : Thread(){
        override fun run() {
            val url = URL("http://cyberadam.cafe24.com/img/orange.jpg")
            // 이미지를 바로 출력
            /*
            val fis = url.openStream()
            val bit = BitmapFactory.decodeStream(fis)
            fis.close()

            val msg = Message()
            msg.obj = bit
            imageHandler.sendMessage(msg)

             */

            // 이미지를 다운로드 받아서 앱에 저장
            var con = url.openConnection() as HttpURLConnection
            // 다운로드 받을 파일의 크기를 설정
            val len = con.contentLength
            // 다운로드 받은 내용을 저장할 배열 생성
            val raster = ByteArray(len)

            // 다운로드 받을 스트림 생성
            val fis = con.inputStream
            // 파일에 저장할 스트림 생성
            var path = Environment.getDataDirectory().absolutePath +
                    "/data/kr.co.tjoeun.webserveruse/files/orange.jpg"
            val fos = openFileOutput("orange.jpg", 0)

            // 읽어서 파일에 쓰기
            while (true) {
                val read:Int = fis.read(raster)
                if(read < 0) {
                    break
                }
                // 파일에 기록
                fos.write(raster, 0, read)
            }
            // 작업 정리
            fis.close()
            fos.close()
            con.disconnect()

            val message : Message = Message()
            // 이미지 파일의 전체 경로 넘겨주기
            message.obj = path
            imageHandler.sendMessage(message)
        }
    }

    val imageHandler= object:Handler(Looper.getMainLooper()){
        override fun handleMessage(msg: Message) {
            /*
            val bitmap = msg.obj as Bitmap
            resultimage.setImageBitmap(bitmap)

             */
            val path = msg.obj as String
            resultimage.setImageBitmap(BitmapFactory.decodeFile(path))
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        htmldownload.setOnClickListener {
            TextDownloadThread().start()
        }
        iamgedownload.setOnClickListener {
            //ImageThread().start()

            // 이미지 파일이 있는지 확인
            //  이미지 파일 경로 생성
            var path = Environment.getDataDirectory().absolutePath +
                    "/data/kr.co.tjoeun.webserveruse/files/orange.jpg"

            // 파일의 존재 여부 확인
            if(File(path).exists()){
               Toast.makeText(this, "이미지가 존재합니다.",
                Toast.LENGTH_LONG).show()
                // 로컬에 있는 이미지 출력
                resultimage.setImageBitmap(BitmapFactory.decodeFile(path))
            } else{
                Toast.makeText(this, "이미지가 존재하지 않아 다운로드 합니다.",
                    Toast.LENGTH_LONG).show()
                // 로컬에 있는 이미지 출력
                ImageThread().start()
            }
        }
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    tools:context=".MainActivity"
    android:orientation="vertical">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/htmldownload"
        android:text="문자열 다운로드"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/iamgedownload"
        android:text="이미지 다운로드"/>
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/resultimage"/>
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/resulttxt"/>
    </ScrollView>
</LinearLayout>

RSS 에서 title 태그의 내용만 추출해서 출력

http://www.hani.co.kr/rss

 

 

HanActivity.kt

package kr.co.tjoeun.webserveruse

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Log
import kotlinx.android.synthetic.main.activity_han.*
import java.io.BufferedReader
import java.io.ByteArrayInputStream
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory

class HanActivity : AppCompatActivity() {
    // 데이터를 다운로드 받을 스레드
    inner class ThreadEx :Thread(){
        override fun run(){
            // 필요한 데이터 다운로드
            val url = URL("http://www.hani.co.kr/rss/")
            // 연결 객체 생성
            val con = url.openConnection() as HttpURLConnection
            // 연결 옵션 설정
            con.connectTimeout = 30000
            con.useCaches = false

            // 문자열 가져올 스트림
            val br = BufferedReader(InputStreamReader(con.inputStream))
            // 가져온 문자열을 저장할 객체
            var xml = ""

            while (true) {
                val line = br.readLine()
                if(line == null) {
                    break
                }
                xml = xml + line + "\n"
            }

            br.close()
            con.disconnect()
            //Log.e("xml", xml)

            // DOM Parsing : xml 문자열을 메모리에 펼침
            val factory = DocumentBuilderFactory.newInstance()
            val builder  = factory.newDocumentBuilder()
            val istream = ByteArrayInputStream(
                xml.toByteArray(charset("utf-8")))
            val doc = builder.parse(istream)
            val root = doc.documentElement

            // 원하는 태그를 추출
            var items = root.getElementsByTagName("title")

            var result = ""
            // 태그 안의 내용만 가져오기
            for (i in 0 until items.length){
                val item = items.item(i)
                val text = item.firstChild
                val title = text.nodeValue
                result = result + title + "\n"
            }

            // 핸들러에게 결과를 전송
            val msg = Message()
            msg.obj = result
            handler.sendMessage(msg)
        }
    }

    val handler = object: Handler(Looper.getMainLooper()){
        override fun handleMessage(msg: Message) {
            val str = msg.obj as String
            hanitext.text = str
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_han)

        hanibtn.setOnClickListener {
            ThreadEx().start()
        }
    }
}

activity_han.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    tools:context=".HanActivity"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="한겨레 실시간 기사 가져오기"
        android:id="@+id/hanibtn"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/hanitext"/>


</LinearLayout>