標籤: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面試七問