Вопрос

I'm using the AsyncTask to open a URL, access the server, fetch the content and display them in a list view in the main activity. The content extracted consists of a title of the newspaper and a URL to the website, which will be displayed on a WebView in a second activity, if a "read" button is clicked. I coded out the program straight away and it works, but when I looked back at it, I found something that seems unreasonable, so mainly I want to make clear how the code works. Here is the code for the main activity:

package com.example.newsapp;

public class MainActivity extends Activity {

    static final private String LOG_TAG = "main";
    private ArrayList<Content> aList;

    private class Content{

        Content() {};
        public String title;
        public String url;
    }

    private class MyAdapter extends ArrayAdapter<Content>{

        int resource;


        public MyAdapter(Context _context, int _resource, List<Content> titles) {
            super(_context, _resource, titles);
            resource = _resource;
        //  this.context = _context;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            LinearLayout newView;

            final Content content = getItem(position);

            // Inflate a new view if necessary.
            if (convertView == null) {
                newView = new LinearLayout(getContext());
                String inflater = Context.LAYOUT_INFLATER_SERVICE;
                LayoutInflater vi = (LayoutInflater) getContext().getSystemService(inflater);
                vi.inflate(resource,  newView, true);
            } else {
                newView = (LinearLayout) convertView;
            }

            // Fills in the view.
            TextView tv = (TextView) newView.findViewById(R.id.listText);
            ImageButton b = (ImageButton) newView.findViewById(R.id.listButton);
            b.setBackgroundResource(0);
            tv.setText(content.title);
            Typeface type = Typeface.createFromAsset(getAssets(),"LiberationSerif-BoldItalic.ttf"); 
            tv.setTypeface(type);

            // Sets a listener for the button, and a tag for the button as well.
            b.setTag(Integer.toString(position));
            b.setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View v) {
                    // Reacts to a button press.
                    Intent intent = new Intent(MainActivity.this, WebPage.class);
                    Bundle bundle = new Bundle();
                    bundle.putString("URL", content.url);
                    intent.putExtras(bundle);
                    startActivity(intent);
                }
            });
            return newView;
        }       
    }

    class MyAsyncTask extends AsyncTask<String, String, String> {
         private ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
         InputStream inputStream = null;
         String result = ""; 
         Content content;

         protected void onPreExecute() {
             super.onPreExecute();
             progressDialog.setMessage("Downloading the news...");
             progressDialog.show();
             progressDialog.setOnCancelListener(new OnCancelListener() {
                 public void onCancel(DialogInterface arg0) {
                     MyAsyncTask.this.cancel(true);
                 }
             });
         }

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

             String url_select = params[0];

             ArrayList<NameValuePair> param = new ArrayList<NameValuePair>();

             try {
                 // Set up HTTP post
                 // HttpClient is more then less deprecated. Need to change to URLConnection
                 HttpClient httpClient = new DefaultHttpClient();

                 HttpPost httpPost = new HttpPost(url_select);
                 httpPost.setEntity(new UrlEncodedFormEntity(param));
                 HttpResponse httpResponse = httpClient.execute(httpPost);
                 HttpEntity httpEntity = httpResponse.getEntity();

                 // Read content & Log
                 inputStream = httpEntity.getContent();
                } catch (UnsupportedEncodingException e1) {
                    Log.e("UnsupportedEncodingException", e1.toString());
                    e1.printStackTrace();
                } catch (ClientProtocolException e2) {
                    Log.e("ClientProtocolException", e2.toString());
                    e2.printStackTrace();
                } catch (IllegalStateException e3) {
                    Log.e("IllegalStateException", e3.toString());
                    e3.printStackTrace();
                } catch (IOException e4) {
                    Log.e("IOException", e4.toString());
                    e4.printStackTrace();
                }
             // Convert response to string using String Builder
             try {
                 BufferedReader bReader = new BufferedReader(new InputStreamReader(inputStream, "iso-8859-1"), 8);
                 StringBuilder sBuilder = new StringBuilder();
                 String line = null;
                 while ((line = bReader.readLine()) != null) {
                     sBuilder.append(line + "\n");
                 }
                 inputStream.close();
                 result = sBuilder.toString();
             } catch (Exception e) {
                 Log.e("StringBuilding & BufferedReader", "Error converting result " + e.toString());
             }
             return result;
         } // protected Void doInBackground(String... params)


         protected void onPostExecute(String result) {
             //parse JSON data
             try {
                 super.onPostExecute(result);
                 Log.i(LOG_TAG, result);
                 JSONObject object = new JSONObject(result);
                 JSONArray jArray = object.getJSONArray("sites");
                 for(int i=0; i < jArray.length(); i++) {
                     JSONObject jObject = jArray.getJSONObject(i);
                     content = new Content();
                     if (jObject.has("title") && jObject.has("url")){
                         content.title = jObject.getString("title");
                         content.url = jObject.getString("url");
                         aList.add(content);
                         aa.notifyDataSetChanged();
                     }
                 } // End Loop
                 progressDialog.dismiss();
             } catch (JSONException e) {
            //   progressDialog.dismiss();
                 Log.e("JSONException", "Error: " + e.toString());
             }

         } // protected void onPostExecute(String result)
    }

    private MyAdapter aa;
    private MyAsyncTask loadTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadTask = new MyAsyncTask();
        loadTask.execute("http://luca-ucsc.appspot.com/jsonnews/default/news_sources.json");
        aList = new ArrayList<Content>();
        aa = new MyAdapter(this, R.layout.list_element, aList);
        ListView myListView = (ListView) findViewById(R.id.listView1);
        myListView.setAdapter(aa);
        aa.notifyDataSetChanged();
    }

    public void refresh(View v){
        if (loadTask.getStatus() == AsyncTask.Status.FINISHED){
            aList.clear();
            aa.notifyDataSetChanged();
            new MyAsyncTask().execute("http://luca-ucsc.appspot.com/jsonnews/default/news_sources.json");
        }
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

}

So you can see that only after loadTask.execute() in onCreate(), do I create the object for alist and aa, but I'm already using them in onPostExecute() in the AsyncTaks class, so I'm not very clear what happens here, because onPostExecute() and the UI are on the same thread, so the code in onPostExecute() should be executed first.

I thought I should put

aList = new ArrayList<Content>();
aa = new MyAdapter(this, R.layout.list_element, aList);

into onPostExecute(), which is more logical to me, but the app crashes this way. Also I think deleting aa.notifyDataSetChanged(); in onPostExecute() shouldn't be a problem because it's also in the onCreate() method, but this actually causes the list view to be blank, without any content. Actually, putting any of the codes after loadTask.execute() into the if block of the onPostExecute() method causes some problem, or crashes the app. That would be great if somebody can give some insight or hint. Thanks for reading.

Это было полезно?

Решение

onPostExecute is called on the UI thread after the background task completes its work. You cannot guarantee the timing of this call in relation to other calls on the UI thread.

Since you are already implementing getView yourself, I recommend you extend BaseAdapter instead of ArrayAdapter and implement the other few required methods. It's not hard and you can use whatever data structure you want to back the adapter. Assuming you use a List<Content> to back the adapter, you can write a method to swap the list in place like so:

public void swapList(List<Content> newList) {
    this.list = newList;
    notifyDataSetChanged();
}

In your AsyncTask, you have complete control of the Params, Progress, and Result parameterized types. They don't all have to be String. You can do this instead:

private class myAsyncTask extends AsyncTask<String, Void, List<Content>> {
    /* ... */
}

The String for Params is the URL (same as you do now). Void for Progress because you don't publish progress anyway. List<Content> for Result because that's the thing you actually want to end up with after doing your task.

You should do ALL of your work in doInBackground. There is no reason to deserialize a String into a JSONArray and mess around with that in onPostExecute, particularly since that is happening on the main thread. Rewrite doInBackground to return a List<Content>, and all you need in onPostExecute is this:

public void onPostExecute(List<Content> result) {
   adapter.swapList(result);
}

Now you can create the adapter once (in onCreate()) and just swap the list whenever it's appropriate.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top