1. 程式人生 > >Android進階——多執行緒和非同步任務小結

Android進階——多執行緒和非同步任務小結

引言

眾所周知,無論是在任何的程式語言和作業系統中。多執行緒、多程序和非同步同步始終都是經久不衰的話題。當然在我們實際的Android專案需求中也是如此,很多的業務需求都通過多執行緒及非同步任務以便使用者能夠在使用App中得到優秀的體驗。而很多App在使用過程中出現各種莫名其妙的問題,多是由於開發人員使用多執行緒不當造成的,因此掌握在多執行緒及非同步任務的原理和使用方法非常有必要。

一Android UI主執行緒設計原則

在開始總結多執行緒前,先講下UI 執行緒(主執行緒)的一個基本原則——不要Block UI Thread;不要在UI執行緒外直接操作UI,每一個App執行之時,Android系統會自動為每一個App建立一個執行緒即主執行緒,只能在主執行緒(UI執行緒)中操作UI

,這是因為在Android原始碼線上閱讀中並沒有對UI操作部分做執行緒同步處理,如果在非UI(非主執行緒)中操作UI就會導致執行緒安全問題,所以在非UI執行緒中操作UI執行時直接報錯了。

二使用多執行緒的意義

我們在開發的過程中,很多業務需求都是非常耗時的,比如說IO操作、網路訪問、資料庫操作、上傳下載檔案等等,如果我們全部都放到主執行緒中去執行就會可能導致主執行緒阻塞,使用者在使用APP的過程中就會產生卡頓的不良體驗,自然對於APP滿意度下降。為了給使用者以最優秀的體驗,前輩建議對於超過50ms(因為1000ms/50ms=20fps剛好是人眼的能感受到的最大值)的操作,都應該使用多執行緒去處理,才不至於給使用者以卡頓的感受。

三使用多執行緒的方式

1、和Java的一樣擴充套件java.lang.Thread類,即new 一個執行緒物件把run()方法寫到執行緒裡面

new Thread(new Runnable(){
    @Override
    public void run() {
        //在這裡做耗時操作
        });
    }
}).start();

2、實現Runnable介面,讓Activity類實現Runnable介面,然後把run方法單獨提出來:

public class MutilThreadActivity extends Activity
implements Runnable {
@Override public void run() { //在這裡做耗時操作 } }

3.1 使用步驟

3.1.1利用Executors的靜態方法newCachedThreadPool()、newFixedThreadPool()、newSingleThreadExecutor()及過載形式例項化ExecutorService介面即得到執行緒池物件
  • 動態執行緒池newCachedThreadPool() 是根據需求建立新執行緒的,需求多時,建立的就多,需求少時,JVM自己會慢慢的釋放掉多餘的執行緒
  • 固定數量的執行緒池newFixedThreadPool()內部有個任務阻塞佇列,假設執行緒池裡有3個執行緒,提交了5個任務,那麼後兩個任務就放在任務阻塞隊列了,即使前3個任務sleep或者堵塞了,也不會執行後兩個任務,除非前三個任務有執行完的
  • 單執行緒newSingleThreadExecutor()返回一個執行緒池(不過這個執行緒池只有一個執行緒),這個執行緒池可以線上程死後(或發生異常時)重新啟動一個執行緒來替代原來的執行緒繼續執行下去
ExecutorService service =Executors.newFixedThreadPool(); 
ExecutorService service =Executors.newFixedThreadPool();
ExecutorService service =Executors.newFixedThreadPool(); 
3.1.2 建立ExecutorService物件之後,然後執行提交submit,可以發起一個Runnable物件。
 service.submit(new Runnable(){  

            @Override  
            public void run() {  
                 // 
            }  
        });  

3.2完整使用執行緒池的程式碼片段

private ExecutorService service =Executors.newFixedThreadPool(6);  
private void testByExecutors(){  
      service.submit(new Runnable(){  

          @Override  
          public void run() {  
            //在這做耗時操作
          }  
      });  
}

3.3 執行緒池的優勢

比如說現在我們要展示800張圖片如果建立800個執行緒去載入,保證系統會死掉。用執行緒池就可以避免這個問題,我們可以差建立用6個執行緒輪流執行,6個一組,執行完的執行緒不直接回收而是等待下次執行,這樣對系統的開銷就可以減小不少。所以用執行緒池來管理的好處是,可以保證系統穩定執行,適用與有大量執行緒,高工作量的情景下使用。

4 非同步任務AsyncTask

四非同步任務AsyncTask

在Android中實現非同步任務機制有兩種方式,Handler和AsyncTask。在這裡先總結下AsyncTask

1 AsyncTask概述

AsyncTask主要用於後臺與介面持續互動的,AsyncTask是個抽象類,使用時需要繼承這個類,(把耗時的後臺操作放到doInBackgound() 方法裡,在onPostExecute()中完成UI操作),然後呼叫execute()方法。注意繼承時需要設定三個泛型Params,Progress和Result的型別,其中:

  1. Params是指呼叫execute()方法時傳入的引數型別和doInBackgound()的引數型別
  2. Progress是指更新進度時傳遞的引數型別,即publishProgress()和onProgressUpdate()的引數型別
  3. Result是指doInBackground()的返回值型別

2幾個常用方法

  1. doInBackgound() 這個方法是繼承AsyncTask必須要實現的,運行於後臺,耗時的操作可以在這裡做
  2. onPostExecute 在主執行緒中執行,可以用來寫一些開始提示程式碼。
  3. publishProgress() 更新進度,給onProgressUpdate()傳遞進度引數
  4. onProgressUpdate() 在publishProgress()呼叫完被呼叫,更新進度

3 AsyncTask執行機制

這裡寫圖片描述
1. 主執行緒呼叫AsynTask子類例項的execute()方法後,首先會呼叫onPreExecute()方法。onPreExecute()在主執行緒中執行,可以用來寫一些開始提示程式碼。
2. 之後啟動新執行緒,呼叫doInBackground()方法,進行非同步資料處理。
3. 處理完畢之後非同步執行緒結束,在主執行緒中呼叫onPostExecute()方法。onPostExecute()可以進行一些結束提示處理。
補充:在doInBackground()方法非同步處理的時候,如果希望通知主執行緒一些資料(如:處理進度)。這時,可以呼叫publishProgress()方法。這時,主執行緒會呼叫AsynTask子類的onProgressUpdate()方法進行處理。
4. 各個函式間資料的傳遞通過上面的呼叫關係,我們就可以大概看出一些資料傳遞關係。如下:
  execute()向doInBackground()傳遞。
  doInBackground()的返回值會傳遞給onPostExecute()。
  publishProgress()向progressUpdate()傳遞。
5. Android為了呼叫關係明確及安全,AsynTask類在繼承時要傳入3個泛型。第一個泛型對應execute()向doInBackground()的傳遞型別。第二個泛型對應doInBackground()的返回型別和傳遞給onPostExecute()的型別。第三個泛型對應publishProgress()向progressUpdate()傳遞的型別。傳遞的資料都是對應型別的陣列,陣列都是可變長的。可以根據具體情況使用。

4 非同步的簡單應用

結合WebView通過非同步載入指定網頁

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <EditText 
        android:id="@+id/title"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"

        />
    <ProgressBar 
        android:id="@+id/progressbar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#00f"
        android:layout_gravity="center"
        />
    <TextView android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <Button 
        android:id="@+id/load_web"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="載入指定網頁"/>
</LinearLayout>
package cmo.learn.activity;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PublicKey;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;

public class HandlerActivity extends Activity implements OnClickListener {
    private Button mUpdProgressBtn;
    private EditText mTitleEdt;
    private ProgressBar mProgressBar;
    private TextView mContentTxt;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_asynctask);
        init();
    }
    private void init(){
        getView();
        mTitleEdt.setText("http://www.hao123.com");
        mUpdProgressBtn.setOnClickListener(this);
    }
    private void getView(){
        mTitleEdt=(EditText) findViewById(R.id.title);
        mProgressBar=(ProgressBar) findViewById(R.id.progressbar);
        mUpdProgressBtn=(Button) findViewById(R.id.load_web);
        mContentTxt=(TextView) findViewById(R.id.content);
    }
    //非同步載入網頁內容
    class WebGetAsyncTask extends AsyncTask<String, Integer, String>{

        @Override
        protected String doInBackground(String... params) {
            try {
                HttpClient client=new DefaultHttpClient();
                HttpGet get=new HttpGet(params[0]);
                HttpResponse response=client.execute(get);
                HttpEntity entity=response.getEntity();
                long length=entity.getContentLength();
                InputStream inStream=entity.getContent();
                String s=null;
                int toCase=0;
                if(inStream !=null){
                    ByteArrayOutputStream boas=new ByteArrayOutputStream();
                    byte[] buf=new byte[128];
                    int ch=-1;
                    int count=0;
                    while((ch=inStream.read(buf))!=-1){
                        boas.write(buf, 0, ch);
                        count +=ch;
                        if(length>0){
                            toCase=(int)((count/(float)length)*100);
                            publishProgress(toCase);
                        }
                        Thread.sleep(100);
                    }
                    s=new String(boas.toByteArray());
                    return s;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } 
            return null;
        }
        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            mContentTxt.setText(result);
        }
        @Override
        protected void onProgressUpdate(Integer... values) {
            mProgressBar.setProgress(values[0]);
        }

    }
    @Override
    public void onClick(View v) {
        new WebGetAsyncTask().execute(mTitleEdt.getText().toString());
    }
}

5 使用非同步必須遵守的準則

  • AsyncTask的例項必須在UI Thread中建立
  • execute方法必須在主執行緒(UI Thread)中呼叫
  • 不要手動的呼叫onPreExecute(), onPostExecute(Result),doInBackground(Params…),
    onProgressUpdate(Progress…)這幾個方法
  • 該AsyncTask例項只能被執行一次,否則多次呼叫時將會出現異常

6AsyncTask小結

初識這個非同步呼叫關係可能覺得很複雜,但其實熟悉了之後會發現這種結構很好用。這種結構將所有的執行緒通訊都封裝成回撥函式,呼叫邏輯容易書寫。尤其是在非同步處理結束之後,有回撥函式進行收尾處理。如果是使用Thread的run()方法,run()結束之後沒有返回值。所以必須要自己建立通訊機制。但是,其實使用Handler+Thread機制其實完全可以替代AsynTask的這種呼叫機制。只要將Handler物件傳給Thread,就可以進行方便的非同步處理。個人經驗,Handler+Thread適合進行大框架的非同步處理,而AsynTask適用於小型簡單的非同步處理。僅僅代表個人觀點和見解。

五一個綜合的例子

佈局檔案很簡單

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <ImageView 
        android:id="@+id/img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        />
    <Button
        android:id="@+id/thread"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load and set in Main Thread"/>
    <Button
        android:id="@+id/thread2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load and set in Thread"/>
    <Button
        android:id="@+id/thread3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load in Thread,set by View.post(Runnable)"/>
    <Button
        android:id="@+id/thread4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load in Thread,set by AsyncTask"/>
</LinearLayout>
package cmo.learn.activity;

import java.net.URL;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class MutilThreadActivity extends Activity {
    /**
     * 
     * Android UI主執行緒簡單原則:不要Block UI Thread;不要在UI執行緒外直接操作UI
     * 
     */
    private Button mMainThreadBtn;
    private Button mThread2Btn;
    private Button mThread3Btn;
    private Button mThread4Btn;
    private ImageView mImg;
    private final static String IMAGE_URL="http://www.lhzhang.org/image.axd?pictrue=/201102/46613566.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mutilthread);
        init();
        //1 在主執行緒中載入圖片到Image中
        mMainThreadBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Drawable drawable=loadImageFromNet(IMAGE_URL,"Main Thread");
                mImg.setImageDrawable(drawable);
            }
        });
        //2 在Thread子執行緒中載入到ImageView,但是會有執行緒安全問題,直接報錯
        mThread2Btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                new Thread(new Runnable(){
                    @Override
                    public void run() {
                        Drawable drawable=loadImageFromNet(IMAGE_URL,"Runnale Thread");
                        mImg.setImageDrawable(drawable);
                    }
                }).start();
            }
        });
        //3 載入圖片在子執行緒,把是通過View.post(Runnable)設定圖片到ImageView
        mThread3Btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                //在子執行緒中從網路中載入Image
                new Thread(new Runnable(){
                    @Override
                    public void run() {
                        final Drawable drawable=loadImageFromNet(IMAGE_URL,"Runnale Thread By Post");
                        //通過post把set操作放到了UI執行緒
                        mImg.post(new Runnable(){

                            @Override
                            public void run() {
                                mImg.setImageDrawable(drawable);
                            }
                        });
                    }
                }).start();
            }
        });
        //4載入圖片 在子執行緒中,通過非同步AsyncTask設定到ImageView
        mThread4Btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                new LoadImgAsyncTask().execute(IMAGE_URL);
            }
        });
    }
    private void init(){
        getView();
    }

    private void getView(){
        mImg=(ImageView) findViewById(R.id.img);
        mMainThreadBtn=(Button) findViewById(R.id.thread);
        mThread2Btn=(Button) findViewById(R.id.thread2);
        mThread3Btn=(Button) findViewById(R.id.thread3);
        mThread4Btn=(Button) findViewById(R.id.thread4);
    }
    private Drawable loadImageFromNet(String imageUrl,String tag){
        Drawable drawable=null;
        try{
            drawable=Drawable.createFromStream(new URL(imageUrl).openStream(), "img_1.png");
        }catch(Exception e){

        }
        if(drawable==null){
            Log.d(tag, "null drawable");
        }else{
            Log.d(tag, "not null drawable");
        }
        return drawable;
    }
    //在後臺操作獲取資料,再把操作資料集返回到前臺進行一些UI更新操作
    private class LoadImgAsyncTask extends AsyncTask<String, Void, Drawable>{

        //在工作執行緒中完成獲得Image,並把返回結果傳遞到AsyncTask.execute()
        @Override
        protected Drawable doInBackground(String... params) {
            return loadImageFromNet(IMAGE_URL, "Thread Runnable AsyncTask");
        }
        //把doInBackGround的結果接收並作為引數傳遞到UI執行緒中實現設定到ImageView
        @Override
        protected void onPostExecute(Drawable result) {
            mImg.setImageDrawable(result);
        }
    }
}