2014-05-01 52 views
0

我使用AsyncTask打開URL,訪問服務器,獲取內容並在主要活動的列表視圖中顯示它們。提取的內容包括報紙的標題和網站的URL,如果單擊「閱讀」按鈕,該內容將顯示在WebView的第二個活動中。我馬上編寫了這個程序並且它可以工作,但是當我回頭看時,我發現了一些看起來不合理的東西,所以我主要想弄清楚代碼是如何工作的。下面是主要活動代碼:android AsyncTask和UI線程交互

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

} 

所以你可以看到,在loadTask.execute()onCreate()後,我創建ALIST和AA的對象,但我已經在AsyncTaks類使用它們onPostExecute() ,所以我不清楚這裏會發生什麼,因爲onPostExecute()和UI在同一個線程中,所以中的代碼應該先執行。

我想我應該把

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

onPostExecute(),這更合乎邏輯給我,但應用程序崩潰這樣。此外,我認爲在onPostExecute()中刪除aa.notifyDataSetChanged();應該不是問題,因爲它也在onCreate()方法中,但這實際上會導致列表視圖爲空白,而沒有任何內容。實際上,將loadTask.execute()之後的任何代碼放入onPostExecute()方法的if區塊中會導致一些問題,或導致應用程序崩潰。如果有人能夠提供一些見解或暗示,那將會很棒。謝謝閱讀。

+0

但你必須在'onPostExecute()'方法中更新你的'UI'。 – Piyush

+0

@PiYusHGuPtA我認爲aa。notifyDataSetChanged()更新UI –

回答

4

onPostExecute在後臺任務完成其工作後在UI線程上調用。您無法保證此調用與UI線程上的其他調用相關的時間。

既然你已經自己實現getView,我建議您擴展BaseAdapter代替ArrayAdapter和實施其他一些必需的方法。這並不難,你可以使用任何你想要的數據結構來支持適配器。假設你使用List<Content>備份適配器,你可以寫一個方法來交換名單的地方,像這樣:

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

在你AsyncTask,你有參數,可以進步的完全控制和結果參數化類型。他們並不都必須是String。您可以改爲:

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

Params的String是URL(與現在一樣)。 Void for Progress,因爲您無論如何都不會發布進度。用於結果的List<Content>,因爲這是您在完成任務後實際想要結束的事情。

您應該完成您在doInBackground的所有工作。沒有理由將字符串反序列化爲JSONArray,並在onPostExecute中搞亂,特別是因爲主線程正在發生這種情況。重寫doInBackground返回一個List<Content>,以及您在onPostExecute需要的是這樣的:

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

現在,您可以一次創建適配器(在onCreate()),只是交換名單時,它是適當的。

+0

onPostExecute將消息發送到主線程的運行循環。當主線程準備好時,它將被主線程執行。很可能在onCreate完成後。 – Arno

+0

非常感謝您的解釋。是的,我認爲分離背景作業和出版物肯定是一種更好,更清潔的方式。但我仍然不明白爲什麼我無法以舊方式在onPostExecute方法中創建我的數組列表和適配器對象。你能解釋爲什麼在這裏擴展BaseAdapter更好嗎?我沒有在基類中找到列表字段,所以我還需要重寫notifyDataSetChanged()方法?謝謝。 –

+0

您不需要重寫'notifyDataSetChanged()',只需更改支持適配器的數據集就可以調用它。我只建議擴展'BaseAdapter'來讓你更好地控制它的行爲,因爲你已經實現了'getView()',它實際上是一個適配器的核心。如果你願意,你可以在'onPostExecute'中每次創建一個新的適配器。 – Karakuri