개발자 끄적끄적
Activity, Intent 본문
<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]
'안드로이드 프로그래밍' 카테고리의 다른 글
메뉴(Menu), 다이얼로그(Dialog), 네비게이션(Navigation) UI (0) | 2023.05.07 |
---|---|
위젯, 리소스 (0) | 2023.04.10 |
안드로이드 레이아웃(LayOut) (0) | 2023.03.30 |