Espresso - Taking screenshot during an AsyncTask - android

I'm currently using Espresso to programmatically take screenshots of an Android app. I'm successfully using
android.support.test.runner.screenshot.Screenshot
Problem is, given the following assumptions from official Espresso docs:
The message queue is empty.
There are no instances of AsyncTask currently executing a task.
All developer-defined idling resources are idle.
By performing these checks, Espresso substantially increases the likelihood that only one UI action or assertion can occur at any given time. This capability gives you more reliable and dependable test results.
I can't perform any test while any AsyncTask is running. I need to take some screenshots while my AsyncTask is running, but can't get my head around the docs on how to perform such task. The most similar SO thread I found doesn't seem to work, don't know if this is because the AsyncTask is too fast to take the screenshot.
// Start the async task
onView(withId(R.id.start_task_button)).perform(click());
// Then "press" the back button (in the ui thread of the app under test)
mActivityTestRule.runOnUiThread(new Runnable() {
#Override
public void run() {
mScreenshotWatcher.captureScreenshot("screenshot1");
}
});
Any clue about this? Is this the correct approach or should I implement IdlingResource in my AsyncTask?
thanks
nicola

From how I understood your question you'd like to take a screenshot when there's an AsyncTask in progress.
I created a minimum Android app to verify your scenario and it didn't work when click() from Espresso was used. The problem is that once Espresso performs its click() it invokes loopMainThreadUntilIdle() afterwards. As your AsyncTask has started at this point already the loop inside UiControllerImpl#loopMainThreadUntilIdle() gets run over and over again until the AsyncTask has finished.
The easiest way to overcome this is to use a custom ViewMatcher making a click and returning immediately.
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.test_edit_text).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
v.setVisibility(View.INVISIBLE);
new AsyncTask<Object, Object, Object>() {
#Override
protected Object doInBackground(Object... objects) {
SystemClock.sleep(20000000);
return null;
}
#Override
protected void onPostExecute(Object o) {
findViewById(R.id.test_edit_text)
.setVisibility(View.VISIBLE);
}
}.execute();
}});
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.question.stackoverflow"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
MainActivityTest.java
#RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
#Rule
public ActivityTestRule<MainActivity> activityActivityTestRule = new ActivityTestRule<MainActivity>(MainActivity.class);
#Test
public void testTakeScreenshot() throws Exception {
Espresso.onView(withId(R.id.test_edit_text)).check(matches(isDisplayed()));
// use a custom ViewAction here because this call has be non-blocking
Espresso.onView(withId(R.id.test_edit_text)).perform(new ViewAction() {
#Override
public Matcher<View> getConstraints() {
return isDisplayingAtLeast(90);
}
#Override
public String getDescription() {
return "NonBlockingTap";
}
#Override
public void perform(UiController uiController, View view) {
view.performClick();
}
});
ScreenCapture screenCapture = Screenshot.capture();
screenCapture.setFormat(Bitmap.CompressFormat.PNG);
screenCapture.setName("test.png");
screenCapture.process();
Espresso.onView(withId(R.id.test_edit_text)).check(matches(isDisplayed()));
}
}

Related

onBackPressed() Internet no longer detected

I have an application that detects if internet is available or not.
I have changed the behaviour of the back button to avoid closing the application when pressed. The issue I'm facing is that when I have 4G or WIFI running and I run the app, it detects that internet is available, however, when I press the back button, the app is put on the background and internet is no longer detected by it.
I noticed that if I remove the permissions in the manifest file, the application works fine, it detects internet both when it is running and when put on background, but I need those permissions for making http post requests later.
Any help on this please?
Thanks.
MainActivity.java
public class MainActivity extends AppCompatActivity {
Timer timer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
}
#Override
public void onBackPressed() {
this.moveTaskToBack(true);
}
private void init() {
timer = new Timer();
Tasker task = new Tasker(MainActivity.this, timer);
task.execute();
}
}
Tasker .java
public class Tasker extends AsyncTask<Void,Void,String> {
final Handler handler = new Handler();
static Timer timer;
Context context;
Toast toast;
private TimerTask doAsynchronousTask;
public Tasker(MainActivity context, Timer timer) {
this.timer = timer;
this.context = context;
}
#Override
protected void onPreExecute() {
}
#Override
protected String doInBackground(Void... voids) {
doTimerTask();
return null;
}
#Override
protected void onPostExecute(String response) {
}
private void showToast(String message){
toast = Toast.makeText(context, message, Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, 0, 0);
if(!((Activity) context).isFinishing()){
toast.show();
}
}
private void doTimerTask() {
doAsynchronousTask = new TimerTask() {
#Override
public void run() {
handler.post(new Runnable() {
#SuppressWarnings("unchecked")
public void run() {
if(haveNetworkConnection()){
showToast("Online");
}else{
showToast("Offline");
}
}
});
}
};
timer.schedule(doAsynchronousTask, 0, 10000);
}
private boolean haveNetworkConnection() {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
return isConnected;
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.e.myapplication">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
Actually, it may not be your desirable answer, but I think there is a way simpler way to do what you really want to do using a Broadcast receiver. you don't need to check network state using the timer. here's the link
I would suggest you to use a WorkManager or AlarmManager, or even Services for background related periodic tasks.
Using the Timer-AsyncTask combination is not a feasible approach and it could lead to many problems, one of which being it could easily be terminated by Android once it's in background.
My top pick would be WorkManager as it handles the condition for the tasks for you nicely, for example, if you want to only launch your task if there's internet connection, then you simply add a Work Constraint to your work request like this
Constraint networkConstraint = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
workRequest.setConstraints(constraints)
And your task will only be executed if the network is connected. BTW, they don't need to be manually re-set after reboot like AlarmManagers does.
However, there are downsides to it, one of them being the perodic requests can only occur every 15 minutes, which you can circumvent (though not recommended) by setting more one time work requests in each perodic request with smaller intervals of delayed start time.
Anyways, stop using the AsyncTask approach you are using now, and use one of the "legitimate" approaches Android approves to do background tasks, I recommend WorkManager, but based on your situation, you can choose whatever approach you like as long as it's meant to work in background.

Unable to update UI from IntentService

A seemingly simple requirement - to update UI from an IntentService. In the shot below, when the Start Service button is clicked, I need to show a ProgressBar above the only TextView and after a delay, remove the ProgressBar.
Found a lot of answers on SO, but somehow unable to still crack it. I understand from this that LocalBroadcastManager is a good way to go. I have also tried following this, (an approach with Handlers), but it fails to show the ProgressBar too. Finally, based on this answer, here's what I have ended up with. The output I've managed so far is just the Toasts appearing in succession, after all the logging is done.
Greatly appreciate if you could point out where I am going wrong, been struggling for quite some time now. Many thanks in advance!
MainActivity updated
public class MainActivity extends AppCompatActivity
{
private static final String TAG = "MainActivity";
private ProgressBar pb;
private MyBroadcastReceiver myBroadcastReceiver;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pb = (ProgressBar) findViewById(R.id.pb);
myBroadcastReceiver = new MyBroadcastReceiver();
LocalBroadcastManager.getInstance(this).registerReceiver(myBroadcastReceiver, new IntentFilter("ACTION"));
}
private void updateUI(boolean show)
{
if (show)
pb.setVisibility(View.VISIBLE);
else
pb.setVisibility(View.GONE);
// Toast.makeText(this, "UI Updated...", Toast.LENGTH_LONG).show();
}
public void startIt(View view)
{
Intent intent = new Intent(this, NanisIntentService.class);
startService(intent);
}
public class MyBroadcastReceiver extends BroadcastReceiver
{
#Override
public void onReceive(final Context context, Intent intent)
{
String action = intent.getAction();
Log.e(TAG, "In onReceive(): " + action);
if (action.equals("ACTION"))
{
updateUI(true);
} // of if (action = "ACTION")
else if (action.equals("NOITCA"))
{
updateUI(false);
} // of else of if (action = "ACTION")
} // of onReceive()
} // of class MyBroadcastReceiver
}
IntentService updated
public class NanisIntentService extends IntentService
{
private static final String TAG = "NanisIntentService";
public NanisIntentService()
{
super("NanisIntentService");
}
#Override
protected void onHandleIntent(Intent intent)
{
Log.e(TAG, "In onHandleIntent(): Intent is being serviced");
LocalBroadcastManager.getInstance(testing.com.myintentservice.NanisIntentService.this).sendBroadcast(new Intent().setAction("ACTION"));
int i = 0;
while (i <= 50)
{
try
{
Thread.sleep(50);
i++;
Log.e("", "" + i);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
#Override
public void onDestroy()
{
super.onDestroy();
LocalBroadcastManager.getInstance(testing.com.myintentservice.NanisIntentService.this).sendBroadcast(new Intent().setAction("NOITCA"));
Log.e(TAG, "In onDestroy(): The service has been destroyed");
}
}
#Override
public void onStart(Intent intent, int startId)
{
super.onStart(intent, startId);
LocalBroadcastManager.getInstance(testing.com.myintentservice.NanisIntentService.this).sendBroadcast(new Intent().setAction("ACTION"));
}
#Override
public void onDestroy()
{
super.onDestroy();
LocalBroadcastManager.getInstance(testing.com.myintentservice.NanisIntentService.this).sendBroadcast(new Intent().setAction("NOITCA"));
Log.e(TAG, "In onDestroy(): The service has been destroyed");
}
}
#Override
public void onDestroy()
{
super.onDestroy();
LocalBroadcastManager.getInstance(testing.com.myintentservice.NanisIntentService.this).sendBroadcast(new Intent().setAction("NOITCA"));
Log.e(TAG, "In onDestroy(): The service has been destroyed");
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="testing.com.myintentservice.MainActivity">
<ProgressBar
android:id="#+id/pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="#+id/tv"
android:layout_centerHorizontal="true"
android:indeterminate="true"
android:visibility="gone"/>
<TextView
android:id="#+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Hello IntentService!"
android:textColor="#1298CE"
android:textSize="32sp"/>
<Button
android:id="#+id/bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="Start Service"
android:onClick="startIt"/>
</RelativeLayout>
AndroidManifest
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name="testing.com.myintentservice.NanisIntentService"/>
</application>
First, you are tying up the main application thread for ~2.5 seconds. This will freeze your UI during this period of time. Do not do this.
Second, you are calling updateUI() once before the ~2.5 seconds, and once after. Since you are tying up the main application thread during that time, this will have the same visual effect as calling updateUI() twice in succession after the delay. updateUI() toggles the ProgressBar visibility, so two calls will cancel each other out, leaving you in the same state as you started.
I need to show a ProgressBar above the only TextView and after a delay, remove the ProgressBar.
Showing a ProgressBar for 2.5 seconds, irrespective of any actual work being done, is rather arbitrary.
That being said, call updateUI() once, then use pb.postDelayed() to schedule a Runnable to run 2500 milliseconds later, where the run() method in the Runnable calls updateUI() the second time. This avoids you blocking the main application thread, so it allows the first updateUI() call to take effect, while still giving you the 2.5-second duration.
Thanks so much, #CommonsWare! Finally got it to work. It needed two receivers for the two different actions, which I discovered here.
For the benefit of those looking for the final working code...
MainActivity
NanisIntentService
Result screens:- after clicking and after processing
Pardon any copy-paste ghosts in the code, it is after all a PoC, albeit functional.

How to connect to a pending request with RoboSpice (Splash > Main activity)?

I have fews questions about RoboSpice (advanced usages).
Note: I use RoboSpice with OrmLite.
My Android application is composed of two main activities: the first one is SplashActivity (start on launch) and the second is MainActivity (lauched 5s after the Splash Screen). I perform 2 requests on splash:
SplashActivity
public class SplashActivity extends BaseActivity {
private fooRequest fooRequest;
private barRequest barRequest;
private exampleRequest exampleRequest;
private NewsRequest newsRequest;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
fooRequest = new fooRequest();
barRequest = new barRequest();
...
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// Launch MainActivity...
}
}, 5000);
}
#Override
protected void onStart() {
super.onStart();
getSpiceManager().execute(fooRequest, new Integer(0), DurationInMillis.ONE_DAY, new fooRequestListener());
getSpiceManager().execute(barRequest, new Integer(1), DurationInMillis.ONE_DAY, new barRequestListener());
}
public final class fooRequestListener implements RequestListener<Foo> {
#Override
public void onRequestFailure(SpiceException spiceException) {
Log.w("MyApp", "RoboSpice - Foo request failure");
}
#Override
public void onRequestSuccess(final Foo result) {
}
}
public final class barRequestListener implements RequestListener<Bar> {
#Override
public void onRequestFailure(SpiceException spiceException) {
Log.w("MyApp", "RoboSpice - Bar request failure");
}
#Override
public void onRequestSuccess(final Bar result) {
}
}
}
The problem
My application logic is not really reliable: we can not be sure that requests are finished when MainAcitivty is launched. On the MainActivity I query my database with OrmLite for fetch some data and display them. So if request started on SplashActivity are not finished, my View display nothing.
Questions
1) I think that I need to add a listener to my pending requests (if such a request exists). On the RoboSpice Wiki, it said to use spiceManager.addListenerToPendingRequest. I have not managed to put it out, despite my tests. Maybe I'm doing something wrong? Can you give me code example? Resolved (see below)
2) Currently, the user is still waiting 5 seconds (timer Splash) before arriving at the home screen. How to check if data are in cache? With spiceManager.getDataFromCache() (it takes into account the expiration duration?).
3) What is the best retry policy to failed requests (a. at the first launched if database is not again created; b. if the database exists but data are expired)?
Edit
Question #1 resolved (I make a mistake in my original code) - #2 and #3 still relevant. Here's what to do (if it can help someone ...):
public class MainActivity extends BaseActivity {
private SpiceManager spiceManager = new SpiceManager(CalendarSpiceService.class);
...
#Override
protected void onStart() {
super.onStart();
spiceManager.start(this);
spiceManager.addListenerIfPending(Foo.class, new Integer(0), new fooRequestListener());
spiceManager.addListenerIfPending(Bar.class, new Integer(2), new newsRequestListener());
spiceManager.getFromCache(Foo.class, new Integer(0), DurationInMillis.ONE_DAY, new fooRequestListener());
spiceManager.getFromCache(Bar.class, new Integer(2), DurationInMillis.ONE_DAY, new newsRequestListener());
}
...
public final class fooRequestListener implements RequestListener<Foo> {
#Override
public void onRequestFailure(SpiceException spiceException) {
Log.w("MyApp", "RoboSpice - Foo request failure");
}
#Override
public void onRequestSuccess(final Foo result) {
String test = result.getResult().iterator().next().getTitle();
Log.w("MyApp", "RoboSpice - Foo request OK ! "+test);
}
}
public final class barRequestListener implements RequestListener<Bar> {
#Override
public void onRequestFailure(SpiceException spiceException) {
Log.w("MyApp", "RoboSpice - Bar request failure");
}
#Override
public void onRequestSuccess(final Bar result) {
Log.w("MyApp", "RoboSpice - Bar request OK ! "+test);
}
}
}
But I don't understand the aim of spiceManager.getFromCache here...
Thanks!
I would make the splash screen last longer. It looks like there is a realy ambiguity on its purpose : displaying a logo or something, or execute requests and display a "please wait message".
In the first case, it would be fine not to execute any request. In the second, it would be fine to wait for them to return.
I really prefer applications that, if they need a splashscreen at all, boot quickly. The splash screen should last less than 1 second. The first screen after it should handle the data querying and please wait message.
But to answer your questions :
2) yes, SpiceManager cache methods use cache expiry in the same way as executeRequest. You can check if there is something in the cache, but you will have to specify what you mean by "valid" data by specifying the expiry limit of the data.
3) I don't see the link with a retry policy and your overall problem. By default, RoboSpice has a retry policy to retry 3 times a failed request. If you can't get the data from your cache then it means there is nothing in it. If your listener's failure hook is invoked, then the network request failed.
Both could be the same listener, and your app should have a general mechanism to 1) display that something is wrong, 2) relaunch request if needed after a while (that could be a refresh button/item menu).
I hope I helped, but not so sure.

Android AsyncTask onPostExecute off of main ui thread

I'm having an issue with AsyncTask and onPostExecute. I am finding that onPostExecute is executing on a different thread than the main ui thread, which is causing a CalledFromWrongThreadException to happen when I modify any views.
I put in some logging to see what threads onPreExecute, doInBackground, and onPostExecute are running on. I would see a result like this...
onPreExecute ThreadId: 1
doInBackground ThreadId: 25
onPostExecute ThreadId: 18
I believe the main ui thread id is 1 and I would expect both onPre and onPost to both execute on thread 1. I am making sure to create and also call the execute method from the ui thread (for example in onCreate of an Activity).
Another thing to note that I have noticed is that later async tasks will run their onPostExecute method on the same thread as previous async task onPostExecute methods (in this case thread 18).
Right now in order to get around this I am wrapping the code in my onPostExecute methods in a call to runOnUiThread, but I think this is hacky and would like to get to the real issue.
I am out of ideas! Any one have any insight? I'm happy to answer any questions that could helper with further investigation!
EDIT:
There are two ways that async tasks are being run in the code. I am wondering if the latter in these examples is causing something weird to happen?
public class SomeActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
new SomeAsyncTask().execute();
}
private class SomeAsyncTask extends AsyncTask<String, Void, Integer> {
#Override
public void onPreExecute() {
Thread.currentThread().getId() // 1
//Show a dialog
}
#Override
public Integer doInBackground(String... params) {
Thread.currentThread().getId() // 25
return 0;
}
#Override
public void onPostExecute(Integer result) {
Thread.currentThread().getId() // 18
//hide dialog
//update text view -> CalledFromWrongThreadException!!!
}
}
}
The above seems like a vanilla use of AsyncTask, but I still see this issue occurring even in simple cases like this. The next example uses an async task to run other async tasks. Maybe there is something I don't know about what happens when an async task gets constructed that is causing some weird behavior?
public class SomeActivity extends Activity implements TaskRunner.OnFinishListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
TaskRunner taskRunner = new TaskRunner();
taskRunner.setOnFinishListener(this);
taskRunner.addTask(new SingleTask());
taskRunner.addTask(new SingleTask());
taskRunner.execute();
}
#Override
public void onTaskFinish(List<Integer> results) {
//Thread id is 18 when it should be 1
//do something to a view - CalledFromWrongThreadException!!
}
}
//In a different file
public class SingleTask extends AsyncTask<String, Void, Integer> {
//This is a an async task so we can run it separately as an asynctask
//Or run it on whatever thread runnerExecute is called on
#Override
public Integer doInBackground(String... params) {
return runnerExecute(params);
}
//Can be called outside of doInBackground
public Integer runnerExecute(String... params) {
//some long running task
return 0;
}
}
//In a different file
public class TaskRunner {
private List<SingleTask> tasks;
private OnFinishListener onFinishListener;
public interface OnFinishListener {
public void onTaskFinish(List<Integer> results);
}
public TaskRunner() {
this.tasks = new ArrayList<SingleTask>();
}
public void setOnFinishListener(OnFinishListener listener) {
this.onFinishListener = listener;
}
public void addTask(SingleTask task) {
tasks.add(task);
}
public void executeTasks() {
new RunnerTask().execute((SingleTask[]) tasks.toArray());
}
//Calls the runnerExecute method on each SingleTask
private class RunnerTask extends AsyncTask<SingleTask, Integer, List<Integer>> {
#Override
public void onPreExecute() {
//Runs on thread 1
}
#Override
public List<Integer> doInBackground(SingleTask... params) {
//Runs on arbitrary thread
List<Integer> results = new ArrayList<Integer>();
for(SingleTask task : params) {
int result =task.runnerExecute(task.getParams());
results.add(result);
}
return results;
}
#Override
public void onPostExecute(List<Integer> results) {
//Runs on thread 18
onFinishListener.onTaskFinish(results);
}
}
}
Maybe what is going on here is just super weird, and not at all how async tasks are meant to be used, either way it would be nice to get to the bottom of the issue.
Let me know if you need any more context.
I have been experiencing the same problem and it turned out the the issue was using Flurry 3.2.1. However, the issue is not limited to the Flurry library.
The issue behind the scenes is having the first ever (when the app is loaded for the first time) AsyncTask call from a looper thread which is not the Main UI thread. This call initializes a sHandler static variable in AsyncTask to the wrong thread id, and this id is then used in all subsequent AsyncTask$onPostExecute() calls.
To solve the problem, I call an empty (do-nothing) AsyncTask on first app load, just to initialize AsyncTask correctly.
try using:
getBaseContext().runOnUiThread(new Runnable()
{
#override
public void run()
{
}
});
and write your code inside the run function
The AsyncTask is designed to be used from the main thread. Your problem is the second case, and is that you call execute on the SingleTask from a background thread. You call it in the doInBackground method of RunnerTask. The onPostExecute is then run from the backgroundthread of RunnerTask
Two options for you.
1: Trash RunnerTask, and execute the SingleTasks from you main thread, they'll all run in parallell and you won't know which finishes first, but onPreExecute and onPostExecute is called on the main thread
2: Trash the SingleTask and define them as Runnables instead, then you can run them in sequence in the RunnerTask's doInBackground. They'll all run in the background thread of RunnerTask, in the order you call Run. When it is finished, the onPostExecute of RunnerTask is run on the main thread.
i just tried your code and onPreExecute and onPostExecute does run on the same thread, how do you output the thread id ? try:
Log.d("THREADTEST","PRE"+Long.toString(Thread.currentThread().getId()));
Log.d("THREADTEST","BACKGROUND"+Long.toString(Thread.currentThread().getId()));
Log.d("THREADTEST","POST"+Long.toString(Thread.currentThread().getId()));
P.S. it should be:
new SomeAsyncTask().execute();
and
private class SomeAsyncTask extends AsyncTask<String, Void, Integer> { ... }
you are actually executing the SingleTask from RunnerTask's doinbackground method which is incorrect as asynctask should be executed from a main thread only. You need to relook into the logic which runs the set of SingleTasks from RunnerTask.

How to communicate a service with a running thread in Android

my goal is to lunch a service that will take care for all of the application network needs.
i thought maybe to open 2 sockets for data transfer. i want the data to be handled asynchronously, so i was thinking my be i should run them in two separated threads, each for every socket, and that way the data could be streamed in two different "links" async..
so, i would appreciate two things:
a better overall design. maybe i completely got it all wrong..
can someone explain to me how can i communicate with those threds once i need to pass data to/from them to/from the main service? as far as i learnt (OS) i need to use SIGNALS:) (just kidding..)
Well, I can say i finally got it, just before giving up. Here is a super duper extremely simple app that runs a thread inside an activity and handles a bi-directional communication using two different Handlers for each entity!
The code:
public class MainActivity extends Activity {
//Properties:
private final String TAG = "Activity"; //Log tag
private MyThread mThread; //spawned thread
Bundle myB = new Bundle(); //used for creating the msgs
public Handler mHandler = new Handler(){ //handles the INcoming msgs
#Override public void handleMessage(Message msg)
{
myB = msg.getData();
Log.i(TAG, "Handler got message"+ myB.getInt("THREAD DELIVERY"));
}
};
//Methods:
//--------------------------
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mThread = new MyThread(mHandler);
mThread.start();
sendMsgToThread();
}
//--------------------------
void sendMsgToThread()
{
Message msg = mThread.getHandler().obtainMessage();
myB.putInt("MAIN DELIVERY", 321);
msg.setData(myB);
mThread.getHandler().sendMessage(msg);
}
}
//=========================================================================================
//=========================================================================================
public class MyThread extends Thread{
//Properties:
private final String TAG = "MyThread"; //Log tag
private Handler outHandler; //handles the OUTgoing msgs
Bundle myB = new Bundle(); //used for creating the msgs
private Handler inHandler = new Handler(){ //handles the INcoming msgs
#Override public void handleMessage(Message msg)
{
myB = msg.getData();
Log.i(TAG, "Handler got message"+ myB.getInt("MAIN DELIVERY"));
}
};
//Methods:
//--------------------------
public void run(){
sendMsgToMainThread(); //send to the main activity a msg
Looper.prepare();
Looper.loop();
//after this line nothing happens because of the LOOP!
Log.i(TAG, "Lost message");
}
//--------------------------
public MyThread(Handler mHandler) {
//C-tor that get a reference object to the MainActivity handler.
//this is how we know to whom we need to connect with.
outHandler = mHandler;
}
//--------------------------
public Handler getHandler(){
//a Get method which return the handler which This Thread is connected with.
return inHandler;
}
//--------------------------
private void sendMsgToMainThread(){
Message msg = outHandler.obtainMessage();
myB.putInt("THREAD DELIVERY", 123);
msg.setData(myB);
outHandler.sendMessage(msg);
}
}
//=========================================================================================
//=========================================================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="test.test.namespace"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" />
<application android:icon="#drawable/icon" android:label="#string/app_name">
<activity android:name=".MainActivity" android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
the output is:
01-26 06:25:40.683: I/Activity(19560): Handler got message123
01-26 06:25:40.683: I/MyThread(19560): Handler got message321
I figured this out while reading the offered post by endian, here.
I hope others will find this useful. good luck:)
Here is good post explaining threads and communication using handlers. Also, the same blog has a number of posts regarding various thread constructs in Android.
Another possibility is to use AsyncTasks. Find some explanation here

Categories

Resources