when you make a network connection in Android you are blocking the main thread , so you have to move "some" of this task to a new thread
I have 2 questions on this part
1- which of the following operation is blocking the main thread (A or B)
//A:
HttpURLConnection c = (HttpURLConnection) (new URL(url)).openConnection();
//B:
InputStream stream=c.getInputStream();
2- if "both" of the above (A & B) must run in a new thread , dose it have a bad effect to run each one in a new separate thread? take a look to the following code:
//I temporary removed try & catch to simplify the code
public class connect{
HttpURLConnection c; String url;
public connect(String url){
this.url=url;
new Thread(new Runnable{
#override public void run(){
c = (HttpURLConnection) (new URL(url)).openConnection();
}
});
}
public InputStream get(){
return c.getInputStream();
//or make this one in a new thread
}
public InputStream post(Sring params){
c.setRequestMethod("POST");
//.. make some code for posting data , and then call get()
//thats why i cannot perform c.getInputStram() at the same time with openConnection()
return get()
}
}
which of the following operation is blocking the main thread (A or B)?
It is pretty evident that both operations A and B will block the main thread. Just calling the following on the main thread will throw an exception(NetworkOnMainThreadException) right away:
HttpURLConnection c = (HttpURLConnection) (new URL(url)).openConnection();
Also when you are calling the following line on main thread:
InputStream stream=c.getInputStream();
You are simply trying to read a stream of bytes over a network. Now there are various factors that will determine the time taken by this operation to complete. For instance, network speed, overall number of bytes that you want to read, etc. The application should not really need to wait and stay idle till the reading process has been completed. All the UI related process should be able to run and consume the resources as a user reacts with your application which will not be possible because of the ongoing byte reading process which is actually blocking the main thread.
if both A and B must run in a new thread , dose it have
a bad effect to run each one in a new separate thread?
Technically, yes it is bad to run both in separate threads. Besides why would you want to do so? Before initiating stream reading process you need to make sure that the connection has been opened. Calling A and B in separate threads will raise concurrency issues. You must call B after A so if you even resolve concurrency issues, it will be of no use to make two separate threads.
EDIT:
So as you said in comments that you want to avoid using AsyncTask. An alternative for that is Java Threads. Check out the following sample usage of threads:
static public class MyThread extends Thread {
#Override
public void run() {
try {
// add your url and open connecttion here
HttpURLConnection c = (HttpURLConnection) (new URL("your url here")).openConnection();
// read stream or whatever data you want
InputStream stream = c.getInputStream();
} catch (Exception e) {
e.printStackTrace();
} finally {
//close your connection & wipe input stream here.
}
}
}
Now here is how we can call this thread:
private Thread downloadThread = new MyThread();
downloadThread.start();
At any time, you can also check if your thread is running or not by using the following code:
if (downloadThread != null && downloadThread.isAlive()) {
// do something when thread is alive here
}
This solution uses handler to connect main thread with background thread(the thread that does the HTTP connection stuff)
public class MainActivity extends AppCompatActivity {
Thread mThread;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startThread();
}
public void startThread(){
String url = "www.google.com";
String result = "";
mThread = new Thread(new Runnable() {
public void run() {
InputStream is = null;
HttpURLConnection conn;
try {
ConnectivityManager connMgr = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = null;
if (connMgr != null) {
networkInfo = connMgr.getActiveNetworkInfo();
}
if (networkInfo != null && networkInfo.isConnected() && !mThread.isInterrupted()) {
conn = (HttpURLConnection) url.openConnection();
is = conn.getInputStream();
//Here you get the result from inputStream
}
threadMsg(result);
}catch (IOException e){
e.printStackTrace();
}
finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void threadMsg(String msg) {
if (msg != null && !msg.equals("") && !mThread.isInterrupted()) {
Message msgObj = handler.obtainMessage();
Bundle b = new Bundle();
b.putString("message", msg);
msgObj.setData(b);
handler.sendMessage(msgObj);
}
}
private Handler handler = new Handler(Looper.getMainLooper()) {
#Override
public void handleMessage(Message msg) {
String result = msg.getData().getString("message");
// What you want to do in UI thread
}
};
});
mThread.start();
}
Related
I am facing some problems trying to call main thread in my background thread.
Based on this post: Running code in main thread from another thread
The solution should be:
private void runOnMainThread() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
// Do something
ottoBus.post(new MyObjectEvent(mMyObject));
// End do something
mMyObject = null;
}
});
}
However my background thread is still being able to read Bluetooth socket data between "Do something" and "End do something"
What am I missing here? Is it possible to lock the background thread, while "Do something" is being executed?
My code for reading socket data is following:
InputStream stream = null;
InputStreamReader reader = null;
BufferedReader bufferedReader = null;
String data = "";
try {
stream = mSocket.getInputStream();
byte[] bytes = new byte[20];
int numRead = 0;
while ((numRead = stream.read(bytes)) >= 0) {
String s = new String(bytes, 0, numRead);
if (mMyObject != null) {
fillData(s); // Can cause NPE
} else {
mMyObject = new MyObject();
fillData(s);
}
// This should be synchronised call
runOnMainThread();
Thanks.
You will need to use a Java pattern called wait/notify. Simply put: it defines two threads,
a producer and a consumer, so that the consumer, after initiating the producer, stops and waits until the producer thread has completed.
It goes like this:
static final object uiActionMonitor = new Object();
transient boolean uiCompleted;
void network_thread_run() {
int numRead = 0;
while ((numRead = stream.read(bytes)) >= 0) {
String s = new String(bytes, 0, numRead);
// This should be synchronised call
uiCompleted = false;
runOnMainThread();
synchronized(uiActionMonitor) { //<---------- wait for UI to complete
while (!uiCompleted) {
uiActionMonitor.wait();
}
}
}
And the UI code:
private void runOnMainThread() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
// Do something
// End do something
uiCompleted = true;
synchronized(uiActionMonitor) { //<---------- release networking thread
uiActionMonitor.notifyAll();
}
}
});
}
Copy the synchronization logic exactly as is. This is where many developers get it wrong.
I must admit I fail to see why you need to block your networking thread while the UI thread is handling your message...
I find CountDownLatch to be the simplest way to accomplish this sort of thing. Here's a reusable method for running Runnables on the main thread and blocking on their completion:
private static final Handler mainHandler = new Handler(Looper.getMainLooper());
private static void runOnMainThreadBlocking(Runnable runnable) throws InterruptedException {
CountDownLatch completionSignal = new CountDownLatch(1);
mainHandler.post(new Runnable() {
#Override public void run() {
runnable.run();
completionSignal.countDown();
}
});
completionSignal.await();
}
I think you need to use locks or synchronized blocs. You can take a look into the java concurency documentation and more specificaly this and this part.
This way you can guaranty that on portion of the code won't be executed muliple times in parallel.
I want process the UI element while lopping.
This code is recoding streaming audio.
But code have some issues.
Application stop in while instruction. So, "Record Button" don't change.
Also, Application can user actions.(button click, etc)
I want that button is clickable and writing data to file same time.
Following the code:
PlayerActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
btnStartStopRecord.setText(R.string.button_record_stop);
try{
conn = new("Streaming URL").openConnection();
is = conn.getInputStream();
outstream = new FileOutputStream(new File(dataDir, fileName).getAbsolutePath());
int c;
while((c = is.read()) != -1) {
outstream.write(c);
}
} catch(Exception e){
e.printStackTrace();
error(e.toString());
}
}
});
The application freezes because you're executing expensive operations on the UI thread.
You need to use background thread.
See:
http://developer.android.com/guide/components/processes-and-threads.html
http://androidresearch.wordpress.com/2012/03/17/understanding-asynctask-once-and-forever/
for reference.
After reading a few posts and reading on the developers page about ASYNCTASK, I came up with the following code, and assigned it to a button:
private class TalkToServerTask extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... params) {
String response = "";
try {
InetAddress serverAddr = InetAddress.getByName(params[0]);
Socket s = new Socket(serverAddr, Integer.valueOf(params[1]));
PrintWriter out = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(s.getOutputStream())), true);
// WHERE YOU ISSUE THE COMMANDS
out.println(params[2]);
// BufferedReader input = new BufferedReader(
// new InputStreamReader(s.getInputStream()));
DataInputStream dataInputStream = null;
dataInputStream = new DataInputStream(s.getInputStream());
st = dataInputStream.readLine().toString();
// String st = s.readLine();
// st = input.readLine();
// read line(s)
s.close();
return st;
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return response;
}
#Override
protected void onPostExecute(String result) {
serverresponse = result;
}
}
The idea being, that when a button is clicked, the ASyncTask sends the word "getDomains" to a console app running on my server, the server acts on this and sends back a string with the list of domains created on an email server.
I have verified that the server is receiving the command "getdomains", and it in turn replies with a pipe-delimited string of domains. The problem being however, that I've set a Toast to pop up with the results of the socket transaction, and the toast shows nothing. If I hit the button again, the Toast shows the list of domains. To me, it seems as if the socket is first returning an empty
Here is the button code:
case R.id.btnDomains:
SharedPreferences sp = PreferenceManager
.getDefaultSharedPreferences(this);
IpAddress = sp.getString("ipaddress", "0.0.0.0");
Serverport = sp.getString("tcpport", "12345");
buttonpressed = "domains";
// TalkToServerTask task = new TalkToServerTask();
new TalkToServerTask().execute(IpAddress, Serverport, "getDomains");
Intent buttonActivity = new Intent(MainActivity.this, Rules.class);
buttonActivity.putExtra(MainActivity.DOMAINLIST, serverresponse);
Toast.makeText(getApplicationContext(), serverresponse,
Toast.LENGTH_SHORT).show();
the variable "serverresponse" is what at first is showing empty, but then shows the list of servers.
As Thepoosh mentioned, AsyncTask is working on a separate thread.
Therefore the thread is being executed and didn't get result yet when you first time press the button.
What you should do is to show the data in onPostExecute method. Also you should pass the context to your AsyncTask.
public TalkToServerTask(Context context) {
this.context = context;
}
#Override
protected void onPostExecute(String result) {
Intent buttonActivity = new Intent(context, Rules.class);
buttonActivity.putExtra(MainActivity.DOMAINLIST, result);
Toast.makeText(context.getApplicationContext(), result, Toast.LENGTH_SHORT).show();
}
You just need to change your code a little,
You need to move this 3 lines,
Intent buttonActivity = new Intent(MainActivity.this, Rules.class);
buttonActivity.putExtra(MainActivity.DOMAINLIST, serverresponse);
// and also the line to startActivity() as well.
Toast.makeText(getApplicationContext(), serverresponse,
Toast.LENGTH_SHORT).show();
from your Switch..case to onPostExecute of the AsyncTask where you have written serverresponse = result
That's because the AsyncTask run in a different thread, while you have executed in Swtich...case it's not like that the next line of code will not get executed untill the AsyncTask finishes, So all dependent code should be written in onPostExecute of the Task.
AsynTask is run on a separate thread from the UI thread. So there are 2 threads running parallel. While you are trying to display the list in a toast on main UI thread, the AsynTask is still preparing to fetch the list, or probably creating sockets in its own thread. Hence the list data is still empty.
The postexecute method of Asynctask is run on the main UI thread, so its safe and correct to update the UI in it.
I have an application in which there is Google map, location overlays on Google map and a separate thread which send the current location of device to server after every 30 seconds. The problem is that when the thread sends the location to server the screen of device halted until the server respond. Here is the following code,
Global Object
private Handler handlerTimer = new Handler();
In onCreate Method
handlerTimer.removeCallbacks(taskUpdateStuffOnDialog );
handlerTimer.postDelayed(taskUpdateStuffOnDialog , 100);
And here is the taskUpdateStuffOnDialog
private Runnable taskUpdateStuffOnDialog = new Runnable()
{
public void run()
{
try
{
URL url3 = new URL("http://"+ appState.getURL()+"//iLocator/IDForClient.php?reg_no="+ Device_ID[0]);
HttpURLConnection conn = (HttpURLConnection) url3.openConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String quote = reader.readLine();
while (quote != null)
{
Device_ID = quote.split("\n");
quote = reader.readLine();
bCheckID = true;
}//End While
positionOverlay.setID(Device_ID[0]);
addEvent(Device_ID[0]);
}//End try
catch (Exception e)
{
e.printStackTrace();
Toast.makeText(MainMapActivity.this, "Communication Issue",Toast.LENGTH_LONG).show();
}//End catch
handlerTimer.postDelayed(this, 9000);
}
};
Please tell me what is wrong with my code.
The problem is that, although you're spawning a new Thread, you aren't spawning a new process. Everything you're doing is still in the user interface process, and that's blocking. You can find more information on the topic on developer.android.com.
The quickest and easiest way to get around this is using the IntentService class. It will only allow one HTTP request to be executed at a time, but will take care of all the problems for you.
Try using the AsyncTask for connecting to the Server. See an example here: http://developer.android.com/reference/android/os/AsyncTask.html
In my application I download and parse a html page. However, I want to be able to stop the download in its tracks (i.e. when the user hits cancel).
This is the code I use now, which is being called from doInBackground from ASyncTask.
How do I cancel this request from outside of the ASyncTask?
I currently use htmlcleaner
HtmlCleaner cleaner = new HtmlCleaner();
CleanerProperties props = cleaner.getProperties();
props.setAllowHtmlInsideAttributes(true);
props.setAllowMultiWordAttributes(true);
props.setRecognizeUnicodeChars(true);
props.setOmitComments(true);
try {
URL url = new URL(urlstring);
URLConnection conn = url.openConnection();
TagNode node = cleaner.clean(new InputStreamReader(conn.getInputStream()));
return node;
} catch (Exception e) {
failed = true;
return;
}
Can't you use AsyncTask.cancel()? You should be able to then use the onCancelled callback to return to the main activity..
Ok, I believe I've solved this.
In my Activity class I have a variable (boolean) failed. Also, I have a private Downloader class within the activity which extends ASyncTask. This way, the Downloader class has access to the failed boolean. When the Activity launches, it starts the Downloader task and a progress dialog pops up. When the task finishes, it closes the dialog and then goes on processing the downloaded content.
However, when the user cancels the progress dialog, failed is set to true, and the user is sent back to the previous activity by a call to finished. In the meantime, Downloader is still busy downloading. Because the results are now unneccessary, we want it to stop using resources asap. In order to accomplish this, I have broken up the doInBackground method in as much steps as possible. After each step I check if failed is still false, when it is set to true, it simply doesn't go to the next step. See it in action below. Furthemore, the BufferedReader reader is public, and in the onCancelled method I execute reader.close(). This will throw all sorts of exceptions, but these are properly caught.
public void DoInBackground(.........) {
try {
URL url = new URL(uri);
URLConnection conn = url.openConnection();
if (!failed) {
isr = new InputStreamReader(conn.getInputStream());
if (!failed) {
reader = new BufferedReader(isr);
publishProgress(1);
if (!failed) {
TagNode node = cleaner.clean(reader);
publishProgress(2);
return node;
}
}
}
} catch (Exception e) {
failed = true;
Log.v("error",""+e);
}
}
#Override
protected void onCancelled() {
failed = true;
if (reader != null)
try {
reader.close();
} catch (IOException e) {
failed = true;
}
if (isr != null)
try {
isr.close();
} catch (IOException e) {
}
}
I know that I could have broken up the downloading process in even tinier bits, but I am downloading very small files, so it's not that important.