android異步處理Handler+Thread使用進階

Android中異步實現的兩個方式,一個是AsyncTask,一個是Handler+Thread。兩個方式基本上都是在每個activity裏去創建一個AsyncTask或者Handler+Thread。自己也如此做過幾個項目,發現的有很多代碼冗餘,而且不利於管理。自己也在琢磨有什麼辦法解決這些問題,想着能不能將handler和Thread獨立出來。自己試着寫了個demo,也算是拋磚引玉,大家評價一下如何。

這裏使用的是handler+thread的方式,與一般的結構不同。Handler和Thread的創建並沒有在Activity中。App中只用了一個全局的handler,還有有個專門管理子線程的類。

 

結構如上圖,Activity A、B通過調用AppRest提供的接口,完成子線程任務的創建。線程任務結束會通過handler發送信息到AppHandler。在這個過程中activity的實例會通過依次傳遞,最終到AppHandler。AppHandler通過instanceof方法判斷調用哪個回調方法。

這樣的設計實現了Activity,Handler,Thread三者的分離。

XML代碼就是一顯示文本內容的Textview和一個頁面跳轉的Button,這裏就不貼代碼了。

代碼:

Base類

package com.example.testh;

import android.app.Activity;

/**
 * @author SunnyCoffee
 * @date 2013-8-4
 * @version 1.0
 * @desc activity的基類,泛型用於指明回調函數的返回值
 */
public abstract class Base<T> extends Activity {

	/**
	 * 回調方法。
	 * 
	 * @param T
	 */
	public abstract void deal(T result);
}


Activity A:

package com.example.testh;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

//通過泛型指明回調函數返回值類型
public class A extends Base<String> implements OnClickListener {

	private Button btn;
	private TextView tv;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.a);
		btn = (Button) findViewById(R.id.btn);
		btn.setOnClickListener(this);
		tv = (TextView) findViewById(R.id.tv);
		AppRest.login(this, "zhangsan", "123456");// 子線程登錄操作
	}

	// 回調函數
	@Override
	public void deal(String result) {
		tv.setText(result);
	}

	@Override
	public void onClick(View v) {
		Intent intent = new Intent();
		intent.setClass(this, B.class);
		startActivity(intent);
	}

}


Activity B:

package com.example.testh;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class B extends Base<String> implements OnClickListener {

	private Button btn;
	private TextView tv;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.b);
		btn = (Button) findViewById(R.id.btn);
		btn.setOnClickListener(this);
		tv = (TextView) findViewById(R.id.tv);
		AppRest.getGoodsList(this);
	}

	@Override
	public void deal(String result) {
		tv.setText(result);
	}

	@Override
	public void onClick(View v) {
		Intent intent = new Intent();
		intent.setClass(this, A.class);
		startActivity(intent);
	}
}

AppRest 類:

package com.example.testh;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

/**
 * @author SunnyCoffee
 * @date 2013-8-4
 * @version 1.0
 * @desc 調用子線程工作的接口
 */
public class AppRest {

	private static final int THREAD_POOL_SIZE = 5;
	private static Handler handler = AppHandler.getInstance();
	private static ExecutorService service = Executors// 線程池
			.newFixedThreadPool(THREAD_POOL_SIZE);

	/**
	 * 啓動登錄線程的接口
	 * 
	 * @param context
	 * @param name
	 * @param passwd
	 */
	public static void login(final Context context, final String name,
			final String passwd) {
		Runnable r = new Runnable() {
			@Override
			public void run() {
				/**
				 * TODO 連接服務器獲取數據的操作寫在這裏,返回結果將通過handler發送
				 * 
				 * 這裏假設服務器返回的都是文本類型的數據。 如果返回的是json對象,建議這裏使用gson進行解析javabean,
				 * 然後通過bundle .putSerializable(key,value)方式發送。
				 * 這會要求javabean實現Serializable接口
				 * 
				 */

				Message msg = handler.obtainMessage();
				Bundle b = new Bundle();
				b.putString("result", "服務器返回登錄結果:hello," + name);// 模擬數據
				msg.setData(b);
				msg.obj = context;
				handler.sendMessage(msg);
			}
		};
		service.execute(r);
	}

	/**
	 * 獲得商品列表數據
	 * 
	 * @param context
	 */
	public static void getGoodsList(final Context context) {

		Runnable r = new Runnable() {
			@Override
			public void run() {
				// TODO 實際的操作
				Message msg = handler.obtainMessage();
				Bundle b = new Bundle();
				b.putString("result", "服務器返回的商品列表數據");
				msg.setData(b);
				msg.obj = context;
				handler.sendMessage(msg);
			}
		};
		service.execute(r);
	}
}
 

AppHandler 類:

package com.example.testh;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

/**
 * @author SunnyCoffee
 * @date 2013-8-4
 * @version 1.0
 * @desc 整個APP的handler,用於處理子線程向主線程的信息傳遞
 */
public class AppHandler extends Handler {

	private static AppHandler handler = new AppHandler();

	// 單例模式
	private AppHandler() {
	}

	public static AppHandler getInstance() {
		return handler;
	}

	@Override
	public void handleMessage(Message msg) {
		Object obj = msg.obj;
		if (msg == null || obj == null)
			return;
		Bundle b = msg.getData();
		if (b == null)
			return;
		/**
		 * obj即爲Activity的實例,這裏建議進行生命週期的判斷,比如puse destroy。
		 * 如果確定服務器返回值爲文本類型且要把json的解析放在主線程,建議使用這種方式 if (obj instanceof Base) {
		 * String json = b.getString("result"); ((Base) obj).deal(result) }
		 * 
		 */
		String result = b.getString("result");

		if (obj instanceof A) {
			((A) obj).deal(result);
		} else if (obj instanceof B) {
			((B) obj).deal(result);
		}

	}
}

這個設計現在還是一個初步設計,覺得和AsyncTask的設計很相似,很多可以借鑑AsyncTask的思想。當然還有好多自己的想法沒有涉及。


1.當一個Activity中有兩個以上的線程的時候,簡單的方法就是通過在Activity再創建一個方法供AppHandler進行回調。

2.如果服務器返回的只用文本信息(如json等),可以考慮將json放在主線程中解析,這樣回調函數就能有一個統一的參數類型String。

3.等待頁面的問題,將等待頁面寫在AppRest類中以實現代碼複用。

4.將AppHandler和AppRest合二爲一?這個我現在還沒打算這麼做,現在這樣覺得更清晰條理。

5.關於泛型的問題。因爲handler使用了單例,所以無法使用泛型。下一步考慮不再使用static對象或方法。

6.如果線程的調用者不是activity而是Fragment,這個問題也再考慮中。

 

改進方案參考:

android異步處理Handler+Thread使用進階(二)

android異步處理Handler+Thread使用進階(三)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章