I'm working on an Android app for a networking exercise and I'm having all sorts of problems. I have a working C server that I am running on my computer, and the app that I am writing aims to connect to the server, authenticate, and then initiate a file transfer from the server to the client. I am very new to Android, and I only very recently learned that blocking the UI thread is generally prohibited.
I have created an AsyncTask to help me with this. However, it completely and totally fails when I call it. It doesn't fail conspicuously, however; the only kind of problem that I see is that my main screen shrinks and moves around weirdly. Sometimes the UI thread blocks completely and the OS tells me that my app isn't responding, which doesn't make any sense to me because I'm threading the action that's blocking!
This is the code that I am using for my AsyncTask:
private class LoginTask extends AsyncTask<String, Void, Boolean> {
@Override
protected Boolean doInBackground(String... input) {
int count = input.length;
if (count != 4)
return false;
String ipAddress = input[0];
int portNumberInt = Integer.parseInt(input[1]);
String username = input[2];
String password = input[3];
// Step 0: Establish a connection to the server
PrintWriter out;
BufferedReader in;
try {
serverSocket = new Socket(ipAddress, portNumberInt);
out = new PrintWriter(serverSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(serverSocket.getInputStream()));
} catch (IOException e) {
((TextView)findViewById(R.id.textView1)).setText(e.getMessage());
((TextView)findViewById(R.id.textView2)).setText("");
return false;
}
// Step 1: send "Authorize" to the server
out.print("Authorize");
// Step 2: server sends a random 64 character challenge string
char[] buffer = new char[64];
String challenge = null;
try {
in.read(buffer);
challenge = new String(buffer);
} catch (IOException e) {
((TextView)findViewById(R.id.textView1)).setText(e.getMessage());
((TextView)findViewById(R.id.textView2)).setText("");
return false;
}
challenge = username + password + challenge;
// Step 3: Compute MD5 hash of username + password + challenge and send it to the server
MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
return false;
}
byte[] digest = md5.digest(challenge.getBytes());
out.print(digest);
// Step 4: Server computes the same hash, determines whether or not to accept the connection
String authResult;
try {
in.read(buffer);
authResult = new String(buffer);
} catch (IOException e) {
((TextView)findViewById(R.id.textView1)).setText(e.getMessage());
((TextView)findViewById(R.id.textView2)).setText("");
return false;
}
if (authResult.equals("Pass")) {
return true;
} else {
return false;
}
}
}
And this is the code that calls the AsyncTask:
public boolean authenticate(final String ipAddress, final int portNumberInt, final String username, final String password) {
try {
Toast.makeText(getApplicationContext(), "Starting async task...", Toast.LENGTH_LONG).show();
boolean result = new LoginTask().execute(ipAddress, Integer.toString(portNumberInt), username, password).get();
Toast.makeText(getApplicationContext(), "Async task done!", Toast.LENGTH_LONG).show();
return result;
} catch (InterruptedException e) {
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
return false;
} catch (ExecutionException e) {
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
return false;
}
}
What could possibly be causing this problem? I am running my server and it isn't showing any indication that a connection is made. If this is the case, then my code should never block... right?
There are a number of issues that I'd fix here although I can't give a definitive answer to your problem. Two quotes from your question...
I only very recently learned that blocking the UI thread is generally prohibited.
Actually it's not just 'generally' prohibited, just don't do it ever.
...the OS tells me that my app isn't responding, which doesn't make any sense to me because I'm threading the action that's blocking!
Not exactly true due to this line in your authenticate
method...
boolean result = new LoginTask().execute(<cut-for-clarity>).get();
You're using the get()
method of AsyncTask
which 'waits' for a result basically turning your call to execute the asynchronous task into a synchronous one.
Using get()
to perform network operations with AsyncTask
is potentially as bad as simply executing the code on the UI thread. I've never understood why AsyncTask
has the get(...)
methods.
The next problems I see are in the following code...
try {
serverSocket = new Socket(ipAddress, portNumberInt);
out = new PrintWriter(serverSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(serverSocket.getInputStream()));
} catch (IOException e) {
((TextView)findViewById(R.id.textView1)).setText(e.getMessage());
((TextView)findViewById(R.id.textView2)).setText("");
return false;
}
Firstly your catch
block is only looking for IOException
. Creating your socket can potentially also throw UnknownHostException
. Creating your PrintWriter
could also throw NullPointerException
etc etc
Secondly, in your catch
block you're attempting to set the text of two TextView
s. The second golden rule of threading (aside from not blocking the UI thread) is to never try to manipulate UI elements from any thread other than the UI thread - it won't work.
So basically
try{...} catch(Exception e) {...}
around everyting in doInBackground(...)
if necessary.get()
method of AsyncTask
doInBackground(...)
EDIT:
Much more generally, how can I pass information from my AsyncTask back to the UI thread?
That's entirely the point of the onPostExecute(...)
method of AsyncTask
which executes on the UI thread.
Use doInBackground(...)
not only to authenticate but also to download the file you need. If there are any problems, pass false
as a result to onPostExecute(...)
and have that method create some sort of alert or error message and then clean up.
If you have a C server running on your computer than you need a client on android. A client would use a Socket. You are using a ServerSocket...