I have found as a common issue in any of my apps that the user may accidentally perform several clicks on the same button causing multiple executions of the onClick listener logic. Typically, my business logic for those onClickListeners usually consists of launching of a heavy-process AsynTask that performs an HTTP request and later modifies the UI.
My way to prevent multiple executions of the asyntask was to unable the button at the beginning of the listener method and enable it again as a first statement of the onPostExecute. That has generally worked for me or at least I have not received any issue regarding to this situation.
Recently, a colleague has pointed me a potential problem of this unable-enable-button method. As shown in the below code that consists of two buttons '+' and '-', quick and alternative presses on those buttons causes a crash the application by an ArrayOutOfIndex exception.
That fact has made me think about my way of managing the concurrency of the onClickListener events and if it is really possible to have the situation in which a second asyntask may be launches prior to the finalization of the first asyntask using the aforementioned method.
What are your suggestions to handle this situation?
For those suggestions that recommend to apply some logic rejecting the second launches of the asyntask until the completion of the first asyntask, is it is really worth to generally apply that logic for a common used application in which the buttons perform an http request?.
CrashActivity.java
public class CrashActivity extends Activity {
private int mNumbers[] = { 1, 2, 3, 4, 5 };
private int position = 0;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final TextView result = (TextView) findViewById(R.id.resultTextView);
final Button minusBtn = (Button) findViewById(R.id.minus_button);
final Button plusBtn = (Button) findViewById(R.id.plus_button);
minusBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
minusBtn.setEnabled(false);
plusBtn.setEnabled(true);
result.setText("" + mNumbers[--position]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
minusBtn.setEnabled((position > 0));
}
});
plusBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
plusBtn.setEnabled(false);
minusBtn.setEnabled(true);
result.setText("" + mNumbers[position++]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
plusBtn.setEnabled((position <= 4));
}
});
minusBtn.setEnabled(false);
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:id="#+id/minus_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="-" />
<Button
android:id="#+id/plus_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="+" />
<TextView
android:id="#+id/resultTextView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="hello stackoverflow!" />
</LinearLayout>
A solution that would guarantee that both methods do not try to update the UI at the same time would be to synchronize the UI update code. You could either create a synchronized method or an Object that acts as a lock for a block of code. Example:
public synchronized void updateUI() {
// ...
}
or
private Object mLock = new Object();
public void updateUI() {
// ...
synchronized (mLock) {
// Critical code here.
}
// ...
}
Expanding this to make sure that task 1 completes before task 2, completes before task 3, etc., you would need to somehow keep track of which started first. Note: synchronization occurs on the UI thread in this example.
private List<AsyncTask> mRunningTasks = new ArrayList<AsyncTask>();
public void onClick(View v) {
v.setEnabled(false);
if (v == task1View) {
Task1 task = new Task1();
mRunningTasks.add(task);
task.execute();
}
else if (v == task2View) {
Task2 task = new Task2();
mRunningTasks.add(task);
task.execute();
}
// ...
}
// Copy and paste for each necessary task.
private Task1 extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... v) {
// Run task 1 code here.
while (this != mRunningTasks.get(0)) {
wait(1000);
}
return v[0];
}
protected void onPostExecute(Void v) {
updateUI();
mRunningTasks.remove(0);
task1View.setEnabled(true);
}
}
public void updateUI() {
// UI update code here.
}
Related
I am working on an Android app that is behaving oddly. One of the first things this app does upon start up, is to start a listening thread that listens to a "server" app, on the same device, to get data. Once this data is received by the listening thread, I use it to update the main view. This, however only works if my app is started after the server app.
First a few details. My app and the server app are on the same device. They communicate with each other via UDP (the server app is a port of a Windows application). My app uses fragments, but the view I want to update is not within a fragment. Now some code.
content_main.xml
<RelativeLayout>
<LinearLayout>
<ImageView
android:id="#+id/my_image_id"
android:src="#drawable/my_image" />
</LinearLayout>
<LinearLayout>
<!-- fragment code here -->
</LinearLayout>
</RelativeLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState){
Listener myListener = new Listener(this);
Thread listenerThread = new Thread(myListener);
listenerThread.setName("My Listener Thread");
listenerThread.start();
}
}
Listener.java
public class Listener implements Runnable {
public Listener (Activity mainActivity) {this.mainActivity = mainActivity;}
private Activity mainActivity;
private ImageView myImageView;
private NewData newData;
#Override
public void run(){
while(true){
// Here is where my app gets the data from the server via UDP.
// This always works, I am always getting the correct data.
myImageView = (ImageView) mainActivity.findViewById(R.id.my_image_id);
mainActivity.runOnUiThread(new Runnable(){
#Override
public void run(){
if(newData == 1){
myImageView.setImageResource(R.drawable.new_image_01);
} else if(newData == 2){
myImageView.setImageResource(R.drawable.new_image_02);
} else {
myImageView.setImageResource(R.drawable.error_image);
}
}
});
}
}
}
Like I said, if I start my app after the server app, then this all works perfectly. The images always changes when the new data changes. However, if I start my app before the server app, or if I restart the server app while my app is still running, then the images never changes, even though I am still getting the correct data from the server app.
What can I do to make sure the view can update at any time?
EDIT: I moved where I get the view to outside of the while loop (as a test) and now the code doesn't work at all.
I gave it a try. apparently your code is working alright, if the thread starts after the activity is been initialized, i don't really know how your server app works so i tried to mimic something like this.
public class TestThreadActivity extends AppCompatActivity {
private Button restart_Button;
private ImageView imageView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_thread);
Toolbar toolbar = (Toolbar) findViewById(R.id.my_custom_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
initializeUI();
}
private void initializeUI() {
restart_Button = (Button) findViewById(R.id.TestThreadActivity_restart_button);
imageView = (ImageView) findViewById(R.id.TestThreadActivity_imageView);
final Listener myListener = new Listener(this);
restart_Button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
imageView.setImageResource(R.drawable.piccolo);
final Thread listenerThread = new Thread(myListener);
listenerThread.setName("My Listener Thread");
listenerThread.start();
}
});
}
public class Listener implements Runnable {
public Listener (Activity mainActivity) {this.mainActivity = mainActivity;}
private Activity mainActivity;
private ImageView myImageView;
int newData = 0;
#Override
public void run(){
while(true){
// Here is where my app gets the data from the server via UDP.
// This always works, I am always getting the correct data.
newData = (new Random()).nextInt(3);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myImageView = (ImageView) mainActivity.findViewById(R.id.TestThreadActivity_imageView);
mainActivity.runOnUiThread(new Runnable(){
#Override
public void run(){
System.out.println("newData: "+newData);
if(newData == 1){
myImageView.setImageResource(R.drawable.gohan);
} else if(newData == 2){
myImageView.setImageResource(R.drawable.goku);
} else {
myImageView.setImageResource(R.drawable.piccolo);
}
}
});
}
}
}
}
activity_test_thread.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="activities.list.first.TestThreadActivity">
<include layout="#layout/my_custom_toolbar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="4dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="#+id/TestThreadActivity_restart_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_weight="0.5"
android:text="Start"
android:contentDescription="this will restart the thread"
android:textAllCaps="false" />
</LinearLayout>
<ImageView
android:id="#+id/TestThreadActivity_imageView"
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="#drawable/gohan" />
</LinearLayout>
</LinearLayout>
Output
Why i can't force Android ANR with this code?
No log messages or pop up. The application is just launched lazily.
[UPDATE]
I can't get it even sleeping a View.setOnClickListener or BroadcastReceiver.onReceive!
Is there a trick?
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
Log.e("Test", "", e);
}
}
}
I'm using Samsung GT-6200L with stock Android 3.2
Try it in onTouchEvent. In onCreate your activity is not fully running
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG,"onTouchEvent");
while(true) {}
}
The ANR-WatchDog project has a test app that produces ANRs in a reliable manner (as reliable as ANRs can be): the app hangs because of a deadlock.
The gist of it:
Prepare a lock object as a private field in your activity:
final Object mutex = new Object();
Have a thread that performs some work in a critical section, and an android.os.Handler that posts work depending on the same lock.
new Thread(new Runnable() {
#Override
public void run() {
synchronized (mutex) {
while (true) {
try {
Thread.sleep(60000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
synchronized (mutex) {
// Shouldn't happen
throw new IllegalStateException();
}
}
}, 1000);
Putting the above code snippet inside a button click handler, for example, should do the trick.
I've been facing the same issue yesterday, and I've found out that using a plain debug build ANR dialogs simply won't show up. (Although the UI thread was completely hanged.)
But after exporting and properly signing the application the dialogs were popped up properly (in every cases mentioned above). However I am still not sure what really prevents to pop up ANR messages, maybe someone else can clarify this later...
Try using:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int a=0;
while(true) {
a++;
}
}
Your code probably didn't work because it got setup too early, and the Activity probably wasn't fully initialized and created yet. With the above code, launch the activity and touch/swipe on the screen and wait for the ANR dialog to popup.
Make a button in your activity.
public void onBtn1(View v) {
int a = 0;
while(true) {
a++;
}
}
Make the button execute the above code.
Spam click the button with your finger =)
I used this code for force ANR
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void force(View view){
while(true) {}
}
I just created a simple button in the xml file and set android:onClick=force
In my activity class i want to perform a series of long calculations that require around 5 sec to complete when i press a button. So in order to do that i have create a new class which does all the calculations in its run method(since it implements Runnable) and when finished i set a variable to true to indicate that. In the code that checks the if the button is pressed i start a new Thread passing my class in it and then cheking whether the run method has finished or not. If it finished i then print the data. The problem with this is that when i check if the calculations have finished they actually havent so it pass that line of code and never prints the data. I have tried to do the Async Class method but still i think it wont work. Is there a way to create the thread when i press the button and keep checking if had finished so i can print the data? Which piece of code in an Activity is actually get executed over and over again? Thanks for any information.
if(v.equals(this.button)) {
EditText param1 = (EditText)findViewById(R.id.param1);
EditText param2 = (EditText)findViewById(R.id.param2);
calculations = new MathCalc(param1.getText().toString(), param2.getText().toString());
new Thread(calculations).start();
while(!calculations.isReady());
Intent intent = new Intent(this,Show.class);
intent.putExtra("show1", calculations.getResult());
startActivity(intent);
}
This is want i want to achieve.
The AsyncTask is the right tool for this. The typical use case for the AsyncTask is that you want to do something small in the background and leave feedback through the UI before, during and/or after the task is done.
Be aware that running things in the background can get you in trouble if the user quits and restarts your activity a lot, since the background task will not end when the Activity is removed from screen.
An example activity is shown below. You could add the onPreExecute and onProgress methods to the AsynchTask to give the user feedback before and during the calculation.
public class CalcActivity extends Activity {
private Button button;
private TextView resultView;
public void onCreate() {
button = (Button) findViewById(R.id.my_button);
resultView = (TextView) findViewById(R.id.result);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
button.setEnabled(false);
AsyncCalculation calc = new AsyncCalculation();
calc.execute();
}
});
}
public class AsyncCalculation extends AsyncTask<Void, Void, Integer> {
#Override
protected Integer doInBackground(Void... params) {
int result = 0;
// Do some calculation
return result;
}
#Override
protected void onPostExecute(Integer result) {
// Set the result, start another activity or do something else
resultView.setText("The result was " + result);
button.setEnabled(true);
}
}
}
I don't see how this won't work with AsyncTask. You basically need to override two methods - doInBackground() and onPostExecute().
You're guaranteed that onPostExecute() will be invoked on the UI thread after the background computation finishes. You also don't have to worry how to update the UI Thread from another thread.
Here's a good example.
Use
Button someButton = (Button) findViewById(R.id.favouriteButton);
someButton.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v) {
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while(!isDone){
doAlotOfCalculations();
}
}
});
thread.start();
}
});
private void doAlotOfCalculations(){
...
if(whenDone){
isDone = true;
}
....
}
Which piece of code in an Activity is actually get executed over and
over again?
There is no such a thing.
It is just onResume which executes every time you start(restart) this activity
i have an rss feed that comes via an XML. There are several events that are returned with information about them. The events are returned with tags...for eg: ....info...
as soon as i encounter tag, i want to update the listview that i am using to show the events.
So the user does not see the loading progress dialog, rather he sees the events getting added to a list.
How do i do this.
thank you in advance.
Here's pseudo codeish example for one way of doing this using SAX parser;
// MyParserThread is assumed to be inner class of Activity here.
private class MyParserThread extends Thread implements MyParserObserver {
private MyParser mParser;
public MyParserThread() {
mParser = new MyParser();
mParser.setObserver(this);
}
public void run() {
try {
// load xml
mParser.parse(xml);
} catch (Exception ex) {
}
}
public void onMyParserEvent(final DataReceivedFromParsing data) {
runOnUiThread(new Runnable() {
public void run() {
// update data to your UI.
}
});
}
public void cancel() {
mParser.cancel();
}
}
And in your parser you're implementing ContentHandler
public void cancel() {
mCancelled = true;
}
public void startElement(....) {
if (mCancelled) {
// If you want to stop Thread from running, all you have to do
// is make parsing stop.
throw new SAXException("Cancelled");
}
....
}
And triggering parsing once your onCreate is called would be;
public void onCreate(...) {
...
mParserThread = new MyParserThread();
mParserThread.start();
...
}
Now this isn't perfect but hopefully gives some idea how to do Thread handling for this purpose. Fundamentally you just have start it, and adding 'cancel' functionality is somewhat more of a bonus - e.g. for cases in which Activity is destroyed while your Thread is running.
I'm trying to have my Activity update an ImageView whenever a boolean value changes in another class.
Therefore i have some sort of timer that starts on my first onCreate()
private void startTimer() {
mHandler.removeCallbacks(mUpdateTimeTask);
mHandler.postDelayed(mUpdateTimeTask, 2000); // first run after 2 secs
}
private Runnable mUpdateTimeTask = new Runnable() {
public void run() {
connectionControl.checkNetworkState();
System.out.println("online?: " + connectionControl.isOnline());
mHandler.postDelayed(this, 15000); // repeat every x/1000 secs
}
};
My class ConnectionControl has a boolean, that gets set to either true or false whether my app can reach a specific http-host.
Now my question is: how can i achieve a automatic change on the ImageView in my Activity to display the boolean's value?
I already looked at Observer-Pattern, EventListener, BroadcastReciever but I'm stuck at finding the right solution.
Also, i want to use the listening in other Activities.
What has worked so far was starting/stopping the timer-thing for each activity and have the ImageView update inside the run() method. But my guess is, there has to be a way around the redundancy.
I think you are in search of AsyncTask , by using it you can made a call to webservice asynchronously and after the successful call to the webservice, we can process the fetched data.
Just go through this article: http://developer.android.com/resources/articles/painless-threading.html.
I think you needs this: (from android.com)
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
For more info about AsyncTask , here is link: http://developer.android.com/reference/android/os/AsyncTask.html
Here is how I did it.
I made a layout file for the custom titlebar thing, that has an ImageView.
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="wrap_content">
<ImageView android:id="#+id/title_statusimg"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:src="#drawable/connected" android:layout_centerVertical="true"
android:layout_alignParentRight="true" />
</RelativeLayout>
</merge>
I include this in every activities layout file i want to use it.
<include layout="#layout/titlebar" />
Also, i made a StatusActivity that extends Activity. It receives a Broadcast that is send from whenever an background thread detects a change in the connectivity to the webserver.
public class StatusActivity extends Activity {
private String CONNECTION_STATUS = "app.CONNECTION_STATUS";
private ImageView conn;
#Override
protected void onResume() {
super.onResume();
IntentFilter iFilter = new IntentFilter(CONNECTION_STATUS);
registerReceiver(mBroadcastReceiver, iFilter);
conn = (ImageView) findViewById(R.id.title_statusimg);
if (ConnectionControl.isOnline()) {
conn.setImageResource(R.drawable.connected);
} else {
conn.setImageResource(R.drawable.nconnected);
}
}
#Override
protected void onPause() {
super.onPause();
unregisterReceiver(mBroadcastReceiver);
}
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (ConnectionControl.isOnline()) {
conn.setImageResource(R.drawable.connected);
} else {
conn.setImageResource(R.drawable.nconnected);
}
}
};
}
Every Activity that shall use this Status-Image extends StatusActivity instead of Activity.
Hope i covered everything, atleast for me it's working.