I am new to android and I am now doing an exercise which the application has already stored a audio file(mp4) in /res/raw/ folder and this file can be referenced in android Service class as R.raw.audiofile. In the Service class I have created three methods
onCreate();
onStartCommand();
onDestroy();
in onCreate() I have created a MediaPlayer and in onStartCommand() I have started the MediaPlayer
mp.start() and
returned STRT_STICKY
to play it and in onDestroy() I have done this:
mp.stop();
in the xml layout I have created a Button with this attribute:
android:onClick="onClickStart"
which calls a method in the MainActivity and this action now shoud playback the audio. however, I am now stuck here on how to link this to the music file so that this button should start the audio. can anyone please give me some idea?
so I have one Main activity class and one (My)Service class (extends to Servcie) and one xml file for layout to perform this action.
You can communicate between the Activity and Service using an Intent or by binding to the Service and sending a Message. In this case, binding to the Service and sending a Message in the Button's click listener is probably the cleanest approach. That also gives you the opportunity to appropriately update UI by passing a Messenger/Handler in the replyTo field of the Message. I'll edit with code snippets when I get to a PC.
Edit:
Most of the code you need is in the Android docs here.
The basic idea is that you need to create a Handler class inside your Service. The proper way to do this (to avoid leaking the Handler and the accompanying Lint warning) is as follows:
private static class MyHandler extends Handler {
private WeakReference<MyService> mService;
public MyHandler(MyService service) {
mService = service;
}
#Override
public void handleMessage(Message msg) {
// Your message handling here...
// You can use the members/methods of the Service with
// mService.get().____
}
}
Then you would create a Messenger member and override the onBind method in the Service, like so:
private Messenger mMessenger = new Messenger(new MyHandler(this));
#Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
Then you need to call bindService with an appropriate Intent and ServiceConnection from your Activity. See the link for a code example. In the onServiceConnected method of the ServiceConnection you can stash a Messenger for the Service with:
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
mMessenger = new Messenger(service);
}
You can likewise create a Handler and Messenger in the Activity and supply it as the replyTo field of any Message objects sent to the Service. In that way, you can tell the Activity whether the MediaPlayer started successfully or not and update UI as appropriate.
You need a OnclickListener for your Button like
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
}
Look here for the MediaPlayer
Related
In my activity, there's a variable (objectList) which I would like to access from a Service (TestService):
public class MainActivity extends AppCompatActivity
{
List<MyObject> objectList;
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService( new Intent( getBaseContext(), TestService.class )
);
}
And I have a skeleton for the Service:
public class TestService extends Service
{
#Override
public IBinder onBind(Intent intent)
{
return null;
}
#Override
public void onCreate()
{
super.onCreate();
}
#Override
public int onStartCommand( Intent intent, int flags, int startId )
{
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onDestroy()
{
super.onDestroy();
}
}
My goal is to loop through every item in the objectList from the TestService every x seconds, process some data, and then update this particular item with new data.
The MyObject class has a lot of properties which I need to update. What is the proper way to pass the objectList from mainActivity to the TestService so I can work with the object list directly? Thanks!
By maintaining a reference to an Activity in a Service, you introduce a memory leak since you prevent the system from garbage collecting the Activity when the view is destroyed as a result of the Activity progressing through its lifecycle.
Instead, you should communicate the changes made by the Service to the Activity using a BroadcastReceiver as explained by #NongthonbamTonthoi in the comment. Basically the Activity should instantiate a BroadcastReceiver that listens for a specific type of broadcasts (identified by a unique key defined by you) which are sent by the Service whenever it performs an update.
Furthermore, I suggest that you move the list so that it is stored in the Service and then make the Activity retrieve the list from the Service by binding to the Service and then invoking a method defined in your IBinder implementation (an instance of which should be returned from onBind(Intent)). This way you can confine all code that makes changes to your model to the Service and keep the Activity as a (dumb) view that simply renders the model. Morover, with this design, you can make your list outlast the Activity by also starting the Service (note: in addition to binding to it) so that you can retain the state of your list even if your Activity is destroyed (e.g., as a result of your application being put to the background). If you choose this design, the broadcast sent by the Service can simply be a notification that the list has changed, and the Activity can then retrieve the updated list by invoking the getList method specified in your IBinder implementation.
If I do startActivity() on an Activity in another process; is there a good way to receive a callback at the starting point (starting the new Activity form a background service) that the new Activity really has started? I mean I can make a broadcast but that seems lame. Is there a better way for this?
Basically what you are asking about is ipc. Google it for more info.
To achieve this you need to create a sevice class and bind the two activities to it.
An example Service class would look like this.
public class MyService extends Service{
//create a handler that will be used to handle messages
//this is just an example. Use static handlers
final Handler handler=new Handler(){
public void handleMessage(Message msg){
//I've just created a static feild.Check below.
MyFirstActivity.activity.seconActivityStarted();
}
}
//create a Messenger object
Messenger messenger=new Messenger(handler);
#Override
public IBinder onBind(Intent intent){
return messenger.getBinder()
}
}
Now things are simple.
Now you have to bind the first activity with the servie.
public class MyFirstActivity extends AppCompatActivity{
//for the time being I'll just create a static field that will be used.
//you can use an interface
static MyFirstActivity activity;
//create a ServiceConnection
ServiceConnection connection=new ServiceConnection(/*contents of the service connection */);
public void onStart(){
super.onStart();
activity=this;
bindService(new Intent(this,MyService.class),connection,Context.BIND_AUTO_CREATE));
}
//following method will be called by the handler in the service
public void secondActivityStarted(){
//some code
}
//you have to handle the activity lifecycle with the bound service.
//right now its described here
}
Now the second activity
public class SecondActivity extends AppCompatActivity{
//create a ServiceConnection
ServiceCOnnection serviceConnection=new ServiceConnection(){
//call the handler at onServiceConnected
public void onServiceCOnnected(ComponentName name, IBinder service){
Messenger messenger=new Messenger(service);
//compose a Message object as you like
messenger.send(new Message());
}
};
//bind this activity to the same service
public void onStart(){
super.onStart();
bindService(new Intent(this,com.package.name.MySerice.class),serviceConnection,Context.BIND_AUTO_CREATE);
}
}
That's it. Modify this according to the requirements.
P.S.Above mentioned code is just working structure.
If you do startActivity() on an Activity in same or another process (and assuming the second activity starts), the calling Activity will go in to first PAUSED, and then STOPPED state. Which means no callback would be processed. However, you can call startActivityForResult() instead of startActivity() and receive onActivityResult() callback.
Add a class MyApplication that extends Application and mention in your Manifest too and than put two boolean variables in it.
private static boolean activityVisible;
public static void activityResumed() {
activityVisible = true;
}
public static void activityPaused() {
activityVisible = false;
}
public static boolean isActivityVisible() {
return activityVisible;
}
and so now you can use call activityPaused() when you do startActivity() on onPause() and onStop() of current activity, and when you return back to same activity do call activityResumed() in overriden onResume() method.
Now, by using MyApplication.isActivityVisible() you can get to know whether your Activity is running or paused.
I have three classes. "actone", "acttwo" and "actthree". I have a button in "actone". When I click that button, I want to be able to run "acttwo" on a different thread in the background, while my UI takes me to "actthree" and I can do whatever I want there while the code in "acttwo" keeps executing(I'll be doing uploading to a server in "acttwo" that is why I want it to keep running in the background).
if(v.getId() == R.id.button1){
//Start "acttwo" in background on another thread.
Intent i= new Intent(actone.this, actthree.class);
startActivity(i);
}
How do I do that? Do I use a service? If yes, then what's the procedure? How to do that? I'm a newbie at Android. Please help. Thanks!
There are two ways to do this, use a Singleton or use a Service (as you mentioned). Personally I don't like the singleton patterns very much and a service follows the Android patter much better. You will want to use a bound Service which is bound to your Applications context (actone.getActivityContext()). I have written a similar answer to this question however you will want to do something like:
public class BoundService extends Service {
private final BackgroundBinder _binder = new BackgroundBinder();
//Binding to the Application context means that it will be destroyed (unbound) with the app
public IBinder onBind(Intent intent) {
return _binder;
}
//TODO: create your methods that you need here (or link actTwo)
// Making sure to call it on a separate thread with AsyncTask or Thread
public class BackgroundBinder extends Binder {
public BoundService getService() {
return BoundService.this;
}
}
}
Then from your actone (I'm assuming Activity)
public class actone extends Activity {
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
Intent intent = new Intent(this, BoundService.class);
bindService(intent, _serviceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection _serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
BoundService.BackgroundBinder binder = (BoundService.BackgroundBinder)service;
_boundService = binder.getService();
_isBound = true;
//Any other setup you want to call. ex.
//_boundService.methodName();
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
_isBound = false;
}
};
}
Then from ActOne and ActThree (Activities?) you can get the bound service and call methods from actTwo.
You can use a AsyncTask for that. Services are not really useful (much more to code).
I have an audio file playing in a foreground Service using MediaPlayer. When a user taps the notification associated with the foreground Service, I launch an Activity using the Intent like so:
Intent audioPlayIntent = new Intent(context, AudioPlayActivity.class);
audioPlayIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
audioPlayIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, audioPlayIntent, 0);
This Activity then binds to the service to show a MediaController to the user.
Here is the binding code in the Service:
public class AudioPlayerServiceBinder extends Binder{
public AudioPlayerService getAudioService(){
return AudioPlayerService.this; //this class is declared in AudioPlayerService.java, so it has access to the Service instance.
}
}
..and in the Activity's onStart I have a call to this method:
private void bindAudioService()
{
Intent i = new Intent(this, AudioPlayerService.class);
serviceConnection = new AudioServiceConnection();
bindService(i, serviceConnection, 0);
}
I'm getting an exception on the mediaController.show(5000) line below:
private class AudioServiceConnection implements ServiceConnection{
AudioPlayerServiceBinder audioServiceBinder;
#Override
public void onServiceConnected(ComponentName name, IBinder serviceBinder)
{
serviceConnected = true;
Log.i(TAG, "Connected to audio player service.");
audioServiceBinder = ((AudioPlayerServiceBinder) serviceBinder);
AudioPlayActivity.this.audioService = audioServiceBinder.getAudioService();
mediaController.show(5000);
}
The exception being:
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRoot.setView(ViewRoot.java:527)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:177)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
at android.view.Window$LocalWindowManager.addView(Window.java:424)
at android.widget.MediaController.show(MediaController.java:304)
at android.widget.MediaController.show(MediaController.java:249)
at com.myapp.AudioPlayActivity$AudioServiceConnection.onServiceConnected(AudioPlayActivity.java:295)
I can recreate the same exception by:
Clicking the notification to open the Activity
Pressing back to close the activity.
Clicking the notification to open a new version of the activity.
This led me to believe that the mediaController is somehow leaking and trying to show itself in the original Activity instance. I couldn't find any reason for that to happen though, as the mediaController is instantiated within the Activity's onCreate() and only tied to the activity itself. (The activity then handles passing commands through to the service).
I think you are calling show() too early, before the previous activity completes the lifecycle. BadTokenException can be avoided by delaying call to show() until all the lifecycle methods are called. You may post a delayed runnable for this. Or you can try following,
if (!((Activity)your_context).isFinishing()) {
mediaController.show(5000);
}
Fixed the issue
I too was having the same issue and fixed it by doing the following,
#Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
try{
mediaController.show(0);
}catch(Exception e){
e.printStackTrace();
}
}
Now it works like a charm.
I believe the Problem is in this line.
AudioPlayActivity.this.audioService = audioServiceBinder.getAudioService();
You can look here for the details.Read all the comments in it, not just the answer.
Inside AudioPlayActivity's onCreate(Bundle):
Instead of using setContentView(int), inflate the layout (if you are already doing this, skip ahead):
Declare a global View variable:
View mContentView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContentView = getLayoutInflater().inflate(R.layout.your_activitys_layout, null);
// initialize widgets
Button b = (Button) mContentView.findViewById(...);
....
....
// Finally
setContentView(mContentView);
}
Change AudioServiceConnection to the following:
private class AudioServiceConnection implements ServiceConnection{
AudioPlayerServiceBinder audioServiceBinder;
#Override
public void onServiceConnected(ComponentName name, IBinder serviceBinder)
{
serviceConnected = true;
Log.i(TAG, "Connected to audio player service.");
audioServiceBinder = ((AudioPlayerServiceBinder) serviceBinder);
AudioPlayActivity.this.audioService = audioServiceBinder.getAudioService();
mContentView.post(new Runnable() {
#Override
public void run() {
mediaController.show(5000);
}
});
}
This should get rid of the WindowManager$BadTokenException.
Apologies if I totally misunderstood the question.
From the steps you have mentioned, It seems onConnected() is being invoked on a leaked instance of previous activity created in step 1. If the service is on demand (bound service) then you should bind/unbind in onResume()/onPause() respectively.
To confirm instance leaks, place :
log.i("LEAKTEST", "Connected to instance " + this.toString());
inside onConnected().
Now, re-create the scenario, and note the object id's in logcat, it'd be like "#1246464". Check that its called only once , on a new object id, every time the activity is started.
I have several activities which use several audio features. For that, I have a MediaPlayer in a singleton java class, so the activities interact with that class and just exist on the media player.
One of the features is to stop automatically the media player after X minutes. So I created a timer in the singleton class and stops perfectly the radio streaming. the problem is that there is no feedback or callback to the running activity. There is a play/stop button wich has to change the image and I do not know how can I capture that onStop event or whatever....or can be called from a single java class the current activity class running, so I could call a function of the activity in order to change the image?
You probably want to use a broadcast receiver for this.
From your singlton class which does the stopping, when your timer stops the music, call this method:
public void broadcastMusicPaused(View v){
Intent broadcast = new Intent();
broadcast.setAction("MUSIC_STOPPED");
sendBroadcast(broadcast);
}
Then, from your controlling activity, set up your receiver like this:
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Toast.makeText(getApplicationContext(), "Music Paused", Toast.LENGTH_SHORT).show();
displayMusicStopped(); //switches images
}
};
#Override
protected void onResume() {
IntentFilter filter = new IntentFilter();
filter.addAction("MUSIC_STOPPED");
registerReceiver(receiver, filter);
super.onResume();
}
#Override
protected void onPause() {
unregisterReceiver(receiver);
super.onPause();
}
First of all, thanks jameo for his answer, sounds pretty good, but i do not know if i will have time to try, i promise i will if i can this week or next time i have a similar issue.
Finally i did the trick this way:
1 - Create a Interface with Method onStopMediaPlayer(); //For example call MediaPlayerStopInterface
public interface MediaPlayerStopInterface {
/**
* Called when the player timer ends
*/
public void onStopMediaPlayer();
}
2 - My activities classes implements the interface switching images.
public class PortadaActivity extends Activity implements MediaPlayerStopInterface{
public void onStopMediaPlayer(){
//Switch images or whatever
}
}
3 - My singletton class has an object of the type of the interface MediaPlayerStopInterface
public class AudioControllerClass { //The Singletton Java Class
private MediaPlayerStopInterface currentActivity;
public void setCurrentActivity(MediaPlayerStopInterface mpsi){
currentActivity=mpsi;
}
}
4 - My activities classes in onResume() do a Singlettonclass.setStoppedPlayerInterface(this), so i always have a reference of the running activitie.
public class PortadaActivity extends Activity implements MediaPlayerStopInterface{
public void onResume() {
AudioControllerClass.getInstance(getApplicationContext()).setCurrentActivity(this); //In every resume the singletton class knows who was the last one in being active
}
}
5 - when timer execute, as i have the activitie class reference, i just call object_StoppedPlayerInterface.stoppedPlayer();
public class AudioControllerClass { //The Singletton Java Class
class TimerRadio extends TimerTask {
public void run() {
if(whatever==true){
currentActivity.onStopMediaPlayer();
}
}
}
}
Finally, i didn't code it, but the callback to onStopMediaplayer in activities must be done with a Handler, if you do not want a "Only UI thread can touch his views" exception :P
It works perfectly :). But i don't know if it is a really bad practice or is not so horrible xD
Anyway thanks Jameo. Yours sound much more elegant :P