☝ 동적 퍼미션이란?
Java에서 퍼미션 요청 다이얼로그를 보여주는 방식
1. 매니패스트에서 퍼미션 등록
2. 이 앱이 알림에 대한 퍼미션을 허용한 상태인지 체크
☝ ContextCompat : 운영체제 호환성(Compat)
3. if문을 사용해 허용되었는지 체크
4. 리턴값이 -1 일때 알림 허용을 요청하는 다이얼로그를 보이기
4-1) 퍼미션 요청 결과를 받아오는 작업을 대행해주는 객체 생성 및 등록 (멤버변수로 생성)
🔨 ActivityResultLauncher<스트링> permissionResultLancher = registerForActivityResult(어떤 종류의 계약이니?,결과 실행);
ActivityResultLauncher<String> permissionResultLancher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback<Boolean>() {public void onActivityResult(Boolean result) {} });
//퍼미션 요청 결과를 받아오는 작업을 대행해주는 객체 생성 및 등록
ActivityResultLauncher<String> permissionResultLancher //퍼미션은 글자 덩어리라서 제네릭 String
= registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback<Boolean>() {
@Override
public void onActivityResult(Boolean result) {
if(result) Toast.makeText(MainActivity.this, "알림 허용", Toast.LENGTH_SHORT).show();
else Toast.makeText(MainActivity.this, "알림을 보낼 수 없습니다", Toast.LENGTH_SHORT).show();
}
});
//Contracts = 계약
//registerForActivityResult(어떤 종류의 계약이니?,결과 실행);
4-2) if문 안에서 퍼미션 요청 결과를 받아주는 대행사 객체 이용
permissionResultLancher.launch(Manifest.permission.POST_NOTIFICATIONS);
6-1) 알림객체 Notification 만들기
6-2) 알림객체를 생성해주는 건축가(builder)객체 생성
6-3) 알림채널 생성 : 운영체제 26버전 이전과 이후로 나누어 작업
📢 중요도에 따른 알림 화면
1. IMPORTANCE_HIGH : 소리 + 알림표시 + 화면 가리기 + 제일 앞
- IMPORTANCE_DEFAULT : 소리 + 알림표시
- IMPORTANCE_LOW : 알림표시
☝ Build.VERSION.SDK_INT
사용자의 운영체제 얻어오는 것
Build.VERSION_CODES.O
api 버전
6-4) 빌더에게 알람에 관련된 설정
6-5) Notification notification한테 위에서 만든 빌더를 대입
6-6) 알림을 확인하면 상태표시줄에서 없어지도록 하기
참고) 요즘들어 보이는 알림창 style 꾸미기
① 알림창에 큰 이미지 넣기
② 알림에 화면 여러곳 가게 하고 싶은 때
🧨 진동은 사용자에게 퍼미션 받아야함
package com.bsj0420.ex58notification;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioAttributes;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn).setOnClickListener(view -> click());
}
private void click() {
//안드로이드의 13버전(API 33버전 - 티라미수버전)부터 알림에 대한 동적 허가(퍼미션)이 추가되었다
//이 앱이 알림에 대한 퍼미션을 허용한 상태인지 체크
//물어보는 기능 액티비티에 있음
//checkSelfPermission() // 이건 26버전부터 있음 (오레오버전)
// ContextCompat : 운영체제 호환성(Compat)
int checkResult=ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS); //운영체제 버전 상관없이 사용가능
//허가o : 0 허가x : -1 / 13버전 이하는 다 0으로 온다
if(checkResult == PackageManager.PERMISSION_DENIED) {
//알림 허용을 요청하는 다이얼로그를 보이기
//다이얼로그는 안드로이드 자체적으로 갖고있음 - 개발자의 악의적 개발을 막기위해
//requestPermissions(); //예전방식
//퍼미션 요청 결과를 받아주는 대행사 객체 이용 - 얜 무조건 멤버변수로 옴
permissionResultLancher.launch(Manifest.permission.POST_NOTIFICATIONS);
return; //다이아로그 띄우고 멈춰
}
//5.
//알림(Notification)을 관리하는 관리자 객체 소환 : NotificationManager
//운영체제에서 받아옴 : 자주쓰는 것이 아니라 바로 못부르고 comtext안에서 찾아야함 getSystemService(식별자)
NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
//리턴값이 Object라서 NotificationManager으로 형변환
//////////////////////////////////////////////////////////////////////////////////////////////
//6-2
//알림객체를 생성해주는 건축가(builder)객체 생성
NotificationCompat.Builder builder = null;
//6-3 알림채널 생성
//빌더를 생성하는 문법이 26버전부터 변경 (오레오)
//알림채널이라는 개념이 도입됨
//그래서 26버전 이상의 문에서는 알림채널객체를 생성해야하고
//그 이전버전에서는 알림채널객체 생성없이 빌더를 만들어야함
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// ★ Build.VERSION.SDK_INT 사용자의 운영체제 버전에 따른 분기처리
// Build.VERSION_CODES.O : api 버전
//알림채널 객체 생성 (채널아이디, "채널에 보여질 글씨, 중요도)
NotificationChannel channel = new NotificationChannel("ch01","Ex58Channel",NotificationManager.IMPORTANCE_HIGH); //소리 + 알림표시 + 화면 가리기 + 제일 앞
//NotificationChannel channel = new NotificationChannel("ch01","Ex58Channel",NotificationManager.IMPORTANCE_DEFAULT); //소리 + 알림표시
//NotificationChannel channel = new NotificationChannel("ch01","Ex58Channel",NotificationManager.IMPORTANCE_LOW); //알림표시
//7-1) 사운드/진동 설정
//Uri soundUri = RingtoneManager.getActualDefaultRingtoneUri(this,RingtoneManager.TYPE_NOTIFICATION); //기본소리
Uri myUri = Uri.parse("android.resource://"+getPackageName() + "/"+ R.raw.s_goodjob); //사운드 경로 지정
//android.resource : R폴더 지정 + 내 패키지명 + 소리파일
channel.setSound(myUri, new AudioAttributes.Builder().build());
//진동
//진동은 사용자 정적 퍼미션 요구됨
channel.setVibrationPattern(new long[]{0,2000,1000,3000}); //대기 , 떨기, 대기, 떨기
//매니저한테 채널을 등록하기 전에 사운드 작업 해야한다
notificationManager.createNotificationChannel(channel); //매니저한테 등록
builder = new NotificationCompat.Builder(this,"ch01");
} else {
builder = new NotificationCompat.Builder(this,""); //채널이 없으니 id로 그냥 빈 문자열
Uri myUri
= Uri.parse("android.resource://"+getPackageName() + "/"+ R.raw.s_goodjob); //사운드 경로 지정
builder.setSound(myUri); //이전 버전에선 builder 한테 준다
}
//6-4
//빌더에게 알람에 관련된 설정
//6-4-1) 상태표시줄에 보이는 아이콘
builder.setSmallIcon(R.drawable.ic_noti);
//6-4-2) 상태바를 내렸을 때 확장된 알림창 대한 설정
builder.setContentTitle("정신 똑바로 차려");
builder.setContentText("알림 보내기가 호락호락한 작업이 아니다...");
//6-4-3) 이미지 추가
//이미지를 자바가 읽을 수 있는 객체 : Bitmap
Resources res = getResources(); //Context능력중 하나인데 자주쓰는 애라 바로 getResources() 있음
Bitmap bm = BitmapFactory.decodeResource(res,R.drawable.thumb_moana);
builder.setLargeIcon(bm);
//builder.setWhen() : 보낸 시간
//6-4-4) 알림창을 클릭했을 때 새로운 화면(SecondActivity)가 실행되도록
Intent intent = new Intent(this, SecondActivity.class);
//intent는 부르자마자 바로 실행해야함
//하지만 지금 쓰려는 인텐트는 알림을 눌러야만 실행되는 것
//인텐트 객체에게 바로 실행하지말고 잠시 보류해달라고 해야함
//보류중인 인텐트 PendingIntent 객체로 생성
PendingIntent pendingIntent
= PendingIntent.getActivity(this,10,intent,PendingIntent.FLAG_IMMUTABLE);
//(context,보류중인 친구의 번호,intent,보류중인데 또 오면 처리 - 무조건 FLAG_IMMUTABLE)
builder.setContentIntent(pendingIntent); // PendingIntent : 보류중인...
//6-6 알림을 확인하면 상태표시줄에서 없어지도록 하기
builder.setAutoCancel(true); //인텐트가 있을때만 쓸수 있는 기능임
// 참고) 요즘들어 보이는 알림창 style 꾸미기
// Notification.BigPictureStyle style = new Notification.BigPictureStyle();
// style.bigPicture(BitmapFactory.decodeResource(res,R.drawable.moana04));
// builder.setStyle(style); //BigPictureStyle 매개변수에 빌더주면 setStyle안해도됨
//알림창 클릭 액션에 의해 실행될 화면이 여러개 일때 실행하는 기능
builder.addAction(R.drawable.ic_noti, "setting", pendingIntent);
builder.addAction(R.drawable.ic_noti, "Impormation", pendingIntent);
//6-1 알림객체 Notification 만들기
//건축가(builder)에게 알림객체 생성을 요청해야함
//6-5 위에서 만든 빌더를 대입해준다
Notification notification = builder.build();
//6.
//알림 매니저에게 알림을 보이도록 요청
notificationManager.notify(100,notification); //notify(식별번호(누구의 알림인지),알림객체)
/////////////////////////////////////////////////////////////////////////////////
//7. 사운드,진동 작업!! - 채널별로 해줘야함
}
//4-1) 퍼미션 요청 결과를 받아오는 작업을 대행해주는 객체 생성 및 등록
ActivityResultLauncher<String> permissionResultLancher //퍼미션은 글자 덩어리라서 제네릭 String
= registerForActivityResult(new ActivityResultContracts.RequestPermission(), new ActivityResultCallback<Boolean>() {
@Override
public void onActivityResult(Boolean result) {
if(result) Toast.makeText(MainActivity.this, "알림 허용", Toast.LENGTH_SHORT).show();
else Toast.makeText(MainActivity.this, "알림을 보낼 수 없습니다", Toast.LENGTH_SHORT).show();
}
});
//Contracts = 계약
//registerForActivityResult(어떤 종류의 계약이니?,결과 실행);
}
💡 방송(Broadcast)이란?
Intent객체가 날아가는것이다
실제 전파를 쏜다는 것이 아니라 Intent 객체가 모든 앱에게 전달되는 모습
방송을 보낸다는 것은 Intent를 모든 앱에게 보내는 것임
인텐트 보내는 2가지 방법
1) 명시적 인텐트
- 특정한 한 놈(리시버)한테만 보내는 방송
- 본인 앱에서만 수신할 수 있다 (블루투스나 nfc 작업 할때나 쓴다)
2) 암시적 인텐트
- 특별한 식별(action)을 이용해 intent-filter로 내가 듣고싶은 식별자만 받음
- 디바이스에 설치된 모든 앱 안에 있는 리시버가 듣는 방송
- 안드로이드 운영체제에서 보내는 방송은 대부분 암시적 인텐트임
- 오레오 버전부터 암시적 인텐트방송은 System만 할수 있다
방송을 받으면 토스트 띄워보기
package com.bsj0420.ex59broadcastreceiver;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn).setOnClickListener(view -> clickBtn());
}
private void clickBtn() {
//브로드캐스트 보내기
//원래 브로드캐스트는 안드로이드라는 운영체제가 보내는 것이다
//이 예제에서는 실습 목적으로 보내보기
//방송이란?
//실제 전파를 쏜다는 것이 아니라 **Intent** 객체가 **모든 앱에게 **전달되는 모습
//방송을 보낸다는 것은 Intent를 모든 앱에게 보내는 것임
//인텐트 보내는 2가지 방법
//1. 명시적 인텐트 - 특정한 한 놈(리시버)한테만 보내는 방송
//1-1) 새로운 리시버 생성(BroadcastReceiver 상속받은 클래스를 만들어서 인텐트로 실행)
Intent intent = new Intent(this, MyReceiver.class);
sendBroadcast(intent);
}
}
package com.bsj0420.ex59broadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
//안드로이드의 4대 컴포넌트 클래스들은 반드시 매니패스트에 꼭 반드시 등록해야만한다
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) { //intent = 방송으로 온애
//방송을 수신했을 때 자동으로 실행하는 콜백메소드
Toast.makeText(context, "Receive", Toast.LENGTH_SHORT).show();
}
}
모두에게 방송 보내고 받는 애들이 선별적으로 받기
보내는 인텐트 식별자(action)을 붙여주고 매니패스트에서 받을 애만 필터에 액션 네임을 추가해준다
오레오 버전부터 암시적 인텐트방송은 System만 할수 있다
그럼에도 꼭 암시적 인텐트를 보내고싶다면 리시버를 매니패스트가 아닌
자바에서 동적으로 등록하면 테스트 가능
아래 작업은 테스트 목적으로 한 것
package com.bsj0420.ex59broadcastreceiver;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn).setOnClickListener(view -> clickBtn());
}
private void clickBtn() {
//2. 암시적 인텐트 - 특별한 식별(action)을 이용해
// 디바이스에 설치된 모든 앱 안에 있는 리시버가 듣는 방송
// 안드로이드 운영체제에서 보내는 방송은 대부분 암시적 인텐트임
// 오레오 버전부터 암시적 인텐트방송은 System만 할수 있다
//그럼에도 꼭 암시적 인텐트를 보내고싶다면 리시버를 매니패스트가 아닌
//자바에서 동적으로 등록하면 테스트 가능
Intent intent = new Intent();
intent.setAction("aaa"); //방송을 구별하는 식별자(action)
sendBroadcast(intent);
}
//멤버변수로 마이리시버 만든다
MyReceiver myReceiver;
//액티비티가 화면에 보여질때 자동으로 실행되는 콜백 메소드
@Override
protected void onResume() { //액티비티가 화면에 보여질때 얘가 불러짐
super.onResume();
//리시버를 등록
//동적 등록
myReceiver = new MyReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("aaa");
//리시버를 등록하면서 필터도 설정
registerReceiver(myReceiver,filter);
}
//액티비티가 화면에서 안보이기 시작할 때 자동으로 실행되는 콜백메소드
@Override
protected void onPause() {
super.onPause();
//화면이 꺼지면 리시버 등록 해제
unregisterReceiver(myReceiver);
}
}
리시버는 명시적 마이리시버와 같음
package com.bsj0420.ex60broadcastreceiverbooting;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
//4대 컴포넌트 반드시 매니패스트에 등록
public class MyBootingReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//안드로이드 13버전부터 액티비티가 없으면 Toast의 발동을 제한함
Log.i("Ex60", "BootingReceiver");
}
}
☝ 부팅완료 리시버 사용하기 위해
안드로이드의 N 버전(api 25) 부터 부팅완료를 들으려면
앱을 설치한 후 적어도 1회 사용자가 직접 런처 화면(앱 목록)에서
앱을 실행한 이력이 있는 앱만 부팅완료를 들을 수 있다
해커들을 막기위해(앱을 몰래 심는것을 방지하기 위해서)
부팅 완료되면 MainActivity 화면이 실행되도록 업그레이드 해보기
package com.bsj0420.ex60broadcastreceiverbooting;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationChannelCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
//4대 컴포넌트 반드시 매니패스트에 등록
public class MyBootingReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//안드로이드 13버전부터 액티비티가 없으면 Toast의 발동을 제한함
Log.i("Ex60", "BootingReceiver");
//안드로이드의 N 버전(api 25) 부터 부팅완료를 들으려면
//앱을 설치한 후 적어도 1회 사용자가 직접 런처 화면(앱 목록)에서
//앱을 실행한 이력이 있는 앱만 부팅완료를 들을 수 있다 - 해커들을 막기위해(앱을 몰래 심는것을 방지하기 위해서)
//부팅 완료되면 MainActivity 화면이 실행되도록
//단, 리시버가 여러개면 내가 받으려는것을 구분하기 위해 액션값을 받아 구분
String action = intent.getAction();
if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
//안드로이드 10버전(api29) 부터 리시버에서 직접 액티비티 실행 금지
// => 폰 켜자마자 얍 홍보한다고 띄우니까 사용자 열받음....앱 홍보 금지하기 위해
//대신 알림(Notification)을 통해 사용자에게 신호를 주고 액티비티 실행할지 여부 선택하도록 변경됨
if (Build.VERSION.SDK_INT >= 29) {
//1.알람매니저
NotificationManagerCompat manager = NotificationManagerCompat.from(context);
//3.채널 생성
NotificationChannelCompat channel
= new NotificationChannelCompat.Builder("ch01", NotificationManagerCompat.IMPORTANCE_HIGH).setName("Ex60").build();
//4.채널 등록
manager.createNotificationChannel(channel);
//2.알림객체를 만들어주는 빌더 필요
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "ch01");
//5.
builder.setSmallIcon(R.drawable.ic_stat_name);
builder.setContentText("부팅완료 됨");
builder.setContentTitle("부팅완료");
//6. 매니저에 notify
if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
return;
} //퍼미션 없음 멈춰라고 하는 거
manager.notify(100, builder.build()); //안드로이드 13버전부터 알림 동적퍼미션 생김 그래서 오류
// => 매니패스트에 퍼미션 등록
//7. 동적 퍼미션 + 팬딩인텐트 달기
} else {
//29버전 이하는 바로 액티비티로 변경 가능
Intent intentForActivity = new Intent(context, MainActivity.class);
//addFlags() : 백스택에 현재 아무것도 쌓여있지않아 필요한 설정
intentForActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intentForActivity);
}
}
}
}
☝ addFlags()
백스택에 현재 아무것도 쌓여있지않아 필요한 설정
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 부팅완료 듣는 퍼미션 2 : 앱 전체한테 등록 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!-- 알림을 보여주는 파미션 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Ex60BroadcastReceiverBooting"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 부팅완료 방송을 수신하는 리시버 등록
인텐트 필터를 쓰면 암시적으로 부르겠단 뜻이고 꼭 exported="true"
부팅완료 듣는 퍼미션 내 앱에도 등록**
-->
<receiver android:name=".MyBootingReceiver"
android:exported="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>