Well, please don't ask me why, but I need to start a synchronous and blocking activity so that the program flow won't continue until it's finished. I know how to do a synchronous Dialog, but how can I do a synchronous activity?
Following are two approaches I tried but failed:
// In the 1st activity, start the 2nd activity in the usual way
startActivity(intent);
Looper.loop(); // but pause the program here
// Program continuse running afer Looper.loop() returns
....
// Then, in the second activity's onBackPressed method:
public void onBackPressed() {
// I was hoping the following quit() will terminate the loop() call in
// the first activity. But it caused an exception "Main thread not allowed
// to quit." Understandable.
new Hanlder().getLooper().quit();
}
I also tried to use another thread to achieve this:
// In the first activity, start a thread
SecondThread m2ndThread = new SecondThread(this);
Thread th = new Thread(m2ndThread, "Second Thread");
th.run();
synchronized(m2ndThread) {
m2ndThread.wait();
}
class SecondThread implements Runnable {
Activity m1stActivity;
SecondThread(Activity a) {
m1stActivity = a;
}
public void run() {
Looper.prepare();
Handler h = new Handler() {
public void handleMessage() {
Intent intent = new Intent(m1stActivity, SecondActivity.class);
m1stActivity.startActivity(intent); // !!!!!!!!!!!!!!
}
}
h.sendEmptyMessage(10); // let it run
Looper.quit();
}
}
However, this approach doesn't work because when I'm using the 1st activity to start the 2nd activity, the main thread is already in wait state and doing nothing. So the 2nd activity didn't even get created. (This is ironical: You have to use an activity to start an activity, but how can you do that when it's already in wait state?)
You can't.
That is all there is to it. You just can't do this. Activities execute on the main thread, always. Your main thread must be actively running its event loop. It is ALWAYS broken to block the main thread's event loop. Always.
All activities are asynchronous. But if you want to block the rest of your application flow until the activity finishes, you can inject a dependency on a flag that the activity sets. The most encapsulated way to do so would be to carry the flag along with the intent/activity/return flow. You could:
Declare the flag in that object's global scope:
boolean syncBlock = false;
int ASYNC_ACTIVITY_REQUEST_CODE = 1; // Arbitrary unique int
In the object's scope that starts the new activity include:
Intent asyncIntent = new Intent(this, AsyncActivity.class);
syncBlock = true;
startActivityForResult(asyncIntent, ASYNC_ACTIVITY_REQUEST_CODE);
while(syncBlock) {}
And in in the object that started the activity:
onActivityResult(int requestCode, int resultCode, Intent data)
{
switch(requestCode)
{
case ASYNC_ACTIVITY_REQUEST_CODE:
{
syncBlock = false;
break;
}
[...]
}
It's a crude hack, and if you're blocking in your UI thread (eg. in your MAIN activity) you're blocking everything, including other UI features your users will probably expect to respond but won't. It's a big nono, and you should learn to go with the async flow that makes apps work the Android way. But if you absolutely must...
Related
I currently have a thread/AsyncTask that I start in Activity A. Before that AsyncTask is started though, Activity A makes an object which takes in Activity A's Context so it can instantiate a NotificationCompat.Builder. The object uses this builder to make/show a notification.
This object is then passed to the AsyncTask as the AsyncTask is created and started. This allows the AsyncTask to update the object (and the notification) with its progress.
Once the AsyncTask has been started though, Activity A calls finish(), and the activity that started it, Activity B, resumes. What I would now like to happen is to have the object send/attempt to send a message to Activity B once the AsyncTask has said it is 100% complete. How can I achieve this?
PS: let me know if a picture would make the scenario more clear.
As far as I have understood your question Activity A is started from Activity B. You can start A from B via startActivityForResult then rather finishing A when AsyncTask starts you finish() A when 100% task is loaded by AsyncTask and send result to A. You can implement callback interface to send result to Activity A from AsyncTask. Now call finish() to Activity A with setResult() and pass the result in intent to Activity B.
You can use the onPostExecute() method of the AsyncTask.
private class DownloadFilesTask extends AsyncTask<Void, Void, MyObject> {
protected Void doInBackground(Void... params) {
//do whatever you need to do
}
protected void onPostExecute(MyObject myObject) {
myObject.sendMessage(activityB);
}
}
Note that onPostExecute() runs on the main UI thread, so it will be able to handle UI updates.
Typing up my own answer both because it was the result of discussion that happened in the comments of my question, and so there is a thorough overview of what I did for a solution.
I ended up going with callbacks as suggested by rusted brain. It is important to note that I am referring to the use of android.os.Handler to create a callback, not the callback pattern that is commonly used with things such as buttons. Anyway, onto the solution.
Step 1 was to set up a handler in Activity B:
messageHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
refreshList();
}
};
Whatever is in handleMessage is what will be called when a message is sent to the handler.
Step 2 is to send a android.os.Messenger object to Activity A. This can easily be done with the Intentused to start Activity A:
Intent intent = new Intent(AlbumGalleryActivity.this, CreateNewPhotoHuntActivity.class);
intent.putExtra("callbackMessenger", new Messenger(messageHandler));
startActivityForResult(intent, Constants.REQUEST_CREATE_NEW_PHOTO_HUNT);
Now, any messages sent by the android.os.Messenger will go to our android.os.Handler.
Step 3, the final step is to create a android.os.Message from the android.os.Messenger which can be sent back to the handler. In the context of my question, this is done inside of the object that controls updating the progress of the notification:
if(totalProgress >= 100) {
notificationBuilder.setContentText("Upload complete! ");
notificationBuilder.setSmallIcon(R.drawable.ic_done_black_24dp);
notificationBuilder.setProgress(0, 0, false);
notificationManager.notify(1, notificationBuilder.build());
Message msg = Message.obtain();
msg.arg1 = 1;
try {
callbackMessenger.send(msg);
}
catch (android.os.RemoteException e1) {
Log.d(Constants.UploadProgressNotificationTag, e1.toString());
}
}
So yep that's it. Feel free to comment if you have a Q or if you see a mistake in what I've posted. Also, I think using Broadcasts would also solve this issue, but that would require a bit more time/effort to implement. Thank you to everyone who suggested a solution!
Under some conditions, when my app starts, it displays an AlertDialog. However, the alert never gets displayed. I discovered that if I add a delay, it works (i.e. gets displayed).
More specifically: on app startup, it executes the main activity onCreate() which under a certain condition starts a 2nd activity. In the 2nd activity, through a separate thread, it makes a check for some web server status. If the Android device doesn't have Internet connectivity, HttpURLConnection returns an error instantly and my enclosing function executes a callback to the 2nd activity. My code then uses post() to attempt to display an alert to the user (using post allows displaying the alert on the UI thread, which is required).
Apparently it tries to display the alert before any of the either activity's UI has been created. If I use postDelayed() in the 2nd activity, the problem still persists. However, if I use the following block of code in the main activity, the alert shows properly:
new Handler().postDelayed (new Runnable ()
{
#Override public void run()
{
Intent intent = new Intent (app, MyClass.class);
app.startActivityForResult (intent, requestCode);
}
}, 3000);
My solution is a hack that happens to work at the moment. I don't mind having a little delay on start-up for this particular situation but I don't want a delay that's longer than necessary or one that may sometimes fail.
What is the proper solution?
Ok, here's a workaround. First, I'll speculate that the problem is that the attempt to display the alert is happening before the looper for the UI thread has been started. Just a speculation.
To work around the problem I added a recursive post which gets called from onResume(), like this:
private boolean paused = true;
#Override public void onResume ()
{
super.onResume();
paused = false;
checkForAlert();
}
#Override public void onPause ()
{
super.onPause();
paused = true;
}
And here's the function that does the post:
private AlertInfo alertInfo = null;
private void checkForAlert()
{
if (alertInfo != null)
{
...code to build alert goes here...
alertInfo = null;
}
if (!paused)
contentView.postDelayed (new Runnable()
{
#Override public void run() { checkForAlert(); }
}, 200);
}
AlertInfo is a simple class where the thread needing the alert can put the relevant info, e.g. title, message.
So, how does this work? checkForAlert() gets called during onResume() and will continue to get called every 200ms until "paused" is false, which happens in onPause(). It's guaranteed to be recurring whenever the activity is displayed. The alert will get built and displayed if alertInfo is not null. In the secondary thread, I simply create an AlertInfo instance and then within 200ms the alert gets displayed. 200ms is short enough that most people won't notice the delay. It could be shorter but then battery use goes up.
Why did I start checkForAlert() in onResume instead of onCreate()? Simply because there's no need for it to run unless the activity is currently "on top". This also helps with battery life.
Ive been struggling with the concept of threads on android. I thought the following code was running on a different thread to the main UI thread but I am not 100% sure so I thought i would come here for clarification as the android docs arent written in any language i understand. below is my code.
public void retrieveImages(final ImagePresenterInt imagepresenter) {
storedImages = new ArrayList<Image>();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
File imagedir = new File("/mnt/shared/images");
File [] myfiles = File.listRoots();
String canread = String.valueOf(imagedir.canRead());
String isfile = String.valueOf(imagedir.isFile());
String isdir = String.valueOf(imagedir.isDirectory());
Log.d("Can Read?","Canread from file :" + canread);
Log.d("is file?","Is a file ? :" + isfile);
Log.d("is dir?","Is a Dir file :" + isdir);
File [] rootfiles =myfiles[0].listFiles();
for (File item : rootfiles)
{
Log.d(item.getAbsolutePath(),item.getName());
}
if(Looper.myLooper() == Looper.getMainLooper())
{
Log.d("main thread ?", "YES");
}
}
}, 2000);
}
my understanding of the above code is that I create a handler. which is associated with the main thread or UI thread. It has a message queue and a looper associated with it. this code is passed to the message queue and run by the looper on a seperate thread to the main UI thread? I could be well wrong here. but mainly I want to know if this is running on the main thread. And how would i get it onto a different thread if not? I tried to verify that the code is running on a different thread using code i found in this question
How to check if current thread is not main thread
this apparently tells me Iam still running in the main thread. thanks for your help
The Handler you create in retrieveImages() is bound to the thread which this function is called from.
The doc on Handler says:
Default constructor associates this handler with the Looper for the current thread. If this thread does not have a looper, this handler won't be able to receive messages so an exception is thrown.
So if retrieveImages() is called from the UI thread, the Handler created in it is also bound to the UI thread.
UPDATE: If you want your code to be executed in different thread, the easiest way is to use AsyncTask.
The Handler is created in the calling thread, which is probably the UI-Thread in your case. If you like to start a new Thread, there are three possibilities I know of: the first is to simple start a new thread:
thread = new Thread() {
#Override
public void run() {
//Do your thing here
}
};
thread.start();
The thread will die, if your Activity gets killed.
The second is to define an IntentService:
public class SimpleIntentService extends IntentService {
public SimpleIntentService() {
super("SimpleIntentService");
}
#Override
protected void onHandleIntent(Intent intent) {
//Do your thing here
}
and start it via
Intent intent = new Intent(this, SimpleIntentService.class);
intent.putExtra("SomeString", "SomeValueYouNeed");
startService(intent);
The IntentService will run on, until onHandleIntent() is done and than close itself.
The third possibility is an AsyncTask:
private class TestTask extends AsyncTask<Datatype1, Datatype2, Datatype3>{
protected Long doInBackground(Datatype1... params) {
// Do your thing here
}
protected void onProgressUpdate(Datatype2... progress) {
//Do a Progress-Bar or something like that
}
protected void onPostExecute(Datatype3 result) {
//Do something, when your work is done
}
}
And in your Activity:
new TestTask().execute(params);
The docs state you shouldn't use Async-Tasks for very long calulations, but I'm not shure why. It might be easier to get your data back to the UI-Thread if you use an Asynctask instead of the Intentservice, but I for myself don't use them very often, so I'm maybe not the best person to ask here.
Edit: I forgot this:
IntentService is executed once for every ntent you pass, the Asynctask will be callable just once.
Furthermore the IntentService has to be declared in the Manifest.
I have a function, AppHelper.isOnline(Context context), I call in various parts of my application to check that a session didn't timeout before making an HTTP request.
public void onClick(View v) {
Intent intent = null;
switch (v.getId()) {
case R.id.buttonPagamenti:
if (AppHelper.isOnline(this))
{
//here AppHelper.isOnline should have finished it's async task
intent = new Intent(this, OrdineCreaActivity.class);
this.startActivityForResult(intent, R.id.buttonPagamenti);
}
break;
...
Inside AppHelper.isOnline(), I am executing an AsyncTask that logs in, thus making a network request, which can't be run on UI because otherwise I get an exception. I need to wait for it to finish BEFORE resuming with the code inside the if. How can I do this ?
Problem is the activity starts firsts, then the AsyncTask executes, so when the activity expects a valid logged in session, it breaks.
You have two options:
Either use the AsyncTask's method get(long timeout, TimeUnit unit) like that:
task.get(1000, TimeUnit.MILLISECONDS);
This will make your main thread wait for the result of the AsyncTask at most 1000 milliseconds (as per #user1028741 comment: actually there is also infinetly waiting method - AsyncTask#get() which might also do the work for you in some cases).
Alternatively you can show a progress dialog in the async task until it finishes. See this thread (No need for me to copy past the code). Basically a progress dialog is shown while the async task runs and is hidden when it finishes.
You have even third option:" if Thread is sufficient for your needs you can just use its join method. However, if the task is taking a long while you will still need to show a progress dialog, otherwise you will get an exception because of the main thread being inactive for too long.
try using
if (AppHelper.isOnline(this))
{
while(!task.isCancelled()){
// waiting until finished protected String[] doInBackground(Void... params)
}
intent = new Intent(this, OrdineCreaActivity.class);
this.startActivityForResult(intent, R.id.buttonPagamenti);
}
For more information read http://developer.android.com/reference/android/os/AsyncTask.html
Rafiq's response did not work for me - the app hung. I think the reason has to do with the nature of isCancelled(): "Returns true if this task was cancelled before it completed normally." If the task completes normally (i.e. is not cancelled) then while(!task.isCancelled()) { } will loop forever.
To solve this create a Boolean flag that you instatiate to false and then flip to true in task.onPostExecute(). Then do while(!flag) { } before switching Activities. Additionally, if you'd like to give the main thread a 'break' to let the AsyncTask process a little faster, you can do try this:
while (!flag) {
try { Thread.sleep(100); }
catch (InterruptedException e) { e.printStackTrace(); }
}
It seems to be working well for me.
intent = new Intent(this, OrdineCreaActivity.class);
context.startActivityForResult(intent, R.id.buttonPagamenti);
Write the above lines in onPostExecute() of you AysncTask. Because if we are using AsyncTask it wont wait there until the task complete.
Android doc says about runOnUiThread: "If the current thread is not the UI thread, the action is posted to the event queue of the UI thread."
My question is, will different activities share the same event queue or each activity has its own event queue?
Suppose activity A starts a thread to do something and finally updates UI using runOnUiThread, but at the same time it starts Activity B like the code below:
public class HelloAndroid extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Thread myThread = new MyThread();
myThread.start();
Intent intent = new Intent(this, B.class);
startActivity(intent);
}
private class MyThread extends Thread {
public void run() {
/* Do somthing expensive */
......
/* Update UI */
HellowAndroid.this.runOnUiThread(new Runnable() {
#Override
public void run() {
/* Do UI update for activity A */;
}
});
}
}
}
What if when the thread is executing the code "HellowAndroid.this.runOnUiThread(new Runnable...)", the visible activity is already B, and the stack is currently A B, with B at the top. Will the code "HellowAndroid.this.runOnUiThread(new Runnable...)" still be executed to update activity A? What will happen? Will activity A's UI be updated or not in this case?
Thanks.
The Activity A thread code will still run and try to update the Activity A UI. But be warned, doing this you are at serious risk of running into runtime errors if the system has stopped your activity for any reason(such as running out of memory.)
It is much better practice to start threads on onResume and stop them again in onPause.