Android使用自己封裝的Http和Thread、Handler實現非同步任務
目錄結構如下:
Http協議的封裝:
使用http協議有request和response這兩個主要的域,下邊是Http協議封裝的結構圖
(1)HttpRequestInter.java:作為request域物件,應該可以獲得客戶端請求的地址和httpRequest物件,這樣的話才可以獲得客戶端請求的引數等資訊;另外public HttpResponseInter request() throws Exception;
使用這個方法,是當執行完request請求之後,返回一個response物件(這裡用介面表示)
/**
* 請求的介面
* @author xuliugen
*/
public interface HttpRequestInter {
//獲得httpRequest
public HttpUriRequest getHttpRequest();
//獲得http請求的url地址
public String getRequestURL();
//請求服務端:要返回一個response物件
public HttpResponseInter request() throws Exception;
}
(2)HttpResponseInter.java作為和(1)中相對應的response物件,應該具有的方法:獲取返回的狀態碼、獲取返回的流、獲取返回返回的string資料等,下邊的方法就是獲取相應的資料
/**
* 響應的介面
* @author xuliugen
*/
public interface HttpResponseInter {
//返回狀態碼
public int statusCode();
// 向客戶端返回流數
public InputStream getResponseStream() throws IllegalStateException,IOException;
//向客戶端返回位元組陣列
public byte[] getResponseStreamAsByte() throws IOException;
//向客戶端返回JSON資料
public String getResponseStreamAsString() throws ParseException, IOException;
}
(3)這是HttpRequestImpl.java介面的實現類,我們可以看到我們不但實現了HttpRequestInter介面還實現了ResponseHandler 第二個就是用於當執行完request請求之後需要返回的資料,存放在一個response的Handler中。
public class HttpRequestImpl implements HttpRequestInter,ResponseHandler<HttpResponseInter> {
protected HttpUriRequest httpUriRequest;// 用於獲取request的url地址
private AbstractHttpClient abstractHttpClient; // client物件
// 構造犯法
public HttpRequestImpl(AbstractHttpClient httpClient) {
this.abstractHttpClient = httpClient;
}
// get方法
public HttpUriRequest getHttpRequest() {
return httpUriRequest;
}
//獲得request的url
public String getRequestURL() {
return httpUriRequest.getURI().toString();
}
//執行request請求,並返回�?個response物件介面
public HttpResponseInter request() throws Exception {
return abstractHttpClient.execute(httpUriRequest, this);//傳入的ResponseHandler物件
}
/**
* 繼承ResponseHandler介面要實現的方法
* 執行完畢之後對response物件的處理介面
*/
public HttpResponseInter handleResponse(HttpResponse response)throws ClientProtocolException, IOException {
//返回實現HttpResponseInter的類:返回給一個response介面
HttpResponseInter httpResponseInter = new HttpResponseImpl(response); //返回的時候需要response
return httpResponseInter;
}
}
(4)然後下邊就是介面的實現類:HttpResponseImpl.java 可以在構造方法中看到一個HttpResponse response物件,這就是在執行完request之後的handler返回的response物件。
/**
* 介面的實現類
* @author xuliugen
*/
public class HttpResponseImpl implements HttpResponseInter {
private HttpResponse response; // HttpResponse物件
private HttpEntity entity; // HttpEntity試題物件
public HttpResponseImpl(HttpResponse response) throws IOException {
this.response = response;
HttpEntity tempEntity = response.getEntity();// 獲得伺服器端返回的entity
if (null != tempEntity) {
entity = new BufferedHttpEntity(tempEntity);
}
}
// 返回response物件的狀態碼
public int statusCode() {
return response.getStatusLine().getStatusCode();
}
// 獲得結果的stream
public InputStream getResponseStream() throws IllegalStateException,
IOException {
InputStream inputStream = entity.getContent();
return inputStream;
}
// 獲得的結果轉化為string
public String getResponseStreamAsString() throws ParseException,
IOException {
return EntityUtils.toString(entity);
}
// 獲得的結果轉化為字元陣列
public byte[] getResponseStreamAsByte() throws IOException {
return EntityUtils.toByteArray(entity);
}
}
(5)ExecuteHttpPost.java這個類繼承了HttpRequestImpl.java在裡邊主要寫了兩個構造方法,構造方法就是實際的進行post請求的方法,和引數的設定:
/**
* 這裡才是真正執行post請求的地�?
*
* 繼承HttpRequestImpl 實現客戶端向伺服器端的請�?
*
* @author xuliugen
*
*/
public class ExecuteHttpPost extends HttpRequestImpl {
public ExecuteHttpPost(AbstractHttpClient httpClient, String url) {
this(httpClient, url, null);
}
public ExecuteHttpPost(AbstractHttpClient httpClient, String url,HttpEntity entity) {
super(httpClient);//父類中的httpClient
this.httpUriRequest = new org.apache.http.client.methods.HttpPost(url);// 初始化httpUriRequest
if (null != entity) {// 設定引數
((HttpEntityEnclosingRequestBase) httpUriRequest).setEntity(entity);
}
}
}
(6)另外一個重要的類就是客戶端的實現了:BaseHttpClient.java在這裡邊我們設定了一系列的方法,用於實現不同客戶端的請求方法,以及如何將客戶端請求的引數轉化為post請求的引數型別、將返回的資料轉化為相應的格式,方法的層疊呼叫,希望大家靜下心慢慢看。
/**
* HttpClient客戶端的頂層類
*/
public class BaseHttpClient {
private AbstractHttpClient httpClient;
public static final int DEFAULT_RETIES_COUNT = 5;
protected int retriesCount = DEFAULT_RETIES_COUNT;
// 設定最大連線數
public final static int MAX_TOTAL_CONNECTIONS = 100;
// 設定獲取連線的最大等待時間
public final static int WAIT_TIMEOUT = 30000;
// 設定每個路由最大連線數
public final static int MAX_ROUTE_CONNECTIONS = 100;
// 設定連線超時時間
public final static int CONNECT_TIMEOUT = 10000;
// 設定讀取超時時間
public final static int READ_TIMEOUT = 10000;
/**
* 構造方法,呼叫初始化方法
*/
public BaseHttpClient() {
initHttpClient();
}
/**
* 初始化客戶端引數
*/
private void initHttpClient() {
//http的引數
HttpParams httpParams = new BasicHttpParams();
//設定最大連線數
ConnManagerParams.setMaxTotalConnections(httpParams,MAX_TOTAL_CONNECTIONS);
//設定獲取連線的最大等待時間
ConnManagerParams.setTimeout(httpParams, WAIT_TIMEOUT);
//設定每個路由最大連線數
ConnPerRouteBean connPerRoute = new ConnPerRouteBean(MAX_ROUTE_CONNECTIONS);
ConnManagerParams.setMaxConnectionsPerRoute(httpParams, connPerRoute);
// 設定連線超時時間
HttpConnectionParams.setConnectionTimeout(httpParams, CONNECT_TIMEOUT);
// 設定讀取超時時間
HttpConnectionParams.setSoTimeout(httpParams, READ_TIMEOUT);
HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(httpParams, HTTP.UTF_8);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));//設定埠80
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));//設定埠443
//就是管理SchemeRegistry的
ClientConnectionManager clientConnectionManager = new ThreadSafeClientConnManager(httpParams, schemeRegistry);
httpClient = new DefaultHttpClient(clientConnectionManager, httpParams);
//建立http重新連線的handler
httpClient.setHttpRequestRetryHandler(new BaseHttpRequestRetryHandler(retriesCount));
}
/**
* 將引數轉化為 List<BasicNameValuePair> 的集合
*/
private List<BasicNameValuePair> parseParams(HashMap<String, Object> params) {
if (params == null || 0 == params.size()){
return null;
}
List<BasicNameValuePair> paramsList = new ArrayList<BasicNameValuePair>(params.size());
for (Entry<String, Object> entry : params.entrySet()) {
paramsList.add(new BasicNameValuePair(entry.getKey(), entry.getValue() + ""));
}
return paramsList;
}
/**
* 向伺服器端請求:當請求只有url 沒有引數的時候
*/
public String post(String url) throws Exception {
return post(url, null); //呼叫有引數的時候執行的post並將引數設定為null
}
/**
* post請求之後返回T型別的結果
*/
public <T> T post(String url, HashMap<String, Object> params, Class<T> clz) throws Exception {
String json = post(url, params);
return JSONUtil.fromJson(json, clz); //轉化為具體的型別返回
}
/**
* 當請求有引數的時候,其他函式間接呼叫該方法
*/
public String post(String url, HashMap<String, Object> params) throws Exception {
//將傳入的引數轉化為引數實體:將params轉化為enrity的物件:表單entity
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parseParams(params));
return request(url, entity).getResponseStreamAsString();
}
/**
* 將post執行的結果直接返回
*/
public Result postAsResult(String url, HashMap<String, Object> params)throws Exception {
return post(url, params, Result.class);
}
/**
* 將post執行的結果一Stream的形式返回
*/
public InputStream postAsStream(String url, HashMap<String, Object> params) throws Exception {
//將傳入的引數轉化為引數實體
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parseParams(params));
return request(url, entity).getResponseStream();
}
public HttpResponseInter request(String url, HttpEntity entity) throws Exception {
HttpRequestImpl httpRequestImpl = new ExecuteHttpPost(httpClient, url, entity);
return httpRequestImpl.request();
}
}
(7)最後一個就是我們在httpClient中使用的一個BaseHttpRequestRetryHandler.java用於實現網路重複請求的次數
/**
* http重新嘗試連線:主要用於完成嘗試重新連線
* @author xuliugen
*/
public class BaseHttpRequestRetryHandler implements HttpRequestRetryHandler {
private int max_retry_count;// 最大嘗試連線的次數
public BaseHttpRequestRetryHandler(int maxretryCount) {
this.max_retry_count = maxretryCount;
}
private static HashSet<Class<? extends IOException>> exceptionWhiteList = new HashSet<Class<? extends IOException>>();
private static HashSet<Class<? extends IOException>> exceptionBlackList = new HashSet<Class<? extends IOException>>();
static {
exceptionWhiteList.add(NoHttpResponseException.class);
exceptionWhiteList.add(UnknownHostException.class);
exceptionWhiteList.add(SocketException.class);
exceptionBlackList.add(SSLException.class);
exceptionBlackList.add(InterruptedIOException.class);
exceptionBlackList.add(SocketTimeoutException.class);
}
public boolean retryRequest(IOException exception, int executionCount,HttpContext context) {
if (executionCount > max_retry_count){
return false;
}
if (exceptionBlackList.contains(exception.getClass())){
return false;
}
if (exceptionWhiteList.contains(exception.getClass())){
return true;
}
HttpRequest request = (HttpRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);
boolean idempotent = (request instanceof HttpEntityEnclosingRequest);
if (!idempotent) {
// 濡傛灉璇鋒眰琚涓烘槸騫傜瓑鐨勶紝閭d箞灝遍噸璇�
return true;
}
Boolean b = (Boolean) context.getAttribute(ExecutionContext.HTTP_REQ_SENT);
boolean sent = (b != null && b.booleanValue());
if (!sent) {
return true;
}
return false;
}
}
Service和AsyncTask的結合使用
大致流程如下:
(1)我們將任務統一的交給Service進行處理,這樣的話我們就需要一個Task實體
public class Task {
private int taskId;// 任務ID
private Map<String, Object> taskParams;// 引數
public static final int USER_LOGIN = 1; //自定義的一個任務ID
//構造方法和get、set方法省略
}
(2)下邊就是統一管理Task的Service,在Service中我們不僅需要統一的管理Task即是非同步任務,我們還需要負責管理更新介面的操作,因為更新介面的操作不能再住UI中進行,所以我們需要統一的管理activity,在Service中,我們執行非同步任務的操作使用過Thread和Handler實現的。
public class MainService extends Service implements Runnable {
// 任務佇列:用於存放任務的佇列
private static Queue<Task> tasks = new LinkedList<Task>();
// 將需要更新的UI新增到集合中
private static ArrayList<Activity> appActivities = new ArrayList<Activity>();
private boolean isRun;// 是否執行執行緒
Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case Task.USER_LOGIN: {// 使用者登入 :更新UI
//根據name找到activity:因為MAinActivity實現了MainActivityInter介面,所以可以強轉為MainActivityInter型別
MainActivityInter activity = (MainActivityInter) getActivityByName("MainActivity");
activity.refresh(msg.obj.toString());
break;
}
default:
break;
}
};
};
/**
* 新增任務到任務佇列中
*/
public static void newTask(Task t) {
tasks.add(t);
}
@Override
public void onCreate() {
isRun = true;
Thread thread = new Thread(this);
thread.start();
super.onCreate();
}
/**
* 讓服務一直遍歷執行
*/
public void run() {
while (isRun) { // 去監聽任務
Task task = null;
if (!tasks.isEmpty()) { // 判斷佇列中是否有值
task = tasks.poll();// 執行完任務後把改任務從任務佇列中移除
if (null != task) {
doTask(task); // TO DO :執行任務
}
}
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
// 處理任務
private void doTask(Task task) {
Message msg = handler.obtainMessage();
msg.what = task.getTaskId();
switch (task.getTaskId()) {
case Task.USER_LOGIN: { // 使用者登入
HashMap<String, Object> paramsHashMap = (HashMap<String, Object>) task.getTaskParams();
//訪問網路,進行判斷使用者是否存在
String url = "http://172.23.252.89:8080/igouServ/userlogin.action";
BaseHttpClient httpClient = new BaseHttpClient();
try {
String result = httpClient.post(url, paramsHashMap);
msg.obj= result; //返回到handler進行處理
} catch (Exception e) {
e.printStackTrace();
}
break;
}
default:
break;
}
handler.sendMessage(msg);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* 新增一個Activity物件到集合中
*/
public static void addActivity(Activity activity) {
if (!appActivities.isEmpty()) {
for (Activity ac : appActivities) {
if (ac.getClass().getName().equals(ac.getClass().getName())) {
appActivities.remove(ac);
break;
}
}
}
appActivities.add(activity);
}
/**
* 根據Activity的Name獲取Activity物件
*/
private Activity getActivityByName(String name) {
if (!appActivities.isEmpty()) {
for (Activity activity : appActivities) {
if (null != activity) {
if (activity.getClass().getName().indexOf(name) > 0) {
return activity;
}
}
}
}
return null;
}
/**
* 退出系統
*/
public static void appExit(Context context) {
// Finish 所有的Activity
for (Activity activity : appActivities) {
if (!activity.isFinishing())
activity.finish();
}
// 結束 Service
Intent service = new Intent("com.xuliugen.frame.task.MainService");
context.stopService(service);
}
}
(3)為了可以讓Service統一的管理activity的話,我們可以書寫一個Interface介面MainActivityInter.java有兩個方法,其中一個就是為了重新整理介面,以便於我們在service中進行介面的操作
public interface MainActivityInter {
/**
* 初始化操作
*/
public void init();
/**
* 重新整理UI
*/
public void refresh(Object... params);
}
測試步驟
(1)建立MainActivity.java 主要是為了模擬一次登入操作,在這裡邊我們需要開啟服務,差UN該就愛弄一個任務,將任務加到Service管理的任務佇列中去,然後其他的操作就交給MainService.java(Service)進行操作了。
public class MainActivity extends Activity implements MainActivityInter {
private Button btn_login;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_login = (Button) this.findViewById(R.id.btn_login);
textView= (TextView) this.findViewById(R.id.textView1);
// 啟動服務
Intent serviceIntent = new Intent(this, MainService.class);
startService(serviceIntent);
btn_login.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
//構造引數傳給Task進行處理
Map<String, Object> paramsHashMap = new HashMap<String, Object>(2);
paramsHashMap.put("userName", "xuliugen");
paramsHashMap.put("password", "123456");
Task task = new Task(Task.USER_LOGIN, paramsHashMap);
MainService.newTask(task);
}
});
// 將activity放入到activity佇列集合中
MainService.addActivity(this);
}
/******************** 以下兩個方法是MainActivityInter介面中的 ********************/
public void init() {
}
public void refresh(Object... params) {
//根據返回的引數進行更新UI
textView.setText(params[0].toString());
}
}