
Android 앱은 컴포넌트(Activity, Service, Broadcast Receiver, Content Provider)들로 구성되어 있다.
각 컴포넌트들은 하나의 독립적인 형태로 존재하며, 고유의 기능을 수행한다.
이 컴포넌트들은 인텐트를 통해 서로 상호작용한다.
Intent: 메시지 객체로 이 객체를 통해 다른 컴포넌트끼리 정보를 주고 받을 수 있다.
Activity
사용자가 애플리케이션과 상호작용하는 단일 화면을 담당하는 컴포넌트를 의미한다. 모든 안드로이드 애플리케이션은 액티비티로 구성되어 있다. 즉, 액티비티는 사용자와 상호작용을 담당하는 인터페이스라고 할 수 있다.
보통 앱은 여러 화면으로 구성되어 있어 여러 액티비티로 구성되어 있다고 할 수 있다. 예를 들어 앱을 시작할 때 나오는 로그인 화면, 로그인을 했을 때 이동하는 메인화면 등 모두 각각의 액티비티들이다. 따라서 안드로이드 애플리케이션을 반드시 하나 이상의 액티비티를 포함하고 있으며, 액티비티는 생명주기(Life Cycle) 관련 메소드를 재정의하여 원하는 기능들을 구현할 수 있다.
특징
- 사용자와 상호작용할 수 있는 UI를 제공한다.
- 액티비티를 생성하면 .kotlin 클래스 파일과 .xml 파일이 함께 생성된다.
- 두 개 이상의 액티비티를 동시에 호출할 수 없다. (하나의 액티비티 내에서 Fragment를 추가하여 화면을 둘 이상으로 나누는 것은 가능)
- 한 개 이상의 View(=widget) 또는 ViewGroup(=layout)을 포함해야 한다.
// 액티비티 예시
// Main Activity
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 다른 엑티비티 호출
Context mContext = MainActivity.this;
Intent mIntent = new Intent(mContext, otherActivity.class);
startActivity(mIntent);
}
}
Service
사용자와 직접적으로 상호작용하지 않고, 백그라운드(Background)에서 실행되는 컴포넌트를 의미한다. 보통 백그라운드에서 어떠한 작업을 처리하기 위해 서비스를 사용한다.
예를 들어 사용자가 다른 앱에 있는 동안에 백그라운드에서 음악을 재생하거나, 카톡 알림과 같이 사용자와 액티비티 간의 상호작용을 차단하지 않고 네트워크를 통해 데이터를 가져오는 경우가 있다.
특징
- 사용자 인터페이스를 가지지 않는다.
- 다른 컴포넌트는 서비스를 시작시킬 수 있다. (앱 컴포넌트에서 startService() 혹은 startForegroundService()에 Intent를 전달하면 서비스를 시작됨)
- 한 번 시작된 서비스는 사용자가 다른 애플리케이션으로 이동하더라도 백그라운드에서 계속 실행된다.
- 모든 컴포넌트는 서비스를 사용할 수 있다. 심지어 다른 애플리케이션에서도 사용이 가능하다.
- 서비스를 이용하면 IPC(프로세스 간 통신) 기능도 구현할 수 있다. (추가 설명)
- 네트워크 트랜잭션, 음악 재생, 파일 입출력, 콘텐트 제공자와의 통신을 위해 주로 사용된다.- 서비스는 기본적으로 어플리케이션의 main thread를 이용하므로, 별도 스레드 생성없이 작업할 코드를 수행하면 메인 스레드에 과부하가 걸린다. 따라서 서비스를 사용할 때는 반드시 새로운 스레드를 생성해 주어야 한다.
- start로 시작된 서비스는 서비스 자체의 고유한 라이프사이클을 가지고 있기 때문에 시스템이 메모리가 부족해서 kill하지 않는이상 시스템이 직접 stop이나 destroy하지 않는다. 따라서 start된 서비스는 반드시 stopSelf() 나 stopService()를 통해 중단해야 한다.
// 서비스 사용 예시
// Main Activity
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button1 = findViewById(R.id.button1);
Button button2 = findViewById(R.id.button2);
Context mContext = this.MainActivity;
button1.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// 서비스 시작
Intent intent = new Intent(mContext, MusicService.class);
startService(intent);
}
});
button2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// 서비스 종료
Intent intent = new Intent(mContext, MusicService.class);
stopService(intent);
}
});
}
}
public class MusicService extends Service {
MediaPlayer mediaPlayer; // 음악 재생을 위한 객체
@Nullable
@Override
public IBinder onBind(Intent intent) {
// Service객체와 Activity사이에서 통신을 할 때 사용되는 메서드
// 데이터 전달이 필요 없으면 null
return null;
}
@Override
public void onCreate() {
// 서비스에서 가장 먼저 호출(최초 한 번)
mediaPlayer = MediaPlayer.create(this, R.raw.music);
mediaPlayer.setLooping(false); // 반복재생
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 서비스가 실행될 때 실행
mediaPlayer.start(); // 음악 시작
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
//서비스가 종료될 때 실행
mediaPlayer.stop(); // 음악 종료
super.onDestroy();
}
}
// https://lakue.tistory.com/65 참고
서비스에는 크게 3가지로 나뉘는데 포그라운드, 백그라운드, 바운드로 나뉜다.
포그라운드 서비스
알림을 표시해 놓고 사용자가 앱과 상호작용하지 않아도 계속 실행되는 서비스 (ex. 오디오 트랙 재생)
백그라운드 서비스
사용자에게 직접 보이지 않는 작업을 수행하는 서비스 (ex. 저장소 압축)
바운드 서비스
클라이언트-서버 인터페이스를 제공하여 특정 컴포넌트와 상호작용하게 하는 서비스
bindService()를 호출하여 서비스를 실행하며 여러 개의 컴포넌트가 한꺼번에 서비스에 바인딩될 수 있지만 모든 컴포넌트에서 바인딩이 해제되면 해당 서비스는 소멸된다. (추가 설명)
포그라운드 서비스와 백그라운드 서비스는 startService() 호출을 통해 실행된다.
Broadcast Receiver
안드로이드 OS(단말기)로부터 발생하는 다양한 이벤트와 정보를 받아 반응하는 컴포넌트를 의미한다. 예를 들어, 화면이 on/off 되었을 때, 재부팅할 때 (+배터리 부족, 문자 수신) 등의 이벤트가 발생하면 해당 이벤트에 맞게 정의한 작업들을 수행한다.
리시버에는 너무 많은 작업, 시간이 오래 걸리는 작업을 하면 안 된다. 처리 지연 시간이 길어진 경우 ANR이 발생하기 때문에 리시버에는 간단한 일을 처리하도록 하고, 긴 작업은 스레드를 별도로 생성해서 처리하도록 해야 한다.
특징
- 현재 실행되지 않은 앱에 대해서도 시스템이 브로드캐스트를 전달할 수 있다.
(ex. 알람을 예약한 경우, 그 알람을 Broadcast Receiver에 전달하면 알람이 울릴 때까지 앱을 실행하고 있을 필요가 없음)
- 대다수의 브로드캐스트는 시스템에서 발생한다.
// 브로드캐스트 리시버 사용 예시
// 송신측
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val broadcaster = LocalBroadcastManager.getInstance(this)
val send = findViewById<View>(R.id.send_broadcast) as Button
send.setOnClickListener {
Log.d("Tag", "클릭 실행 브로드캐스트 실행")
val intent = Intent("MyData")
intent.putExtra("phone", "010-0000-1111")
broadcaster.sendBroadcast(intent)
}
}
// 수신측(커스텀 이벤트)
private val mMessageReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("Tag", "커스텀 브로드캐스트 실행 받음")
Toast.makeText(context, intent.getStringExtra("phone"), Toast.LENGTH_LONG).show()
}
}
// 수신측(시스템 이벤트)
var br: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BATTERY_CHANGED) {
val toast = Toast.makeText(context, "Battery status is changed.", Toast.LENGTH_LONG)
toast.show()
} else if (intent.action == Intent.ACTION_SCREEN_ON) {
val toast = Toast.makeText(context, "Screen on.", Toast.LENGTH_LONG)
toast.show()
} else if (intent.action == "example.test.broadcast") {
val toast = Toast.makeText(context, "Customize broadcast!", Toast.LENGTH_LONG)
toast.show()
}
}
}
// https://stickode.tistory.com/347 참고
Broadcast Reciever를 등록하는 방법은 정적 리시버, 동적 리시버로 나뉜다.
정적 리시버
매니페스트에 등록하여 리시버를 구현하는 형태
한 번 등록하면 해제할 수 없음
동적 리시버
클래스 파일에서 리시버를 등록, 해제할 수 있는 형태로 앱에 부하를 줄일 수 있음
하지만 해제를 적절히 해주지 않는다면 메모리 릭(leak)이 발생할 수 있음
Memory Leak: 컴퓨터 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상
Content Provider
데이터를 관리하고 다른 앱에 데이터를 제공해주는 컴포넌트를 의미한다. 데이터를 저장하고, 불러와서 사용할 수 있는 시스템을 의미하며, 데이터베이스나 파일 시스템 등이 존재한다. 콘텐트 제공자는 특정한 애플리케이션이 사용하고 있는 데이터베이스를 공유하기 위해 사용하며, 보통 내부 데이터를 외부 데이터베이스로 전달할 때 주로 사용한다.
다른 앱의 데이터를 사용하고자 하는 앱에서는 URI를 이용하여 Content Resolver를 통해 다른 앱의 Content Provider에게 데이터를 요청한다. 요청 받은 Content Provider는 URI를 확인하고 내부에서 데이터를 꺼내어 Content Resolver에게 전달한다. (추가 설명)
- SQLite DB, Web, 파일 입출력 등을 통해서 데이터를 관리한다.
- 외부 애플리케이션이 현재 실행 중인 앱 내에 있는 데이터베이스에 함부로 접근하지 못하게 할 수 있으며, 공개하고 공유하고 싶은 데이터만 공유할 수 있도록 도와준다. (exported 옵션의 설정으로 접근 설정 가능)
- 작은 데이터들은 인텐트로 앱끼리 공유가 가능하지만, 음악 또는 사진 파일과 같이 용량이 큰 데이터들은 콘텐트 프로바이더를 통해 공유하는 게 적합하다.
- 데이터베이스에서 흔히 사용되는 CRUD(Create, Read, Update, Delete) 원칙을 준수한다. (ContentResolver
메서드가 영구 저장소의 기본적인 CRUD 기능을 제공)
- 애플리케이션 간의 데이터 공유를 위해 표준화된 인터페이스를 제공한다. (추가 설명)
- 콘텐트 제공자를 통해 다른 앱의 데이터 변경이 가능하다. (ex. 사용자의 전화번호 주소록을 이용하여, 응용 애플리케이션을 만들려고 할 때, 전화번호부 콘텐트 제공자를 이용하여 전화번호 주소록 데이터를 바꿀 수 있음) (추가 설명)
- 콘텐트 제공자를 이용하기 위해서는 권한을 획득해야 한다. (임시 권한, 읽기/쓰기 원한, 읽기 권한, 쓰기 권한 존재) (추가 설명)
Reference