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,這個問題也再考慮中。
改進方案參考: