오늘은 mp3 재생 어플을 만들어보았다.
메신저 바인딩, AIDL 통신 기법, 잡스케줄러를 이용해 MP3를 재생하는 어플을 만들었다.
프로세스간 통신 연습을 위해 모듈을 2개 만들어 연동하는 방식이다.
재생 화면
mp3 파일이 재생되면서 아래의 진행도가 올라간다.
와이파이 연결시 알림
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.ch15_service" >
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<queries>
<package android:name="com.example.ch15_outer" />
</queries>
<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.AndroidLab" >
<service
android:name=".MyJobService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
<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>
</application>
</manifest>
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
var connectionMode = "none"
//Messenger
lateinit var messenger: Messenger
lateinit var replyMessenger: Messenger
var messengerJob: Job? = null;
//aidl
var aidlService: MyAIDLInterface? = null
var aidlJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
//messenger
onCreateMessengerService()
//aidl
onCreateAIDLService()
//jobscheduler
val permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) {
if (it.all { permission -> permission.value == true }) {
onCreateJobScheduler()
} else {
Toast.makeText(this, "permission denied...", Toast.LENGTH_SHORT).show()
}
}
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(
this,
"android.permission.POST_NOTIFICATIONS"
) == PackageManager.PERMISSION_GRANTED
) {
onCreateJobScheduler()
} else {
permissionLauncher.launch(
arrayOf(
"android.permission.POST_NOTIFICATIONS"
)
)
}
}else {
onCreateJobScheduler()
}
}
override fun onStop() {
super.onStop()
if(connectionMode === "messenger"){
onStopMessengerService()
}else if(connectionMode === "aidl"){
onStopAIDLService()
}
connectionMode="none"
changeViewEnable()
}
fun changeViewEnable() = when (connectionMode) {
"messenger" -> {
binding.messengerPlay.isEnabled = false
binding.aidlPlay.isEnabled = false
binding.messengerStop.isEnabled = true
binding.aidlStop.isEnabled = false
}
"aidl" -> {
binding.messengerPlay.isEnabled = false
binding.aidlPlay.isEnabled = false
binding.messengerStop.isEnabled = false
binding.aidlStop.isEnabled = true
}
else -> {
//초기상태 / stop 상태 / 두 play 버튼 활성상태
binding.messengerPlay.isEnabled = true
binding.aidlPlay.isEnabled = true
binding.messengerStop.isEnabled = false
binding.aidlStop.isEnabled = false
binding.messengerProgress.progress = 0
binding.aidlProgress.progress = 0
}
}
//messenger handler
inner class HandlerReplyMsg: Handler(Looper.getMainLooper()){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when(msg.what){
10 -> {
val bundle = msg.obj as Bundle
bundle.getInt("duration")?.let {
when {
it > 0 -> {
binding.messengerProgress.max = it
val backgroundScope = CoroutineScope(Dispatchers.Default + Job())
messengerJob = backgroundScope.launch {
while(binding.messengerProgress.progress < binding.messengerProgress.max){
delay(1000)
binding.messengerProgress.incrementProgressBy(1000)
}
}
changeViewEnable()
}
else -> {
connectionMode="none"
unbindService(messengerConnection)
changeViewEnable()
}
}
}
}
}
}
}
//messenger connection
val messengerConnection: ServiceConnection = object: ServiceConnection {
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
messenger = Messenger(p1)
val msg = Message()
msg.replyTo = replyMessenger
msg.what = 10
messenger.send(msg)
connectionMode = "messenger"
}
override fun onServiceDisconnected(p0: ComponentName?) {
}
}
private fun onCreateMessengerService() {
replyMessenger = Messenger(HandlerReplyMsg())
binding.messengerPlay.setOnClickListener {
val intent = Intent("ACTION_SERVICE_Messenger")
intent.setPackage("com.example.ch15_outer")
bindService(intent, messengerConnection, Context.BIND_AUTO_CREATE)
}
binding.messengerStop.setOnClickListener {
val msg= Message()
msg.what = 20
messenger.send(msg)
unbindService(messengerConnection)
messengerJob?.cancel()
connectionMode="none"
changeViewEnable()
}
}
private fun onStopMessengerService() {
val msg = Message()
msg.what = 20
messenger.send(msg)
unbindService(messengerConnection)
}
//aidl connection
val aidlConnection: ServiceConnection = object: ServiceConnection {
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
aidlService = MyAIDLInterface.Stub.asInterface(p1)
aidlService!!.start()
binding.aidlProgress.max = aidlService!!.maxDuration
val backgroundScope = CoroutineScope(Dispatchers.Default + Job())
aidlJob = backgroundScope.launch {
while(binding.aidlProgress.progress < binding.aidlProgress.max){
delay(1000)
binding.aidlProgress.incrementProgressBy(1000)
}
}
connectionMode="aidl"
changeViewEnable()
}
override fun onServiceDisconnected(p0: ComponentName?) {
aidlService = null
}
}
private fun onCreateAIDLService() {
binding.aidlPlay.setOnClickListener {
val intent = Intent("ACTION_SERVICE_AIDL")
intent.setPackage("com.example.ch15_outer")
bindService(intent, aidlConnection, Context.BIND_AUTO_CREATE)
}
binding.aidlStop.setOnClickListener {
Log.d("kkang","stop.....")
aidlService!!.stop()
unbindService(aidlConnection)
aidlJob?.cancel()
connectionMode="none"
changeViewEnable()
}
}
private fun onStopAIDLService() {
unbindService(aidlConnection)
}
//JobScheduler
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private fun onCreateJobScheduler(){
var jobScheduler: JobScheduler? = getSystemService(JOB_SCHEDULER_SERVICE) as JobScheduler
val builder = JobInfo.Builder(1, ComponentName(this, MyJobService::class.java))
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
val jobInfo = builder.build()
jobScheduler!!.schedule(jobInfo)
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class MyJobService : JobService() {
override fun onStartJob(p0: JobParameters?): Boolean {
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val channel = NotificationChannel("oneId", "oneName", NotificationManager.IMPORTANCE_DEFAULT)
channel.description="oneDesc"
manager?.createNotificationChannel(channel)
Notification.Builder(this, "oneId")
}else {
Notification.Builder(this)
}.run {
setSmallIcon(android.R.drawable.ic_notification_overlay)
setContentText("Content Message")
setContentTitle("JobScheduler Title")
setAutoCancel(true)
manager?.notify(1, build())
}
return false
}
override fun onStopJob(p0: JobParameters?): Boolean {
return true
}
}
interface MyAIDLInterface {
int getMaxDuration();
void start();
void stop();
}
class MyAIDLService : Service() {
lateinit var player: MediaPlayer
override fun onCreate() {
super.onCreate()
player = MediaPlayer()
}
override fun onDestroy() {
super.onDestroy()
player.release()
}
override fun onBind(intent: Intent): IBinder {
return object: MyAIDLInterface.Stub() {
override fun getMaxDuration(): Int {
return if(player.isPlaying)
player.duration
else 0
}
override fun start() {
if(!player.isPlaying){
player = MediaPlayer.create(this@MyAIDLService, R.raw.music)
try {
player.start()
}catch (e: Exception){
e.printStackTrace()
}
}
}
override fun stop() {
if(player.isPlaying)
player.stop()
}
}
}
}
class MyMessengerService : Service() {
lateinit var messenger: Messenger
lateinit var replyMessenger: Messenger
lateinit var player: MediaPlayer
override fun onCreate() {
super.onCreate()
player = MediaPlayer()
}
override fun onDestroy() {
super.onDestroy()
player.release()
}
inner class IncomingHandler(
context: Context,
private val applicationContext: Context = context.applicationContext
): Handler(Looper.getMainLooper()){
override fun handleMessage(msg: Message) {
when(msg.what){
10 -> {
replyMessenger = msg.replyTo
if(!player.isPlaying){
player = MediaPlayer.create(this@MyMessengerService, R.raw.music)
try{
val replyMsg = Message()
replyMsg.what = 10
val replyBundle = Bundle()
replyBundle.putInt("duration", player.duration)
replyMsg.obj = replyBundle
replyMessenger.send(replyMsg)
player.start()
}catch (e: Exception){
e.printStackTrace()
}
}
}
20 -> {
if(player.isPlaying)
player.stop()
}
else -> super.handleMessage(msg)
}
}
}
override fun onBind(intent: Intent): IBinder {
messenger = Messenger(IncomingHandler(this))
return messenger.binder
}
}