개발자 끄적끄적

Activity, Intent 본문

안드로이드 프로그래밍

Activity, Intent

햏치 2023. 4. 15. 21:02

<Activity>
- 앱 구성 요소, 사용자와 상호작용할 수 있는 화면 제공
- 안드로이드 앱 구성 요소
  - 액티비티, 서비스, 브로드캐스트 리시버, 컨텐프 프로바이더

- 앱의 시작은 보통 액티비티에서 시작한다
- 앱에서 2개 이상의 액티비티가 포함될 수 있다
- 액티비티 AppCompatActivity(또는 Activity)를 상속하여 만든다
  -  AppCompatActivity는 androidx(Jetpack) 라이브러리에 포함된 것
    - 안드로이드 SDK 버전에 상관 없이 동일한 기능을 제공해준다
  - Activity는 안드로이드 SDK에 포함된 것
  - 앱 호환성을 높이기 위해 AppCompatActivity 사용 권장



<액티비티(Activity) 사용>
- setConetentView()를 이용하여 액티비티의 View를 draw
- ex)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) //Layout영역(UI영역)
  }
}

- Android Manifest 파일에 Activity를 등록
- ex)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
… 생략 … >
<activity android:name=".MainActivity“ android:exported="true"> //activity android : name=클래스이름, android:exported=외부로 노출시킬지에 대한 여부
<intent-filter> //앱을 시작할 때 제일 먼저 시작하는 액티비티를 지정
하기 위한 것
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
  </activity>
 </application>
</manifest>


<액티비티(Activity)사용 - 메소드 오버라이드>
- 라이프 사이클 콜백 처리
  - onCreate, onStart, onResume, onPause, onStop, onDestroy()

- 설정 변경(세로/가로 보기 전환 등)에 따른 콜백 처리
  - onConfigurationChanged()



<액티비티 라이프 사이클>
- onCreate()
- onStart()  //화면에 표시될 때 불려진다
- onResume() //Activity가 정상적으로 사용자 입력을 받고 동작을 할 때 불려진다
- 액티비티 활성화(Activity Running)
- onPause() //Another activity comes into the foreground
- onStop() //화면에서 완전히 사라질 때(The activity is no longer visible)
- onDestroy() //The activity is finishing or deing destoryed by the system



<액티비티 전환 시 라이프 사이클 콜백>
MainActivity에서 SecondActivity 시작
  - MainActivity의 onPause()
  - SecondActivity의 onCreate(), onStart(), onResume()
  - MainActivity의 onStop()

- 단말기의 뒤로가기 버튼 누름
  - SecondActivity의 onPause()
  - MainActivity의 onRestart(), onStart(), onResume()
  - SecondActivity의 onStop(), onDestroy()



<인텐트(Intent)>
- Intent는 일종의 메시지 객체
- 다른 앱 구성 요소에 Intent를 보내 작업을 요청
- 기본적인 사용 사례
  - 액티비티 시작하기
  - 서비스 시작하기
  - 브로드캐스트 전달하기



<인텐트로 액티비티 시작하기>
- startActivity() 메소드를 사용하여 다른 액티비티를 시작할 수 있다
- ex)
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
  - 시작하려는 액티비티(SecondActivity.class)를 지정하고 Intent 생성
    - 이렇게 명시적으로 액티비티 클래스를 지정하는 것을 '명시적 인텐트' 라고 한다
  - startActivity()에 인텐트 객체를 인자로해서 호출

- startActivity() 메소드는 액티비티나 서비스에서 사용 가능
- 시작한 액티비티로부터 결과를 받으려면
  - registerForActivityResult() 사용 : 결과를 받을 함수를 미리 등록
  *참고: startActivityForResult() 는 deprecated 되어서 사용 중단



<인텐트 유형>
- 명시적(explicit) 인텐트 - 시작할 구성 요소의 이름을 지정
- 암시적(implicit) 인텐트 - 이름을 지정하지 않고 일반적인 작업(전화걸기, 지도보기 등)을 지정
- ex) Activity A -> startActivity() -> Intent -> Android System -> Intent -> onCreate() -> Activity B



<암시적(Implicit) 인텐트 사용>
- 필요한 Action을 지정하고 Intent를 생성
- ex) 
val implicitIntent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:114")) //Dial액션과 추가 처리("tel:114")
startActivity(implicitIntent)

- 해당 액션과 데이터를 처리할 수 있는 태스크가 시작된다
  - 만일 여러 개가 있을 경우 선택 창이 나타난다
  - 이 예에서 전화걸기 앱이 시작되고 114라는 번호가 미리 입력되어 있다



<암시적 인텐트 받기(인텐트 필터)>
- 암시적인 인텐트를 받기 위해서는 받으려는 액션과 데이터 스키마를 지정해주면 된다
- AndroidManifest.xml에 인텐트 필터를 추가
- ex) 
<activity android:name=".SecondActivity" android:exported="false">
 <intent-filter>
  <action android:name="android.intent.action.DIAL" /> //action
  <category android:name="android.intent.category.DEFAULT" /> //category 
  <data android:scheme="tel" /> //data
 </intent-filter>
</activity>
- ACTION_DIAL 로 startActvity()를 하면 기존 전화앱과 SecondActivity를 선택하는 창이 나타난다




<명시적 인텐트에 데이터 넣어서 보내고 받기>
- MainActivity의 onCreate() 에서 액티비티 결과를 받을 콜백 등록
- ex)
val activityResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{
val msg = it.data?.getStringExtra("ResultString") ?: ""
Snackbar.make(findViewById(R.id.buttonThirdActivity),
"ActivityResult:${it.resultCode} $msg", Snackbar.LENGTH_SHORT).show()
Log.i(TAG, "ActivityResult:${it.resultCode} $msg")
}
  - ActivityResultContracts.StartActivityForResult() : Activity 결과를 받는 contract
  - it.data? : 데이터의 속성을 지정

- MainActivity에서 ThirdActivity로 인텐트를 보내고 결과 인텐트를 기다려서 받기
- ex)
val intent = Intent(this, ThirdActivity::class.java)
intent.putExtra("UserDefinedExtra", "Hello") 
activityResult.launch(intent) //intent를 인자로 넘겨준다
  - ThridActivity에 전달할 내용을 put.Extra로 추가 :  key, value 쌍




<명시적 인텐트에 데이터 넣어서 보내고 받기>
- ThridActivity에서 MainActivity가 보낸 인텐트 받기
- ex)
class ThirdActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_third)
val msg: String = intent?.getStringExtra("UserDefinedExtra") ?: ""
findViewById<EditText>(R.id.editText)?.setText(msg)

- ThirdActivity에서 결과 인텐트 되돌려 주기
- ex)
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val et = findViewById<EditText>(R.id.editText)
val resultIntent = Intent()
resultIntent.putExtra("ResultString", et.text.toString())
setResult(RESULT_OK, resultIntent)
finish()
    }
   }) 
  }
 }
  - setResult는 언제든 불러도 된다(finish()가 종료되기 전까지)




<MVVM(Model - view - viewmodel> - 데이터를 보여주는 것에 더 집중하는 뷰 모델
- 뷰와 모델을 분리하는 대표적인 소프트웨어 아키텍처 패턴
  - 여기에서 뷰는 GUI를 의미하고 모델은 GUI에 표시되거나 GUI에 의해 변경되는 내부에 서 관리하는 데이터를 의미

- 안드로이드 Jetpack의 ViewModel 클래스를 사용하면 MVVM을 쉽게 구현할 수 있다
  - 액티비티가 뷰이고 모델은 파일이나 DB 등이 될 수 있다

- 뷰(액티비티)와 모델(액티비티 클래스 내 변수 등)을 구분하지 않는다면,
  - 액티비티 클래스 내에서 바로 모델을 관리하면 액티비티 설정 변경으로 액티비티가 다시 시작할 때 관리하던 모델도 다시 만들어진다
  - ex) 언어 설정을 변경하거나 화면을 회전시키면 액티비티가 새로 생성된다. 즉, 상태유지가 되지 않는다
    - 즉, onDestory(), onCreate()가 호출되면서 새로 액티비티가 만들어진다
    - 액티비티 내에 관리하던 모델들도 모두 새로 생성된다
    *주의 : 안드로이드의 View 클래스와 여기서 말하는 뷰를 혼동하지 말 것



<액티비티에서 모델을 관리하는 경우>
- 버튼을 누를 때마다 값이 증가하지만, 언어 설정을 바꾸거나 화면을 회전시키면 값이 0으로 초기화되는 문제가 있음
- ex)
class MainActivity : AppCompatActivity() {
private var count = 0 // 액티비티가 다시 만들어지면 함께 초기화됨

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

findViewById<TextView>(R.id.textView_count)?.text = getString(R.string.count_in_activity, count)

findViewById<Button>(R.id.button_incr)?.setOnClickListener {
  count++
  findViewById<TextView>(R.id.textView_count)?.text = getString(R.string.count_in_activity, count)
  }
 }



<ViewModel을 사용하여 문제 해결 - ViewModel 클래스>
- ViewModel 클래스
- ex)
class MyViewModel : ViewModel() {
var count = 0
fun increaseCount() {
count++
  }
}

- ViewModel 객체는 직접 생성하지 않고 반드시 ViewModelProvider를 이용해야 한다
  - ViewModelProvider(this)[MyViewModel::class.java]

- ViewModel에 생성자 인자를 추가하려면 ViewModelFactory도 만들어서 사용해야 한다




<ViewModel을 사용하여 문제 해결>
- 뷰 모델을 가져오기 위해 ViewModelProvider(라이프사이클 소유자).get(뷰모델 클래스)을 호출
  - 라이프사이클 소유자(여기서는 액티비티)가 실제로 사라질 때까지 뷰 모델을 유지한다
  - 해당 뷰 모델 클래스에 대한 객체가 하나만 만들어진다(Singleton과 유사)

- ex)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_main)

  val viewModel = ViewModelProvider(this)[MyViewModel::class.java] // 뷰 모델 가져오기 get 대신에 [] 연산자 사용

  findViewById<TextView>(R.id.textView_count_viewmodel)?.text = getString(R.string.count_in_ViewModel, viewModel.count)

  findViewById<Button>(R.id.button_incr)?.setOnClickListener {
  viewModel.increaseCount()
  findViewById<TextView>(R.id.textView_count_viewmodel)?.text = getString(R.string.count_in_ViewModel, viewModel.count)
  }
}



<MVVM에 LiveData 추가>
- LiveData는 데이터가 변경될 때마다 호출되는 콜백 함수를 등록하여 사용할 수 있다
  - 액티비티와 같이 라이프사이클 소유자와 연결하여 해당 소유자가 활동중일 때만 콜백을 받는다

- 앞의 MyViewModel을 다음과 같이 변경
- ex)
class MyViewModel : ViewModel() {
  val countLivedata: MutableLiveData<Int> = MutableLiveData<Int>() //Data의 타입을 지정 : <Int>
  
  init {
  countLivedata.value = 0 //countLivedata=0이 아니라 속성값으로 써야한다 -> countLivedata.value=0
  }

  fun increaseCount() {
  countLivedata.value = (countLivedata.value ?: 0) + 1 //값을 변경할 때마다 콜백이 호출
  }
}

-  MyViewModel의 countLivedata에 observe() 메소드를 호출하여 콜백 함수 등록
  - 버튼이 클릭될 때는 뷰 모델의 데이터를 변경만 하면
  - countLivedata에 등록된 콜백이 불리고 콜백에서 뷰를 업데이트한다
  - ex)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val viewModel = ViewModelProvider(this)[MyViewModel::class.java]

viewModel.countLivedata.observe(this) {
  findViewById<TextView>(R.id.textView_livedata)?.text = getString(R.string.count_in_ViewModel_LiveData, it)


findViewById<Button>(R.id.button_incr)?.setOnClickListener {
  viewModel.increaseCount()
  }
}



<초기값을 받는 ViewModel 만들기>
- 아래와 같이 생성자 인자를 받아서 초기값을 주고 싶은 경우
- ex)
class MyViewModel2(count: Int) : ViewModel() {
  val countLivedata: MutableLiveData<Int> = MutableLiveData<Int>()
  init {
    countLivedata.value = count
  }
  fun increaseCount() {
    countLivedata.value = (countLivedata.value ?: 0) + 1
  }
}
- ViewModelProvider로 ViewModel을 생성, 생성자 인자를 줄 방법이 없다
  - val viewModel = ViewModelProvider(this)[MyViewModel::class.java]



<초기값을 받는 VeiwModel을 위해 VeiwModelProvider.Factory>
- ViewModelProvider.Factory로 생성자 인자를 받을 수 있는 커스텀 ViewModelProvider를 생성
- ex)
class MyViewModel2Factory(private val count: Int) : ViewModelProvider.Factory {
  override fun <T : ViewModel> create(modelClass: Class<T>): T {
    @Suppress("UNCHECKED_CAST")
    return MyViewModel2(count) as T
  }
}

- 다음과 같이, 생성자 인자로 10이 MyViewModel2에 주어진다
- ex)
val viewModel2 = ViewModelProvider(this, MyViewModel2Factory(10))[MyViewModel2::class.java]