1

I have AsyncTask2 called from AsyncTask1... that's my 'scenario':

  • AsyncTask1 download an rss feed, parse the xml and for every item recognized create&execute AsyncTask2 inside the doInBackground method.
  • AsyncTask2 in the doInBackground method download the enclosure url attribute of the item passed from AsyncTask1 and in onPostExecute method add the item to the global items array and notify the change of items to the associated adapter.

It works fine and not crashing, why? AsyncTasks are supposed to run from the UI thread (threading rules) and now I'm a little confused about this supposition.

Sorry for bad english, I hope question is clear enough.

EDIT Here some code... DownloadRssAsyncTask = AsyncTask2, RssAsyncTask = AsyncTask1

public class ParseActivity extends Activity {

public class FeedItemAdapter extends ArrayAdapter<FeedItem> {

    int resource;

    public FeedItemAdapter(Context context, int resource, List<FeedItem> items) {
        super(context, resource, items);

        this.resource = resource;
    }

    public View getView(int position, View convertView, ViewGroup parent) {

        LinearLayout myView;
        FeedItem item = getItem(position);

        if (convertView == null) {
            myView = new LinearLayout(getContext());
            String inflaterService = Context.LAYOUT_INFLATER_SERVICE;
            LayoutInflater li = (LayoutInflater) getContext().getSystemService(inflaterService);
            li.inflate(resource, myView, true);
        } else {
            myView = (LinearLayout) convertView;
        }

        TextView titleFeedItem = (TextView) myView.findViewById(R.id.itemTitle);
        TextView dateFeedItem = (TextView) myView.findViewById(R.id.itemDate);
        ImageView imageFeedItem = (ImageView) myView.findViewById(R.id.imageThumb);

        titleFeedItem.setText(item.mTitle);
        dateFeedItem.setText(item.mPubDate);
        imageFeedItem.setImageBitmap(item.bitmapEnclosure);

        return myView;
    }
}


private class DownloadRssAsyncTask extends AsyncTask<FeedItem, Void, FeedItem> {

    @Override
    protected FeedItem doInBackground(FeedItem... params) {

        FeedItem item = params[0];
        if (item.mEnclosure == null) {
            Log.i("info: ", "no enclosure tag");
            item.bitmapEnclosure = null;
            return item;
        }

        try {
            URL imageUrl = new URL(item.mEnclosure);
            item.bitmapEnclosure = BitmapFactory.decodeStream(imageUrl.openStream());
        } catch (IOException e) {
            Log.e("error", "download image resource error: "+item.mEnclosure);
            item.bitmapEnclosure = null;
        }

        return item;
    }

    @Override
    protected void onPostExecute(FeedItem result) {
        items.add(result);
        arrayAdapter.notifyDataSetChanged();

        dbHelper.putItem(result.mGuid, result.mTitle, result.mDescription, result.mEnclosure, result.mPubDate);
    }
}


private class RssAsyncTask extends AsyncTask<String, Integer, Void> {

    @Override
    protected Void doInBackground(String... params) {
        int dimParams = params.length;
        for (int i=0; i<dimParams; i++) {
            Log.i("doInBackground", "rss feed num "+ (i+1) + " of "+ dimParams+ ": " + params[i]);
            refreshFeed(params[i]);
        }

        return null;
    }

    @Override
    protected void onPostExecute(Void result) {
        Log.i("onPostExecute in RssAsyncTask", "notifyDataSetChanged");
    }

}

public static class FeedItem {


    public String mAuthor;
    public String mCategory;
    public String mComments;
    public String mDescription; //r
    public String mEnclosure;
    public Bitmap bitmapEnclosure;
    public String mGuid;
    public String mLink;    //r
    public String mPubDate;
    public String mSource;
    public String mTitle;   //r

    public FeedItem() {
        // TODO Auto-generated constructor stub
    }

    @Override
    public String toString() {
        return
            "Data: "+mPubDate+
            "\nLink:\n"+mLink+
            "\nAutore:\n"+mAuthor+
            "\nTitolo:\n"+mTitle+
            "\nEnclosure:\n"+mEnclosure;
    }
}

private FeedReaderDbHelper dbHelper;
private FeedItemAdapter arrayAdapter;
private ArrayList<FeedItem> items;
private ListView myListView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_parse);

    items = new ArrayList<FeedItem>();
    new ArrayList<FeedItem>();

    myListView = (ListView) findViewById(R.id.myListView);
    arrayAdapter = new FeedItemAdapter(this, R.layout.feed_item, items);
    myListView.setAdapter(arrayAdapter);


    dbHelper = new FeedReaderDbHelper(this);

    //RssAsyncTask: download and parsing rss feed
    new RssAsyncTask().execute(getString(R.string.my_feed));

}

public void refreshFeed(String feed) {

    final String TAG = "refreshFeed";

    Log.i(TAG, feed);
    URL url = null;
    try {
        url = new URL(feed);

        HttpURLConnection httpConnection =  (HttpURLConnection) url.openConnection();
        int httpCode = httpConnection.getResponseCode();

        if (httpCode == HttpURLConnection.HTTP_OK) {
            processFeed(httpConnection.getInputStream());
        } else {
            Log.i(TAG, httpCode + httpConnection.getResponseMessage());
        }

    } catch (MalformedURLException e1) {
        Log.i(TAG, "MalformedUrlException in " + feed);
    } catch (IOException e) {
        Log.i(TAG, "IOException in " + url.toString());
    }
}

private void processFeed(InputStream inputStream ) {

    final String TAG = "processFeed";
    final String ITEM = "item";
    final String AUTHOR ="author";
    final String TITLE ="title";
    final String CATEGORY ="category";
    final String COMMENTS ="comments";
    final String DESCRIPTION ="description";
    final String GUID ="guid";
    final String LINK ="link";
    final String PUBDATE="pubDate";
    final String SOURCE ="source";
    final String ENCLOSURE = "enclosure";


    Log.i(TAG, inputStream.toString());
    XmlPullParserFactory pullParserFact;
    try {
        pullParserFact = XmlPullParserFactory.newInstance();
        pullParserFact.setNamespaceAware(true);

        XmlPullParser pullParser = pullParserFact.newPullParser();
        pullParser.setInput(inputStream, null);
        int eventType = pullParser.getEventType();

        while (eventType != XmlPullParser.END_DOCUMENT) {

            if (eventType == XmlPullParser.START_TAG && pullParser.getName().equals(ITEM)){
                final FeedItem item = new FeedItem();
                eventType = pullParser.next();

                while ( !(eventType == XmlPullParser.END_TAG && pullParser.getName().equals(ITEM)) ) {

                    if ( eventType == XmlPullParser.START_TAG ) {
                        String name = pullParser.getName();

                        switch (name) {
                        case AUTHOR:
                            item.mAuthor = pullParser.nextText();
                            break;
                        case TITLE:
                            item.mTitle = pullParser.nextText();
                            break;
                        case CATEGORY:
                            item.mCategory = pullParser.nextText();
                            break;
                        case COMMENTS:
                            item.mComments = pullParser.nextText();
                            break;
                        case DESCRIPTION:
                            item.mDescription = pullParser.nextText();
                            break;
                        case GUID:
                            item.mGuid = pullParser.nextText();
                            break;
                        case LINK:
                            item.mLink = pullParser.nextText();
                            break;
                        case PUBDATE:
                            item.mPubDate = pullParser.nextText();
                            break;
                        case SOURCE:
                            item.mSource = pullParser.nextText();
                            break;
                        case ENCLOSURE:
                            item.mEnclosure = pullParser.getAttributeValue(null, "url");
                        default:
                            break;
                        }

                    }

                    eventType = pullParser.next();
                }

                //download the optional enclosure resource and update UI
                new DownloadRssAsyncTask().execute(item);

            }

            eventType = pullParser.next();
        }


    } catch (XmlPullParserException e) {
        Log.i(TAG, "XmlPullparserException");
    } catch (IOException e) {
        Log.i(TAG, "IOException");
    }
}

}

Murray
  • 97
  • 8
  • "Threading rules" mentions, that running from UI thread is done automatically as of Jelly Bean. – Egor Jun 01 '14 at 12:30
  • @pingw33n: in threading rules... – Murray Jun 01 '14 at 12:30
  • It would help if you included your code. Are you creating and executing this `AsyncTask2` specifically in the `doInBackground()` method of `AsyncTask1`? Every method in the abstract `AsyncTask` that's not `doInBackground` is run on the UI thread. – Justin Jasmann Jun 01 '14 at 12:36
  • Also, these are just threading rules. It is what you must follow if you want defined behaviour. If you use it outside of these rules, it won't _necessarily_ fail with an exception, but you might get some undefined behaviour. – Justin Jasmann Jun 01 '14 at 12:38

1 Answers1

1

Because of the inner workings of AsyncTask.

AsyncTask internally uses a static Handler instance, basically the Android way for thread communication. With a Handler you can send messages and run code on threads; in particular, AsyncTask uses it to run its callbacks such as onPostExecute().

Now, when Handler is initialiazed, it binds on the thread that initializes it. In AsyncTask this is done during class initialization/loading at the line:

private static final InternalHandler sHandler = new InternalHandler();

Since sHandler is also final, it cannot be modified after that, and the callbacks will be always triggered on that thread.

In your case, you create an instance of RssAsyncTask in onCreate(), which is run on the UI thread. This triggers the loading of the AsyncTask class and bind AsyncTask's Handler to the UI thread. Therefore, from that point your onPostExecute()s will always be run on the UI Thread. This is despite you create some AsyncTasks in another background thread.

The Threading Rules want to ensure the class is loaded/initialized on the UI thread (see this) and want to enforce good threading practices.

Also, I recommend IntentService for simple network operations, rather than AsyncTask.

Community
  • 1
  • 1
Gil Vegliach
  • 3,542
  • 2
  • 25
  • 37
  • Great answer, thank you! Where can I find more information about AsyncTask and its implementation? In the developer android reference I see nothing about the AsyncTask's static handler. – Murray Jun 01 '14 at 13:56
  • I edited a bit my answer to make it more accessible. The best source would be the code directly, for instance you can read AsyncTask code here: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.2_r1/android/os/AsyncTask.java?av=f – Gil Vegliach Jun 01 '14 at 14:00
  • Also other two things. First, your subclasses of `AsyncTask` are not static, so you have hidden references to your Activity. If you rotate the device, the Activity instance will be destroyed but it won't be garbage collected until the tasks are destroyed themselves. Also, the tasks will notify the dead Activity, and you won't see the results. Solution: IntentService. Second: accept the answer, if it helped, so to help other readers – Gil Vegliach Jun 01 '14 at 14:12