Wednesday, June 1

Android Development : MusicMachine

Currently building the App 'MusicMachine'.
From course 'Threads & Services' by Ben Deitch.

Completed 2 chapters out of 4.

1 screen/page, with a download button. When we click the download button, songs start to download one by one.
And a play/pause button.

Chap-1 Threads:
(Thread, Message Queue, Handler, Looper, Message)

Each Android app has its own main thread. And if we do too much work on the main thread, then our UI can become unresponsive.
We can fix this by putting the burden of our download onto a different thread than the main thread.
In Stormy, we solved this by using a method from the OkHttp library that handled all of this for us.
But this time we're going to create and use our own threads. Let's start by adding another lane to our highway by creating a new thread object.
This way we can keep our download from blocking the main thread. And more importantly, we can keep our users happy.
If you're ever curious about what's going on inside your device, the Android device monitor is a great place to start looking.

If our playlist changes for any reason, all of a sudden we'd be downloading the wrong songs.
What we need is a queue for the songs we want to download. Once one song finishes, the next song in the queue would start downloading.
We can implement this by adding something called a message queue to our thread.
And in order to use a message queue, we'll also need to be aware of handlers, loopers, and messages.

In Android each handler is associated with only one thread. And that handler is responsible for sending and processing messages and runnables for the thread.

A message is just a way for us to send some arbitrary data to our handler. It's then up to the handler how it will handle the message.

The message queue contains all the runnables and messages that our thread still needs to handle.

Looper.prepare(); - This creates a looper for our thread and also creates the message queue.
By default, a handler is associated with the looper for the current thread.
Looper.loop(); - To start looping over the message queue.

Message.obtain(); - We could create a new message object, but since messages are used all over the operating system, Android keeps a pool of message objects available for us to use. This way, Android doesn't need to keep creating and deleting a bunch of message objects. Once a message has been handled, it just goes back to the pool to be re-used.
message.obj = song; - The obj property of a message lets us add any type of object we want to our message. Since we want to tell our handler about a song to download, we'll attach the song name to our message.
thread.mHandler.sendMessage(message); - To send our message to our handler, so it can be added to the message queue.

The last thing we need to do, is tell our handler what it should do, when it gets a message. We can do this by overriding our handler's, handleMessage() method.

Looper allows tasks to be executed sequentially on a single thread. 
It retrieves Messages from the Message Queue in a sequential order and dispatches them to the Handler.
Handler defines those tasks that we need to be executed.
The Handler is responsible for processing the messages on the consumer thread.

Chap-2 Services:

A service is an application component, that can perform long-running operations in the background, and does not provide a user interface.
In android, there are two ways we can use a service:
1. We can start a service.
2. We can bind to a service.
Started Service
Bound Service

Started Service:
Override the onStartCommand() method.

Starting a service is almost exactly the same as staring an activity. The only difference is that we use startService() instead of startActivity().
Let's start by creating an intent for our service.
return Service.START_REDELIVER_INTENT;

We need to declare the service in the Android Manifest.

By default, everything in an app runs on the main thread, including our service.
If we want our service to do work on a separate thread, we need to create that separate thread ourselves.

Override the onCreate() method.

When you kill an app's process, everything in that app is killed, including all the activities and services, but depending on what we return from onStartCommand(), the services might come right back.

The return value from onStartCommand() represents what we want Android to do, if our service's process is killed, after we've returned we've returned from onStartCommand() but before  being stopped.

We can stop a service by calling stopSelf() from the service itself or from the outside we can call stopService() and pass in an intent, just like when we started our service.

The purpose of DownloadService is to download one song. We call DownloadService for each song in the playlist. So when that one song is finished downloading, it would be a good time to stop the service.

Let's attach the startId to the message by using one of the argument properties.

It's important to make sure that we that we stop our services. If we don't, they can hog system resources almost forever.

There are three constants that we can return from onStartCommand(). Those constants are:
START_STICY : Restart the service with a null intent.
START_NOT_STICKY : Not restart the service.
START_REDELIVER_INTENT : Restart the service and redeliver all unfinished intents.

IntentService
IntentService is a simple type of service that can be used to handle asynchronous work off the main thread by way of Intent requests. Each intent is added to the IntentService’s queue and handled sequentially.
Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.
All requests are handled on a single worker thread -- they may take as long as necessary, but only one request will be processed at a time.

To use it, extend IntentService and implement onHandleIntent(Intent).
By default, an IntentService will use START_STICKY, but we need it to use START_REDELIVER_INTENT.

http://www.coderzheaven.com/2013/07/12/main-differences-service-intent-service/
https://www.quora.com/What-is-the-difference-between-services-and-intent-services-in-android

Chap -3 Bound Services:

If we want to communicate with our Service after starting it, we'll need to bind to it.
(A bound service can be thought of as the server and the activity binding to that service can be thought of as the client.)
A good example would be a service used for music playback.
Created the service, by creating a new class PlayerService.
Declared our new Service in AndroidManifest.
The goal is to be able to play and pause a single song using a bound service.
Added a song in the resource directory res/raw.
In PlayerService, override the onCreate method. Initialized the MediaPlayer in it.
Override the onDestroy method. Added a call to release MediaPlayer in it.
Override the onBind and onUnbind methods.
Added methods for play and pause.
Added a play/pause button in activity_main.xml. Created the button and added onClickListener in MainActivity.java.
We need a way to call the music playback methods in our service, when we tap buttons in our activity.
So haw can we pass our service to our activity?
We do the binding in onStart() method of our activity and unbinding in onStop() method. 
In MainActivity, override the onStart() method. Created an Intent for PlayerService class. Called bindService() method.
When we call bindService(), Android starts building a connection between our activity and our service and when that connection is finally established, Android will call the onServiceConnected() method of our ServiceConnection that we provide as a parameter.
ServiceConnection
An anonymous class is just a way to extent a class without having to create a whole new class.
Override the onStop method. Called unbindService() method.
unbindService() method can throw an error if the service isn't bound.
onServiceDisconnected() is only called when something unexpected happens.

How do we get an instance of our service into our activity?
The onServiceConnected method gives us an IBinder object.
The return value from onBind() method in PlayerService is an IBinder object.
When we return from onBind(), the returned IBinder object is what will be used in the call to onServiceConnected.
Binder: is a communication channel. Enables Activity (client) to communicate with the Service (server).
The only thing we are sending from our service to our activity is an IBinder.
The whole point of our Binder is to give access to our service.


We decide which button to show (play or pause) based on if the music is playing or not.
In PlayerService, added an isPlaying() method, which returns a boolean.
Also, when we launch our activity, we should check to see if music is already playing, and update the button accordingly. 

june 5:
Bound and Started (sometimes we need to both bind to and start a Service!)
We'd like the music to keep playing even when our activity is long gone.
Right now our service is basically created and destroyed right along with our activity.
We can keep our service from being destroyed by starting our service and not stopping it until it's done playing.
In the onClickListener of our play button, created an Intent for PlayerService. Called startService method.
In PlayerService, override the onStartCommand method.
Added an onCompletionListener to our MediaPlayer. Implemented the onCompletion method. Called the stopSelf method in it. Calling stopSelf with no parameters stops the service immediately, regardless of whether it's still working or not.

Chap -4 Processes:

We can think of a process as a group of threads.
We know, that by default everything in an app runs on one thread, the main thread.
By default, each app also runs in its own process.
So when we launch an app, all of the code execution will happen on the main thread and this thread will be a part of our apps process. When we create new threads, they're also a part of our apps process.
However, we are not limited to one process.
There are a few occasions where it might make sense for an app to run in more than one process.
Using one process for our activity, and a separate process for our service, gives them both access to more memory.
Having 2 separate processes, lets android reclaim the activity's process when it's no longer needed.

Android groups processes into 5 groups of varying importance:
1. Foreground processes
2. Visible processes
3. Service processes
4. Background process
5. Empty process (for caching)

june 15:
"Using a separate process":
(How to use two separate processes for our app 'MusicMachine'?)

If you're going to have a multi-process app, just make sure that you really need that extra process.

In AndroidManifest, added a new attribute android:process in the player service tag.

There is more than one way we can bind to our service. We can extend the binder class or we can use a messenger object. We could also use the Android interface definition language, or AIDL.

If our service runs in the same process as our client, then we can extend the binder class.
But if our service runs in a separate process, then we'll need to use a messenger.

So now instead of returning a local binder in onBind method, we'll be returning a messenger.

A messenger is just a reference to a handler. In fact, when we create a messenger, we do it by passing in a handler. But a messenger can also be transformed into a binder, and created from a binder. This way, we can pass our messenger between our service and our activity, as a binder.

There are only certain things we can send across processes and our service just isn't one of them. We'll need to replace every instance where our activity calls a service method directly, by sending messages instead.
So instead of calling mPlayerService.play, we'll be sending a message.

Messenger: is a reference to a Handler. The implementation underneath is a simple wrapper around a Binder.

Handler: is responsible for sending and processing messages.

Message: a way for us to send some arbitrary data to a Handler. Its upto the Handler how it will handle the message. This object contains two extra int fields named arg1 and arg2 and an extra Object field named obj.

june 16:
Created a new class PlayerHandler. Implemented the constructor. Override the handleMessage method.
Back in PlayerService, created a new field for Messenger. It takes in a Handler, which takes in a PlayerService.

june 18:
Created a new class ActivityHandler. Override the handleMessage method. Implemented constructor.
In MainActivity, created a new field for the Messenger we receive from our Service. Created a new field for our Activity's own Messenger.

june 21:
Foreground Services:
If you want your service to run in the foreground, then you need to have a notification.
A Foreground Service is a service that the user is actively aware of.
Thus a foreground service is given special priority when Android encounters low memory situations.

In PlayerService, in onStartCommand(),
Notification.Builder notificationBuilder = new Notification.Builder(this);

Completed building the app 'MusicMachine'. Completed the course 'Threads & Services'.
Thank u Ben Deitch. Thank u God. I feel grateful, blessed. :)

No comments:

Post a Comment