▷ 퍼미션 설정하기
▷ 10장 실습
- API 레벨 호환성 고려하기
□ API 레벨 설정
minSdkVersion 16
targetSdkVersion 30
→ targetSdkVersion 30 은 30 버전의 API로 앱을 개발한다는 뜻이고 minSdkVersion은 최소 16 버전 기기에서도 오류가 발생하지 않아도 동작한다는 뜻이다.
→ 앱을 개발할 때 minSdkVersion 설정값보다 상위 버전에서 API를 사용하면 호환성(compatibility)을 고려해야 한다.
→ style이라는 함수는 API Level 1이라고 표시되어 있으므로 API 레벨 1에 추가된 것이다.
→ 만약 'Added in API level 20'이라면 minSdkVersion 16이므로 호환성에 문제가 생긴다.
이런 경우 애너테이션(annotation)을 추가해 오류를 해결한다.
□ API 호환성 애너테이션
@RequiresApi()
@TargetApi()
→ 애너테이션은 안드로이드 스튜디오에서 오류를 무시하는 설정일 뿐이므로 앱이 실행될 때 API 레벨 호환성 문제를 막으려면 직접 코드로 처리해 줘야 한다.
API 레벨이 달라 호환성 문제가 생기는 경우 if문을 이용하여 특정 버전에서만 실행될 수 있게 할 수 있다.
- 퍼미션 설정하기
퍼미션 : 앱의 특정 기능에 부여하는 접근 권한
내가 만든 기능을 다른 앱에서 사용할 수 없도록 보호하고 권한을 얻는 앱에서만 허용하고 싶을 때 사용한다.
□ 퍼미션 태그
<permission> : 기능을 보호하려는 앱의 매니페스트 파일에 설정
name | 퍼미션의 이름 |
label, description | 퍼미션을 설명 |
protectionLevel | 보호 수준 {normal = 낮은 수준의 보호, dangerous= 높은수준의 보호이고 사용자에게 권한 허용을 요청해야한다, signature = 같은 키로 인증한 앱만 실행, signatureOrSystem = 안드로이드 시스템 앱이거나 같은 키로 인증한 앱만 실행 가능하다} |
{ normal = 낮은 수준의 보호, dangerous= 높은 수준의 보호이고 사용자에게 권한 허용을 요청해야 한다, signature = 같은 키로 인증한 앱만 실행, signatureOrSystem = 안드로이드 시스템 앱이거나 같은 키로 인증한 앱만 실행 가능하다}
<uses-permission> : 퍼미션으로 보호된 기능을 사용하려는 앱의 매니페스트 파일에 설정
□ 퍼미션 사용 설정
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
ACCESS_NETWORK_STATE : protectionLevel이 normal
ACCESS_FINE_LOCATION : protectionLevel이 dangerous
매니페스트 파일에 퍼미션을 설정했다고 해서 컴포넌트가 보호되지 않는다.
퍼미션을 설정한 후에 해당 퍼미션을 보호하려는 컴포넌트에 적용해야 한다.(android:permission 속성을 이용)
□ 시스템이 보호하는 기능
ACCESS_FINE_LOACTION | 위치 정보 접근 | ACCESS_NETWORK_STATE | 네트워크 정보 접근 |
ACCESS_WIFI_STATE | 와이파이 네트워크 정보 접근 | BATTERY_STATS | 배터리 정보 접근 |
BLUETOOTH | 블루투스 장치에 연결 | BLUETOOTH_ADMIN | 블루투스 장치 검색 및 페어링 |
CAMERA | 카메라 장치에 접근 | INTERNET | 네트워크 연결 |
READ_EXTERNAL_STORAGE | 외부 저장소에 파일 읽기 | WRITE_EXTERNAL_STORAGE | 외부 저장소에 파일 쓰기 |
READ_PHONE_STATE | 전화기 정보 접근 | SEND_SMS | 문자 메시지 발신 |
RECEIVE_SMS | 문자 메시지 수신 | RECEIVE_BOOT_COMPLETED | 부팅 완료 시 실행 |
VIBRATE | 진동 울리기 |
- 퍼미션 허용 확인
API 레벨 23(Android 6) 버전부터 정책이 바뀌어서 <uses-permission>으로 선언했더라도 사용자가 권한 화면에서 이를 거부할 수 있게 되었다.
앱을 실행할 때 필요한 권한을 사용자가 거부했다면 다시 퍼미션을 허용해 달라고 요청해야 한다.
□ 사용자가 퍼미션을 허용했는지 확인하는 방법
open static fun checkSelfPermission(
@NonNull context: Context,
@NonNull permission: String,
): Int
→ checkSelfPermission함수를 이용하여 확인한다.
→ 두 번째 매개변수가 퍼미션을 구분하는 이름이며 결괏값은 아래와 같이 상수로 반환된다.
PackageManger.PERMISSION_GRANTED : 권한을 허용한 경우
PackageManger.PERMISSION_DENIED : 권한을 거부한 경우
□ 퍼미션 허용 요청 함수
open static fun requestPermissions(
@NonNull activity: Activity,
@NonNull permissions: Array<String!>
@IntRange(0) requestCode: Int
): Int
Activity.requestPermissions(this,arrayOf<String>("android.permission.ACCESS_FINE_LOCATION"),100)
→ 두 번째 매개변수의 문자열 배열이 허용을 요청하는 퍼미션의 이름이다.
→ 한꺼번에 여러 개를 요청할 수 있으며 퍼미션 개수만큼 다이얼로그 화면이 갱신된다.
□ 퍼미션 요청 결과 확인하는 함수
abstract fun onRequestPermissionResult(
requestCode: Int,
@NonNull permissions: Array<String!>,
@NonNull grantResults: IntArray
): Unit
→ 다이얼로그를 띄워 퍼미션을 요청해도 사용자가 거부할 수 있다.(다이얼로그 후에도 사용자가 퍼미션을 허용했는지 확인해야 한다)
- 다양한 다이얼로그
다이얼로그 : 사용자와 상호 작용하는 대화 상자
ex) 토스트, 날짜 및 시간 입력, 알림 창 등
- 토스트 메시지 띄우기
토스트(Toast) : 화면 아래쪽에 잠깐 보였다가 사라지는 문자열 (간단한 메시지로 특정 상황을 알릴 때 사용)
□ 토스트 함수
open static fun makeText(context: Context!, text: CharSequence!, duration: Int): Toast!
open static fun makeText(context: Context!, resId: Int, duration: Int): Toast!
→ 두 번째 매개변수가 출력할 문자열, 세 번째 매개변수는 토스트가 화면에 출력되는 시간
→ makeText()로 만든 토스트는 show() 함수로 화면에 출력한다.
세 번째 매개변수 출력되는 시간 상수
→ val LENGTH_LONG: INT // 일반적으로 3초
→ val LENGTH_SHORT: INT // 5초
□ 토스트 만드는 세터 함수
open fun setDuration(duration: Int): Unit
open fun setGravity(gravity:Int, xOffset: Int, yOffset: Int): Int
open fun setMargin(horizontalMargin: Float, verticalMargin: Float): Unit
open fun setText(resId: Int): Unit
setDuration(), setText() : 문자열이나 화면에 보이는 시간을 설정
setGravity(), setMargin() : 토스트가 뜨는 위치를 정할 수 있다.
콜백 기능 : 토스트가 화면에 보이거나 사라지는 순간을 감지해 특정 로직을 수행하는 기능(API레벨 30 버전에 추가)
@RequiresApi(Build.VERSION_CODES.R) //API레벨 호환성 애너테이션
fun showToast{
val toast = Toast.makeText(this, "종료하려면 한 번 더 누르세요.", Toast.LENGTH_SHORT)
toast.addCallback(
object : Toast.Callback(){
override fun onToastHidden(){
super.onToastHidden()
Log.d("kkang","toast hidden")
}
override fun onToastShown(){
super.onToastShown()
Log.d("kkang","toast shown")
}
})
toast.show()
}
→ Toast.Callback 타입의 객체를 토스트 객체의 addCallback() 함수로 등록하면 된다.
→ 화면에 토스트가 뜨는 순간 자동으로 콜백 객체의 onToastShown() 함수가 호출
→ 화면에서 사라지는 순간 onToastHidden() 함수가 자동으로 호출
- 날짜 및 시간 입력받기
피커(Picker) 다이얼로그 : 사용자에게 날짜나 시간을 입력받는 데 사용하는 다이얼로그
→ 데이트 피커 다이얼로그 : 날짜 입력받을 때 사용
→ 타임 피커 다이얼로그 : 시간을 입력받을 때 사용
□ 데이트 피커 다이얼로그 생성자
DatePickerDialog(context: Context, listener: DatePickerDialog.onDataSetListener?,
year: Int, month: Int, dayOfMonth: Int)
→ DatePickerDialog.OnDateSetListener 구현 객체를 등록하면 다이얼로그에서 사용자가 설정한 날짜를 콜백 함수로 얻을 수 있다.
→ Int타입 매개변수는 처음에 보이는 날짜이고 month값은 0부터 11까지 지정된다(0은 1월을 의미)
□ 데이트 피커 다이얼로그 사용 예
DatePickerDialog(this, object: DatePickerDialog.OnDateSetListener{
override fun onDateSet(p0: DatePicker?, p1: Int, p3: Int){
Log.d("kkang","year : $p1, month : ${p2+1}, dayOfMonth : $p3")
}
},2020,8,21).show()
□ 타임 피커 다이얼로그 생성자
TimePickerDialog(context: Context!, listener: TimePickerDialog.OnTimeSetListener!,
hourOfDay: Int, minute: Int, is24HourView: Boolean)
→ TimePickerDialog.OnTimeSetListener를 구현한 객체를 지정하면 사용자가 다이얼로그에서 설정한 시간을 얻을 수 있다.
→ is24HourView = false : 12시간 형태로 출력, 오전/오후를 선택하는 부분이 보인다
→ is24HourView = true : 24시간 형태로 출력
- 알림 창 띄우기
AlertDialog : 안드로이드 다이얼로그의 기본
(타임 피커와 데이트 피커도 AlertDialog의 하위 클래스이다)
알림 창은 3가지 영역으로 구분된다.
→ 제목, 내용, 버튼
알림 창을 설정할 때 제목만 설정했다면 제목만 보인다.
□ 알림 창 빌더
AlertDialog.Builder(context: Context!)
알림 창이 생성자는 접근 제한자가 protected로 선언되어 객체를 직접 생성할 수 없다.
AlertDialog.Builder를 사용하여 알림 창을 만든다.
□ 알림 창에 아이콘, 제목, 내용을 지정하는 함수
open fun setIcon(iconId: Int): AlertDialog.Builder!
open fun setTitle(title: CharSequence!): AlertDialog.Builder!
open fun setMessage(message: CharSequence!): AlertDialog.Builder!
→ setIcon() : 제목 영역에 아이콘을 출력
→ setTitle() : 제목 문자열을 출력
→ setMessage() : 내용 영역에 간단한 문자열을 출력
□ 알림 창에 버튼을 지정하는 함수
open fun setPositiveButton(text: CharSequence!, listener: DialogInterface.OnClickListener!):
AlertDialog.Builder!
open fun setNegativeButton(text: CharSequence!, listener: DialogInterface.OnClickListener!):
AlertDialog.Builder!
open fun setNeutralButton(text: CharSequence!, listener: DialogInterface.OnClickListener!):
AlertDialog.Builder!
→ 첫 번째 매개변수 : 버튼의 문자열
→ 두 번째 매개변수 : 사용자가 버튼을 클릭했을 때 처리할 이벤트 핸들러(버튼을 클릭했을 때 처리할 내용이 없다면 두 번째 매개변수에 null 대입)
→ 알림 창 버튼은 최대 3개까지 추가할 수 있다.(여러 번 사용 시 중복되어 하나만 나타난다)
□ 알림 창 띄우기
AlertDialog.Builder(this).run{
setTitle("test dialog")
setIcon(android.R.drawable.ic_dialog_info)
setMessage("정말 종료하시겠습니까?")
setPositiveButton("OK",null)
setNegativeButton("Cancel",null)
setNeutralButton("More",null)
setPositiveButton("Yes", null)
setNegativeButton("No",null)
show()
}
→ 버튼 함수를 3가지로 구분하는 이유는 이벤트 핸들러에서 어떤 버튼이 클릭되었는지 구분하기 위해서이다.
→ setPositiveButton() 함수로 만든 버튼은 이벤트 구분자가 DialogInterface.BUTTON_POSITIVE로 지정된다.
→ setNegativeButton() 함수로 만든 버튼은 이벤트 구분자가 DialogInterface.BUTTON_NEGATIVE로 지정된다.
□ 선택받는 알림 창을 만드는 함수
open fun setItems(items: Array<CharSequence!>!, listener: DialogInterface.OnClickListener!):
AlerDialog.Builder!
open fun setMultichoiceItems(items: Array<CharSequence!>!, checkedItems: BooleanArray!,
listener: DialogInterface.OnMultiChoiceClickListener!): AlertDialog.Builder!
open fun setSingleChoiceItems(items: Array<CharSequence!>!, checkedItem: Int,
listener: DialogInterface.OnClickListener!): AlertDialog.Builder!
→ setItems() 두 번째 매개변수는 항목을 선택할 때의 이벤트 핸들러이고 항목을 선택하면 onClick() 함수 자동 호출
→ setMultiChoiceItems() 함수는 다중 선택을 위한 체크박스가 함께 출력되는 항목을 만든다.
→ setMultiChoiceItems() 함수 세 번째 매개변수는 항목을 선택할 때의 이벤트 핸들러이고 선택 시 onClick() 자동 호출
→ setSingleChoiseItems() 함수는 하나만 선택할 수 있는 라디오 버튼으로 구성된 항목을 만든다.
□ 속성을 설정하는 함수
open fun setCancelable(cancelable: Boolean): AlertDialog.Builder!
open fun setCanceledOnTouchOutside(cancel: Boolean): Unit
→ setCancelable() 함수는 사용자가 기기의 뒤로 가기 버튼을 눌렀을 때
→ setCanceledOnTouchOutside() 함수는 알림 창의 바깥 영역을 터치했을 때 매개변수가 true면 닫고 false면 닫지 않는다.(기본값은 true)
- 커스텀 다이얼로그
개발자가 원하는 형태로 창을 구성하고 싶을 때 만드는 다이얼로그
AlertDialog를 이용한다.
LayoutInflater 클래스 : 레이아웃 XML 파일을 코드에서 초기화하는 기능을 제공한다.
□ LayoutInflater로 레이아웃 XML 파일 초기화
val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val rootView = inflater.inflate(R.layout.activity_one, null)
→ XML 파일의 루트 태그가 <LinearLayout>이라면 LinearLayout 객체를 반환한다.
→ inflate() 함수의 반환 값은 초기화된 XML의 루트 태그에 해당하는 객체이다.
□ 뷰 바인딩을 적용한 XML 파일 초기화
val binding = ActivityOneBinding.inflate(layoutInflater)
val rootView = binding.root
→ 초기화할 XML에 해당하는 바인딩 클래스의 inflate() 함수를 호출하면서 layoutInflater를 전달하면 자동으로 초기화되고 루트 뷰 객체를 얻을 수 있다.
□ 커스텀 다이얼로그 dialog_input.xml 파일
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<RadioGroup
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="male" />
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="female" />
</RadioGroup>
</LinearLayout>
□ 커스텀 다이얼로그 MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val dialogBinding = DialogInputBinding.inflate(layoutInflater)
AlertDialog.Builder(this).run {
setTitle("Input")
setView(dialogBinding.root)
setPositiveButton("닫기", null)
show()
}
}
}
- 소리와 진동 알림
- 소리 알림
안드로이드 시스템은 알림(NOTIFICATION), 알람(ALARM), 벨소리(RINGTONE) 등의 소리를 제공한다.
RingtonManger에서 얻을 수 있다.
□ 소리 얻기
val notification: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val ringtone = RingtoneManager.getRingtone(applicationContext, notification)
ringtone.play()
→ RingtoneManager.getDefaultUri() 함수를 이용하여 소리의 식별 값을 얻는다.
→ Uri 객체를 식별 값으로 얻어서 RingtonManager.getRingtone() 함수의 두 번째 매개변수로 전달한다.(Ringtone 객체를 얻는다)
→ Ringtone객체의 play() 함수를 호출하면 소리가 재생된다.
→ 음원 리소스 디렉터리는 res/raw이다.
→ 음원 재생하는 클래스는 MediaPlayer이고 play() 함수를 호출하면 음원이 재생된다.
□ 음원 재생하기
val player: MediaPlayer = MediaPlayer.create(this, R.raw.fallbackring)
player.start()
- 진동 알림
앱에서 진동이 울리게 하려면 매니페스트 파일에 <uses-permission>으로 퍼미션을 얻어야 한다.
□ 진동 퍼미션 얻기(매니페스트 파일)
<uses-permission android:name="android.permission.VIBRATE" />
→ getSystemService() 함수로 객체를 얻는다.
→ Vibrator 객체의 함수로 진동을 한 번이나 여러 번 울리게 할 수 있다.
시간과 패턴을 지정해 진동 울리기
API 레벨 1부터 제공한 진동 알림 함수가 26 버전 (Android 8)에서 deprecated 되었다.
(deprecated : 사용을 보장할 수 없으니 더는 사용하지 말라는 뜻)
□ deprecated 된 진동 알림 함수
open fun vibrate(milliseconds: Long): Unit
open fun vibrate(pattern: LongArray!, repeat: Int): Unit
→ 첫 번째 함수 매개변수는 진동이 울리는 시간을 의미한다.(500인 경우 0.5초간 울린다)
→ 두 번째 함수의 첫 번째 매개변수는 진동 패턴을 배열로 받고 두 번째 매개변수는 패턴을 몇 번 반복할지 지정한다
→ repeat을 -1로 지정하면 반복하지 않고 패턴을 한 번만 진동이 울린다
→ repeat을 0으로 지정하면 코드에 cancel() 함수로 진동 알림을 끄지 않는 한 패턴대로 계속 울린다.
□ API레벨 26부터 제공하는 함수
open fun vibrate(vibe: VibrationEffect!): Unit
open static fun createOneShot(milliseconds: Long, amplitude: Int): VibrationEffect!
→ VibrationEffect 객체로 진동이 울리는 시간 이외에 진동의 세기까지 제어할 수 있다.
→ vibrate() 함수에 VibrationEffect 객체를 대입하면 첫 번째 매개변수의 시간 동안 진동이 울린다.
→ 두 번째 매개변수를 이용해 진동의 세기를 지정할 수 있다.(0~255 사이의 숫자로 표현하고, 0으로 지정하면 울리지 않고 255로 설정하면 가장 세게 울린다)
→ VibrationEffect.DEFAULT_AMPLITUDE처럼 상수를 지정해 설정할 수 있다.
□ 반복해서 진동을 울리는 createWaveform() 함수
open static fun createWaveform(timings: LongArray!, amplitudes: IntArray!, repeat:Int):
VibrationEffect!
→ 첫 번째 매개변수는 진동이 울리는 시간 패턴의 배열
→ 두 번째 매개변수는 진동 세기 패턴의 배열
→ 세 번째 매개변수가 이 패턴의 반복 횟수
- 알림 띄우기
알림 : 상태 바에 앱의 정보를 출력하는 것
NotificationManager | NotificationChannel | |||
↓대입 | ||||
Notification | Create ← |
NotificationCompat.Builder( ) | ||
↓대입 | ||||
notify( ) |
→ 26 버전 이전까지는 빌더를 만들 때 NotificationChannel 정보가 필요 없었지만, 26 버전에서 Builder(context:Context!) 생성자가 deprecated 되면서 Builder(context: Context!, channelld: String!) 생성자를 사용해야 한다.
→ 26 버전 이전까지는 채널이라는 개념이 없어서 알림을 안 받겠다고 하면 앱에서 띄우는 모든 알림이 발생하지 않았지만 26 버전부터는 채널 개념이 도입되면서 앱의 알림을 채널별로 구분하여 받고 싶은 알림을 받을 수 있다.
□ 알림 채널 생성자
NotificationChannel(id: String!, name: CharSequence!, importance: Int)
→ 매개변수로 채널의 식별 값과 설정 화면에 표시할 채널 이름을 문자열로 지정
→ 세 번째 매개변수는 채널에서 발생하는 알림의 중요도이며 상수로 지정한다.
□ 중요도 상수
중요도 상수 | 설명 |
NotificationManager.IMPORTANCE_HIGH | 긴급 상황으로 알림음의 울리며 헤드업으로 표시 |
NotificationManager.IMPORTANCE_DEFAULT | 높은 중요도이며 알림음이 울림 |
NotificationManager.IMPORTANCE_LOW | 중간 중요도이며 알림음이 울리지 않음 |
NotificationManager.IMPORTANCE_MIN | 낮은 중요도이며 알림음도 없고 상태 바에도 표시되지 않음 |
□ 채널의 각종 정보의 함수 및 프로퍼티
fun setDescription(description: String!): Unit //채널의 설명 문자열
fun setShowBadge(showBadge: Boolean): Unit //홈 화면의 아이콘에 배지 아이콘 출력 여부
fun setSound(sound: Uri!, audioAttributes: AudioAttributes!): Unit //알림음 재생
fun enableLights(lights: Boolean): Unit //불빛 표시 여부
fun setLightColor(argb: Int): Unit //불빛이 표시된다면 불빛의 색상
fun enableVibration(vibration: Boolean): Unit //진동을 울릴지 여부
fun setVibrationPattern(vibrationPattern: LongArray!): Unit //진돋을 울린다면 진동의 패턴
→ setShowBadge(true)로 설정하면 홈 화면의 앱 아이콘에 확인하지 않는 알림 개수가 표시된 배지 아이콘이 보인다.
□ 알림 빌더 작성
val manager= getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val builder: NotificationCompat.Builder
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val channelId= "one-channel"
val channelName= "My Channel One"
val channel = NotificationChannel(
channelId,channelName,NotificationManager.IMPORTANCE_HIGH
)
channel.description = "My Channel One Description"
channel.setShowBadge(true)
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
.build()
channel.setSound(uri, audioAttributes)
channel.enableLights(true)
channel.lightColor = Color.RED
channel.enableVibration(true)
channel.vibrationPattern = longArrayOf(100,200,100,200)
manager.createNotificationChannel(channel)
builder = NotificationCompat.Builder(this, channelId)
}else{
builder = NotificationCompat.Builder(this)
}
- 알림 객체
스몰 아이콘 : 앱에서 알림이 발생하면 상태바에 출력되는 작은 아이콘
□ 알림 객체 설정
builder.setSmallIcon(android.R.drawable.ic_notification_overlay)
builder.setWhen(System.currentTimeMillis())
builder.setContentTitle("Content Title")
builder.setContentText("Content Message")
□ 알림 발생 및 알림 취소
manager.notify(11, builder.build()) //알림발생
manager.cancel(11) //알림 취소
→ NotificationManager 클래스의 notify() 함수를 이용해 알림을 띄운다.
→ 첫 번째 매개변수는 알림을 식별하는 데 사용하는 숫자, 개발자 임의로 지정(사용자 폰에서 발생한 알림 취소 시 사용)
→ 알림 취소 시 첫 번째 매개변수와 같은 숫자를 사용한다.
□ 알림 취소 막기
builder.setAutoCancel(false)
builder.setOngoing(true)
→ setAutoCancel(false)로 지정하면 알림을 터치할 때 이벤트는 발생하지만 알림이 사라지지 않는다.
→ setOngoing(true)로 설정하면 사용자가 알림을 스와이프 해도 사라지지 않는다.
→ 두 가지 모두 설정했다면 사용자가 알림을 취소할 수 없으며 코드에서 특정 순간에 cancel() 함수로 취소해야 한다.
- 알림 구성
# 알림 터치 이벤트
→ 알림은 앱의 관할하는 화면이 아니므로 알림에서 발생한 터치 이벤트는 앱의 터치 이벤트로 처리할 수 없다.
→ 사용자가 알림을 터치하면 앱의 액티비티 또는 브로드캐스트 리시버를 실행하기 위해 인텐트(intent)를 사용한다.
(인텐트(intent) : 앱의 컴포넌트를 실행하는데 필요한 정보)
→ 인텐트는 앱의 코드에서 준비하지만 실제 컴포넌트를 실행하는 시점은 앱에서 정할 수 없다.
→ 인텐트를 준비한 후에 Notification 객체에 담아 이벤트가 발생할 때 인텐트를 실행해 달라고 시스템에 의뢰해야 한다.
→ 의뢰는 PendingIntent 클래스를 이용
□ PendingIntent 클래스 함수들
static fun getActivity(context: Context!, requestCode: Int, intent: Intent!, flags: Int):
PendingIntent!
static fun getBoardcast(context: Context!, requestCode: Int, intent: Intent!, flags: Int):
PendingIntent!
static fun getService(context: Context!, requestCode: Int, intent: Intent, flags: Int):
PendingIntent!
→ 각 함수의 세 번째 매개변수에 인텐트 정보를 등록한다.
□ 알림 객체에 액티비티 실행 정보 등록
val intent = Intent(this,DetailActivity::class.java)
val pendingIntent =
PendingIntent.getActivity(this,10,intent,
PendingIntent.FLAG_UPDATE_CURRENT)
builder.setContentIntent(pendingIntent)
→ DetailActivity라는 액티비티의 실행 정보를 Notification 객체에 등록하는 코드
→ setContentIntent() 함수를 이용하여 터치 이벤트 등록을 한다.
# 액션
→ 알림에 액션을 최대 3개까지 추가할 수 있다.
→ 알림에서 간단한 이벤트는 액션으로 처리한다(ex 알람 취소, 전화 앱의 전화 수신이나 거부 등)
→ 액션을 터치할 때 시행할 인텐트 정보를 PendingIntent로 구성해서 등록해야 한다.
□ 액션 등록 함수
open fun addAction(action: Notification.Action!): Notification.Builder
→ Action객체를 매개변수로 전달한다.(Action객체는 Action.Builder로 만든다)
□ 액션 빌더 생성자
Builder(icon: Int, title: CharSequence!, intent: PendingIntent!)
→ 액션 빌더의 생성자에 아이콘 정보, 액션 문자열, 액션 클릭 시 이벤트를 위한 PendingIntent 객체를 전달한다.
□ 액션 등록하기
val actionIntent = Intent(this, OneReceiver::class.java)
val actionPendingIntent = PendingIntent.getBroadcast(this, 20, actionIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
builder.addAction(
NotificationCompat.Action.Builder(
android.R.drawable.stat_notify_more,
"Action",
actionPendingIntent
).build()
)
# 원격 입력
원격 입력(remoteIntent) : 알림에서 사용자 입력을 직접 받는 기법
□ 원격 입력 예시
val KEY_TEXT_REPLY = "key_text_reply"
var replyLabel: String = "답장"
var remoteInput: RemoteInput = RemoteInput.Builder(KEY_TEXT_REPLY).run{
setLabel(replyLabel)
build()
}
→ 빌더에 들어가는 정보는 RemoteInput의 입력을 식별하는 값과 입력란에 출력되는 힌트 문자열이다.
→ 식별 값은 개발자가 임의로 작성할 수 있으며 사용자가 입력한 글을 가져올 때 사용된다.
→ RemoteInput은 API 레벨 20에서 추가되었다.( API 레벨 호환성을 고려해야 한다)
□ 원격 인텐트 준비
val replyIntent = Intent(this, ReplyReceiver::class.java)
val replyPendingIntent = PendingIntent.getBroadcast(this,30, replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
→ RemoteInput도 액션이므로 액션의 터치 이벤트 처리를 위한 PendingIntent를 준비해야 한다.
→ RemoteInput은 알림에서 사용자의 입력을 받는 것이므로 이벤트로 액티비티를 실행해 앱의 화면이 출력되지 않고 브로드캐스트 리시버를 실행해 백그라운드에서 사용자의 입력을 처리하는 게 일반적이다.
□ 원격 입력 액션 등록하기
builder.addAction(
NotificationCompat.Action.Builder(
R.drawable.send,
"답장",
replyPendingIntent
).addRemoteInput(remoteInput).build()
)
→ 액션 등록 코드와 비교하면 builder에 addRemoteInput() 함수를 추가적으로 사용했다.
→ 함수의 매개변수로 RemoteInput 객체를 전달하면 된다.
□ 브로드캐스트 리시버에서 사용자가 입력한 글을 입력받는 코드
val replyTxt = RemoteInput.getResultsFromIntent(intent)
?.getCharSequence("key_text_reply")
→ getCharSequence() 함수의 매개변수 부분의 문자열은 RemoteInput을 만들 때 지정한 식별 값과 같아야 한다.
□ 알림 갱신
manager.notify(11,builder.build())
→ 알림 갱신 신호를 보내야지 알림에서 글을 입력하는 부분이 사라진다.
# 프로그래스
→ 앱에서 어떤 작업이 이루 어지 데 시간이 걸리는 경우 알림 창에서 진행 상황을 프로그레스에 바로 알려준다.
□ 프로그레스 바 함수
open fun setProgress(max: Int, progress: Int, indeterminate: Boolean): Notification.Builder
→ 알림의 프로그레스 바는 화면을 따로 준비하지 않고 setProgress() 함수만 추가하면 자동으로 출력된다.
→ 첫 번째 매개변수 : 프로그레스 바의 최댓값
→ 두 번째 매개변수 : 진행 값
→ 세 번째 매개변수 : true인 경우 프로그레스 바는 왼쪽에서 오른쪽으로 계속 흘러가듯이 표현된다.
→ 처음 현재 값을 지정한 후 스레드(thread) 같은 프로그램을 사용해 진행 값을 바꾸면서 상황을 알려주면 된다.
□ 프로그레스 바의 진행 값을 증가시키는 스레드
builder.setProgress(100, 0, false)
manager.notify(11, builder.build())
thread {
for(i in 1..100){
builder.setProgress(100,i,false)
manager.notify(11, builder.build())
SystemClock.sleep(100)
}
}
→ 스레드를 이용해 10초 도안 프로그래스 바의 진행 값을 증가시키는 예
- 알림 스타일
# 큰 이미지 스타일
알림에 큰 이미지를 출력할 때 BigPictureStyle을 이용한다.
□ 큰 이미지 스타일
val bigPicture = BitmapFactory.decodeResource(resources, R.drawable.test)
val bigStyle = NotificationCompat.BigPictureStyle()
bigStyle.bigPicture(bigPicture)
builder.setStyle(bigStyle)
→ BigPicture 객체의 bigPicture 프로퍼티에 출력할 이미지를 비트맵 형식으로 지정한다
→ BigPicture 객체를 빌더의 setStyle() 함수에 지정한다.
# 긴 텍스트 스타일
→ 알림에 긴 문자열을 출력해 사용자가 앱을 실행하지 않아도 많은 정보를 알 수 있게 한다.
→ BigTextStyle을 이용한다.
→ 이메일 수신했을 때 제목, 발신자, 일부 내용을 보여주는 것
□ 긴 텍스트 스타일
val bigTextStyle = NotificationCompat.BigTextStyle()
bigTextStyle.bigText(resources.getString(R.string.long_text))
builder.setStyle(bigTExtStyle)
# 상자 스타일
→ 문자열을 목록으로 출력하는 InboxStyle을 사용한다.
□ 상자 스타일
val style = NotificationCompat.InboxStyle()
style.addLine("1코스 - 수락.불암산코스")
style.addLine("2코스 - 용마.아차산코스")
style.addLine("3코스 - 고덕.일자산코스")
builder.setStyle(style)
# 메시지 스타일
→ 여러 사람이 메시지를 구분해서 출력할 때 사용
□ 메시지 객체
Message(text: CharSequence, timestamp: Long, sender: Person?)
→ 첫 번째 매개변수 : 메시지 내용
→ 두 번째 매개변수 : 메시지가 발생한 시각
→ 세 번째 매개변수 : Person객체(알림에 출력될 한 사람의 정보를 담는 클래스)로 표현
□ Person 객체 생성
val sender1: Person = Person.Builder()
.setName("kkang")
.setIcon(IconCompat.createWithResource(this, R.drawable.person1))
.build()
→ setName(), setIcon() 함수를 이용해 사람의 이름과 프로필 사진을 등록할 수 있다.
→ Person은 API레벨 28 버전에 추가된 클래스(androidx.core.app.Person 라이브러리를 임포트 해야 한다)
□ 메시지 객체 생성
val message1 = NotificationCompat.MessagingStyle.Message(
"hello",
System.currentTimeMillis(),
sender1
)
□ 메시지 스타일 만들기
val messageStyle = NotificationCompat.MessagingStyle(sender1)
.addMessage(message1)
.addMessage(message2)
builder.setStyle(messageStyle)
→ Message객체를 MessageStyle에 대입
- 10장 실습
카카오톡 알림 만들기
□ activiy_main.xml 파일
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@+id/notificationButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="알림 발생"/>
</LinearLayout>
□ ReplyReceiver 파일
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.RemoteInput
class ReplyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val replyTxt = RemoteInput.getResultsFromIntent(intent)?.getCharSequence("key_text_reply")
Log.d("kkang","replyTxt : $replyTxt")
val manager = context.getSystemService(
AppCompatActivity.NOTIFICATION_SERVICE) as NotificationManager
manager.cancel(11)
}
}
□ MainActivity파일
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.graphics.BitmapFactory
import android.media.AudioAttributes
import android.media.RingtoneManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationCompat
import androidx.core.app.RemoteInput
import com.example.ch10_notification.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.notificationButton.setOnClickListener {
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val builder: NotificationCompat.Builder
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//버전 26이상
val channelId = "one-channel"
val channelName = "My channel One"
val channel = NotificationChannel(
channelId,
channelName,
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "My Channel One Description"
setShowBadge(true)
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
.build()
setSound(uri, audioAttributes)
enableVibration(true)
}
manager.createNotificationChannel(channel)
builder = NotificationCompat.Builder(this, channelId)
} else {
builder = NotificationCompat.Builder(this)
}
builder.run {
// 알림의 기본 정보
setSmallIcon((R.drawable.small))
setWhen(System.currentTimeMillis())
setContentTitle("홍길동")
setContentText("안녕하세요")
setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.big))
}
val KEY_TEXT_REPLY = "key_text_reply"
var replyLabel: String = "답장"
var remoteInput: RemoteInput = RemoteInput.Builder(KEY_TEXT_REPLY).run {
setLabel(replyLabel)
build()
}
val replyIntent = Intent(this, ReplyReceiver::class.java)
val replyPendingIntent = PendingIntent.getBroadcast(
this, 30, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT
)
builder.addAction(
NotificationCompat.Action.Builder(
R.drawable.send,
"답장",
replyPendingIntent
).addRemoteInput(remoteInput).build()
)
manager.notify(11, builder.build())
}
}
}
13일 차 공부 끝!
'KOTLIN' 카테고리의 다른 글
[Kotlin] 공부 15일차 (2022-02-02) (0) | 2022.02.03 |
---|---|
[Kotlin] 공부 14일차 (2022-02-01) (0) | 2022.02.02 |
[Kotlin] 공부 12일차 (2022-01-26) (0) | 2022.01.28 |
[Kotlin] 공부 11일차 (2022-01-25) (0) | 2022.01.25 |
[Kotlin] 공부 10일차 (2022-01-24) (0) | 2022.01.24 |