알파채널 개념을 개발한 토마스 포터와 톰 더프의 이름을 따서 만들었다.

 

대상(렌더링 대상의 콘텐츠)을 사용하여

소스(렌더 할 그래픽 개체)의 합성 결과 색상을 계산하는 방법으로

12개의 합성 연산자로 구성되어 있다.

 

 

 

 

 

구현해보기

 

        private fun drawPorterDuffSample(canvas: Canvas) {
            val sampleBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
            val sampleCanvas = Canvas(sampleBitmap)
 
            sampleCanvas.drawBitmap(getCircleBitmap(), 0f, 0f, Paint())
            sampleCanvas.drawBitmap(getRectangleBitmap(), 0f, 0f, Paint().apply {
                xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
            })
 
            canvas.drawBitmap(sampleBitmap, 0f, 0f, Paint())
        }
cs

 

 

canvas의 drawRect, drawCircle과 같은 메소드에 적용하면 기대와 다른 결과가 나온다.

SRC, DST 모두 Bitmap으로 사용해야 기대한 결과를 볼 수 있었다.

 

위 코드에서 rectangleBitmap이 SRC, circleBitmap이 DST가 된다.

  

 

전체 코드

더보기
    class Renderer {
        private var width: Int = 0
        private var height: Int = 0
        private var centerX: Int = 0
        private var centerY: Int = 0
 
        fun setWidthAndHeight(width: Int, height: Int) {
            this.width = width
            this.height = height
            centerX = width/2
            centerY = height/2
        }
 
        fun onDraw(canvas: Canvas) {
            drawPorterDuffSample(canvas)
        }
 
        private fun drawPorterDuffSample(canvas: Canvas) {
            val sampleBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
            val sampleCanvas = Canvas(sampleBitmap)
 
            sampleCanvas.drawBitmap(getCircleBitmap(), 0f, 0f, Paint())
            sampleCanvas.drawBitmap(getRectangleBitmap(), 0f, 0f, Paint().apply {
                xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
            })
 
            canvas.drawBitmap(sampleBitmap, 0f, 0f, Paint())
        }
 
        private fun getCircleBitmap() = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
            val canvas = Canvas(this)
            val paint = Paint().apply {
                flags = Paint.ANTI_ALIAS_FLAG
                color = Color.parseColor("#E9B639")
            }
            val radius = width*3.5f/10
 
            canvas.drawCircle(radius, radius, radius, paint)
        }
 
        private fun getRectangleBitmap() = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
            val canvas = Canvas(this)
            val paint = Paint().apply {
                flags = Paint.ANTI_ALIAS_FLAG
                color = Color.parseColor("#4899C5")
            }
            val startTop = width*3/10
            val endBottom = width*9/10
 
            canvas.drawRect(
                Rect(startTop, startTop, endBottom, endBottom),
                paint
            )
        }
 
    }
cs

 

 

 

 

 

 

결과

 

 

위에 코드를 안드로이드 커스텀뷰에서 그린 결과이다.

 

안드로이드 커스텀뷰 코드

더보기
class CustomView(context: Context, attrs: AttributeSet?, defStyle: Int) : View(context, attrs, defStyle) {
    constructor(context: Context) : this(context, null0)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
 
    private val renderer = Renderer()
 
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        renderer.setWidthAndHeight(measuredWidth, measuredHeight)
    }
 
    override fun onDraw(canvas: Canvas) {
        renderer.onDraw(canvas)
    }
 
    class Renderer {
        private var width: Int = 0
        private var height: Int = 0
        private var centerX: Int = 0
        private var centerY: Int = 0
 
        fun setWidthAndHeight(width: Int, height: Int) {
            this.width = width
            this.height = height
            centerX = width/2
            centerY = height/2
        }
 
        fun onDraw(canvas: Canvas) {
            drawPorterDuffSample(canvas)
        }
 
        private fun drawPorterDuffSample(canvas: Canvas) {
            val sampleBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
            val sampleCanvas = Canvas(sampleBitmap)
 
            sampleCanvas.drawBitmap(getCircleBitmap(), 0f, 0f, Paint())
            sampleCanvas.drawBitmap(getRectangleBitmap(), 0f, 0f, Paint().apply {
                xfermode = PorterDuffXfermode(PorterDuff.Mode.SCREEN)
            })
 
            canvas.drawBitmap(sampleBitmap, 0f, 0f, Paint())
        }
 
        private fun getCircleBitmap() = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
            val canvas = Canvas(this)
            val paint = Paint().apply {
                flags = Paint.ANTI_ALIAS_FLAG
                color = Color.parseColor("#E9B639")
            }
            val radius = width*3.5f/10
 
            canvas.drawCircle(radius, radius, radius, paint)
        }
 
        private fun getRectangleBitmap() = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
            val canvas = Canvas(this)
            val paint = Paint().apply {
                flags = Paint.ANTI_ALIAS_FLAG
                color = Color.parseColor("#4899C5")
            }
            val startTop = width*3/10
            val endBottom = width*9/10
 
            canvas.drawRect(
                Rect(startTop, startTop, endBottom, endBottom),
                paint
            )
        }
 
    }
 
}
cs

 

 

 

 

 

참고

 

https://ko.wikipedia.org/wiki/%EC%95%8C%ED%8C%8C_%EC%B1%84%EB%84%90

 

알파 채널 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전.

ko.wikipedia.org

 

https://developer.android.com/reference/android/graphics/PorterDuff.Mode

 

PorterDuff.Mode  |  Android 개발자  |  Android Developers

 

developer.android.com

 

 

 

'Common' 카테고리의 다른 글

암호화와 해시  (0) 2020.06.19
Git 기본 명령어  (0) 2019.08.19
Android Studio/IntelliJ 단축키 (Window)  (0) 2019.08.17
시간 표시  (0) 2019.06.16

 

 

 

 

 

RecyclerView.ItemDecoration을 이용한 StickyHeader 구현

외부 라이브러리없이 전체 코드로 구현한다.

 

ItemDecoration

RecyclerView를 더 커스텀하여 사용할 수 있게 해준다.

아이템 간의 구분선을 만들거나 여백을 조정할 수 있으며 해당 포스팅처럼 RecyclerView 자체에 그릴 수도 있다.

 

ItemDecoration의 onDrawOver 메소드를 오버라이딩하여 구현한다.

RecyclerView가 draw할 때마다 콜백으로 호출되며 RecyclerView 위에 그려지게 된다.

 

 

RecyclerView의 Header 아이템과 일반 아이템으로 구분이 되어야 한다.

ViewType은 따로 나누지 않아도 상관없으며 구분할 함수만 구현하면 된다.

 

 

 

 

 

 

 

ItemDecoration 코드와 사용 코드

 

 

HeaderItemDecoration.kt

 

더보기
class HeaderItemDecoration(
    recyclerView: RecyclerView,
    private val isHeader: (itemPosition: Int) -> Boolean,
    onClickHeader: ((itemPosition: Int) -> Unit)? = null
) : RecyclerView.ItemDecoration() {
 
    private var currentHeaderToShow: Pair<Int, RecyclerView.ViewHolder>= null
 
    init {
        recyclerView.adapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
            override fun onChanged() {
                // clear saved header as it can be outdated now
                currentHeaderToShow = null
            }
        })
 
        recyclerView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
            // clear saved layout as it may need layout update
            currentHeaderToShow = null
        }
 
        // handle click on sticky header
        onClickHeader?.let {
            initHeaderClickListener(recyclerView, it)
        }
    }
 
    override fun onDrawOver(c: Canvas, recyclerView: RecyclerView, state: RecyclerView.State) {
        super.onDrawOver(c, recyclerView, state)
 
        val topChildView = getTopChildView(recyclerView) ?: return
        val topChildViewPosition = recyclerView.getChildAdapterPosition(topChildView)
        if (topChildViewPosition == RecyclerView.NO_POSITION) return
 
        val headerView = getHeaderViewToShow(topChildViewPosition, recyclerView) ?: return
 
        val contactedNewHeader = getContactedNewHeader(headerView, recyclerView)
        if (contactedNewHeader != null) {
            drawMovedHeader(c, headerView, contactedNewHeader, recyclerView.paddingTop)
        } else {
            drawHeader(c, headerView, recyclerView.paddingTop)
        }
    }
 
    private fun getTopChildView(recyclerView: RecyclerView)
            = recyclerView.findChildViewUnder(
                recyclerView.paddingStart.toFloat(),
                recyclerView.paddingTop.toFloat()
            )
 
    private fun getHeaderViewToShow(topChildItemPosition: Int, recyclerView: RecyclerView): View? {
        recyclerView.adapter ?: return null
 
        val headerPositionToShow = getHeaderPositionToShow(topChildItemPosition)
        if (headerPositionToShow == RecyclerView.NO_POSITION) return null
 
        return getHeaderView(headerPositionToShow, recyclerView)
    }
 
    private fun getHeaderView(headerPositionToShow: Int, recyclerView: RecyclerView): View? {
        val headerHolderType = recyclerView.adapter!!.getItemViewType(headerPositionToShow)
        if (currentHeaderToShow?.first == headerPositionToShow && currentHeaderToShow?.second?.itemViewType == headerHolderType) {
            return currentHeaderToShow?.second?.itemView
        }
 
        val headerHolder = recyclerView.adapter!!.createViewHolder(recyclerView, headerHolderType)
        recyclerView.adapter!!.onBindViewHolder(headerHolder, headerPositionToShow)
        fixLayoutSize(recyclerView, headerHolder.itemView)
 
        currentHeaderToShow = headerPositionToShow to headerHolder
 
        return headerHolder.itemView
    }
 
    private fun getHeaderPositionToShow(topChildItemPosition: Int): Int {
        var headerPosition = RecyclerView.NO_POSITION
        var currentPosition = topChildItemPosition
        do {
            if (isHeader(currentPosition)) {
                headerPosition = currentPosition
                break
            }
            currentPosition -= 1
        } while (currentPosition >= 0)
        return headerPosition
    }
 
    private fun fixLayoutSize(parent: ViewGroup, view: View) {
        // Specs for parent (RecyclerView)
        val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY)
        val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED)
 
        // Specs for children (headers)
        val childWidthSpec = ViewGroup.getChildMeasureSpec(
            widthSpec,
            parent.paddingStart + parent.paddingEnd,
            view.layoutParams.width
        )
        val childHeightSpec = ViewGroup.getChildMeasureSpec(
            heightSpec,
            parent.paddingTop + parent.paddingBottom,
            view.layoutParams.height
        )
 
        view.measure(childWidthSpec, childHeightSpec)
        view.layout(00, view.measuredWidth, view.measuredHeight)
    }
 
    private fun drawHeader(c: Canvas, header: View, paddingTop: Int) {
        c.save()
        c.translate(0f, paddingTop.toFloat())
        header.draw(c)
        c.restore()
    }
 
    private fun getContactedNewHeader(headerView: View, recyclerView: RecyclerView): View? {
        val contactPoint = headerView.bottom + recyclerView.paddingTop
        val contactedChildView = getContactedChildView(recyclerView, contactPoint) ?: return null
        val contactedChildViewPosition = recyclerView.getChildAdapterPosition(contactedChildView)
 
        return if (isHeader(contactedChildViewPosition)) {
            contactedChildView
        } else {
            null
        }
    }
 
    private fun getContactedChildView(recyclerView: RecyclerView, contactPoint: Int): View? {
        var childInContact: View? = null
        for (i in 0 until recyclerView.childCount) {
            val child = recyclerView.getChildAt(i)
            val bounds = Rect()
            recyclerView.getDecoratedBoundsWithMargins(child, bounds)
            if (bounds.bottom > contactPoint) {
                if (bounds.top <= contactPoint) {
                    childInContact = child
                    break
                }
            }
        }
        return childInContact
    }
 
    private fun drawMovedHeader(c: Canvas, contactedTopHeader: View, contactedBottomHeader: View, paddingTop: Int) {
        c.save()
        c.translate(0f, (contactedBottomHeader.top - contactedTopHeader.height).toFloat())
        contactedTopHeader.draw(c)
        c.restore()
    }
 
    private fun initHeaderClickListener(recyclerView: RecyclerView, onClickHeader: (Int) -> Unit) {
        recyclerView.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() {
            override fun onInterceptTouchEvent(
                recyclerView: RecyclerView,
                motionEvent: MotionEvent
            ): Boolean {
                return if (motionEvent.action == MotionEvent.ACTION_DOWN) {
                    val hasClickedOnHeaderArea = motionEvent.y <= currentHeaderToShow?.second?.itemView?.bottom ?: 0
                    if (hasClickedOnHeaderArea) {
                        currentHeaderToShow?.first?.let { position ->
                            onClickHeader.invoke(position)
                        }
                        true
                    } else {
                        false
                    }
                } else false
            }
        })
    }
}
cs

 

 

사용 코드

 

        rv_main.addItemDecoration(
            HeaderItemDecoration(
                rv_main,
                { it%4 == 0 },
                { Toast.makeText(this, it.toString(), Toast.LENGTH_SHORT).show() }
            )
        )
cs

 

HeaderItemDecoration 클래스에

recyclerView, header 판단 함수, header 클릭시 동작 함수를 입력해 사용하면 된다.

 

 

 

 

onDrawOver

    override fun onDrawOver(c: Canvas, recyclerView: RecyclerView, state: RecyclerView.State) {
        super.onDrawOver(c, recyclerView, state)
 
        val topChildView = getTopChildView(recyclerView) ?: return
        val topChildViewPosition = recyclerView.getChildAdapterPosition(topChildView)
        if (topChildViewPosition == RecyclerView.NO_POSITION) return
 
        val headerView = getHeaderViewToShow(topChildViewPosition, recyclerView) ?: return
 
        val contactedNewHeader = getContactedNewHeader(headerView, recyclerView)
        if (contactedNewHeader != null) {
            drawMovedHeader(c, headerView, contactedNewHeader, recyclerView.paddingTop)
        } else {
            drawHeader(c, headerView, recyclerView.paddingTop)
        }
    }
cs

 

onDrawOver 함수에 모든 핵심 내용이 다 있다.

 

1. recyclerView에서 보이는 가장 최상단 아이템 포지션을 얻는다.

2. 최상단 아이템 index 이하의 가장 가까운 header 아이템의 뷰를 얻는다.

3. (기존의 그리고 있는 header) 아래로 (접촉이 있는 새로운 header)가 있는지 확인한다.

 3-1. 있다면 새로운 header가 간섭한 만큼 위로 올려 그린다.

 3-2. 없다면 그대로 그린다.

 

만약 헤더끼리 접촉했을 때 하단 헤더가 상단 헤더를 밀도록 구현하는게 아닌 아래로 들어가듯이 구현하고 싶다면 contactedNewHeaer를 구할 필요없이 drawHeader만 사용하면 된다.

 

 

 

 

 

 

원본 코드 및 참고한 링크

 

원본코드

원본코드에서 필요하다고 생각하는 코드로만 다시 정리했다.

https://gist.github.com/filipkowicz/1a769001fae407b8813ab4387c42fcbd

 

Item Decorator for sticky headers in Kotlin

Item Decorator for sticky headers in Kotlin. GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

 

참고한 링크

StickyHeader를 구현하는 여러 코드들이 있다.

https://www.it-swarm.dev/ko/android/recyclerview%EC%97%90%EC%84%9C-%EB%81%88%EC%A0%81%ED%95%9C-%ED%97%A4%EB%8D%94%EB%A5%BC-%EB%A7%8C%EB%93%A4%EB%A0%A4%EB%A9%B4-%EC%96%B4%EB%96%BB%EA%B2%8C%ED%95%B4%EC%95%BC%ED%95%A9%EB%8B%88%EA%B9%8C-%EC%99%B8%EB%B6%80-lib%EC%97%86%EC%9D%B4/1055032283/

 

android — RecyclerView에서 끈적한 헤더를 만들려면 어떻게해야합니까? (외부 lib없이)

에야디야, 이것은 스크린에서 벗어나기 시작할 때 한 종류의 홀더 스틱을 원한다면 (우리는 어떤 섹션도 신경 쓰지 않습니다) 당신이 그것을하는 방법입니다. 재활용 아이템의 내부 RecyclerView 로

www.it-swarm.dev

 

 

'Android > UI' 카테고리의 다른 글

BottomSheet  (0) 2020.08.10

 

 

 

 

dependency

 

implementation 'com.google.android.material:material:1.3.0-alpha02'

 

 

 

BottomSheetDialog

 

 

기존의 다이얼로그 사용법과 동일하다.

 

        BottomSheetDialog(this).apply {
            val view = layoutInflater.inflate(R.layout.view_bottom_sheet, null)
            view.btn_view_bottom_sheet.setOnClickListener {
                Toast.makeText(this@MainActivity, "FIN", Toast.LENGTH_SHORT).show()
                dismiss()
            }
            setContentView(view)
        }.show()
cs

 

 

 

 

 

 

BottomSheet With Behavior

 

 

 

activity_main.xml

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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">
 
    <!--  Content  -->
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="50dp">
 
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello, World!"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />
 
    </androidx.constraintlayout.widget.ConstraintLayout>
 
    <!--  BottomSheet  -->
    <LinearLayout
        android:id="@+id/ll_main_bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#66A9EC"
        app:behavior_hideable="true"
        app:behavior_peekHeight="50dp"
        app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
 
        <TextView
            android:id="@+id/tv_main_bottom_sheet_title"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center"
            android:text="TODO List"
            android:textSize="20dp"
            android:textColor="@android:color/black"
            android:textStyle="bold"/>
 
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_main_bottom_sheet"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:paddingBottom="20dp"
            android:clipToPadding="false"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
 
    </LinearLayout>
 
</androidx.coordinatorlayout.widget.CoordinatorLayout>
cs

 

 

behavior_hideable : 완전하게 숨길 수 있을지 없을지를 정한다.

behavior_peekHeight : 최소 높이를 정한다.

layout_behavior : 행동을 정한다. 물론 BottomSheet을 만들기 위해서이기 때문이니

  com.google.android.material.bottomsheet.BottomSheetBehavior로 값을 넣어준다.

 

 

 

 

 

 

MainActivity.kt

 

class MainActivity : AppCompatActivity() {
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        initViews()
    }
 
    private fun initViews() {
        rv_main_bottom_sheet.adapter = TodoRecyclerAdapter {
            Toast.makeText(this"$it clicked", Toast.LENGTH_SHORT).show()
            hideBottomSheet()
        }.apply {
            updateDataSet(sample)
        }
 
        tv_main_bottom_sheet_title.setOnClickListener {
            showBottomSheet()
        }
    }
 
    private fun showBottomSheet() {
        BottomSheetBehavior.from(ll_main_bottom_sheet).let {
            if (it.state != BottomSheetBehavior.STATE_EXPANDED) {
                it.state = BottomSheetBehavior.STATE_EXPANDED
            }
        }
    }
 
    private fun hideBottomSheet() {
        BottomSheetBehavior.from(ll_main_bottom_sheet).let {
            if (it.state != BottomSheetBehavior.STATE_COLLAPSED) {
                it.state = BottomSheetBehavior.STATE_COLLAPSED
            }
        }
    }
 
    private val sample = IntRange(020).map { "Job $it" }.toList()
 
}
cs

 

 

기본적으로 드래그 액션을 통해서 BottomSheet을 제어할 수 있지만

코드를 통해서 특정상황에 확장하거나 줄일 수 있다.

 

 

 

'Android > UI' 카테고리의 다른 글

RecyclerView - StickyHeader  (0) 2020.08.17

 

 

SpringBoot

스프링을 사용하기 위한 라이브러리 모음

 

IntelliJ Ultimate를 사용한다면 New Project로 바로 스프링부트 프로젝트를 만들 수 있다.

하지만 Ultimate는 1년에 149$의 사용료가 필요하다.

대학생의 경우 학교 이메일을 통해 무료로 제공하고 있다.

 

IntelliJ Community에서도 스프링부트를 사용할 수 있다.

다만 gradle설정이 필요하다.

생각보다 간단하니 무리해서 Ultimate를 살필요가 없다.

 

 

 

 

본 포스팅의 목표는 간단하다.

InteilliJ Community에서 위 사진의 스프링을 보는 것이다.

 

 


 

EmptyProject를 만들자

 

 

New Project에서 Empty Project를 선택한다.

 

 

 

 

 

프로젝트 이름을 설정하고 완료한다.

 

 

 

스프링부트 프로젝트를 위한 설정파일을 만들자

 

스프링 사이트에서 스프링부트 설정파일들을 만들 수 있다.

https://start.spring.io/

 

 

1. Maven을 사용할지 Gradle을 사용할지 고른다.

2. 언어를 선택한다.

3. 버전을 선택하자. (버전에 대해서는 잘 몰른다. default가 stable한 버전일 것이라 생각해 그래로 두었다.)

4. Group에 패키지를 입력하고 Artifact에 프로젝트 이름을 쓴다.

5. 자바 버전을 선택한다.

6. 사용할 라이브러리들을 선택한다.

 

Lombok의 경우 플러그인을 설치해야한다.

JUnit은 JUnit5로 설정되니 JUnit4을 사용하고 싶다면 depency를 따로 추가해야 한다.

 

EXPLORE를 선택한다.

 

 

 

 

그러면 위의 사진같이 패키지 구조를 어떻게 설정할지 나온다.

DOWNLOAD해서 패키지를 다운받고 압축을 푼다.

 

사실 여기서 중요한 것은 gradle 파일이다.

위에서 Empty Project 대신 gradle 프로젝트를 만들고 gradle을 설정해도 된다.

다만 gitignore나 SpringApplication을 따로 만들어야 하고 gradle 버전이 맞지 않으면 설정해야하는 등 다운받는게 더 간단한 것 같다.

 

 

 

스프링부트 구조를 프로젝트에 적용하자

 

 

New - Module from existing sources...

다운받은 패키지를 선택한다.

 

 

 

 

 

해당하는 것을 선택하고 완료한다.

 

 

 

 

 

위에서 본 패키지 구조대로 설정된다.

 

 

 

 

 

 

프로젝트이름이 들어간 Application 파일에 main function을 실행시켜보자.

그럼 우리의 목표인 스프링을 볼 수 있다.

 

 


 

목표는 완료했지만 브라우저에서도 볼 수 있는 것을 만들어 보자

 

 

 

아직 정확히 어떤 것이지 모르지만

GetMapping을 통해 RestAPI 같다는 느낌이 든다.

Annotation으로 GetMapping 외에서 PostMapping, PutMapping, DeleteMapping이 있다.

 

 

 

 

 

 

브라우저에서 localhost:8080/hello로 접근하면 위와같은 결과화면을 볼 수 있다.

 

 

 

 

'Server' 카테고리의 다른 글

서버알못의 AWS 설정2 - RDS  (0) 2020.06.18
서버알못의 AWS 설정1 - EC2 생성  (0) 2020.06.17

 

 

 

 

프리뷰 홈

https://developer.android.com/preview

 

Android 개발자  |  Android Developers

테스트, 개발 및 의견 제공을 위해 Android 차기 버전을 미리 살펴봅니다.

developer.android.com

 

 

 


 

 

 

Component Changing

 

Window Insets

IME Animation

IME를 에니메이션을 커스텀해 좀 더 뷰와 interaction한 UI를 만들 수 있다.

Insets에서 IME에 대한 정보를 통해 동기화된 UI를 만들 수 있다.

 

Conversations

기본적인 Notification이 아닌 Conversation을 통해 커뮤니케이션이 필요한 내용을 따로 관리하고 더 많은 정보를 보여줄 수 있다.

 

Bubbles

커뮤니케이션을 하기위한 depth를 줄일 수 있는 컴포넌트

기존에는 개발자 모드에서 사용할 수 있는 기능이었지만 11부터는 모든 사용자가 사용할 수 있다.

 

 

 

Privacy

 

Data Access Auditing

 

One-Time Permission

기존의 지속적인 권한허용이 아닌 1회용 권한허용 추가

 

Background Location

11에서는 더욱 제한적

우선 포그라운드에서 권한을 허용해야하고 백그라운드에서 사용하기 위해서는 한번 더 설정창(앱 내부가 아닌)에서 허용해야 한다.

 

Package visibility restrictions

패키지에 접근하기 위해서는 메니페스트 설정이 필요한듯 보인다.

 

Scoped Storage

MedialStore API로 편집하는 기능이 추가

 

Auto-reset permissions

앱을 오랜기간 사용하지 않는다면 권한 설정이 초기화될 수 있다.

 

 

 

Utility for Developer

 

Wi-Fi Debugging

현재는 수동으로 설정해야 하지만 Canry에서는 도구를 이용해 접근이 가능하다.

 

Nullability Annotations

@RecentlyNullable, @RecentlyNonNull makes Warnings

@Nullable, @NonNull makes Errors

 

Crash Reasons Reporting

앱에 문제가 생겼을 때 더 자세한 정보를 얻을 수 있다.

 

GWP-ASan

HWASan in Andorid10

 

ADB Incremental

용량이 큰 앱을 위해 10배 더 빠른 설치가 가능하게 해준다.

 

 

 

Graphics & Media

 

NDK Image Decoder 해독기를 직접 사용할 수 있다.

Animated HEIF GIF보다 가벼운 HEIF를 디코딩하여 에니메이션이 가능하다.

OpenSL ES deprecated -> Oboe

 

 

 

기타

 

Neural Networks API 머신러닝을 위한 API

5G 코드를 통해 네트워크가 5G를 지원하는 상황인지에 대한 결정할 수 있다.

Autofill/Keyboard Integration 

 

 

 

Jetpack

 

Hilt : Dagger에 설치된 의존성 라이브러리, DI 작업에 권장하는 새로운 방식

Paging

CameraX beta

Compose pre-alpha version

 

 

 

Android Studio

 

4.0 : Stable

Motion Editor

LayoutInspector

 

4.1 : Beta

DatabaseInspector(Room, SQLite) 기존의 다운로드 받아서 확인해야했던 방식에서 도구로 내장되었다.

 

4.2 : Canary

Wireless debugging with Android11

Jetpack Compose development

 

 

 

Google Play

 

New Play console beta

https://play.google.com/apps/

 

로그인 - Google 계정

하나의 계정으로 모든 Google 서비스를 Google 계정으로 로그인

accounts.google.com

 


 

AndroidDevelopers Youtube

위의 기본 소개영상 외에 분야별 영상들도 있다.

https://www.youtube.com/c/AndroidDevelopers/videos

 

Android Developers

Welcome to the official Android Developers YouTube channel. Get the latest Android news, best practices, live videos, demonstrations, tutorials, and more. Su...

www.youtube.com

 

'Android > Version' 카테고리의 다른 글

Q -> Android 10  (0) 2019.06.01
Pie_파이  (0) 2018.06.07
Oreo_오레오  (0) 2018.06.06
Nougat_누가  (0) 2018.06.06
Marshmallow_마시멜로  (0) 2018.06.06

 

 

 

 

이름 그대로 인스턴스가 만들어진 순간부터 소멸하는 순간까지의 루틴을 의미한다.

중요하고 반복적으로 발생하는 상황에 대한 처리를 위해 만들어졌다고 생각한다.

 

수명주기는 보통 많은 리소스를 가지고 있는 컴포넌트들이 가지고 있다.

그렇기 때문에 컴포넌트의 상태에 따라 필요없어지는 리소스 해제를 통해 가용공간을 늘리거나 컴포넌트의 소멸시 리소스 해제를 통해 메모리 누수를 막을 수 있다.

 

 

 


 

액티비티

 

 

 

 

onCreate / onDestroy : 액티비티가 생성되고 종료되는 순간에서의 처리

onStart / onStop : 액티비티가 조금이라도 화면에 보이는 순간과 보이지 않는 순간에서의 처리

onResume / onPause : 액티비티가 사용자와 상호작용을 할 수 있는 순간과 없는 순간에서의 처리

 

보통 위와 같이 짝으로 묶는다.

할당과 해제, 시작과 종료가 필요한 조합이기 때문이다.

 

 

 

https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ko

 

Activity 수명 주기에 대한 이해  |  Android 개발자  |  Android Developers

An Activity is an application component that provides a screen with which users can interact in order to do something, such as dial the phone, take a photo, send an email, or view a map. Each activity is given a window in which to draw its user interface��

developer.android.com

 

 

 


 

프래그먼트

 

 

https://developer.android.com/guide/components/fragments?hl=ko

 

프래그먼트  |  Android 개발자  |  Android Developers

A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section��

developer.android.com

 

 

 


 

 

 

 

https://www.charlezz.com/?p=29013

 

Android에서 View의 생명주기 | 찰스의 안드로이드

https://proandroiddev.com/the-life-cycle-of-a-view-in-android-6a2c4665b95e을 번역한 내용입니다. 안드로이드 앱을 실행할 때 우리가 가장 먼저 스크린에서 볼 수 있는 것이 View라고 말할 수 있다. View 클래스는 사�

www.charlezz.com

 

https://hyeonu1258.github.io/2018/03/26/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%20%EB%A9%B4%EC%A0%91/

 

안드로이드 면접 | 현우의 개발노트

View가 그려지는 과정 뷰는 포커스를 얻으면 레이아웃을 그리도록 요청한다. 이때 레이아웃의 계층구조 중 루트 뷰를 제공해야한다. 따라서 그리기는 루트노드에서 시작되어 트리를 따라 전위 ��

hyeonu1258.github.io

 

'Android > Common' 카테고리의 다른 글

비동기  (0) 2020.05.14
안드로이드 개발자 로드맵  (2) 2020.05.07
안드로이드 프로젝트 루틴  (0) 2020.02.20

 

 

 

 

안드로이드를 하다보면 MD5, SHA1, SHA-256과 같은 것들을 필연적으로 접하게 된다.

릴리즈 APK를 만들 때 .jks 확장자인 키스토어가 필요하다.

AWS 서버를 만들면 확장자가 pem인 파일을 받아야 한다.

공인인증서는 .der, .pfx와 같은 확장자로 쓰인다.

 

위에 것들을 무엇인지 알기 위해서는 해시함수, 대칭키, 공개키, 인증서 등을 알아야 한다.

 

간략하게 각각의 목적을 보자면

 

해시함수는 유일하게 식별할 수 있는 값을 만드는 함수이고

대칭키와 공개키는 데이터를 안전하게 전달하기 위해 암복호화를 위해 만들어졌다.

인증서는 자신을 증명하기 위해 사용된다.

 

 

 


 

해시함수

대상을 식별할 수 있는 해시값을 만드는 함수이다.

 

해시값의 크기와 해시함수에 따라 충돌이 일어날 수도 있지만

사용하는 대부분의 알고리즘은 유일하게 식별할 수 있게 만든다.

 

해시의 사용

1. HashMap과 같이 대상을 찾기 위한 키값으로 사용

2. 원본 데이터를 보장하기 위한 체크섬으로 사용

 

 

MD5(Message-Digest algorithm 5)

128비트 암호화 해시 함수

주로 프로그램이나 파일이 원본 그대로인지를 확인하는 무결성 검사 등에 사용.

임의의 길이의 메시지를 입력받아, 128비트짜리 고정 길이의 출력값을 낸다.

현재는 결함으로 인해 보안관련 용도로 쓰이는 것을 권장하지 않음.

 

SHA(Secure Hash Alogorithm)

암호화 해시 함수

최초의 SHA 알고리즘을 SHA-0라고 한다.

SHA-1은 SHA-0의 변형으로 TLS, SSL, PGP, SSH 등 많은 보안 프로토콜과 프로그램에 사용된다.

SHA-2 4종류의 변형(SHA-224, SHA-256, SHA-384, SHA-512) 통칭

 

 

 


 

암호화

 

대칭키

암호화와 복호화에 하나의 키를 사용하는 암호화 방식

하나의 키를 사용하기 때문에 키 노출시 암호화가 무의미해진다.

DES, 3DES, AES...

 

DES(Data Encryption Standard)

블록 암호의 일종, 미국 NBS에서 국가 표준으로 정한 암호

56비트의 키를 사용
3DES : DES를 세번 중첩해 만듬

 

AES(Andvanced Ecryption Standard)

DES를 대체해서 사용 중이다.

 

 

 

비대칭키(공개키)

암호화는 공개키를, 복호화는 비밀키를 사용하는 암호화 방식

누구나 암호화할 수 있지만 내용은 비밀키를 가지고 있는 사람만 볼 수 있다.

대칭키와 비교하면 매우 오래 걸린다.

RSA...

 

RSA

1024, 2048 비트의 키를 사용하며 소인수 분해를 이용한 암호화 알고리즘

기존의 알고있던 비대칭키와 반대로 비밀키로 암호화하고 공개키로 복호화가 가능하다.

 

비밀키를 암호화에 사용하면 나만 암호문을 만들 수 있다.

그렇기 때문에 전자서명의 용도로 사용할 수 있다.

 

비밀키를 복호화에 사용하면 나만 상대의 암호문을 읽을 수 있다.

서버에서 공개키를 공유하고 유저들이 암호화한 값을 복호화하여 서버만 데이터를 볼 수 있도록 사용한다.

 

 

 


 

암호화 사용법

 

A와 B가 통신하고 싶다.

 

대칭키 사용

1. 대칭키를 사용한다면 한쪽에서 우선 대칭키를 보내고

2. 서로 대칭키를 이용해 암호화 복호화하며 통신한다.

Problem. 대칭키를 보내는 과정에서 키가 노출될 수 있다.

 

공개키 사용

1. 공개키의 경우 서로 공개키만 전달한다.

2. 서로의 공개키로 암호화해서 전달한다.

3. 전달받은 데이터를 각자의 개인키로 복호화 한다.

Problem. 공개키 방식은 대칭키에 비해 속도가 현저하게 느리다.

 

대칭키 + 공개키 사용

1. 서로에게 공개키를 보낸다.

2. 한쪽에서 공개키로 대칭키를 암호화해서 전달한다.

3. 다른 한쪽은 개인키로 복호화해 대칭키를 얻는다.

대칭키를 암호화해서 보내기 때문에 대칭키가 노출되지 않고

대칭키를 통해 통신함으로 좋은 성능을 얻을 수 있다.

 

 

 


SSL 암호화 방식

 

대칭키 + 공개키를 사용해 통신에 문제가 생겼다.

공개키를 얻는 과정 중 외부의 악의적(해커) 행위로 인해 공개키를 신뢰할 수 없게 되었다.

 

기본적으로 위의 대칭키 + 공개키 방식을 사용한다.

공개키를 전달하는 과정이 복잡해졌을 뿐이다.

공개키를 인증기관에 전달해 공개키를 포함한 인증서를 만들어

전달하여 공개키에 대한 신뢰성을 높였다.

 

 

 

 

1. 사이트는 사이트의 공개키CA(인증기관)에 등록한다.

2. 사이트의 공개키해싱하고 CA의 비밀키로 암호화한다. 이 암호화값을 디지털 서명이라고 한다. 그리고 발급대상, 발급대상의 공개키, 발급자, 발급자의 서명(2번의 암호화한 값) 정보를 가지고 있는 인증서를 만든다.

3. 만들어진 인증서를 서버에 전달한다.

 

 

 

4. 유저는 CA에서 CA의 공개키를 받아 브라우저에 보관하고 있는다.

5. 서버에 접속을 요청한다.

6. 서버에서 인증서를 받는다.

7. ( 인증서의 발급자 서명을 CA 공개키로 복호화한 값 )과 ( 인증서에 포함된 공개키를 해싱한 값 )을

비교해 동일하다면 신뢰성이 있는 공개키라고 판단

 

 

 

8. 인증서의 공개키를 이용해 대칭키를 암호화하고 싸이트는 복호화하여 대칭키를 얻는다.

9. 대칭키를 이용해 통신한다.

 

인증기관에 의해서 암호화하는 것은 암호화 대상을 숨기려는 의도보다는

인증기관의 공개키를 통해 복호화됨을 확인하여 신뢰성을 얻는 것이다.

 

 

SSL(Secure Sockets Layer)

종단간 전송된 데이터를 암호화하여 인터넷 연결 보안을 유지하는 표준 기술

SSL 인증서 파일 포멧 종류로 .pem, .crt, .cer, .csr, .der, .pfx, .p12, .key, .jks 등의 포멧이 있다.

TLS(Transport Layer Security)와 함께 거론되는데 방식은 동일하다.

넷스케이프에서 SSL1.0, SSL2.0, SSL3.0까지 개발했고 SSL3.0을 기초로 IETF에서 만든 것이 TLS이다.

현재는 SSL은 결함으로 인해 사용이 중지되었음으로 우리가 사용하는 SSL은 TLS이다.

TLS를 SSL이라고 부르는 것에 대해 약간의 논란?이 있는 것 같아 보인다.

정확히 알지는 못하지만 우분투를 리눅스라 부를지 우분투라 부를지정도의 문제라고 보인다.

 

HTTP + SSL -> HTTPS

FTP + SSL + SFTP

 

 

 


 

인증서

공개키 + 비밀키 소유자 정보

 

 

인증서에 들어가는 내용

 - 일련번호

 - 소유자

 - 서명 알고리즘

 - 발행자

 - 유효기간(시작)

 - 유효기간(끝)

 - 키 사용 목적

 - 공인 키

 - 서명 알고리즘

 - 서명

 

 

 


 

이해에 도움이 된 포스팅들

 

https://rsec.kr/?p=426

 

RSA 인증서 (Certification) 와 전자서명 (Digital Sign)의 원리

공개키 비밀키로 이루어지는 RSA 알고리즘을 이해. 인증서에 포함된 공개키의 무결성을 보증하는 인증서 체인 (Certificate Chain) 에 대한 설명무결성 보증시 사용되는 해쉬 (지문, Finger Print) 와 전자

rsec.kr

https://preamtree.tistory.com/38

 

[IT 기술면접 준비자료] 대칭키, 공개키 방식과 SSL(TLS)

 정보보호 관련 수업을 들으면, 가장 먼저 배우게 되는 내용이 SSL이다. 암호화의 기원, DES, AES와 같은 개요에 해당하는 내용은 생략하고, 바로 본론으로 들어가도록 하겠다.  대칭키(Symmetric Key)

preamtree.tistory.com

 

'Common' 카테고리의 다른 글

PorterDuff  (0) 2020.08.18
Git 기본 명령어  (0) 2019.08.19
Android Studio/IntelliJ 단축키 (Window)  (0) 2019.08.17
시간 표시  (0) 2019.06.16

 

 

 

서버가 실행될 공간, 공간에 접근할 수 있는 방법. 서버에 접근할 수 있는 고정 IP가 준비되었다.

남은 것은 서버에서 없어서는 안될 데이터베이스이다.

 

 

 

RDS(Relational Database Service)

콘솔에서 RDS를 검색하고 RDS 페이지로 간다.

 

 

데이터베이스 생성 선택

 

 

 

 

 

사용할 데이터베이스를 선택한다.

프리티어로 가능한 엔진은 MySQL, MariaDB, PostgreSQL, Microsoft SQL Server이다.

 

가능 엔진 중 원하는 엔진을 선택하면 될 것 같다.

 

 

 

 

 

프리티어가 가능한 엔진에 한해 프리티어를 선택할 수 있다.

 

DB 인스턴스 식별자에는 말그대로 식별할 수 있는 이름을 넣어준다.

DB에 접근할 아이디와 비밀번호를 설정한다.

 

 

 

 

 

프리티어로 가능한 20기가로 설정한다.

 

 

 

 

 

퍼블릭 엑세스를 가능하도록 설정하고 보안그룹을 설정한다.

차후에 변경할 것이니 EC2 인스턴스를 만들 때 만든 보안그룹으로 설정해두자.

 

모두 설정했으니 데이터베이스 생성을 누르자.

 

 

 

 

 

기다리면 일정 시간 후 데이터베이스가 위와 같이 사용가능으로 표시된다.

 

데이터베이스를 선택하면 상세정보에 보안그룹이 표시된다.

보안그룹을 선택하던가 EC2페이지에서 보안그룹 페이지로 이동한다.

 

그리고 새 보안그룹을 만든다.

 

 

 

 

RDS의 보안그룹임을 나타낼수 있도록 이름을 만들고

인바운드 규칙을 위와 같이 만들자.

 

유형은 RDS가 속한 엔진을 선택한다.

EC2 인스턴스가 데이터베이스에 접근할 수 있도록 EC2 인스턴스의 보안그룹을 대상으로 인바운드 규칙을 만든다.

또 유틸 프로그램을 통해 데이터베이스에 접근하도록 내가 작업할 PC에서의 IP를 추가한다.

 

보안그룹을 만들었으니 데이터베이스에 해당 보안그룹을 설정하자.

 

여기까지 데이터베이스를 사용하기 위한 서비스 설정이 끝났다.

해당 데이터베이스에 수정을 눌러 기존에 임시로 설정했던 보안그룹을 제거하고

위에서 만든 RDS용 보안그룹으로 설정하자.

 

 

 

 

DBeaver

데이터베이스 정보를 유틸 프로그램을 통해 확인해보자.

DBeaver 외에도 다른 유틸도 상관없다.

 

https://dbeaver.io/

 

DBeaver Community | Free Universal Database Tool

DBeaver Universal Database Tool Free multi-platform database tool for developers, database administrators, analysts and all people who need to work with databases. Supports all popular databases: MySQL, PostgreSQL, SQLite, Oracle, DB2, SQL Server, Sybase,

dbeaver.io

 

 

 

설치 후 실행하면 아래와 같은 프로그램이 보인다.

 

 

파일 아래 아이콘을 선택한다.

데이터베이스 종류가 뜨면 해당하는 데이터베이스를 선택한다.

 

 

 

 

 

Authentication에는 RDS를 만들때 입력했던 아이디와 패스워드를 입력한다.

 

Server Host에는 해당 데이터베이스를 선택하면 표시되는 엔드포인트 값을 입력한다.

 

 

 

 

 

완료하면 왼쪽 리스트에 데이터베이스가 추가된다.

 

 

설정이 끝났다.

해당 프로그램을 통해 데이터베이스 작업이 가능하다.

 

 

 

 

파라미터 그룹

설정해야할 일이 하나 더 있다.

위에 데이터베이스의 Default Charset은 utf8로 설정되어 있지만

기본 설정은 latin으로 되어 있다.

 

기본 설정값들은 RDS의 파라미터 그룹에서 관리한다.

 

 

기본 파라미터 그룹은 변경이 불가능하다고 한다.

 

새로운 파라미터 그룹은 만들자.

그룹 이름을 적당히 만들고 생성하자.

 

새로 만든 파라미터 그룹을 선택하고 파라미터 그룹 작업에서 편집을 선택하자.

 

 

 

 

2페이지, 3페이지에 charset과 관련된 파라미터들이 있다.

 

character_set_client, character_set_connection, character_set_database, character_set_filesystem, character_set_results, character_set_server은 utf8로

collation_connection, collation_server은 utf8_general_ci로 설정하자.

 

해당 데이터베이스에 파라미터 그룹을 설정하자.

수정을 누르면 데이터베이스 옵션에서 파라미터 그룹을 변경할 수 있다.

 

변경 후 데이터베이스를 재부팅해야 적용된다.

작업 / 재부팅을 선택한다.

 

 

 

 

 

EC2 서버에서도 데이터베이스를 확인할 수 있다.

 

mysql 설치
sudo yum install mysql

mysql 접속
mysql -u 계정 -p -h Host주소

 

 

 

 

 

 

 

 

 

'Server' 카테고리의 다른 글

서버알못의 스프링부트 프로젝트 만들기  (0) 2020.06.22
서버알못의 AWS 설정1 - EC2 생성  (0) 2020.06.17

 

 

윈도우에서 AWS 클라우드 서버를 사용하려 한다.

윈도우 기반으로 작성하니 맥북 사용자는 아래 포스팅 링크를 참조하자.

 

AWS에 가입하면 12개월 동안 서비스의 일정부분은 무료로 사용할 수 있는 프리티어를 제공한다.

일정 부분 무료라고 써놨지만 사실상 무료이다.

 

다만 보안설정없이 사용하는 경우 내 계정이 코인 체굴에 쓰이고 요금 폭탄을 맞는 경우도 있다고는 한다.

물론 고객센터에 영어로 잘 문의하면 선처해주는 분위기인듯 하다.

 

보안설정을 통해 나만 접근하게 설정한다면 위와 같은 문제는 없을 것이다.

 

 

 

AWS 프리 티어

https://aws.amazon.com/ko/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc

 

AWS 프리 티어

프리 티어를 이용해 60가지가 넘는 제품을 체험하고 AWS에 구축할 수 있습니다. 사용하는 제품에 따라 세 가지 유형의 프리 티어 오퍼가 제공됩니다. 각 제품에 대한 자세한 내용은 아래를 참조하

aws.amazon.com

 

해당 포스팅 내용을 보고 다시 작성하는 것이다.

https://jojoldu.tistory.com/259?category=635883

 

4) 스프링부트로 웹 서비스 출시하기 - 4. AWS EC2 & RDS 구축하기

이번 시간엔 SpringBoot를 운영할 AWS 환경을 구축하겠습니다. (모든 코드는 Github에 있습니다.) Tip) 운영 서버는 크게 클라우드 서비스(AWS, Azure 등) 과 호스팅 서비스(Cafe24, 코리아호스팅 등)을 이용��

jojoldu.tistory.com

 

 


 

1. AWS 가입

2. EC2 인스턴스 생성

3. EIP 설정

4. putty 사용

5. RDS 생성

6. DBeaver 사용

7. FileZilla 사용

 

EC2 서버를 위한 공간

EIP 만들 웹 서비스에 접근하기 위한 고정 IP

putty 서버를 위한 공간에 접근하기 위한 프로그램

RDS 데이터베이스 서비스

DBeaver DB에 접근하기 위한 프로그램

 

해당 포스팅에서는 4번까지 진행하면서

서버를 위한 기본이 되는 공간을 만들고 접속해보려 한다.

 

 


 

AWS 가입

가입한 시점부터 프리티어 12개월이 진행됨으로 별다른 신청이 필요없다.

가입하고 아래 콘솔화면을 볼 수 있다.

 

 

오른쪽 위에 리전을 선택하는 곳이 있는데 서울이 아니라면 서울을 선택한다.

 

 

 


 

EC2

 

EC2 페이지로 가면 위와 같은 화면을 볼 수 있다.

인스턴스 시작 선택

 

 

운영체제를 Amazon Linux AMI를 선택한다.

 

 

 

프리티어에서 가능한 t2.micro를 선택

 

 

 

 

인스턴스 세부 정보 구성은 기본 설정을 따른다.

 

 

 

프리티어에서 사용 가능한 최대 크기 30기가로 설정

 

 

 

 

서버에 접근할 수 있는 보안 그룹을 설정한다.

SSH 접근은 사용자만 접근할 수 있도록 "내 IP"만 선택하고

외부에서 웹서비스에 접근할 수 있도록 HTTP, HTTPS는 열어둔다.

 

다른 장소에서 SSH에 접근하고 싶거나 FTP, DB에 필요한 포트가 필요하다면

후에 추가로 접근하고 싶은 서비스 혹은 IP를 설정하면 된다.

 

 

인스턴스에 대한 설정이 모두 끝났음으로

검토 및 시작에서 시작하기를 선택

 

 

 

새 키 페어 생성을 선택하고

키 페어 이름을 입력한 뒤 키 페어를 다운로드하여 저장해둔다.

 

 

 

 

 

생성을 완료하고 나면 인스턴스가 돌아가는 것을 확인할 수 있다.

 

 

 

 

탄력적 IP(EIP) 설정

서버를 만들기 위한 자원은 갖췄지만 고정된 IP가 없다면 아무 의미가 없다.

내 서버의 IP를 얻어보자.

 

EC2 메뉴에서 네트워크 및 보안 / 탄력적 IP를 선택

우상단의 탄력적 IP 주소 할당 선택

 

 

할당하면 주소가 생성된다.

주소와 인스턴스가 각각 만들어졌을뿐 연결되어 있지 않다.

 

우상단의 Actions의 주소연결을 선택한다.

 

 

 

위에서 만든 EC2 인스턴스를 선택하고

프라이빗 IP 주소 EC2 인스턴스의 프라이빗 IP를 입력한다.

(누르면 자동으로 항목이 나온다.)

 

 

연결 후 EC2 인스턴스를 확인한다.

 

 

탄력적 IP가 할당된 것이 보이면 된다.

 

 

 

 

Putty 사용

서버를 위한 공간을 할당받았으니 접속해보자.

Putty를 사용해 SSH로 서버 공간에 접근하겠다.

 

SSH(Secure Shell)

네트워크 상의 다른 컴퓨터에 로그인하거나 원격 시스템에서 명령을 실행하고 다른 시스템으로 파일을 복사할 수 있도록 해주는 응용프로그램 또는 프로토콜

 

우선 다운받아 설치해보자

https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html

 

Download PuTTY: latest release (0.73)

This page contains download links for the latest released version of PuTTY. Currently this is 0.73, released on 2019-09-29. When new releases come out, this page will update to contain the latest, so this is a good page to bookmark or link to. Alternativel

www.chiark.greenend.org.uk

 

 

 

위 두개의 프로그램을 사용할 것이다.

 

우선 PuTTYgen을 이용해 private key를 만들 것이다.

 

 

 

통해 EC2 인스턴스를 생성하면서 받은 pem을 Load한다.

Save Private Key를 선택하여 저장한다.

passphrase가 없다고 경고가 뜨는데 무시한다.

 

pem과 같은 이름으로 만들라고 블로그에서 봤는데 이유는 잘 모르겠다.

 

 

그럼 이제 PuTTY를 이용해 서버에 접근해보자.

 

 

HostName : [user]@[IP]

 - user : EC2의 경우 ec2-user를 입력한다.

 - IP : EC2 인스턴스 정보에 퍼블릭 DNS 또는 EIP를 입력한다.

 - ex : ec2-user@12.345.670.891

Port : 22

 - EC2를 만들 때 보안그룹에서 SSH, 22 포트를 내 IP 설정한 이유이다.

Private Key 설정

 - Connection / SSH / Auth

 - Private key file for authentication에 위에서 생성한 .ppk 파일은 선택한다.

Saved Sessions에 설정을 저장해두고 사용하면 된다.

 

Open을 누르면

 

 

정상적으로 접속된 것을 볼 수 있다.

 

처음 접속하면 업데이트가 필요하다는 메세지가 뜬다.

sudo yum update 명령어를 통해 업데이트하자.

 

또 자바 버전은 1.7이 기본 설정인듯 하다.

1.8이 필요한 경우 업데이트를 하자

 

설치 가능 버전 확인
yum list java*jdk-devel

설치
sudo yum java-1.8.0-openjdk-devel.x86_64

자바 버전 변경
sudo alternatives --config java

 

 

 

 

'Server' 카테고리의 다른 글

서버알못의 스프링부트 프로젝트 만들기  (0) 2020.06.22
서버알못의 AWS 설정2 - RDS  (0) 2020.06.18

 

 

 

Github : https://github.com/junghun9102/AndroidTemplate

Branch : library/retrofit

 

junghun9102/AndroidTemplate

Contribute to junghun9102/AndroidTemplate development by creating an account on GitHub.

github.com

 

 


Commit ea187676

 

기본 사용법

1. Service

2. OkHttpClient

3. Retrofit을 이용한 Service 구현체

 

1번과 3번만 구현하면 API통신이 가능하다.

2번은 선택적이지만 보통 구현한다.

 

GoogleNews RSS를 사용해 Retrofit 기본 사용법을 정리하려고 한다.

https://news.google.com/rss?hl=ko&gl=KR&ceid=KR:ko 

 

 

 

 

 


https://square.github.io/retrofit/

 

Retrofit

A type-safe HTTP client for Android and Java

square.github.io

dependencies

    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
    implementation 'com.squareup.okhttp3:okhttp:3.12.0'
    implementation 'com.squareup.retrofit2:converter-simplexml:2.1.0'
//    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

 

 

 

1. Service 

HTTP Method(GET/POST/PUT/DELETE)로 API 요청을 위한 interface

 

interface GoogleNewsService {

    @GET("/rss")
    fun getNews(
        @Query("hl") hl: String,
        @Query("gl") gl: String,
        @Query("ceid") ceid: String
    ): Call<RespNewsRss>

}

 

추가적인 사용 방법들은 아래 포스팅에 잘 설명되어 있다.

 

https://medium.com/@ggikko/retrofit-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B3%A0-%EC%93%B0%EA%B8%B0-a6ce90cf7768

 

Retrofit 홈페이지 한글로 긁어보기 RESTFUL SERVICE VERY EASY!!

원래 Volley를 쓰다가 부분적으로 적용해서 사용해 보려했는데 하다보니 홈페이지에 있는 글을 다 읽고 말았다. 그래서 영어로 읽으면 시간이 오래걸려서 한글로 작성해보려한다.. 허접하게 번역

medium.com

 

 

 

 

2. OkHttpClient

API 요청과 응답에 관한 설정을 할 수 있다.

object GoogleNewsServiceFactory

    private const val HTTP_TIMEOUT_READ = 10L
    private const val HTTP_TIMEOUT_CONNECT = 5L

    private fun makeOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor, appendHeaderInterceptor: Interceptor) = OkHttpClient.Builder()
        .connectTimeout(HTTP_TIMEOUT_CONNECT, TimeUnit.MILLISECONDS)
        .readTimeout(HTTP_TIMEOUT_READ, TimeUnit.MILLISECONDS)
        .addInterceptor(httpLoggingInterceptor)
        .addInterceptor(appendHeaderInterceptor)
        .build()

    private fun makeLoggingInterceptor(debug: Boolean): HttpLoggingInterceptor {
        return HttpLoggingInterceptor().apply {
            level = if (debug) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
        }
    }

    private fun makeAppendHeaderInterceptor() = Interceptor { chain ->
            val newRequest = chain.request().newBuilder()
//            newRequest.addHeader("Version", "v1.0")
            chain.proceed(newRequest.build())
    }

 

connectTimeout / readTimeout / writeTimeout / callTimeout

타임아웃을 설정 가능

 

HttpLoggingInterceptor

요청과 응답 로그를 볼 수 있다.

 

 

makeAppendHeaderInterceptor

1번의 Service에서도 각각의 요청마다 Header를 붙일 수 있다.

하지만 디폴트 헤더 데이터가 필요한 경우 interceptor를 만들어 요청마다 같은 헤더를 추가할 수 있다.

 

 

 

 

3. Retrofit을 이용한 Service 구현체

실제 사용하기 위한 구현체 생성

object GoogleNewsServiceFactory

    fun makeNewsService(debug: Boolean, baseUrl: String): GoogleNewsService {
        val okHttpClient = makeOkHttpClient(
            makeLoggingInterceptor(debug),
            makeAppendHeaderInterceptor()
        )
        return makeNewsService(
            baseUrl,
            okHttpClient
        )
    }

    private fun makeNewsService(baseUrl: String, okHttpClient: OkHttpClient): GoogleNewsService {
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okHttpClient)
            .addConverterFactory(SimpleXmlConverterFactory.create())
            .build()

        return retrofit.create(GoogleNewsService::class.java)

 

baseUrl(baseUrl)

기본 URL을 설정

 

client(okHttpClient)

2번에서 만들었던 okHttpClient 설정

 

addConverterFactory(SimpleXmlConverterFactory.create())

응답 데이터를 파싱해 객체로 받기 위한 설정

 

 

 

 

XML

 

dependecies

implementation 'com.squareup.retrofit2:converter-simplexml:2.1.0'

 

Retrofit Builder 

addConverterFactory(SimpleXmlConverterFactory.create())

 

<rss xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
  <channel>
  <generator>NFE/5.0</generator>
  <title>주요 뉴스 - Google 뉴스</title>
  <link>https://news.google.com/?hl=ko&gl=KR&ceid=KR:ko</link>
  <language>ko</language>
  <webMaster>news-webmaster@google.com</webMaster>
  <copyright>2020 Google Inc.</copyright>
  <lastBuildDate>Fri, 29 May 2020 03:09:37 GMT</lastBuildDate>
  <description>Google 뉴스</description>
  <item>
    <title>[속보] 코로나19 확진 58명 늘어…신규 확진자 전원 수도권 - 한겨레</title>
    <link>https://news.google.com/__i/rss/rd/articles/CBMiNWh0dHA6Ly93d3cuaGFuaS5jby5rci9hcnRpL3NvY2lldHkvaGVhbHRoLzk0NzA1Ny5odG1s0gEA?oc=5</link>
    <guid isPermaLink="false">52782408608000</guid>
    <pubDate>Fri, 29 May 2020 01:23:03 GMT</pubDate>
    <description><ol><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiNWh0dHA6Ly93d3cuaGFuaS5jby5rci9hcnRpL3NvY2lldHkvaGVhbHRoLzk0NzA1Ny5odG1s0gEA?oc=5" target="_blank">[속보] 코로나19 확진 58명 늘어…신규 확진자 전원 수도권</a>&nbsp;&nbsp;<font color="#6f6f6f">한겨레</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9UEFEOE1UVXBoMmfSAQA?oc=5" target="_blank">쿠팡발 감염 확산에 수도권 '비상'…모자·신발서 코로나 나와</a>&nbsp;&nbsp;<font color="#6f6f6f">뉴스TVCHOSUN</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiMWh0dHBzOi8vd3d3Lnl0bi5jby5rci9fbG4vMDEwM18yMDIwMDUyOTEwNTI0NDc0NDbSAUNodHRwczovL20ueXRuLmNvLmtyL25ld3Nfdmlldy5hbXAucGhwP3BhcmFtPTAxMDNfMjAyMDA1MjkxMDUyNDQ3NDQ2?oc=5" target="_blank">신규 발생 58명...지역 발생 55명 모두 수도권에 집중</a>&nbsp;&nbsp;<font color="#6f6f6f">YTN</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiJ2h0dHBzOi8vbmV3cy5qb2lucy5jb20vYXJ0aWNsZS8yMzc4ODg4MNIBK2h0dHBzOi8vbW5ld3Muam9pbnMuY29tL2FtcGFydGljbGUvMjM3ODg4ODA?oc=5" target="_blank">코로나 신규환자 58명, 지역감염 55명···모두 수도권서 터졌다 - 중앙일보</a>&nbsp;&nbsp;<font color="#6f6f6f">중앙일보 모바일</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9cnVjU2VzMVB0R2_SAQA?oc=5" target="_blank">수도권 덮친 '쿠팡 물류센터 집단감염'…"2주가 고비" / JTBC 뉴스룸</a>&nbsp;&nbsp;<font color="#6f6f6f">JTBC News</font></li><li><strong><a href="https://news.google.com/stories/CAAqOQgKIjNDQklTSURvSmMzUnZjbmt0TXpZd1NoTUtFUWlBMHR6bGxZQU1FVmdIQWUwRXpZRzRLQUFQAQ?oc=5" target="_blank">Google 뉴스에서 전체 콘텐츠 보기</a></strong></li></ol></description>
    <source url="http://www.hani.co.kr">한겨레</source>
  </item>

 

@Root(name = "rss", strict = false)
data class RespNewsRss(
    @field:Element(name = "channel") var channel: RespNewsRssChannel? = null
)

@Root(name = "channel", strict = false)
class RespNewsRssChannel(
    @field:ElementList(entry = "item", inline = true) var newsList: List<RespNews>? = null
)

@Element(name = "item")
data class RespNews(
    @field:Element(name = "title") var title: String? = null,
    @field:Element(name = "link") var link: String? = null,
    @field:Element(name = "guid") var guid: String? = null,
    @field:Element(name = "pubDate") var pubDate: String? = null,
    @field:Element(name = "description") var description: String? = null,
    @field:Element(name = "source") var source: String? = null
)

 

 

Json

 

depenencies

implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

 

Retrofit Builder

.addConverterFactory(GsonConverterFactory.create())

 

{
  "rss" : {
    "channel" : {
    	"items" : [{
          "title" : "인천시 “쿠팡물류센터 확진자 딸 등 2명 추가 확진” - 한겨레",
          "link" : "https://news.google.com/__i/rss/rd/articles/CBMiM2h0dHA6Ly93d3cuaGFuaS5jby5rci9hcnRpL2FyZWEvY2FwaXRhbC85NDcyMjIuaHRtbNIBAA?oc=5",
          "guid" : "52782408608000",
          "pubDate" : "Sun, 31 May 2020 05:56:12 GMT",
          "description" : "<ol><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiM2h0dHA6Ly93d3cuaGFuaS5jby5rci9hcnRpL2FyZWEvY2FwaXRhbC85NDcyMjIuaHRtbNIBAA?oc=5" target="_blank">인천시 “쿠팡물류센터 확진자 딸 등 2명 추가 확진”</a>&nbsp;&nbsp;<font color="#6f6f6f">한겨레</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiOGh0dHBzOi8vd3d3Lmhhbmtvb2tpbGJvLmNvbS9OZXdzL1JlYWQvMjAyMDA1MzAxNjc3MDQ3MzMy0gEA?oc=5" target="_blank">이태원 클럽→쿠팡 물류센터→여의도 학원 전파?</a>&nbsp;&nbsp;<font color="#6f6f6f">한국일보</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiNWh0dHA6Ly93d3cuaGFuaS5jby5rci9hcnRpL3NvY2lldHkvaGVhbHRoLzk0NzE4OS5odG1s0gEA?oc=5" target="_blank">부천 쿠팡물류센터 관련 코로나19 확진자 최소 109명</a>&nbsp;&nbsp;<font color="#6f6f6f">한겨레</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiJ2h0dHBzOi8vbmV3cy5qb2lucy5jb20vYXJ0aWNsZS8yMzc4OTYxONIBK2h0dHBzOi8vbW5ld3Muam9pbnMuY29tL2FtcGFydGljbGUvMjM3ODk2MTg?oc=5" target="_blank">쿠팡 물류센터 총 108명 확진…"이태원클럽 학원강사발 추정" - 중앙일보</a>&nbsp;&nbsp;<font color="#6f6f6f">중앙일보 모바일</font></li><li><a href="https://news.google.com/__i/rss/rd/articles/CBMiK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9R1RwQ3ZuQ2I2U1nSAQA?oc=5" target="_blank">코로나19 신규 확진 39명…수도권 이어 지역서도 산발 감염</a>&nbsp;&nbsp;<font color="#6f6f6f">뉴스TVCHOSUN</font></li><li><strong><a href="https://news.google.com/stories/CAAqOQgKIjNDQklTSURvSmMzUnZjbmt0TXpZd1NoTUtFUWlBMHR6bGxZQU1FV1BYSElmd2VCR2RLQUFQAQ?oc=5" target="_blank">Google 뉴스에서 전체 콘텐츠 보기</a></strong></li></ol>",
          "soruce" : "한겨레"
        }]
    }
  }
}

 

data class RespNewsRss(
    @field:SerializedName("channel") var channel: RespNewsRssChannel? = null
)

class RespNewsRssChannel(
    @field:SerializedName("items") var newsList: List<RespNews>? = null
)

data class RespNews(
    @field:SerializedName("title") var title: String? = null,
    @field:SerializedName("link") var link: String? = null,
    @field:SerializedName("guid") var guid: String? = null,
    @field:SerializedName("pubDate") var pubDate: String? = null,
    @field:SerializedName("description") var description: String? = null,
    @field:SerializedName("source") var source: String? = null
)

 

 

 

 

실제 사용

MainActivity

    private val newsService = GoogleNewsServiceFactory.makeNewsService(BuildConfig.DEBUG, "https://news.google.com")

    private fun initViews() {
        btn_main.setOnClickListener {
            newsService.getNews("ko", "KR", "KR:ko")
                .enqueue(object : Callback<RespNewsRss> {
                    override fun onResponse(
                        call: Call<RespNewsRss>,
                        response: Response<RespNewsRss>
                    ) {
                        tv_main.text = response.body()?.channel?.newsList?.toString()
                    }

                    override fun onFailure(call: Call<RespNewsRss>, t: Throwable) {
                        Toast.makeText(this@MainActivity, t.message, Toast.LENGTH_SHORT).show()
                    }
                })
        }
    }

 

 

 

 


Commit ad3ddc26

 

 

+Rx

1. Service

2. OkHttpClient

3. Retrofit을 이용한 Service 구현체

 

 

dependecies 추가

implementation "com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0"

 

 

1. Service

interface GoogleNewsService {

    @GET("/rss")
    fun getNews(
        @Query("hl") hl: String,
        @Query("gl") gl: String,
        @Query("ceid") ceid: String
    ): Single<RespNewsRss>

}

리턴 타입만 바꼈다.

 

 

2. 동일함

 

 

3. Retrofit을 이용한 Service 구현체

    private fun makeNewsService(baseUrl: String, okHttpClient: OkHttpClient): GoogleNewsService {
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okHttpClient)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(SimpleXmlConverterFactory.create())
            .build()

        return retrofit.create(GoogleNewsService::class.java)
    }

 

addCallAdapterFactory(RxJava2CallAdapterFactory.create())

기존의 리턴받던 Call에서 Rx객체로 변경하기 위한 한줄이 추가되었다.

 

 

실제사용

    private val newsService = GoogleNewsServiceFactory.makeNewsService(BuildConfig.DEBUG, "https://news.google.com")
    private val compositeDisposable = CompositeDisposable()

    private fun initViews() {
        btn_main.setOnClickListener {
            newsService.getNews("ko", "KR", "KR:ko")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe { resp ->
                    val newsList = resp.channel?.newsList
                    tv_main.text = newsList.toString()
                }.let {
                    compositeDisposable.add(it)
                }
        }
    }

 

 

 

 

'창고' 카테고리의 다른 글

custom_dialog (+custom_toast)  (0) 2020.05.02
Activity간의 화면 전환  (0) 2020.03.11
internal_storage  (0) 2020.02.26
permission  (0) 2020.02.26
DP(Device Independence Pixel)?  (0) 2019.06.09

+ Recent posts