Why onActivityResult() is blocked by worker thread? - java

I'm a newbie in multithreading, and can't understand why onActivityResult() is never called in my code.
I need to pause a thread until my needed variable is changed. I call java function ShowFileDialog() from cocos2d-x using jni, and it's called from a thread other than UI thread. Then I start activity to pick an image from gallery, and all this is done on UI thread. After that I need to wait until filePath variable is changed, and when it will happen - return filePath string to cocos2d-x.
This is my code:
private static MyActivity thisActivity;
private static String filePath;
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
thisActivity = this;
}
public static String ShowFileDialog() {
filePath = new String();
thisActivity.runOnUiThread(new Runnable() {
public void run() {
Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);
photoPickerIntent.setType("image/*");
thisActivity.startActivityForResult(photoPickerIntent, 1);
}
});
synchronized(filePath)
{
try {
filePath.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return filePath;
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
...
filePath.notifyAll();
}
Activity from Intent is started, but onActivityResult() is never called. I can't understand why it happens - does it mean that UI thread is somehow blocked?

filePath.wait() blocks your GLThread in GLSurfaceView because Cocos2d-x mainloop was called from the GLThread. And calling startActivityForResult() will cause to call onPause() of the current active Activity, it's Cocos2dxActivity. Cocos2dxActivity.onPause() will call GLSurfaceView.onPause(). GLSurfaceView.onPause() will call sGLThreadManager.wait(). But the GLThread was blocked by your code. Thus the deadlock will occur.
I recommend you to use callback mechanism instead of just waiting on GLThread. In this case, you might be able to use this CCImagePicker.
https://github.com/stubma/cocos2dx-better/blob/master/cocos2dx-better/java/org/cocos2dx/lib/CCImagePicker.java
https://github.com/stubma/cocos2dx-better/blob/master/cocos2dx-better/java/org/cocos2dx/lib/ImagePickerActivity.java

Related

Android - StartActivityForResult on finish() method freezes UI

I have the following situation:
Activity A starts Activity B, by using startActivityForResult
Activity B then returns an ArrayList of Strings to Activity A by using on finish().
Here is a code example of what exactly Activity B does:
ArrayList<String> urls = new ArrayList<>();
urls.add("Some string");
Intent intent = new Intent();
intent.putStringArrayListExtra(KEY, urls);
setResult(Activity.RESULT_OK, intent);
finish();
Then Activity A receive the data in onActivityResult(...)
The issue I have is that when the user taps the done button and Activity B's code example executes, Activity B freezes for about 3 seconds (when I have about 2 strings in the ArrayList). The more strings I have in the ArrayList the longer it freezes. I have more or less determined that it is finish() that causes the UI thread to freeze.
Is there a way to call finish() without freezing Activity B? If not, why is this happening?
EDIT:
Here is the full example:
/**
* Background task
*/
private class gatherUrlsTask extends AsyncTask<ArrayList<PictureEntry>, Integer, Intent> {
#Override
protected void onPreExecute() {
progressBar.setVisibility(View.VISIBLE);
bt_done.setVisibility(View.GONE);
fab_add_picture.setVisibility(View.GONE);
}
#SafeVarargs
#Override
protected final Intent doInBackground(ArrayList<PictureEntry>... params) {
ArrayList<String> imagePaths = new ArrayList<>();
for (PictureEntry pictureEntry : params[0]) {
if (pictureEntry.isSelected()) {
imagePaths.add(pictureEntry.getPath());
}
}
if (imagePaths.size() == 0) {
Toast.makeText(getApplicationContext(), R.string.please_select_atleast_one_image, Toast.LENGTH_LONG).show();
return null;
} else {
Intent intent = new Intent();
intent.putStringArrayListExtra(SELECTED_IMAGES_KEY, imagePaths);
return intent;
}
}
#Override
protected void onPostExecute(Intent intent) {
setResult(Activity.RESULT_OK, intent);
finish();
}
}
However I can remove everything from the AsyncTask since it did not have any effect on performance.
i don't know what is cause of that, but for prevent ui getting freezed, use asyncTask and then in the onPostExcecute call finish()

setActivityForResult from Fragment returns null data

I have a Fragment MainFragment and I do:
Intent i = new Intent(getActivity(), PersonActivity.class);
startActivityForResult(i, 0);
The activity starts ok and it starts its own PersonFragment and inside the PersonFragment I do:
#Override
public void onDestroy() {
super.onDestroy();
Intent i = new Intent();
i.putExtra(PERSON_ID_EXTRA, getPersonId());
i.putParcelableArrayListExtra(PERSON_CONTACT_LIST, (ArrayList<? extends Parcelable>) contactFriends);
getActivity().setResult(Activity.RESULT_OK, i);
}
Back in my MainFragment I do:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if ( requestCode != 0) {
return;
}
int personId = data.getIntExtra(PERSON_ID_EXTRA, -1);
List<Person> contacts = data.getParcelableArrayListExtra(PERSON_CONTACT_LIST);
for(Person p:contacts) {
Log.d("APP", p.getFullName());
}
}
I see that the code goes to onActivityResult but the data is null. What am I messing up here?
Update:
I see that pressing back button does not call onDestroy().
But where am all examples I saw used getActivity.finish() and I don't want to finish the activity. Only when the user presses e.g. back send the data
Update2:
I added the following and I go through that code but the Intent data in the result onActivityResult is still null
#Override
public void onPause() {
super.onPause();
Intent i = new Intent();
i.putExtra(PERSON_ID_EXTRA, getPersonId());
i.putParcelableArrayListExtra(PERSON_CONTACT_LIST, (ArrayList<? extends Parcelable>) contactFriends);
getActivity().setResult(Activity.RESULT_OK, i);
}
From the Activity documentation about finish:
Call this when your activity is done and should be closed. The
ActivityResult is propagated back to whoever launched you via
onActivityResult().
From the documentation about onActivityResult:
Called when an activity you launched exits, giving you the requestCode
you started it with, the resultCode it returned, and any additional
data from it.
So, onActivityResult will only be called when the second activity finishes.
If you don't want to finish your PersonActivity to send the result to your main activity, then you may want to start another intent to send the data or pass the data using static fields (not a best practice at all).
In your case, to set your result when you press the back button, you can write a code like this:
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
Intent i = new Intent();
i.putExtra(PERSON_ID_EXTRA, getPersonId());
i.putParcelableArrayListExtra(PERSON_CONTACT_LIST, (ArrayList<? extends Parcelable>) contactFriends);
getActivity().setResult(Activity.RESULT_OK, i);
finish();
return true;
}
return false;
}
Follow up based on the comments:
The code for the finish() on the Activity class looks as follows:
// Call this when your activity is done
// and should be closed. The ActivityResult
// is propagated back to whoever launched
// you via onActivityResult().
public void finish() {
if (mParent == null) {
int resultCode;
Intent resultData;
synchronized (this) {
resultCode = mResultCode;
resultData = mResultData;
}
if (false) Log.v(TAG, "Finishing self: token=" + mToken);
try {
if (resultData != null) {
resultData.prepareToLeaveProcess();
}
if (ActivityManagerNative.getDefault()
.finishActivity(mToken, resultCode, resultData)) {
mFinished = true;
}
} catch (RemoteException e) {
// Empty
}
} else {
mParent.finishFromChild(this);
}
}
Here you can see that is the responsibility for setting the result values lays on the finish() method.
Then, before the Activity is destroyed, the onDestroy() method is called. That is the reason why setting the result of the Activity on the onDestroy() method won't work.

android waiting on something

I was wondering how to make a program pause and wait for an event to finish before starting another event, for example I could have a text to speech that says something and right after that a google voice recognizer should fire, however they both fire at the same time and makes the speech recognizer listen to the text to speech. I tried searching on here about it and found some answers but they were not really clear for me, can anyone help me with it?
here is an example code i tested out :
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_menu);
tts=new TextToSpeech(getApplicationContext(),
new TextToSpeech.OnInitListener() {
#SuppressWarnings("deprecation")
#Override
public void onInit(int status) {
if(status != TextToSpeech.ERROR){
tts.setLanguage(Locale.UK);
tts.speak("Welcome", TextToSpeech.QUEUE_FLUSH, null);
}
}
});
if(!(tts.isSpeaking())){
startVoiceRecognitionActivity();
}
}
private void startVoiceRecognitionActivity()
{
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Speak Up");
startActivityForResult(intent, REQUEST_CODE);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK)
{
matches = data.getStringArrayListExtra(
RecognizerIntent.EXTRA_RESULTS);
if (matches.contains("list")){
Intent gotoList = new Intent(MenuActivity.this, ListActivity.class );
startActivity(gotoList);
}
}
super.onActivityResult(requestCode, resultCode, data);
}
Welcome to the wonderful world of asynchronous events.
In order to do what you want, the approach is not to "wait" (because everything would then freeze), but to "listen".
When something takes long in Android, such as the Text To Speech, you have to listen for a event.
If you look at the docs for the Text To Speech object, you'll find it accepts a listener for different things: http://developer.android.com/reference/android/speech/tts/TextToSpeech.html#setOnUtteranceProgressListener(android.speech.tts.UtteranceProgressListener) and http://developer.android.com/reference/android/speech/tts/UtteranceProgressListener.html
So you'd do (assuming your Text to Speech object is tts):
tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
public void onDone(String utteranceId) {
// The text has been read, do your next action
}
public void onError(String utteranceId, int errorCode) {
// Error reading the text with code errorCode, can mean a lot of things
}
public void onStart(String utteranceId) {
// Reading of a sentence has just started. You could for example print it on the screen
}
});
This is called "subscribing to an event", and is asynchronous because your program can do other things and you'll be notified when "something" (what you have subscribed to) happens.
Then for example when you do
tts.speak ("welcome", ....)
And it finishes, the method onDone in your listener will be called. You can start the voice recognizer there.

NullPointerException when using getInt(String)

I'm android beginner so please be easy on me. I'm doing some "exercises" and i'm writing simple app which will tell RSSI strength of home wifi network. Getting that number is pretty easy, but updating it and showing that on screen it's a little more complicated as i thought.
First this is my onCreate Activity. In this activity i'm launching another android component - Service. Because the code will run in background (i know i could use thread or something else, but this is for "practice" sake, and i have a few ideas what to do with this app, while running service and not interacting with UI )
public class MainActivity extends Activity {
TextView wifi_check;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
referenceViews();
startService(new Intent(this, CheckingWifiService.class));
//wifi_check.setText(""+getIntent().getExtras().getInt("RSSI"));
}
private void referenceViews() {
wifi_check = (TextView) findViewById(R.id.wifiCheck_TV);
}
}
Because my code will run every second or so, i will use TimerTask for this purpose. And here is my TimerTask class, which includes run() method, and code for executing inside
public class TimerTsk extends TimerTask {
Context act;
WifiManager wifiMan;
WifiInfo info;
Bundle sendInfo;
Intent intent;
int rssi;
public TimerTsk(Context context) {
act = context;
}
#Override
public void run() {
intent = new Intent();
sendInfo = new Bundle();
wifiMan = (WifiManager) act.getSystemService(Activity.WIFI_SERVICE);
info = wifiMan.getConnectionInfo();
rssi = info.getRssi();
Log.d("WORKED", "RUNNING SUCESSFULLY");
// i want to send info to my activity
sendInfo.putInt("RSSI", rssi);
intent.putExtras(sendInfo);
}
}
From this class , i want to send result of RSSI to my activity and then update a text. But when i call this code below, on activity i always get NullPointerException.
wifi_check.setText(""+getIntent().getExtras().getInt("RSSI"));
To be honest i had hard time figuring out which part of code is throwing an exepction. And i found that more exactly, this part of code is throwing an exepction.
getInt("RSSI")
Overall i see that service is running, because in my LOGCAT i see a message that i create with Log.d in TimerTsk class.
Any ideas why is this happening?
Here is my service class:
public class CheckingWifiService extends Service{
int rssi;
Timer time;
TimerTsk ttsk;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
time = new Timer();
time.schedule(new TimerTsk(getApplicationContext()), 500);
return START_STICKY;
}
}
Here is my LogCat:
I see a common mistake. Don't do this:
sendInfo.putInt("RSSI", rssi);
intent.putExtras(sendInfo); // This adds a Bundle to your existing Bundle!
You are creating an Intent, with a Bundle of extras, with a Bundle that holds rssi. Leave out this unnecessary Bundle:
intent.putExtras("RSSI", rssi);
Now in your next Activity you can use:
getIntent().getIntExtra("RSSI", 0);
However you should always check to make sure there aren't any surprise null variables:
Intent in = getIntent();
if(in != null) {
int rssi = in.getIntExtra("RSSI", -1);
if(rssi < 0)
wifi_check.setText(""+rssi);
else
wifi_check.setText("Unknown");
}
is your activity starting? I don't see any call to startActivity(). In any case as mentioned by Sam you just need to call putExtra for your intent. don't forget to call
is your activity starting? I don't see any call to startActivity(). In any case as mentioned by Sam you just need to call putExtra for your intent. don't forget to call
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
you need to put this flag when start activies from background

Stop method execution until another activity finished

Good day to everyone!
I need to stop method execution until another activity will end.
At the moment I'm trying to do it in this way:
private boolean isPausedWhileSplash;
public void showSplashWorldChangeAd(String oldWorldName, String newWorldName) {
overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
Intent intent = new Intent(this, SplashScreen.class);
intent.putExtra(SplashScreen.MSG_STRING_KEY, oldWorldName + " -> "
+ newWorldName);
intent.putExtra(SplashScreen.OLD_WORLD, oldWorldName);
intent.putExtra(SplashScreen.NEW_WORLD, newWorldName);
startActivityForResult(intent, RESULT_OK);
isPausedWhileSplash = true;
while (isPausedWhileSplash) {
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
isPausedWhileSplash = false;
}
But it's not working.
Can you help me?
Thanks!
UPD: Maybe there is any way to prevent view from drawing? Because all what I need right now is delay calling of methods, which will redraw view of this activity. Now I have the new world drawn before the splash screen, saying about the world change, is shown, which is not looking good.
I'm a little rushed for time so here's a generic answer:
public class MonitorObject{
}
public class MyWaitNotify{
MonitorObject myMonitorObject = new MonitorObject(); //To be used for synch
public void doWait(){
synchronized(myMonitorObject){
try{
myMonitorObject.wait(); // Wait until the notify method is called by another thread
} catch(InterruptedException e){...}
}
}
public void doNotify(){ //Notify waiting threads that they can continue
synchronized(myMonitorObject){
myMonitorObject.notify();
}
}
}
I'll come back and get you a working example this afternoon if you haven't got a solution by then...
This article should get you started
EDIT: This article demonstrates other approaches, all of which should be an improvement on your current solution. It talks you through how to update the UI from events in different threads and the benefits/costs of the various solutions.
I guess you are trying to do something like this:
public void showSplashWorldChangeAd(String oldWorldName, String newWorldName){
/* You have done some initialization work */
startActivityForResult(intent, RESULT_OK);
/* This is what you want to do after the activity returns */
afterActivityReturns();
}
Then why not proceed this way?
public void showSplashWorldChangeAd(String oldWorldName, String newWorldName){
/* You have done some initialization task here*/
startActivityForResult(intent, RESULT_OK);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
/* This is what you want to do after the activity finishes */
afterActivityReturns();
}
But if you really want to stop the showSplashWorldChangeAd method for some reason, you need lock and key (as Basic says).

Categories

Resources