1. 서비스
- 서비스(service)는 일반적으로 화면 없이 동작하는 프로그램을 말합니다.
- 다른 말로는 데몬(daemon), 백그라운드 프로세스(background process)라고도 합니다.
- 액티비티 응용 프로그램은 화면(액티비티)이 종료되면 동작하지 않지만 서비스는 백그라운데서 실행되므로 화면과 상관없이 계속 동작합니다.
- 서비스의 생명주기는 액티비티의 생명주기보다 단순하며 아래와 같습니다.
- 서비스 요청이 시작되면 onCreate( ), onStartCommand( )가 동작하고 서비스가 계속됩니다.
- 서비스 중지 요청을 받으면 onDestroy( )가 동작하여 서비스가 종료됩니다.
- 안드로이드 서비스의 가장 일반적인 예는 음악 플레이어처럼 화면에 보이지 않아도 음악이 계속 재생되는 것입니다.
- 그 외 필요한 서비스는 프로그래머가 직접 정의하고 실행할 수 있습니다.
실습 14-1 서비스 테스트 앱 만들기
예제 14-1 activity_main.xml 코드
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnStart"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="음악서비스 시작" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnStop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="음악서비스 종료" />
</androidx.appcompat.widget.LinearLayoutCompat>
예제 14-2, 3 MusicService.java 코드
public class MusicService extends Service {
private MediaPlayer mediaPlayer;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
Toast("음악서비스 준비중");
super.onCreate();
}
@Override
public void onDestroy() {
Toast("음악 중지");
mediaPlayer.stop();
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast("음악 재생");
mediaPlayer = MediaPlayer.create(this, R.raw.darkside);
mediaPlayer.setLooping(true);
mediaPlayer.start();
return super.onStartCommand(intent, flags, startId);
}
public void Toast(String msg){
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
예제 14-4 MainActivity.java 코드
public class MainActivity extends AppCompatActivity {
private Intent intent;
private Button btnStart, btnStop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle("서비스 테스트 예제");
init();
initLr();
}
public void init(){
intent = new Intent(this, MusicService.class);
btnStart = findViewById(R.id.btnStart);
btnStop = findViewById(R.id.btnStop);
}
public void initLr(){
btnStart.setOnClickListener(v -> {
startService(intent);
Toast("음악서비스 시작 요청");
});
btnStop.setOnClickListener(v -> {
stopService(intent);
Toast("음악 서비스 중지 요청");
});
}
public void Toast(String msg){
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
예제 14-5 AndroidManifest.xml
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cos.project14_1">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Project14_1">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="MusicService" />
</application>
</manifest>
2. 브로드캐스트 리시버
- 브로드캐스트 리시버(Broadcast Receiver, BR)를 활용하면 안드로이드에서 발생하는 많은 이벤트(문자 메시지, 배터리 방전, 날짜 변경 등)를 감지하고 이를 처리하는 앱을 작성할 수 있습니다.
- 안드로이드는 문자 메시지 도착, 배터리 방전, SD 카드 탈부착, 네트워크 환경 변화 등이 발생하면 방송 신호를 보내는데, 이러한 방송 신호를 받아서 처리하는 것이 바로 브로드캐스트 리시버입니다.
- 브로드캐스트 리시버의 대표적인 응용은 배터리 상태 확인입니다.
- 안드로이드는 배터리 상태가 변할 때마다 이를 방송하는데, 배터리 상태를 확인하기 위한 관련 액션은 아래 표와 같습니다.
- 위의 액션 중에서 ACTION_BATTERY_CHANGED만 잘 확인하면 됩니다.
실습 14-2 배터리 상태 체크 앱 만들기
- 텔넷이나 푸티를 사용해서 확인 해야하므로 추후 진행보겠습니다.
3. 콘텐트 프로바이더
- 안드로이드는 보안상 앱에서 사용하는 데이터를 외부에서 접근할 수 없습니다.
- 예를 들면 간단 일기장에서 생성한 일기 파일이나 SQLite의 그룹 이름 데이터는 자신의 앱에서만 사용할 수 있고 다른 앱에서는 사용할 수 없습니다.
- 이러한 파일이나 데이터베이스를 외부 앱에서 사용하게 하려면 콘텐트 프로바이더(Content Provider, CP)를 만들어서 외부에 제공해야 합니다.
- 콘텐트 프로바이더와 관련이 깊은 것은 URI(Uniform Resource Identifier)인데, URI는 콘텐트 프로바이더에서 제공하는 데이터에 접근하기 위한 주소라고 생각하면 됩니다.
- URI는 'content://패키지명/경로/아이디' 형식으로 지정할 수 있습니다.
3-1 안드로이드 제공 콘텐트 프로바이더
- 안드로이드에서는 자체적으로 몇 갖기 콘텐트 프로바이더를 제공하고 있습니다.
- 연락처 전화번호, 통화 기록, 주소록, 브라우저의 북마크 등을 예로 들 수 있습니다.
- 아래 그림은 안드로이드 자체에서 제공하는 콘텐트 프로바이더를 사용하는 방법을 보여줍니다.
- 예로 통화 기록은 URI를 Contacts.Phones.CONTENT_URI로 제공합니다.
- 사용자는 getContentResolver( ) 메서드를 이용하여 ContentResolver 클래스를 얻은 후 통화 기록에 접근할 수 있습니다.
- 아래는 주요 콘텐트 프로바이더와 URI를 정리한 표입니다.
- AVD에서 통화 버튼을 눌러 통화 기록을 몇 건 남겨놓습니다.
- 안드로이드에서 통화 기록에 접근하려면 AndroidManifest.xml의 <application 위에 아래 코드를 추가하여 접근 권한을 줘야 합니다.
<uses-permission android:name="android.permission.READ_CALL_LOG" />
예제 14-10 activity_main.xml
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp"
tools:context=".MainActivity">
<Button
android:id="@+id/btnCall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="통화 기록 가져오기" />
<EditText
android:id="@+id/etCall"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="100"
android:gravity="top"/>
</androidx.appcompat.widget.LinearLayoutCompat>
예제 14-11
public class MainActivity extends AppCompatActivity {
private Button btnCall;
private EditText etCall;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
initLr();
}
public void init(){
ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.READ_CALL_LOG}, MODE_PRIVATE);
btnCall = findViewById(R.id.btnCall);
etCall = findViewById(R.id.etCall);
}
public void initLr(){
// 버튼을 클릭하면 getCallHistory( )를 호출하고 그 결과를 에디트텍스트에 출력
btnCall.setOnClickListener(v -> {
etCall.setText(getCallHistory());
});
}
// 통화 기록의 내용을 검색하고 통화 기록을 문자열로 만들어 반환하는 함수
public String getCallHistory() {
// 통화기록 중에서 통화 날짜, 발신, 또는 착신 여부, 전화번호, 통화 시간에 대한 문자열 배열을 준비
String[] callSet = new String[] {CallLog.Calls.DATE, CallLog.Calls.TYPE,
CallLog.Calls.NUMBER, CallLog.Calls.DURATION};
// 통화 기록에 대해 callSet 에서 설정한 내용을 조회
Cursor c = getContentResolver().query(CallLog.Calls.CONTENT_URI, callSet, null, null, null);
// 통화 기록이 단 한 건도 없다면 종료
if (c == null) {
return "통화기록 없음";
}
// 통화 기록의 문자열을 저장할 StringBuffer 변수를 준비
StringBuffer callBuff = new StringBuffer();
callBuff.append("\n 날짜 : 구분 : 전화번호 : 통화시간 \n\n");
// 통화 기록 앱의 처음 행으로 이동
c.moveToFirst();
// 통화 기록의 마지막까지 반복
do {
// 첫 필드(0번)를 가져와서 날짜 형식으로 버퍼에 저장
long callDate = c.getLong(0);
SimpleDateFormat datePattern = new SimpleDateFormat("yyyy-MM-dd");
String date_str = datePattern.format(new Date(callDate));
callBuff.append(date_str + " : ");
// 1번 필드의 데이터(발신 / 착신) 문자열을 버퍼에 저장
if (c.getInt(1) == CallLog.Calls.INCOMING_TYPE) {
callBuff.append("착신 : ");
} else {
callBuff.append("발신 : ");
}
// 2번 필드의 내용(전화번호)을 버퍼에 저장
callBuff.append(c.getString(2) + ":");
// 3번 필드의 내용(통화시간)을 버퍼에 저장
callBuff.append(c.getString(3) + "초\n");
} while (c.moveToNext());
c.close();
// 저장한 모든 정보를 반환
return callBuff.toString();
}
}