Android非同步之Asynctask與Handler面試七問

來源:互聯網
上載者:User

標籤:android   handler asynctask   非同步 面試   

提出問題:

1、Android中的非同步處理方式?

2、如何使用Handler以及在使用過程中如何避免Handler引起的記憶體泄露?

3、從源碼角度分析MessageQueue,Message,handler,looper,主線程,子thread之間的關係

4、Handler通過sendMessage以及post Runable對象有什麼區別

5、如何給一個線程建立訊息迴圈,即如何構建一個looper線程?

6、Asynctask中有哪些方法,分別如何使用,哪些方法在主線程執行,哪些方法在子線程執行,Asynctask中的參數關係

7、Asynctask與使用Handler+thread的優缺點對比(區別)


解決問題:

問題1 在Android中的非同步處理方式?

Android中的非同步處理方式可以採用

a:Handler+Thread

b:非同步任務Asynctask

c:Thread+回調

d:a+c

問題2 如何使用Handler以及在使用過程中如何避免Handler引起的記憶體泄露?

問題4   Handler通過sendMessage以及post Runable對象有什麼區別

public class ThreadHandlerActivity extends Activity {private static final int MSG_SUCCESS = 0;// 擷取圖片成功的標識private static final int MSG_FAILURE = 1;// 擷取圖片失敗的標識private ImageView mImageView;private Button mButton;private Thread mThread;private Handler mHandler;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.thread_layout);mHandler = new MyHandler(this);mImageView = (ImageView) findViewById(R.id.imageView);// 顯示圖片的ImageViewmButton = (Button) findViewById(R.id.button);mButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if (mThread == null) {mThread = new Thread(runnable);mThread.start();// 線程啟動} else {Toast.makeText(getApplication(),getApplication().getString(R.string.thread_started),Toast.LENGTH_LONG).show();}}});}Runnable runnable = new Runnable() {@Overridepublic void run() {// run()在新的線程中運行HttpClient hc = new DefaultHttpClient();HttpGet hg = new HttpGet("http://csdnimg.cn/www/images/csdnindex_logo.gif");// 擷取csdn的logofinal Bitmap bm;try {HttpResponse hr = hc.execute(hg);bm = BitmapFactory.decodeStream(hr.getEntity().getContent());} catch (Exception e) {mHandler.obtainMessage(MSG_FAILURE).sendToTarget();// 擷取圖片失敗return;}mHandler.obtainMessage(MSG_SUCCESS, bm).sendToTarget();// 擷取圖片成功,向ui線程發送MSG_SUCCESS標識和bitmap對象// mImageView.setImageBitmap(bm); //出錯!不能在非ui線程操作ui元素            /* *  * 另外一種更簡潔的發送訊息給ui線程的方法。mHandler.post(new Runnable() {//其實這個Runnable並沒有建立什麼線程,而是發送了一條訊息 * 通過源碼可以看出其實post出的runnable對象最終也是轉化成message加入到隊列@Overridepublic void run() {    // run()方法會在ui線程執行mImageView.setImageBitmap(bm);}});**/}};    /** * @author ss * 問題:如何避免Handler引起的記憶體泄露 * 1、不使用非靜態內部類,繼承Handler的時候,要麼放在單獨的類檔案中,要麼就使用靜態內部類 * why----->在java中,非靜態內部類和匿名內部類都會隱式地持有其外部類的引用,而靜態內部類 * 不會持有外部類的引用。 * 假如線上程中執行如下方法:           mHandler.postDelayed(new Runnable() {           這裡的runnable如果不採用匿名內部類,而是在外面聲明,則也應該設定成靜態                 @Override                 public void run() {                  ...                 } }, 1000 * 60 * 10); 外部調用finish()銷毀Activity * 如果我們使用非靜態MyHandler, 當我們的代碼執行finish方法時,被延遲的訊息會在被處理之前存在於 * 主線程訊息佇列中10分鐘,我們發送一個target為這個Handler的訊息到Looper處理的訊息佇列時,實際上 * 已經發送的訊息已經包含了一個Handler執行個體的引用,只有這樣Looper在處理到這條訊息時才可以 * 調用Handler#handleMessage(Message)完成訊息的正確處理。 * 但是非靜態MyHandler持有外部ThreadHandlerActivity的引用 * 所以這導致了ThreadHandlerActivity無法回收,進行導致ThreadHandlerActivity持有的很多資源都 * 無法回收,這就是我們常說的記憶體泄露。 *  */private static class MyHandler extends Handler {//2、當你需要在靜態內部類中調用外部的Activity時,我們可以使用弱引用來處理。WeakReference<ThreadHandlerActivity> thisLayout;MyHandler(ThreadHandlerActivity layout) {thisLayout = new WeakReference<ThreadHandlerActivity>(layout);}public void handleMessage(Message msg) {// 此方法在ui線程運行final ThreadHandlerActivity theLayout = thisLayout.get();if (theLayout == null) {return;}switch (msg.what) {case MSG_SUCCESS:theLayout.mImageView.setImageBitmap((Bitmap) msg.obj);// imageview顯示從網路擷取到的logoToast.makeText(theLayout,theLayout.getString(R.string.get_pic_success),Toast.LENGTH_LONG).show();break;case MSG_FAILURE:Toast.makeText(theLayout,theLayout.getString(R.string.get_pic_failure),Toast.LENGTH_LONG).show();break;}}}}

問題3   從源碼角度分析MessageQueue,Message,handler,looper,主線程,子thread之間的關係

問題5   如何給一個線程建立訊息迴圈,即如何構建一個looper線程?

public class LooperThreadActivity extends Activity{ /** * MessageQueue,Message,handler,looper,主線程,子thread之間的關係 *  * MessageQueue:訊息佇列,每個線程最多擁有一個 *  * Message 訊息對象 *  * Looper:與當前線程綁定,保證一個線程只會有一個Looper執行個體,同時一個Looper也只有一個 * MessageQueue不斷從MessageQueue中去取訊息,交給訊息的target屬性對應的Handler的dispatchMessage * 去處理 *  * Looper是MessageQueue的管理者。每一個MessageQueue都不能脫離Looper而存在,Looper * 對象的建立是通過prepare函數來實現的。同時每一個Looper對象和一個線程關聯。通過調用 * Looper.myLooper()可以獲得當前線程的Looper對象 。建立一個Looper對象時,會同時建立 * 一個MessageQueue對象。除了主線程有預設的Looper,其他線程預設是沒有MessageQueue對象的 * ,所以,不能接受Message。如需要接受,自己定義 一個Looper對象(通過prepare函數), * 這樣該線程就有了自己的Looper對象和MessageQueue資料結構了。  Looper從MessageQueue中 * 取出Message然後,交由Handler的handleMessage進行處理。處理完成後,調用 * Message.recycle()將其放入Message Pool中。 * */           private final int MSG_HELLO = 0;      private Handler mHandler;           @Override      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.main);          new CustomThread().start();//建立並啟動CustomThread執行個體                    findViewById(R.id.send_btn).setOnClickListener(new OnClickListener() {                            @Override              public void onClick(View v) {//點擊介面時發送訊息                   String str = "hello";                  Log.d("Test", "MainThread is ready to send msg:" + str);                  mHandler.obtainMessage(MSG_HELLO, str).sendToTarget();//發送訊息到CustomThread執行個體                                 }          });                }            class CustomThread extends Thread {          @Override         public void run() {              //建立訊息迴圈的步驟              Looper.prepare();//1、初始化Looper              //Handler的構造方法,會首先得到當前線程中儲存的Looper執行個體,            //進而與Looper執行個體中的MessageQueue關聯。            mHandler = new Handler(){//2、綁定handler到CustomThread執行個體的Looper對象                   public void handleMessage (Message msg) {//3、定義處理訊息的方法                       switch(msg.what) {                      case MSG_HELLO:                         Log.d("Test", "CustomThread receive msg:" + (String) msg.obj);                      }                  }              };              Looper.loop();//4、啟動訊息迴圈           }      }  } 

問題6 Asynctask中有哪些方法,分別如何使用,哪些方法在主線程執行,哪些方法在子線程執行,Asynctask中的參數關係

輕量級的非同步抽象類別
定義了三種泛型型別Params,Progress和Result,都是Object類型
onPreExecute() 當任務執行之前開始調用此方法,可以在這裡顯示進度對話方塊。
doInBackground(Params...) 此方法在後台線程執行,完成任務的主要工作,通常需要較長的時間。在執行過程中可以調用publicProgress(Progress...)來更新任務的進度。
onProgressUpdate(Progress...) 此方法在主線程執行,用於顯示任務執行的進度。
onPostExecute(Result) 此方法在主線程執行,任務執行的結果作為此方法的參數返回。
onCancelled()  使用者調用取消方法時,將回調該函數。

執行個體必須在UI主線程中建立 
execute方法必須在主線程中調用 
永遠不要手動的調用
onPreExecute(), 
onPostExecute(Result),
doInBackground(Params...),
onProgressUpdate(Progress...)這幾個方法 
一個AsyncTask執行個體只能被Execute執行一次,否則多次Execute時將會出現異常 
配置了5個可用線程,
  Executor(Executors.newCachedThreadPool())

/**   * 產生該類的對象,並調用execute方法之後   * 首先執行的是onProExecute方法   * 其次執行doInBackgroup方法     */  public class ProgressBarAsyncTask extends AsyncTask<Integer, Integer, String> {        private TextView textView;      private ProgressBar progressBar;                  public ProgressBarAsyncTask(TextView textView, ProgressBar progressBar) {          super();          this.textView = textView;          this.progressBar = progressBar;      }          /**       * 這裡的Integer參數對應AsyncTask中的第一個參數        * 這裡的String傳回值對應AsyncTask的第三個參數       * 該方法並不運行在UI線程當中,主要用於非同步作業,所有在該方法中不能對UI當中的空間進行設定和修改       * 但是可以調用publishProgress方法觸發onProgressUpdate對UI進行操作       */      @Override      protected String doInBackground(Integer... params) {      //Integer... params代表0或者N個Integer類型的值        NetOperator netOperator = new NetOperator();          int i = 0;          for (i = 10; i <= 100; i+=10) {              netOperator.operator();              publishProgress(i);          }         //這裡面params[0]即為execute(1000)中的1000        return i + params[0].intValue() + "";      }          /**       * 這裡的String參數對應AsyncTask中的第三個參數(也就是接收doInBackground的傳回值)       * 在doInBackground方法執行結束之後在運行,並且運行在UI線程當中 可以對UI空間進行設定       */      @Override      protected void onPostExecute(String result) {          textView.setText("非同步作業執行結束" + result);      }          //該方法運行在UI線程當中,並且運行在UI線程當中 可以對UI空間進行設定      @Override      protected void onPreExecute() {          textView.setText("開始執行非同步線程");      }          /**       * 這裡的Intege參數對應AsyncTask中的第二個參數       * 在doInBackground方法當中,,每次調用publishProgress方法都會觸發onProgressUpdate執行       * onProgressUpdate是在UI線程中執行,所有可以對UI空間進行操作       */      @Override      protected void onProgressUpdate(Integer... values) {          int vlaue = values[0];          progressBar.setProgress(vlaue);      }    }  /** * 類比網路耗時 * */class NetOperator {        public void operator(){        try {              //休眠1秒              Thread.sleep(1000);          } catch (InterruptedException e) {              // TODO Auto-generated catch block              e.printStackTrace();          }      } }  
public class AsyncTaskActivity extends Activity { private Button startTaskBtn;      private ProgressBar progressBar;      private TextView progress_info;            @Override      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.asyntask_layout);                    startTaskBtn = (Button)findViewById(R.id.startTaskBtn);          progressBar = (ProgressBar)findViewById(R.id.progressBar);                progress_info = (TextView)findViewById(R.id.progress_info);                startTaskBtn.setOnClickListener(new OnClickListener() {            @Override              public void onClick(View v) {                  ProgressBarAsyncTask asyncTask = new ProgressBarAsyncTask(progress_info, progressBar);                  asyncTask.execute(1000);              }          });      }  }  

問題7  Asynctask與使用Handler+thread的優缺點對比(區別)

採用線程+Handler實現非同步處理時,當每次執行耗時操作都建立一條新線程進行處理,效能開銷會比較大, 如果耗時操作執行的時間比較長,就有可能同時運行著許多線程,系統終將不堪重負. 為了提高效能我們使用AsyncTask實現非同步處理,事實上其內部也是採用線程+handler來實現非同步處理的.只不過是其內部使用了JDK5提供的線程池技術,有效降低了線程建立數量及限定了同時啟動並執行線程數,還有一些針對性的對池的最佳化操作.

AsyncTask規定同一時刻能夠啟動並執行線程數為5個,線程池總大小為128。也就是說當我們啟動了10個任務時,只有5個任務能夠立刻執行,另外的5個任務則需要等待,當有一個任務執行完畢後,第6個任務才會啟動,以此類推。而線程池中最大能存放的線程數是128個,當我們嘗試去添加第129個任務時,程式就會崩潰。

因此在3.0版本中AsyncTask的改動還是挺大的,在3.0之前的AsyncTask可以同時有5個任務在執行,而3.0之後的AsyncTask同時只能有1個任務在執行。為什麼升級之後可以同時執行的任務數反而變少了呢?這是因為更新後的AsyncTask已變得更加靈活,如果不想使用預設的線程池,還可以自由地進行配置


AsyncTask
優點:
1.簡單,快捷,只需要在doInBackground ()中處理業務,在onPostExecute()方法中更新UI,代碼階層簡單
2.當相同非同步任務的資料特別龐大,AsyncTask這種線程池結構的優勢會充分體現出來
缺點:
1.AsyncTask類包含一個全域靜態線程池,預設配置了5個可用線程,如果超過5個線程則會進入到緩衝隊列中等待。緩衝隊列最多有128個等中的線程
2.AsyncTask可能存在新開大量線程消耗系統資源和導致應用FC(Force Close)的風險
3.AsyncTask在處理大量多種不同非同步任務的時候顯得不適合。

Handler
優點:
Handler原理是僅僅就是發送了一個訊息佇列,並沒有新開線程帶來的資源消耗,對於紛繁複雜的不同小非同步任務處理起來相對簡單。
缺點:
相對AsyncTask來說顯得代碼過多,過於臃腫,結構過於複雜,層次不明朗


Demo:https://github.com/feifei003603/CustomAsyncTask.git
終於整理完了,好睏,覺得有用的小夥伴給個評論吧,有什麼不對的地方歡迎拍磚,歡迎補充!

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

Android非同步之Asynctask與Handler面試七問

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.