KOTLIN

[Kotlin] 공부 13일차 (2022-01-28)

HJ39 2022. 1. 29. 01:01

 API 레벨 호환성 고려하기

퍼미션 설정하기

    ▶ 퍼미션 허용 확인

더보기

▷ 소리와 진동 알림

    ▶ 소리 알림

    ▶ 진동 알림

10장 실습

 

 

 

  • API 레벨 호환성 고려하기

□ API 레벨 설정

minSdkVersion 16
targetSdkVersion 30

→ targetSdkVersion 30 은 30 버전의 API로 앱을 개발한다는 뜻이고 minSdkVersion은 최소 16 버전 기기에서도 오류가 발생하지 않아도 동작한다는 뜻이다.

→ 앱을 개발할 때 minSdkVersion 설정값보다 상위 버전에서 API를 사용하면 호환성(compatibility)을 고려해야 한다.

 

 

안드로이드 API문서

→ 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())
        }
    }
}

실습 실행 화면 1

 

실습 실행 화면 2

13일 차 공부 끝!