【问题标题】:android AsyncTask and UI thread interactionandroid AsyncTask与UI线程交互
【发布时间】:2014-05-01 06:01:29
【问题描述】:

我正在使用 AsyncTask 打开 URL、访问服务器、获取内容并将它们显示在主活动的列表视图中。提取的内容包括报纸的标题和网站的 URL,如果单击“阅读”按钮,它将在第二个活动中显示在 WebView 上。我直接把程序写出来了,它可以运行,但是当我回头看它时,我发现了一些似乎不合理的地方,所以我主要是想弄清楚代码是如何工作的。这是主要活动的代码:

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;
    }

}

所以你可以看到只有在onCreate()中的loadTask.execute()之后,我才为alist和aa创建对象,但是我已经在AsyncTaks类的onPostExecute()中使用它们了,所以我不是很清楚这里发生了什么,因为onPostExecute()和UI在同一个线程上,所以应该先执行onPostExecute()中的代码。

我想我应该放

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

进入onPostExecute(),这对我来说更合乎逻辑,但应用程序以这种方式崩溃。另外我认为在onPostExecute() 中删除aa.notifyDataSetChanged(); 应该不是问题,因为它也在onCreate() 方法中,但这实际上导致列表视图为空白,没有任何内容。实际上,将loadTask.execute() 之后的任何代码放入onPostExecute() 方法的if 块中会导致一些问题,或者使应用程序崩溃。如果有人可以提供一些见解或提示,那就太好了。感谢阅读。

【问题讨论】:

  • 但是你必须在onPostExecute()方法中更新你的UI
  • @PiYusHGuPtA 我认为 aa.notifyDataSetChanged() 更新了 UI

标签: android android-listview android-asynctask


【解决方案1】:

onPostExecute 在后台任务完成后在 UI 线程上调用。您无法保证此调用相对于 UI 线程上的其他调用的时间。

由于您已经在自己实现getView,我建议您扩展BaseAdapter 而不是ArrayAdapter,并实现其他几个必需的方法。这并不难,您可以使用任何您想要支持适配器的数据结构。假设您使用 List&lt;Content&gt; 支持适配器,您可以编写一个方法来交换列表,如下所示:

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

在您的 AsyncTask 中,您可以完全控制 Params、Progress 和 Result 参数化类型。他们不必都是String。你可以这样做:

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

Params 的String 是 URL(和现在一样)。 Void 表示进度,因为无论如何您都不会发布进度。 List&lt;Content&gt; 表示结果,因为这是您在完成任务后真正想要结束的事情。

您应该在doInBackground 中完成所有工作。没有理由将 String 反序列化为 JSONArray 并在onPostExecute 中搞乱它,特别是因为这发生在主线程上。重写doInBackground 以返回List&lt;Content&gt;,在onPostExecute 中你只需要这样:

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

现在您可以创建一次适配器(在 onCreate() 中),然后在适当的时候交换列表。

【讨论】:

  • onPostExecute 向主线程的运行循环发送消息。这将在主线程准备就绪时由主线程执行。很可能在 onCreate 完成之后。
  • 非常感谢您的解释。是的,我认为将背景工作和出版分开绝对是一种更好、更清洁的方式。但是我仍然不明白为什么我不能以旧的方式在 onPostExecute 方法中创建我的数组列表和适配器对象。你能解释一下为什么在这里扩展 BaseAdapter 更好吗?基类中没有找到list字段,所以还需要重写notifyDataSetChanged()方法?谢谢。
  • 您不需要覆盖notifyDataSetChanged(),您只需在更改支持适配器的数据集时调用它。我只建议扩展BaseAdapter 以便更好地控制其行为,因为您已经在实现getView(),这实际上是适配器的核心。如果您愿意,您可以每次在 onPostExecute 中创建一个新适配器。
猜你喜欢
  • 1970-01-01
  • 2020-06-22
  • 1970-01-01
  • 2010-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-04
  • 1970-01-01
相关资源
最近更新 更多