Reputation: 45
I am writing a program and need to run separate thread for server connection, but I don't know how to properly use the AsyncTask
. I have read the documentation and am still not certain as to how I would properly use it.
The method that's supposed to connect me to the server:
public boolean start()
{
// try to connect to the server
try {
socket = new Socket(server, port);
}
// if it failed not much I can so
catch(Exception ec) {
display("Error connectiong to server:" + ec);
return false;
}
/* Creating both Data Stream */
try
{
sInput = new ObjectInputStream(socket.getInputStream());
sOutput = new ObjectOutputStream(socket.getOutputStream());
}
catch (IOException eIO) {
display("Exception creating new Input/output Streams: " + eIO);
return false;
}
// creates the Thread to listen from the server
new ListenFromServer().start();
// Send our username to the server this is the only message that we
// will send as a String. All other messages will be ChatMessage objects
try
{
sOutput.writeObject(username);
}
catch (IOException eIO) {
display("Exception doing login : " + eIO);
disconnect();
return false;
}
// success we inform the caller that it worked
return true;
}
The ListenFromServer class used above:
class ListenFromServer extends Thread {
public void run() {
while(true) {
try {
String msg = (String) sInput.readObject();
// if console mode print the message and add back the prompt
if(clientActivity == null) {
System.out.println(msg);
System.out.print("> ");
}
else {
clientActivity.append(msg);
}
}
catch(IOException e) {
if(clientActivity != null)
clientActivity.connectionFailed();
display("Server has close the connection: " + e);
break;
}
// can't happen with a String object but need the catch anyhow
catch(ClassNotFoundException e2) {
}
}
}
}
I know that I am supposed to use AsyncTask
, but I don't know how.
Upvotes: 1
Views: 1281
Reputation: 167
AsyncTask is a lot easier than it looks. There are three main methods that you need to worry about to get started. Calling execute() on an AsyncTask will kick off these methods:
onPreExecute();
This is called before doInBackground() is called. You can do your setup here if it's needed. Maybe set a ProgressBar's visibility or something.
doInBackground()
This is where the asynchronous operations take place. If you need to make a network call or whatever, put the code here.
onPostExecute();
This is called when doInBackground() is finished. Whatever is returned from doInBackground() will be passed to this method. It's important to note that this method is run on the main thread so you can make changes to UI from it.
Now for the actual class itself:
public class MyTask extends AsyncTask<String, Integer, Boolean> {
}
You'll notice that it takes three type parameters. The first type (String) is the type of paramters that will be sent to the task upon execution. For example, if you need to send a String(s) to the Task when it executes, you'll pass them into the execute() method. Example:
myTask.execute("these", "strings", "will", "be", "passed", "to", "the", "task");
Note that you can use 'Void' here (capital V) if you don't want to pass in anything from the execute() method. (To be clear, you can pass in any object of any type for these parameters -- Integer, Boolean, CustomClass, etc.. Just remember that if you're using a primitive type, you need to use its wrapper class, e.g. Integer, Boolean.)
public class MyTask extends AsyncTask<Void, Integer, Boolean> {
}
Now, to understand how to access these strings, we need to go to the doInBackground() method in the task.
@Override
protected Boolean doInBackground(String... params) {
//...
}
You'll notice that the doInBackground() method has a vararg parameter.
String... params
are the Strings that you passed in earlier through execute(), packaged into an array. So the String "these" is located at params[0] and "will" is located at params[2].
The second type paramter,
Integer
is the type of object that will be passed to onProgressUpdate() when you call publishProgress() from doInBackground(). This is used to update UI from the main thread while the Task's execution is still ongoing. We won't worry about this for now -- You can figure that out later. You can pass 'Void' here as well if you don't plan on using the onProgressUpdate().
Now for the third and final type paramter
Boolean
This is the type of object that doInBackground() will return. It's pretty self-explanatory. You can pass 'Void for this paramter as well if you want, just note that you still need to
return null;
from doInBackground(). So what happens to this return Value once doInBackground() finishes? It gets sent to
@Override
protected void onPostExecute(Boolean...aBoolean) {
}
From here, you can do whatever you want with the result, which in this case is a boolean. Again, this method is run on the main thread so you can update Views from here. A lot of times, I'll pass a listener to the AsyncTask and call it from onPostExecute(). For example:
@Override
protected void onPostExecute(Boolean aBoolean) {
mListener.onTaskFinished(aBoolean);
}
If your AsyncTask class is declared directly within your Activity, then you can just update Views or do whatever you need to do directly from onPostExecute().
One last thing, you can also pass objects to your Task in the more conventional way -- through the constructor. You don't have to pass arguments to execute() if you don't want to.
new MyTask("hello", 55).execute();
Just set those to the correct fields within your AsyncTask class and you're set. You can use them just like any other class.
Edit: Had some time so I figured I'd show the full implementation of an AsyncTask, including the interface used to create a callback for the result.
So let's say I need to make a network call to some API that will return a String of JSON data (or whatever kind of data, what matters is that it's a string). My AsyncTask will need a url to give to whatever network client I'm using, so we'll pass that in through the execute()
method. So the first type parameter of the AsyncTask will be a String
. Since I'm only making one network call and getting one response, I don't need to update any progress, so the second type parameter will be Void
. The response I'm getting from the API call is a String, so I'm going to be returning that String from the doInBackground()
method. So the third and final type parameter is String
.
So my AsyncTask class will look like this:
public class NetworkTask extends AsyncTask<String, Void, String> {
//I'll use this to pass the result from onPostExecute() to the
//activity that implements the NetworkTaskListener interface, as
//well as notify that activity that onPreExecute has been called.
private NetworkTaskListener mListener;
//Set the NetworkTaskListener
public NetworkTask(NetworkTaskListener listener) {
mListener = listener;
}
//onProgressUpdate() doesn't need to be overridden
//Called after execute(), but before doInBackground()
@Override
protected void onPreExecute() {
mListener.onBeforeExecution();
}
//Called automatically after onPreExecute(). Runs in background.
@Override
protected void doInBackground(String... params) {
//"params" holds the Strings that I will pass in when I call
//execute() on this task. Since I'm only passing in one thing
//(a String URL), "params" will be an array with a length of one
//and the the String URL will be at params[0].
String url = params[0];
//Make the network call using whatever client you like.
//I'll use OkHttp just as an example.
OKHttpClient client = new OKHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Call call = client.newCall(request);
//It's okay to use a blocking network call here since we're
//already off of the main thread.
try {
Response response = call.execute();
//response.body().string() is the JSON data we want
String jsonData = response.body().string();
//This will be sent to onPostExecute()
return jsonData;
} catch (IOException e) {
e.printStackTrace();
Log.e("NetworkTask", "Error during network call");
//If there was an error, return null
return null;
}
}
//Called automatically after doInBackground(). The return value
//from doInBackground() is passed in here automatically.
@Override
protected void onPostExecute(String aString) {
//Check if the String is null to determine if there was an error
//while making the network call.
if (aString == null) {
mListener.onNetworkCallError();
return;
}
/*
If this is executed, it means that the response was successful.
Call the listner's onNetworkResponse() method and pass in the
string. The String will be used by the Activity that implements
the NetworkTaskListener.
*/
mListener.onNetowrkResponse(aString);
}
}
So now that we've got the AsyncTask all set, we need to create the NetworkTaskListener interface. It will contain 3 methods (which you've already seen in the NetworkTask class) that must be implemented by any class that implements the interface. In this case, the activity will implement this interface and thus must override all 3 methods.
public interface NetworkTaskListener {
//We call this method in onPreExecute() of the NetworkTask
void onBeforeExecution();
//Called from onPostExecute() when there was an error during the
//network call
void onNetworkCallError();
//Called from onPostExecute() when the response was successful
void onNetworkResponse(String jsonData);
}
Now that the Interface is taken care of, we just need to have our Activity implement it. For simplicity, I'm going to have the NetworkTask begin as soon as the Activity starts. You'll notice that we pass this
into the NetworkTask. What that means is that we're passing the Activity as a NetworkTaskListener into the NetworkTask. Remember, the Activity is also a NetworkTaskListener since it implements it. So when we call mListener.onNetworkResponse(jsonData)
from the the NetworkTask, it is calling the 'onNetworkResponse(String jsonData)' method from within the Activity. (I know, I know... now I'm explaining basic Java but I want this to be clear since AsyncTasks are used frequently by beginners.)
public class MainActivity extends Activity implements NetworkTaskListener {
//ProgressBar that will be displayed when NetworkTask begins
private ProgressBar mProgressBar;
//TextView that will be used to display the String that we get back
//from the NetworkTask
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate();
setContentView(R.layout.activity_main);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
mTextView = (TextView) findViewById(R.id.textView);
String url = "https://www.exampleAPIurl.com";
//The listener that we are passing in here is this Activity
NetworkTask task = new NetworkTask(this);
//We pass the URL through the execute() method
task.execute(url);
/*
From this point, the NetworkTask will begin calling the
NetworkTaskListener methods, which are implemented below.
Specifically, it will call onBeginExecution(), and then either
onNetworkResponse(String jsonData) or onNetworkCallError();
*/
}
///////Implemented methods from NetworkTaskListener
@Override
public void onBeginExecution() {
//Called when the task runs onPreExecute()
mProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void onNetworkCallError() {
//Called from task's onPostExecute() when a Network error
//occured
mProgressBar.setVisibility(View.INVISIBLE);
mTextView.setText("An error occured during the network call");
}
@Override
public void onNetworkResponse(String jsonData) {
//Called from task's onPostExecute() when network call was
//successful
mProgressBar.setVisibility(View.INVISIBLE);
mTextView.setText(jsonData);
}
}
Note that you can also use the NetworkResponseListener as an anonymous class and the Activity won't have to implement it.
Upvotes: 1
Reputation: 9369
See below code,
private class ListenFromServer extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
while(true) {
try {
String msg = (String) sInput.readObject();
// if console mode print the message and add back the prompt
if(clientActivity == null) {
System.out.println(msg);
System.out.print("> ");
}
else {
clientActivity.append(msg);
}
}
catch(IOException e) {
if(clientActivity != null)
clientActivity.connectionFailed();
display("Server has close the connection: " + e);
break;
}
// can't happen with a String object but need the catch anyhow
catch(ClassNotFoundException e2) {
}
}
}
@Override
protected void onPostExecute(Voidresult) {
//after completed
try
{
sOutput.writeObject(username);
}
catch (IOException eIO) {
display("Exception doing login : " + eIO);
disconnect();
return false;
}
// success we inform the
}
@Override
protected void onPreExecute() {}
@Override
protected void onProgressUpdate(Void... values) {}
}
Call this Asyntask,
new ListenFromServer().excute();
Upvotes: 0