Somehow I had a task to transfer data from a service to an activity. The search began for a solution in the standard SDK, but since there was no time, I created a bad solution in the form of using a database. But the question was open and after some time I figured out a more correct method, which is in the SDK - using the Message, Handler, Messenger classes.

Idea

We need to transfer data from the activity to the service and back. How do we do this? We already have everything we need to solve our problem. All you need is to bind the service to the activity using bindService, pass the necessary parameters and a little magic in the form of using the Message classes. And the magic is to use the Message instance variables and, in particular, replyTo. We need this variable so that we can access the Messanger service instance from the activity and in the service the Messanger instance of the activity. In fact, it's not that simple. At least to my less than gifted mind. In part, I'm just improving the documentation that already exists - Services Also, there is a good example on StackOverflow. In any case, I hope the article will be useful to at least someone and my work was not in vain.

Example

As an example, we will implement a service that will increase and decrease the counter value and return the result in the activity, in the TextView. I’ll omit the layout code, because there are two buttons and a text field - everything is simple.

Implementation

Here's the complete activation code:

Public class MainActivity extends Activity ( public static final String TAG = "TestService"; TestServiceConnection testServConn; TextView testTxt; final Messenger messenger= new Messenger(new IncomingHandler()); Messenger toServiceMessenger; @Override public void onCreate(Bundle savedInstanceState) ( super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); testTxt = (TextView)findViewById(R.id.test_txt); bindService(new Intent(this, TestService.class ), (testServConn = new TestServiceConnection()), Context.BIND_AUTO_CREATE); ) @Override public void onDestroy() ( super.onDestroy(); unbindService(testServConn); ) public void countIncrClick(View button)( Message msg = Message. obtain(null, TestService.COUNT_PLUS); msg.replyTo = messenger; try ( toServiceMessenger.send(msg); ) catch (RemoteException e) ( e.printStackTrace(); ) ) public void countDecrClick(View button)( Message msg = Message.obtain(null, TestService.COUNT_MINUS); msg.replyTo = messenger; try ( toServiceMessenger.send(msg); ) catch (RemoteException e) ( e.printStackTrace(); ) ) private class IncomingHandler extends Handler ( @Override public void handleMessage(Message msg)( switch (msg.what) ( case TestService.GET_COUNT: Log.d(TAG, "(activity)...get count"); testTxt.setText(""+msg.arg1); break; ) ) ) private class TestServiceConnection implements ServiceConnection ( @Override public void onServiceConnected(ComponentName name, IBinder service) ( toServiceMessenger = new Messenger(service); //send the initial value of the counter Message msg = Message.obtain(null, TestService.SET_COUNT); msg.replyTo = messenger; msg.arg1 = 0; //our counter try ( toServiceMessenger.send(msg); ) catch (RemoteException e) ( e.printStackTrace(); ) ) @Override public void onServiceDisconnected(ComponentName name) ( ) ) )

Let me explain. When creating an activity, we immediately bind to the service, implementing the ServiceConnection interface and in it we send a message to the service “set the counter value”, passing zero and creating a toServiceMessanger, passing the IBinder interface to the constructor. By the way, this copy must be returned to the service, otherwise there will be an NPE. Using this class we send messages to the service. And here is the magic - in the replyTo variable we save our other Messenger instance - the one that receives a response from the server and it is through it that communication with the activity will be carried out.

To receive a message from the service, we use our Handler and simply look for the variables we need and take actions on them. By clicking on the buttons (methods countIncrClick, countDecrClick) we send requests to the service, indicating required action in the msg.what variable.

Package com.example.servicetest; import android.app.Service; import android.content.*; import android.os.*; import android.os.Process; import android.util.Log; public class TestService extends Service ( public static final int COUNT_PLUS = 1; public static final int COUNT_MINUS = 2; public static final int SET_COUNT = 0; public static final int GET_COUNT = 3; int count = 0; IncomingHandler inHandler; Messenger messanger; Messenger toActivityMessenger; @Override public void onCreate())( super.onCreate(); HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); inHandler = new IncomingHandler(thread.getLooper()); messanger = new Messenger(inHandler); ) @Override public IBinder onBind(Intent arg0) ( return messanger.getBinder(); ) @Override public int onStartCommand(Intent intent, int flags, int startId) ( return START_STICKY; ) //message handler activity private class IncomingHandler extends Handler ( public IncomingHandler(Looper looper)( super(looper); ) @Override public void handleMessage(Message msg)( //super.handleMessage(msg); toActivityMessenger = msg.replyTo; switch (msg.what) ( case SET_COUNT: count = msg.arg1; Log.d(MainActivity.TAG, "(service)...set count"); break; case COUNT_PLUS: count++; Log.d(MainActivity.TAG , "(service)...count plus"); break; case COUNT_MINUS: Log.d(MainActivity.TAG, "(service)...count minus"); count--; break; ) //send the counter value in activity Message outMsg = Message.obtain(inHandler, GET_COUNT); outMsg.arg1 = count; outMsg.replyTo = messanger; try ( if(toActivityMessenger != null) toActivityMessenger.send(outMsg); ) catch (RemoteException e) ( e.printStackTrace(); ) ) ) )

Everything is similar to the logic in Activiti. I don’t even know if I need to explain anything. The only point is that I immediately send the request back to the activity in handleMessage, using the magic replyTo variable and pulling out the desired Messenger above. And the second point I already talked about is:

@Override public IBinder onBind(Intent arg0) ( return messanger.getBinder(); )

without which everything will fall. It is this interface instance that will be passed to the ServiceConnection

Conclusion

All in all. This is a far-fetched example of interaction between an activity and a service. It seems to me that this is a rather non-trivial interaction, although others may think otherwise.

Questions, clarifications, etc. in PM. There may be inaccuracies regarding some aspects, so feel free to write and correct.
I hope the post was useful to readers.

An application does not always consist of one screen. For example, we have created very useful program and the user wants to know who its author is. He clicks on the “About the program” button and is taken to a new screen, where there is useful information about the program version, author, website address, how many cats the author has, etc. Think of the activity screen as a web page with a link to another page. If you look at the code in the file MainActivity.java from previous lessons, you will see that our class MainActivity also applies to Activity(or his heirs) or, more precisely, inherited from him.

Public class MainActivity extends AppCompatActivity

As you might guess, we should create new class, which may be similar to MainActivity and then somehow switch to it when the button is pressed.

For the experiment, we will take the program from the first lesson and use a button for experiments (or create a new project with one button on the screen). Next we will create new uniform to display useful information. For example, let's show the user what a cat does when it goes left and right. Agree, this is very important information, which provides the key to the Universe.

We will create a new activity manually, although the studio has ready-made templates. But there is nothing complicated there and for a better understanding it is useful to do everything by hand.

Let's create a new XML markup file activity_about.xml in folder res/layout. Click right click mouse on folder layout and select from context menu New | Layout resource file. A dialog box will appear. In the first field, enter the file name activity_about. In the second, you need to enter the root element. By default it's there ConstraintLayout. Erase the text and enter ScrollView. Entering a few characters is enough for the studio to suggest ready-made options; you can immediately press Enter without waiting for the full word to be entered:

You will get a corresponding blank into which we will insert the element TextView.

Information will be retrieved from resources, namely the string resource about_text. Now it is highlighted in red, signaling the absence of information. It was possible to press Alt+Enter and enter text in the dialog box. But for our example, this method will not work, since our text will be multi-line, using control characters. So let's do it differently. Let's open the file res/values/strings.xml and enter the following text manually:

There is a green oak near the Lukomorye;\n A golden chain on that oak:\n Both day and night scientist cat\nEverything goes around and around in a chain;\nIt goes right- the song starts,\n Left- he tells a fairy tale.

We used the simplest HTML text formatting tags like , , . For our example, it is enough to highlight in bold the words that relate to the cat and the direction of movement. To translate text into new line use symbols \n. Let's add another string resource for the title of the new screen:

About the program

We figured out the markings. Next you need to create a class for the window AboutActivity.java. Select from the menu File | New | Java Class and fill in the required fields. At first, it is enough to indicate only the name. Then you'll deal with other fields.

Let's get the blank.

Now the class is almost empty. Let's add the code manually. The class must inherit from an abstract class Activity or his relatives like FragmentActivity, AppCompatActivity etc. Let's add extends Activity. The activity class must have a method onCreate(). Place the mouse cursor inside the class and select from the menu Code | Override Methods(Ctrl+O). In the dialog box we look for the required class, you can type the first characters on the keyboard for quick search. In the created method you need to call the method setContentView(), which will load the prepared markup onto the screen. We will have this option.

Package ru.alexanderklimov.helloworld; import android.app.Activity; import android.os.Bundle; /** * Created by Alexander Klimov on 12/01/2014. */ public class AboutActivity extends Activity ( @Override protected void onCreate(Bundle savedInstanceState) ( super.onCreate(savedInstanceState); setContentView(R.layout.activity_about); ) )

Now comes the most important part. Our task is to go to a new screen when we click a button on the first screen. Let's go back to class MainActivity. Let's write a button click handler:

Public void onClick(View view) ( Intent intent = new Intent(MainActivity.this, AboutActivity.class); startActivity(intent); )

Here I used the button click processing method described in the lesson.

To launch a new screen you need to create an instance of the class Intent and indicate the current class in the first parameter, and the class to go to in the second, we have this AboutActivity. After this the method is called startActivity(), which launches a new screen.

If you now try to test the application in the emulator, you will receive an error message. What did we do wrong? We missed one important step. You need to register a new one Activity in the manifesto AndroidManifest.xml. Find this file in your project and double click on it. The file editing window will open. Add new tag after the closing tag for the first activity. Type on your own and actively use the prompts. You will get the following:

This is where the string resource comes in handy about_title. We launch the application, click on the button and get a window About the program. Thus, we learned how to create a new window and call it by clicking a button. And we have a mega-convenient program at our disposal - now we will always have at hand a hint of what the cat does when it goes left.

Once again, please note that the second activity class created must inherit from the class Activity or similar ones ( ListActivity etc.), have an XML markup file (if required) and be specified in the manifest.

After calling the method startActivity() a new activity will be launched (in this case AboutActivity), it will become visible and move to the top of the stack containing the running components. When calling a method finish() from a new activity (or when the hardware return key is pressed) it will be closed and removed from the stack. The developer can also navigate to the previous (or any other) activity using the same method startActivity().

Creating a third screen - a method for the lazy

Programmers, like cats, are lazy creatures. Always remember that for an activity you need to create markup and a class that inherits from Activity, and then don’t forget to register the class in the manifest - oh well.

In this case, select from the menu File | New | Activity | Basic Activity(or other template). Next, the familiar window for creating a new activity will appear. Fill in the required fields.

Click on the button Finish and the activity will be ready. To verify this, open the manifest file and check for new entry. I’m not even talking about class and markup files, they will appear in front of you on their own.

Add it yourself new button on the main activity screen and write the code to switch to the created activity.

At first, I would advise you to manually create all the necessary components for the new activity so that you understand the relationship between the class, markup and manifest. And when you get the hang of it, you can use the Activity Creation Wizard to speed up your work.

Passing data between activities

We used simplest example to bring up another activity screen. Sometimes you not only need to call a new screen, but also transfer data to it. For example, username. In this case, you need to use a special area extraData, which the class has Intent.

Region extraData is a list of pairs key/value, which is transmitted along with the intention. Strings are used as keys, and any primitive data types, arrays of primitives, class objects can be used for values Bundle and etc.

To transfer data to another activity, use the method putExtra():

Intent.putExtra("Key", "Value");

The receiving activity must call some appropriate method: getIntExtra(), getStringExtra() etc.:

Int count = getIntent().getIntExtra("name", 0);

Let's redo the previous example. We already have three activities. The first activity will have two text fields and a button. Appearance could be as follows:

At the second activity SecondActivity set the element TextView, in which we will display the text received from the first activity. Let's write the following code for the method onCreate() at the second activity.

@Override protected void onCreate(Bundle savedInstanceState) ( super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); String user = "Animal"; String gift = "donut hole"; TextView infoTextView = (TextView)findViewById( R.id.textViewInfo); infoTextView.setText(user + " , you were given " + gift); )

If we now run the program and simply call up the second window, as was described in the first part of the article, then we will see the default inscription Animal, they handed you a donut hole. Agree, it’s quite annoying to receive such messages.

Let's correct the situation. Add the code to the first activity:

Public void onClick(View view) ( EditText userEditText = (EditText) findViewById(R.id.editTextUser); EditText giftEditText = (EditText) findViewById(R.id.editTextGift); Intent intent = new Intent(MainActivity.this, SecondActivity. class); // into the username key we push the text from the first text field intent.putExtra("username", userEditText.getText().toString()); // into the gift key we push the text from the second text field intent.putExtra("gift ", giftEditText.getText().toString()); startActivity(intent); )

We placed the object in a special container Intent two keys with values ​​that are taken from text fields. When the user enters data into the text fields, it will go into this container and be passed to the second activity.

The second activity should be ready to warmly receive messages as follows (in bold).

// Default values ​​String user = "Animal"; String gift = "donut hole"; user = getIntent().getExtras().getString("username"); gift = getIntent().getExtras().getString("gift"); TextView infoTextView = (TextView)findViewById(R.id.textViewInfo); infoTextView.setText(user + " , you were given " + gift);

Now the message looks less offensive, and even pleasant for some. In complex examples, it is advisable to add a check when processing data. There may be situations where you launch a second activity with empty data of type null, which may cause the application to crash.

In our case, we know that we are expecting a string value, so the code can be rewritten like this:

Intent intent = getIntent(); user = intent.getStringExtra("username");

User = getIntent().getStringExtra("username");

The program has a drawback - it is not clear from whom we receive greetings. Any well-bred monkey will not accept a gift from an anonymous source. So as homework, add another text field to enter the name of the user who is sending the message.

Google recommends using the following format for keys: your package name as a prefix, followed by the key itself. In this case, you can be sure of the uniqueness of the key when interacting with other applications. Something like this:

Public final static String USER = "ru.alexanderklimov.myapp.USER";

Who framed the cat Vaska - we get the result back

It's not always enough to simply pass data to another activity. Sometimes you need to get information back from another activity when it is closed. If earlier we used the method startActivity(Intent intent), then there is a related method startActivityForResult(Intent intent, int RequestCode). The difference between the methods is the additional parameter RequestCode. It's basically just an integer number that you can come up with yourself. It is needed in order to distinguish from whom the result came. Let's say you have five additional screens and you assign them values ​​from 1 to 5, and by this code you will be able to determine whose result you need to process. You can use the value -1, then it will be the same as calling the method startActivity(), i.e. we won't get any results.

If you use the method startActivityForResult(), then you need to override the method in your code to receive the result onActivityResult() and process the result. Confused? Let's look at an example.

Let's say you're a detective. Information was received that two pieces of sausage and other products were stolen from the table of an influential person in a restaurant. Suspicion fell on three suspects - a crow, a fucking dog and Vaska the cat.

One of the visitors provided a series of photos from his show-off iPhone:


There is also testimony from another witness: And Vaska listens and eats.

Create a new project Sherlock with two activities. On the first screen there will be a button to switch to the second screen and a text label in which the thief's name will be displayed.