88일차(2)/Android App(54) : mp3 파일 재생 예제 / Notification(2)
- 새 서비스 생성
MusicService 생성
package com.example.step23mp3player;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class MusicService extends Service {
//서비스가 최초 활성화될 때 한번 호출되는 메소드
@Override
public void onCreate() {
super.onCreate();
}
//최초 활성화 혹은 이미 활성화된 이후 이 서비스를 활성화 하는 Intent가 도착하면 호출되는 메소드
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
switch (intent.getAction()){
case AppConstants.ACTION_PLAY:
Log.d("onStartCommand()", "play!");
break;
case AppConstants.ACTION_PAUSE:
Log.d("onStartCommand()", "pause!");
break;
case AppConstants.ACTION_STOP:
Log.d("onStartCommand()", "stop!");
break;
}
return START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
- 객체를 매번 새로 생성하지 않고, 이미 생성된 것을 싱글톤으로 사용한다.
- onStartCommand 만 다시 호출되는 것이다.
- 활성화시킬 Intent가 각각의 버튼에서 사용되도록 한다.
- 활성화시킬 수 있는 객체를 PendingIntent 에 담기
- Service는 이미 만들어져 있는 상태에서 저 메소드만 반복적으로 실행되도록 한다.
- Intent 안에 구별할 수 있는 정보를 담아놓고, 해당 Intent로 각각 다른 동작을 해볼 것이다.
- 알림을 띄우면서 Action 버튼을 만들 수 있는데,
각각의 Action 버튼은 고유한 PendingIntent 객체를 가지고 있게 할 수 있다.
PendingIntent 객체에 Intent 객체를 담으면서 Intent에 어떤 정보를 담아서 전달하면
그 정보를 서비스에서 읽어낼 수가 있다.
- 나중에 사용될 intent 객체를 가지고있는 객체가 PendingIntent 객체이다.
- 동일한 서비스를 활성화시킬 수 있는 intent인데 안에 있는 일부 정보만 다르게 담을 것이다.
구별해서 사용할 수 있도록!
- 상수를 저장할 클래스 생성
AppConstants
package com.example.step23mp3player;
public class AppConstants {
public static final String ACTION_PLAY="action_start";
public static final String ACTION_PAUSE="action_pause";
public static final String ACTION_STOP="action_stop";
}
MainActivity
package com.example.step23mp3player;
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.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity implements MediaPlayer.OnPreparedListener {
//채널의 아이디
public static final String CHANNEL_ID="my_channel";
//알림의 아이디
public static final int NOTI_ID=999;
MediaPlayer mp;
//재생 준비가 되었는지 여부
boolean isPrepared=false;
ImageButton playBtn;
ProgressBar progress;
TextView time;
SeekBar seek;
//UI를 주기적으로 업데이트하기 위한 Handler
Handler handler=new Handler(){
/*
이 Handler에 메세지를 한번만 보내면 아래의 handleMessage() 메소드가 1/10초마다 반복적으로 호출된다.
handleMessage() 메소드는 UI 스레드 상에서 실행되기 때문에 마음대로 UI를 업데이트할 수 있다.
*/
@Override
public void handleMessage(@NonNull Message msg) {
int currentTime=mp.getCurrentPosition();
//음악 재생이 시작된 이후에 주기적으로 계속 실행이 되어야 한다.
progress.setProgress(currentTime);
seek.setProgress(currentTime);
//현재 재생 시간을 TextView에 출력하기.
String info=String.format("%d min, %d sec",
TimeUnit.MILLISECONDS.toMinutes(currentTime),
TimeUnit.MILLISECONDS.toSeconds(currentTime)
-TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(currentTime)));
time.setText(info);
//알림띄우기
makeManualCancelNoti();
//자신의 객체에 빈 메세지를 보내서 handleMessage() 가 일정 시간 이후에 호출되도록 한다.
handler.sendEmptyMessageDelayed(0, 100); // 1/10초 이후에
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//TextView의 참조값 얻어와서 필드에 저장
time=findViewById(R.id.time);
// %d는 숫자, %s 는 문자.
String info=String.format("%d min, %d sec", 0, 0);
time.setText(info);
//ProgressBar의 참조값 얻어오기
progress=findViewById(R.id.progress);
seek=findViewById(R.id.seek);
//재생 버튼
playBtn=findViewById(R.id.playBtn);
//재생 버튼을 사용 불가 상태로 일단 설정
playBtn.setEnabled(false);
playBtn.setOnClickListener(v -> {
//만일 준비되지 않았으면
if(!isPrepared){
return; //메소드를 여기서 종료
}
//음악 재생
mp.start();
//알림 띄우기
makeManualCancelNoti();
//핸들러에 메세지 보내기
handler.sendEmptyMessageDelayed(0,100);
});
//일시중지 버튼
ImageButton pauseBtn=findViewById(R.id.pauseBtn);
pauseBtn.setOnClickListener(v -> {
mp.pause();
});
//알림 채널 만들기
createNotificationChannel();
}
@Override
protected void onStart() {
super.onStart();
//음악을 재생할 준비를 한다.
try {
mp=new MediaPlayer();
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
mp.setDataSource("http://192.168.0.34:9000/boot07/resources/upload/mp3piano.mp3");
mp.setOnPreparedListener(this);
//로딩하기
mp.prepareAsync();
}catch (Exception e){
Log.e("MainActivity", e.getMessage());
}
}
@Override
protected void onStop() {
super.onStop();
mp.stop();
mp.release();
}
//재생할 준비가 끝나면 호출되는 메소드
@Override
public void onPrepared(MediaPlayer mp) {
Toast.makeText(this, "로딩 완료!", Toast.LENGTH_SHORT).show();
isPrepared=true;
playBtn.setEnabled(true);
//전체 재생 시간을 ProgressBar의 최대값으로 설정한다.
progress.setMax(mp.getDuration());
seek.setMax(mp.getDuration());
Log.e("전체 시간", "duration:"+mp.getDuration());
//handler 객체에 빈 메세지를 보내서 handleMessage() 가 일정 시간 이후에 호출되도록 한다.
handler.sendEmptyMessageDelayed(0, 100); // 1/10초 이후에
}
//앱의 사용자가 알림을 직접 관리 할수 있도록 알림 체널을 만들어야한다.
public void createNotificationChannel(){
//알림 채널을 지원하는 기기인지 확인해서
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//알림 채널을 만들기
//셈플 데이터
String name="Music Player";
String text="Control";
//알림 채널 객체를 얻어내서
//알림을 1/10 초마다 새로 보낼 예정이기 때문에 진동은 울리지 않도록 IMPORTANT_LOW 로 설정한다.
NotificationChannel channel=
new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW);
//채널의 설명을 적고
channel.setDescription(text);
//알림 메니저 객체를 얻어내서
NotificationManager notiManager=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
//알림 채널을 만든다.
notiManager.createNotificationChannel(channel);
}
}
//수동으로 취소하는 알림을 띄우는 메소드
public void makeManualCancelNoti(){
if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
//권한이 필요한 목록을 배열에 담는다.
String[] permissions={android.Manifest.permission.POST_NOTIFICATIONS};
//배열을 전달해서 해당 권한을 부여하도록 요청한다.
ActivityCompat.requestPermissions(this,
permissions,
0); //요청의 아이디
return;
}
//현재 재생 시간을 문자열로 얻어낸다.
int currentTime=mp.getCurrentPosition();
String info=String.format("%d min, %d sec",
TimeUnit.MILLISECONDS.toMinutes(currentTime),
TimeUnit.MILLISECONDS.toSeconds(currentTime)
-TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(currentTime)));
//인텐트 전달자 객체
//PendingIntent pendingIntent = PendingIntent.getActivity(this, NOTI_ID, intent, PendingIntent.FLAG_MUTABLE);
Intent iPlay=new Intent(this, MusicService.class);
iPlay.setAction(AppConstants.ACTION_PLAY);
PendingIntent pIntentPlay=PendingIntent.getService(this, 1, iPlay, PendingIntent.FLAG_MUTABLE);
Intent iPause=new Intent(this, MusicService.class);
iPlay.setAction(AppConstants.ACTION_PAUSE);
PendingIntent pIntentPause=PendingIntent.getService(this, 1, iPlay, PendingIntent.FLAG_MUTABLE);
Intent iStop=new Intent(this, MusicService.class);
iPlay.setAction(AppConstants.ACTION_STOP);
PendingIntent pStop=PendingIntent.getService(this, 1, iPlay, PendingIntent.FLAG_MUTABLE);
//띄울 알림을 구성하기
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(android.R.drawable.star_on) //알림의 아이콘
.setContentTitle("쇼팽 녹턴") //알림의 제목
.setContentText(info)
.setPriority(NotificationCompat.PRIORITY_DEFAULT) //알림의 우선순위
.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_play,"Play",pIntentPlay))
.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_play,"Pause",pIntentPause))
.addAction(new NotificationCompat.Action(android.R.drawable.ic_media_play,"Stop",pStop))
.setProgress(mp.getDuration(), mp.getCurrentPosition(), false)
//.setContentIntent(pendingIntent) //인텐트 전달자 객체
.setAutoCancel(false); //자동 취소 되는 알림인지 여부
//알림 만들기
Notification noti=builder.build();
//알림 메니저를 이용해서 알림을 띄운다.
NotificationManagerCompat.from(this).notify(NOTI_ID , noti);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case 0:
//권한을 부여 했다면
if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
//자동 취소 알림 띄우기
makeManualCancelNoti();
}else{//권한을 부여 하지 않았다면
Toast.makeText(this, "알림을 띄울 권한이 필요합니다.",
Toast.LENGTH_SHORT).show();
}
break;
}
}
}
- 액티비티를 활성화시킬거면 getActivity(), 서비스를 활성화시키려면 getService() 를 사용하면 된다.
- 활성화시킬 Intent를 가지고있는 객체에 따라... getXX 메소드로 사용
- 이것을 여기 intent 인자 자리에 넣어서 서비스를 활성화시키기!
- 이런 액션을 가지고있는 intent를 담고 있는 pendingIntent를
notification의 addAction() 의 인자로 넣어주는 것이다.
- play라는 액션을 얻어내서 작업을 하겠다는 것!
- 각 Intent가 액션만 다르게 가지고 있는 상태이다. 같은 Service를 사용한다.
- 액션명은 임의로 정한 것! 상수화 해놓았다.
- 만들어놓은 저 Intent를 PendingIntent에 담아서 서비스를 활성화시키기
- MusicService 안에서 같은 메소드를 활용하지만 들어오는 intent 값이 다르다.
→ 들어오는 intent 값으로 분기할 수 있다!
- setAction() 했던 것은 getAction() 메소드로 읽어오면 된다.
- Switch문으로 분기해서, 각각의 case가 로그로 찍히도록 해본다.
- 알림의 버튼을 클릭했을 때 위와 같이 로그가 찍힌다.
'국비교육(22-23)' 카테고리의 다른 글
90일차(1)/Android App(56) : mp3 파일 재생 예제 / 재생목록 출력(ListView) (0) | 2023.02.16 |
---|---|
89일차(1)/Android App(55) : mp3 파일 재생 예제 / Notification(3) (0) | 2023.02.16 |
88일차(1)/Android App(53) : mp3 파일 재생 예제 / Notification(1) (0) | 2023.02.14 |
86일차(2)/Android App(52) : mp3 파일 재생 예제 / ProgressBar 사용 (0) | 2023.02.11 |
86일차(1)/Android App(51) : mp3 파일 재생 예제 / MediaPlayer(서버) (0) | 2023.02.10 |