Android Studio - Activity

지훈·2021년 10월 4일
0

안드로이드 액티비티

안드로이드 앱을 실행 시 단말기의 화면에 앱 UI를 표시하고 이벤트에 따라 앱의 기능 수행, UI의 내용 변경 혹은 아예 새로운 UI로 변경되기도 한다.

액티비티(Activity)는 UI 구성을 위한 가장 기본이 되는 컴포넌트. 처음 Project를 생성하면 MainActivity가 초기 실행되는 액티비티로 되어있다. 이는 Manifests에서 변경할 수 있다.

화면에는 View | ViewGroup의 다양한 조합이 배치되어 UI를 표시한다. UI에는 Layout 리소스 XML 파일 혹은 android.view.View에서 상속받은 클래스(TextView, Button 등)이다. setContentView() 함수를 통해 정한다.

즉, 액티비티는 화면에 UI를 표시하기 위한 틀

안드로이드 애플리케이션은 액티비티라는 컴포넌트가 하나 이상 결합되어 생성된다. 액티비티는 애플리케이션의 기능을 갖는 단일의 독립 실행형 모듈. 하나의 사용자 인터페이스 화면 및 기능과 밀접한 연관
예) 어떤 앱에서는 당일로 설정된 일정들을 보여주는 액티비티 화면 A를 가질 수 있다. 또한 새로운 일정을 입력할 수 있는 화면으로 두번째 액티비티 B를 사용할 수도 있따.

액티비티는 재사용과 교체 가능한 구성요소로 만들어져서 서로 다른 앱에 공유할 수 있다.
예) A1 : 이메일 앱. 새로 개발하는 A2 앱에서 A1의 기능이 필요한 경우 또 다시 새로운 이메일 액티비티를 작성하지 않고 기존 이메일 애플리케이션의 액티비티를 사용할 수도 있다.

액티비티는 안드로이드 Activity 클래스의 서브 클래스로 생성되어야 하며, 앱의 다른 액티비티와 완전히 독립적으로 구현되어야 한다. 즉, 액티비티는 다른 액티비티의 메서드를 직접 호출할 수 없고 데이터도 직접 액세스할 수 없다. 대신 "인텐트(Intent)"와 "컨텐트 제공자(Content Provider)" 를 사용해서 액티비티를 공유할 수 있다.

Activity 실행

startActivity()라는 함수로 다른 Activity를 실행할 수 있다. 하지만 어떠한 의도(Intent)로 Activity를 실행하는지 를 기술해주어야 한다.
개발자가 직접 만들고 명시한(explicit) Activity A2를 실행하기 위해서는 startActivity(A2) 로 실행할 수 있다. 만약 안드로이드가 제공하는 Activity를 실행시키고 싶다면 전자와 다른 방식으로 Intent를 사용해야 한다. 안드로이드 제공하는 Activity는 정해진 기능을 제공하므로 이 액티비티를 실행하는 이유는 암묵적인 의도(implicit intent)가 있기 때문이다.

Intent(인텐트)

인텐트는 하나의 액티비티가 다른 액티비티를 시작시킬 수 있는 매커니즘. 이때 액티비티가 안드로이드 런타임에 인텐트를 요청하면 그 인텐트에 부합하는 액티비티를 안드로이드 런타임이 찾아서 시작시킴. 한 액티비티가 다른 액티비티를 직접 시작시키는 것이 아님. 인텐트에서는 수행될 동작을 나타내고, 필요하면 전달할 데이터도 포함시킬 수 있다.

인텐트는 앱 component가 무엇을 할 것인지를 담는 매시지 객체, 메시지를 사용하는 가장 큰 목적은 다른 액티비티, 서비스, 브로드캐스트 리시버, 컨텐트 프로바이더 등을 실행하는 것이다. 인텐트는 그들 사이에 데이터를 주고 받기 위한 용도로도 쓰인다.

Intent로 다른 Activity 실행

  • 명시적(explicit) 인텐트 : 클래스 이름으로 액티비티 참조. 특정 액티비티 실행을 안드로이드 런타임에 요청. 실행할 액티비티를 우리가 정확히 알아야 한다.
        val intent =Intent(this, SubActivity::class.java)
        startActivity(intent)
  • 암시적(implicit) 인텐트 : 우리가 하길 원하는 작업(액션 타입)을 안드로이드 런타임에 알려준다. 그러면 그런 작업을 할수 있다고 등록한 액티비티를 안드로이드 런타임이 찾아서 실행시킴. 그런 액티비티가 여러 개면 사용자가 선택할 수 있게 해준다.
        val intent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:010-999-9999"))
        startActivity(intent)

Intent로 다른 Activity와 데이터 송수신

액티비티 간 데이터를 전달할 때도 Intent는 유용하게 쓰인다. String, Int, 배열이나 List 같은 형태도 전달 가능하며 Serializable을 상속받으면 클래스 형태까지 전달할수 있다. 데이터를 보내는 메서드는 putExtra()와 getExtra() 로!

  • 아래는 MainActivity에서 mainPlayerLayout의 클릭리스너로 song.title과 song.singer를 intent에 넣어 보내고 있다
class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val song = Song(binding.mainMiniplayerTitleTv.text.toString(),
        binding.mainMiniplayerSingerTv.text.toString())

        binding.mainPlayerLayout.setOnClickListener {
            val intent = Intent(this, SongActivity::class.java)
            intent.putExtra("title", song.title)
            intent.putExtra("singer", song.singer)
            startActivity(intent)
        }
  • 아래는 SongActivity에서 hasExtra로 조건을 확인하고 getExtra로 데이터를 받고 있다.
class SongActivity : AppCompatActivity() {

    lateinit var binding: ActivitySongBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivitySongBinding.inflate(layoutInflater)
        setContentView(binding.root)

        if(intent.hasExtra("title") && intent.hasExtra("singer")){
            binding.songMusicTitleTv.text = intent.getStringExtra("title")
            binding.songSingerNameTv.text = intent.getStringExtra("singer")
        }
    }
}

Broadcast Intent(브로드캐스트 인텐트)

브로드캐스트 인텐트는 브로드캐스트 수신자(Broadcast Receiver)로 등록된 모든 애플리케이션에 전송되는 시스템 차원의 인텐트.
예) 안드로이드 시스템에서 장치 상태의 변화를 알려주기 위해 브로드 캐스트 인텐트를 전송. 장치의 전원을 켜서 시스템이 부팅될 때 또는 충전 커넥터를 연결할 때와 같은 경우

브로드캐스트 인텐트는 일반적인 인텐트와 유사하게 동작하지만, 지정된 컴포넌트만 인텐트를 받는 것이 아니라 시스템의 여러 컴포넌트가 동시에 받을 수 있다.

  • 정규(normal) 브로드캐스트 인텐트 : 관련 이벤트에 관심있는 브로드캐스트 수신자에게 비동기 형태로 동시에 전송
  • 순차(ordered) 브로드캐스트 인텐트 : 한 번에 하나의 브로캐스트 수신자에게 전송됨. 그리고 그 다음 차례의 브로드 캐스트에게 계속 전송되거나 중단될 수 있음.

Broadcast Receiver (브로드캐스트 수신자)

브로드캐스트 수신자는 애플리케이션이 브로드캐스트 인텐트에 응답할 수 있는 매커니즘. 브로드캐스트 수신자는 애플리케이션에서 등록해야 하며, 관심있는 브로드캐스트 타입을 나타내는 인텐트 필터(Intent Filter)를 사용해서 구성한다. 그리고 등록된 브로드캐스트 타입이 인텐트와 일치하면 안드로이드 런타임이 그 수신자를 시작시킨다.
※ 수신자를 등록한 애플리케이션의 현재 실행 여부와 무관

그 다음 그 수신자는 필요한 작업(데이터 변경이나 사용자에게 메시지 통지 등)을 5초 이내에 수행하고 복귀해야 한다. 브로드캐스트 수신자는 백드라운드로 실행되며 사용자 인터페이스를 갖지 않는다.

Content Provider (콘텐트 제공자)

콘텐트 제공자(Content Provider)는 "애플리케이션 간에 데이터를 공유"하는 매커니즘을 구현. 모든 애플리케이션이 자신의 내부 데이터에 대한 액세스를 다른 애플리케이션에게 제공할 수 있다. 이 때 퍼미션(permission)된 그 데이터를 추가, 삭제, 조회하는 능력을 갖는 콘텐트 제공자를 구현.

그리고 데이터의 액세스는 콘텐트 제공자가 정의한 URI(Universal Resource Identifier)를 통해 제공됨. 데이터는 파일 또는 SQLite 데이터베이스 전체의 형태로 공유될 수 있다.
연락처와 같은 네이티브 안드로이드 애플리케이션들은 다른 애플리케이션에서 데이터를 액세스할 수 있는 표준 콘텐트 제공자를 많이 포함하고 있다.

애플리케이션 Manifest(매니페스트)

앱을 구성하는 다양한 요소를 합치는 접착제 역할. XML 파일이며 애플리케이션에 관련된 액티비티, 서비스, 브로드캐스트 수신자, 데이터 제공자, 퍼미션(permission) 등이 정의되어있다. 이 정보들은 안드로이드 런타임에서 참조하고 사용한다.

애플리케이션 리소스

안드로이드 애플리케이션은 애플리케이션 패키지(APK)로 빌드되어 장치에 설치 및 실행된다. 이 때 APK에는 자바 실행코드(dex 파일), 매니페스트 파일의 메타 데이터, 각종 리소스 파일들이 포함된다. 그리고 리소스 파일에는 여러 종류의 리소스가 포함된다.
예) XML로 정의된 인터페이스 레이아웃과 인터페이스에 나타나는 문자열, 이미지, 폰트, 색상 등

애플리케이션 Context(컨텍스트)

애플리케이션이 컴파일되면 애플리케이션의 모든 리소스에 대한 참조를 포함하는 R이라는 이름의 클래스가 자동 생성된다. 그리고 애플리케이션의 매니페스트 파일과 그런 리소스들이 결합되어 Application Context(애플리케이션 컨텍스트)가 생성된다.

컨텍스트는 안드로이드 Context 클래스로 나타내며,런타임 시 애플리케이션의 리소스를 액세스하기 위해 애플리케이션 코드에서 사용될 수 있다. 또한, 런타임 시의 정보를 수집하고 애플리케이션의 환경을 변경하기 위해 여러가지 메서드들이 호출될 수 있다.

Activity의 종료

현재 Activity 종료를 원한다면 finish() 함수 호출. 혹은 finishActivity() 함수를 호출하여 이전에 실행한 특정 Activity를 종료할 수도 있다.

하지만 직접적인 사용은 피하는게 좋다. Activity LifeCycle에 영향을 줌.

Activity 관리

기본적으로 한 Activity에서 다른 Activity을 실행하면(추가로 기존의 Activity의 종료를 선언하지 않으면) Activity는 Stack의 구조로 쌓인다. 즉, 4 번의 서로 다른 Activity를 실행하고 2번째 Activity로 다시 돌아가고 싶다면 단말기의 뒤로가기 버튼을 두번 누르면 될 것이다. (물론 3,4번째 Activity는 종료된다.)

요약1


  • 액티비티와 인텐트의 형태로 독립적으로 실행 가능한 모듈을 생성 -> 재사용성, 상호운용성↑.

  • Content Provider(콘텐트 제공자)를 구현하면 앱 간의 데이터 공유 가능

  • 액티비티에서는 사용자가 앱과 상호작용하는 것에 초점.

  • 백그라운드 프로세싱은 서비스와 브로드캐스트 수신자에 의해 처리됨.
    ※ 기본적으로 액티비티는 하나의 사용자 인터페이스 화면과 연관.

  • 애플리케이션을 구성하는 컴포넌트들은 매니페스트 파일에 정의되어 안드로이드 런타임 시스템에 의해 참조된다.

  • 애플리케이션 매니페스트 파일과 애플리케이션 리소스들이 결합되어 애플리케이션의 컨텍스트를 나타낸다.


Activity 클래스

거의 예외 없이 애플리케이션의 액티비티들은 안드로이드 Activity 클래스 또는 Activity 클래스의 서브 클래스인 다른 클래스(예를 들어, 하위 버전과의 호환성을 고려하여 만든 AppCompatActivity 또는 FragmentActivity 중 하나의 서브 클래스로 생성된다.

안드로이드 앱은 액티비티가 근간이 되며 액티비티는 리눅스 커널의 프로세스로 실행된다. 그리고 하나의 UI를 갖고 처리한다. 필요하다면 하나의 앱에서 여러 개의 액티빝를 갖고 한 액티비티가 다른 액티비티를 연계하여 실행하도록 할 수도 있다. 이때 인텐트(Intent)를 사용한다. 이것과는 다른 방법으로 하나의 액티비티에서 여러 개의 프래그먼트(Fragment)를 갖고 실행할 수도 있다.

안드로이드 앱을 개발할 때 주고 사용하는 액티비티 클래스에는 Activity, FragmentActivity, AppCompatActivity가 있다.

  • Activity 클래스(android.app.Ativity) - 해당 안드로이드 버전의 기본 라이브러리 액티비티 클래스이며, 모든 다른 액티비티 클래스는 이 클래스의 서브 클래스가 된다.

  • FragmentActivity 클래스 (android.support.v4.app.FragmentActivity) - 과거 버전과의 호환성을 유지하면서 프래그먼트를 사용할 때 필요한 액티비티 클래스이다.

  • AppCompatActivity 클래스 (android.app.support.v7.app.AppCompatActivity) - 과거 안드로이드 버전과의 호환성을 유지하면서 새로운 버전의 기능도 사용할 수 있도록 만든 액티비티 클래스이다. 이 클래스는 안드로이드6.0의 새로운 기능도 많은 부분 구현하고 있다. 이런 이유로 안드로이드 스튜디오 1.5 버전부터는 프로젝트 생성 시에 액티비티를 AppCompatActivity 클래스의 서브 클래스로 생성해준다.

    Activity 클래스와 그것의 서브 클래스들은 안드로이드 런타임이 자동으로 호출하는 메서드를 많이 갖고 있다. 액티비티의 상태가 변경된다는 것을 액티비티에게 알려주기 위해서다. 그런 메서드들을 액티비티 생명주기메서드(activity lifecycle method)라고 한다.

보통 액티비티 클래스에서는 그런 메서드들을 오버라이드(override)하여 필요한 기능들을 구현만 하면 된다.
액티비티가 변경될 때 우리가 원하는, 필요로 하는 처리를 하기 위해 오버라이드 한다. onCreate() 매서드는 반드시 구현해야 한다.

동적 상태 / 영속적 상태

액티비티 생명주기를 관리하는 주된 목적은 알맞은 때에 액티비티 상태를 저장하거나 복원하기 위함이다. 상태(state)는 액티비티가 현재 보존하고 있는 데이터와 현재 보이는 사용자 인터페이스 데이터(일반적으로 폰이나 태블릿 등의 화면에 나타난 뷰 객체의 데이터)를 의미한다.

예를 들어서 액티비티는 데이터베이스, 콘텐트 제공자, 파일 등에 저장될 필요가 있는 메모리의 데이터를 유지할 수 있다. 이런 상태 정보를 영속적 상태(persistent state)라고 한다.

화면에 보이는 사용자 인터페이스는 동적 상태(dynamic state) 라고 한다. (사용자 인터페이스 상태 또는 인스턴스 상태라고도 한다.) 왜냐면 애플케이션이 실행되는 동안에만 보존되기 때문.
예를 들어서 화면의 텍스트 필드에 입력된 텍스트는 아직 애플리케이션의 영속적 상태 데이터로 확정되지 않은 것이다. 이외에도 파일이나 데이터베이스 등에는 저장하지 않지만 액티비티 내부에서는 보존해야 하는 변수들이 필요할 수 있다. 이런 변수 데이터까지를 포함하는 것이 동적 상태이다.

영속적 상태 저장 -> 데이터 유실을 막기 위해서. 액티비티가 백그라운드 상태로 있을 때는 런타임 시스템이 그 액티비티를 종료시키면서 액티비티의 데이터가 없어질 수 있음.
액티비티의 사용자 인터페이스가 현재 화면에 보이면서 사용자가 데이터를 입출력할 수 있을 때는 포그라운드 상태라고 한다.

이것과 달리 동적 상태는 복잡한 이유로 데이터가 저장되고 복원된다. 예를 들어 하나의 텍스트 필드와 몇개의 radio button을 갖는 액티비티 A 가 있다. 앱을 사용하는 동안 사용자는 텍스트 필드에 텍스트를 입력하고 라디오 버튼을 선택한다.
※ radio button은 여러개의 선택지 중 하나만 선택하게끔 구성되어 있는 버튼. (객관식 문제 풀 때 선택할 수 있게끔 하는 것)

이 때 변경사항을 저장하기 전에 사용자가 다른 액티비티B 로 전환하면 액티비티 A는 액티비티 스택으로 들어가고 백그라운드 상태가 됨. 이후 장치의 메모리가 부족해져서 런타임 시스템이 부족한 리소스를 보충하기 위해서 액티비티 A를 kill함.(종결시킴) 그러나 사용자는 액티비티 A가 그냥 백드라운드에 있다가 언제든지 포그라운드로 올 준비가 되어있다고 생각한다. 즉, 액티비티 A가 포그라운드로 돌아오면 자신이 입력했던 텍스트와 선택한 라이도 버튼들이 그대로 남아 있으리라 생각한다.

그러나 액티비티 A의 새로운 인스턴스가 생성되므로 동적 상태 데이터가 저장되어 복원되지 않았다면 사용자가 이전에 입력한 데이터는 유실된다. 또한 안드로이드 장치의 구성이 변경되는 경우에도 항상 그 액티비티의 인스턴스를 새로 생성한다. <- 장치를 세웠다가 눕혔다가 하는 것 등. 그래서 동적 상태를 저장하는 것의 주 목적은 포그라운드와 백그라운드 액티비티들 간의 매끄러운 전환을 제공하기 위함.

안드로이드 액티비티 생명주기 메서드

Activity 클래스는 생명주기 메서드를 많이 갖고 있으며, 그 메서드들은 액티비티의 상태가 변경될 때 이벤트 처리기(event handler)처럼 동작.

  • onCreate(saveInstanceState : Bundle) : 이 메서드는 액티비티 인스턴스가 최초로 생성될 때 호출되며 대부분의 초기화 작업을 진행. 메서드의 parameter(매개변수)는 동적 상태 정보를 포함할 수 있는 Bundle 객체가 전달된다. 그리고 동적 상태 정보는 직전에 생성되었다가 소멸된 동일한 액티비티의 인스턴스로부터 받으며, 일반적으로 사용자 인터페이스의 상태와 관련되는 데이터.

  • onRestart() : 액티비티가 런타임 시스템에 의해 이전에 중단되었다가 막 다시 시작될 때 호출된다.

  • onStart() : 예외없이 onCreate()나 onRestart() 메서드가 호출된 후 바로 호출됨. 액티비티의 사용자 인터페이스가 곧 사용자에게 보이게 될 것이라고 액티비티에게 알려준다.

  • onResume() : 액티비티가 액티비티 스택의 맨 위에 있으며 사용자가 현재 실행중인 액티비티임을 알려줌.
    ※ 액티비티 스택의 맨위에 있는 액티비티가 현재 화면에 보이는 액티비티이다.

  • onPause() : 이 메서드 호출 다음에는 onResume() 또는 onStop() 메서드 중 하나가 호출. 액티비티가 포그라운드로 돌아가는 경우 계속 실행하기 위해 onResume(), 액티비티의 사용자 인터페이스를 사용자가 못보게 되면 중단되면서 onStop() (<-예를 들어 상태 바를 아래로 내린다거나 홈 화면으로 가는 경우)
    이 메서드 내부에서는 액티비티에서 필요한 영속적 데이터(콘텐트 제공자, 데이터베이스, 파일에 저장되는 데이터)를 저장하는 일을 해야 함. 이 메서드는 또한 애니메이션과 같이 CPU를 지나치게 사용하는 작업들을 중단.

  • onStop() : 이 메서드가 호출될 때는 액티비티가 사용자에게 안 보일 때. 이 메서드 호출 다음은 onRestart() OR onDestroy(). 액티비티가 다시 포그라운드로 들어가면 onRestart()가 호출되고, 액티비티가 아예 종료되면 onDestroy()가 호출됨.

  • onDestroy() : 이 메서드는 액티비티가 막 소멸되려고 하거나 자발적으로 소멸될 때 호출. 액티비티가 자신의 작업을 모두 완료하고 finish() 메서드를 호출했거나, 메모리 해제, 혹은 구성 변경(화면 세로->가로)이 생겨서 런타임이 액티비티를 종결하기 때문. 그러나 "액티비티가 종료될 때 항상 onDestry() 메서드가 호출되는 것은 아니다."

위와 같은 생명주기 메서드와 더불어 아래에 액티비티의 동적 상태를 저장하고 복원하기 위해 특별히 만들어진 두 개의 메서드

  • onRestoreInstanceState(savedInstanceState : Bundle) : 상태 정보가 저장되었던 이전 액티비티 인스턴스로부터 액티비티가 다시 시작하는 경우에 이 메서드는 onStart() 메서드가 호출된 후 곧바로 호출된다. onCreate() 메서드처럼 이 메서드도 이전 상태 데이터를 포함하는 Bundle 객체를 파라미터로 받는다.

  • onSaveInstanceState(outState : Bundle) : 현재의 동적 상태 데이터가 저장될 수 있게 액티비티가 소멸되기 전에 호출됨. 여기서 동적 상태 데이터는 대게 사용자 인터페이스와 관련된 것들. 이 메서드도 Bundle 객체를 파라미터로 받고, 저장되어야 하는 상태 데이터를 Bundle 객체에 넣는다.
    그리고 이 Bundle 객체는 액티비티가 다시 시작될 때 onCreate()와 onRestoreInstanceState() 메서드에 전달된다. 동적 상태 데이터가 저장될 필요가 있다는 것을 런타임이 알 때만 호출됨.

예를 들어서 아래 메서드는 onRestart() 메서드를 오버라이딩 하는 코드. 메서드 구현 코드에서 슈퍼 클래스 인스턴스의 오버라이딩되는 메서드를 호출

override fun onRestart() {
	super.onRestart()
    Log.i(TAG,"onRestart")
    }

오버라이딩하는 메서드에서 슈퍼클래스의 오버라이딩되는 메서드를 호출하지 않으면 액티비티 실행 중 런타임이 예외를 발생시킴. 단, onRestoreIntanceState(savedInstaceState : Bundle) 메서드와 onSaveInstanceState(outState : Bundle) 메서드의 경우 슈퍼클래스 메서드 호출을 하지 않아도 된다.
※ 호출할 경우의 큰 장점이 있긴 함.

Activity LifeCycle (액티비티 생명주기)

액티비티는 전체(entire), 가시적(visible), 포그라운드(foreground)의 세 가지 생애를 오간다.

  • 전체 생애(Entire Lifetime) : 액티비티가 생성될 때 최초 호출되는 onCreate() 메서드 호출과 종결되기 전 호출되는 onDestroy() 호출 사이에 액티비티에서 발생하는 모든 것을 나타내는 데 'Entire Lifetime' 이라는 용어가 사용된다.

  • 가시적 생애(Visible Lifetime) : onStart()와 onStop() 호출 사이의 액티비티 실행 시기이다. 이 시기 동안 액티비티는 자신을 사용자에게 화면으로 보여줄 수 있다.

  • 포그라운드 생애(Foregound Lifetime) : onResume() 메서드 호출과 onPause() 호출 사이의 액티비티 실행 시기.

액티비티는 자신의 Entire Lifetime 동안 포그라운드와 가시적 생애를 여려번 거칠 수 있다.

요약2

  • 모든 액티비티들은 안드로이드 Activity 클래스로부터 상속된다.

  • 액티비티의 상태가 변경될 때 런타임 시스템에 의해 호출되도록 설계된 이벤트 메서드들을 갖는다. 이 메서드들을 오버라이딩함으로써 액티비티는 상태 변화에 응답할 수 있다.

  • 액티비티와 애플리케이션의 모든 현재 상태 데이터를 저장 및 복원도 할 수 있다.

  • 액티비티의 두가지 상태

  • 영속적 상태 : 앱이 실행되는 동안 파일 또는 데이터 베이스 등에 저장해야 하는 데이터

  • 동적 상태 : 현재 화면에 보이는 사용자 인터페이스와 관련되는 데이터 및 액티비티 내부에서만 보존해야 하는 변수들의 데이터

출처 :
https://mattlee.tistory.com/73
https://recipes4dev.tistory.com/67
https://developer.android.com/reference/android/app/Activity
https://keykat7.blogspot.com/2021/03/android-activity.html

profile
안드로이드 개발 공부

0개의 댓글