閱讀598 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Android 線程池

https://blog.csdn.net/sunyuyangg123/article/details/8887399

 Android操作UI的方法不是線程安全的,也就是說開發者自己生成的線程對象是不能去操作UI的,比如在新線程裏修改某個TextView,生成某個Toast。

 

  為了能在處理耗時較長的業務、而又要兼顧我們的UI,不得不去新生產一個線程,但是這個線程不能兼顧到UI,能做的是向主線程發送更新UI的Message,由主線程的消息泵抓取到消息後並處理。

 

  Android也為開發者封裝了上述解決方案,就是用AsynTask。但是個人感覺這個不太好用,畢竟不同的任務需要去新編寫AsynTask,而這個AsynTask編寫起來也沒那麼方便。還不如直接去實現Runnable接口,用自己的線程池和Handler去實現異步處理,(其實AsynTask就是封裝了一個線程池和一個Handler,有興趣的可以參考我的上一篇介紹AsynTask的博文)。

 

  現在就來介紹如何自己去實現Android異步處理。

 

  首先,異步處理需要新的一個線程,在這個線程裏放上會阻塞的業務,比如HTTP請求。那麼我們需要一個線程池來管理自己的線程對象。具體使用java.util.concurrent.ThreadPoolExecutor類,concurrent是jdk 1.5新的一個包,為開發者提供線程以及和線程有關的一些機製。聽我的一個同學說,現在起就不要自己去new Thread()了,這樣會降低性能。我們應該為整個應用提供僅有的一個ThreadPoolExecutor對象,當我們需要新的線程的時候,去那裏取。

 

複製代碼
package com.chenjun.utils;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * 線程池輔助類,整個應用程序就隻有一個線程池去管理線程。
 * 可以設置核心線程數、最大線程數、額外線程空狀態生存時間,阻塞隊列長度來優化線程池。
 * 下麵的數據都是參考Android的AsynTask裏的數據。
 * @author zet
 *
 */
public class ThreadPoolUtils {
    
    private ThreadPoolUtils(){
        
    }
    
    //線程池核心線程數
    private static int CORE_POOL_SIZE = 5;
    
    //線程池最大線程數
    private static int MAX_POOL_SIZE = 100;
    
    //額外線程空狀態生存時間
    private static int KEEP_ALIVE_TIME = 10000;
    
    //阻塞隊列。當核心線程都被占用,且阻塞隊列已滿的情況下,才會開啟額外線程。
    private static BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(
            10);
    
    //線程工廠
    private static ThreadFactory threadFactory = new ThreadFactory() {
        private final AtomicInteger integer = new AtomicInteger();

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "myThreadPool thread:" + integer.getAndIncrement());
        }
    };
    
    //線程池
    private static ThreadPoolExecutor threadPool;
    
    static {
        threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
                MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, workQueue,
                threadFactory);
    }
    
    
    /**
     * 從線程池中抽取線程,執行指定的Runnable對象
     * @param runnable
     */
    public static void execute(Runnable runnable){
        threadPool.execute(runnable);
    }

}
複製代碼

 

   有了線程池之後,我們就隻要編寫自己的Runnable(或者是Callable)去實現業務,然後交給線程池讓它分配線程並完成業務。

 

   這裏的業務以Android的HTTP下載為例。

 

   對於Android的HTTP服務,我們的整個應用程序也隻需要一個HttpClient對象,可以生成一個線程安全的HttpClient,這個HttpClient可以為我們多個HttpGet、HttpPost提供服務。具體代碼如下,這裏直接拷貝了《精通Android 3》裏的源碼:

 

複製代碼
package com.chenjun.network.http;

import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;


/**
 * 輔助類,為整個應用程序提供唯一的一個HttpClient對象。
 * 這個對象有一些初始化的屬性連接屬性,這些屬性可以被HttpGet、HttpPost的屬性覆蓋
 * @author zet
 *
 */
public class HttpClientHelper {
    private static HttpClient httpClient;
    
    private HttpClientHelper(){
        
    }
    
    public static synchronized HttpClient getHttpClient(){
        if(null == httpClient){
            //初始化工作
            HttpParams params = new BasicHttpParams();
            
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
            HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
            HttpProtocolParams.setUseExpectContinue(params, true);
            
            
            //設置連接管理器的超時
            ConnManagerParams.setTimeout(params, 1000);
            
            //設置連接超時
            HttpConnectionParams.setConnectionTimeout(params, 5000);
            //設置Socket超時
            HttpConnectionParams.setSoTimeout(params, 10000);
            
            SchemeRegistry schReg = new SchemeRegistry();
            schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 80));
            
            ClientConnectionManager conManager = new ThreadSafeClientConnManager(params, schReg);
            
            httpClient = new DefaultHttpClient(conManager, params);
        }
        
        return httpClient;
    }
}
複製代碼

 

 

    

   這個HttpClient有一些初始化配置的屬性,如果HttpGet和HttpPost沒有設定特定的屬性,那麼生成的HttpGet和HttpPost會沿用HttpClient的初始化屬性。但是我們可以根據不同的情況,為HttpGet和HttpPost設置屬性,這些屬性將覆蓋掉HttpClient的初始化屬性,這樣,我們得到的HttpGet和HttpPost就有特定的屬性了。

   

   具備以上的一些內容,我們就可以在自己的Activity裏去實現一個Runnable和Handler即可。在Runnable裏完成我們的業務邏輯,並適時的發送Message給Handler來更新UI,在Handler裏處理Message並和UI交互。實例代碼:

 

複製代碼
package com.chenjun.httpdemo;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Toast;

import com.chenjun.asynctask.DownloadImageTask;
import com.chenjun.network.http.HttpClientHelper;
import com.chenjun.utils.ThreadPoolUtils;

public class HttpDemoActivity extends Activity {
    private static final int START_DOWNLOAD_MESSAGE = 0x01;
    private static final int FINISH_DOWNLOAD_MESSAGE = 0x02;
    private static final int ERROR_DOWNLOAD_MESSAGE = 0x03;
    
    private Handler myHandler;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        myHandler = new MyHandler();
        ThreadPoolUtils.execute(new MyRunnable());
    }
    private class MyRunnable implements Runnable{
        @Override
        public void run() {
            
            HttpGet httpGet = new HttpGet("https://www.sina.com.cn");
            
            //為這個HttpGet設置一些特定的屬性,別的屬性沿用HttpClient
            HttpParams params = new BasicHttpParams();
            HttpConnectionParams.setConnectionTimeout(params, 60000);
            httpGet.setParams(params);
            
            myHandler.sendEmptyMessage(START_DOWNLOAD_MESSAGE);
            
            try {
                
                HttpResponse httpResponse = HttpClientHelper.getHttpClient().execute(httpGet);;
                
                byte[] bytes = EntityUtils.toByteArray(httpResponse.getEntity());
                //在大多數情況下,這個下載下來的是XML或者Json。應該解析完組裝成對象再放置到Message中。
                //這裏簡單起見,直接變成字符串打印了
                String result = new String(bytes);
                
                Message msg = myHandler.obtainMessage();
                msg.what = FINISH_DOWNLOAD_MESSAGE;
                msg.obj = result;
                
                myHandler.sendMessage(msg);
                
            } catch (Exception ex){
                ex.printStackTrace();
                myHandler.sendEmptyMessage(ERROR_DOWNLOAD_MESSAGE);
            }
        }
    }
    
    private class MyHandler extends Handler{
        @Override
        public void dispatchMessage(Message msg) {
            switch(msg.what){
            case START_DOWNLOAD_MESSAGE:
                Toast.makeText(HttpDemoActivity.this, "開始下載", Toast.LENGTH_SHORT).show();
                break;

            case FINISH_DOWNLOAD_MESSAGE:
                Toast.makeText(HttpDemoActivity.this, "下載成功", Toast.LENGTH_SHORT).show();
                
                //簡單起見,直接輸出了。
                System.out.println(msg.obj);
                break;

            case ERROR_DOWNLOAD_MESSAGE:
                Toast.makeText(HttpDemoActivity.this, "下載失敗", Toast.LENGTH_SHORT).show();
                break;

            default:
                System.out.println("nothing to do");
                break;
            }
        }
    }
}
複製代碼

 

  總結:個人有點不習慣用AsynTask,更傾向於這種寫法。也許Google開發的AsynTask有更為深遠的意義,但是我暫時還沒領會到,所以就暫時沿用自己的這種寫法了。


最後更新:2017-04-03 18:52:10

  上一篇:go Transact-SQL語言基礎
  下一篇:go 傳遞數據給Fragment的方法