最近项目中需要上传文件,各种上传异常、重传、断点续传状态很多很复杂,决定使用状态模式,所以研究一下状态模式。
以上传文件为例学习一下状态模式
给IState设置3个接口,分别是请求上传、上传、上传完成;定义三个状态实现IState接口,每个状态都需要实现这三个接口,如下:
定义Uploader类作为Context,在Uploader中定义三个状态,分别问空闲状态、排队状态、上传状态。
public class Uploader implements IState {
private IState mIdleState;
private IState mQueuingState;
private IState mUploadingState;
private IState mState;
public Uploader() {
mIdleState = new IdleState(this);
mQueuingState = new QueuingState(this);
mUploadingState = new UploadingState(this);
}
public void start() {
setState(mIdleState);
request();
}
@Override
public void request() {
mState.request();
}
@Override
public void upload() {
mState.upload();
}
@Override
public void finished() {
mState.finished();
}
public IState getState() {
return mState;
}
public void setState(IState state) {
mState = state;
}
public IState getIdleState() {
return mIdleState;
}
public IState getQueuingState() {
return mQueuingState;
}
public IState getUploadingState() {
return mUploadingState;
}
}
public interface IState {
public static final String TAG = "State";
void request();
void upload();
void finished();
}
public class IdleState implements IState {
private Uploader mUploader;
public IdleState(Uploader uploader) {
mUploader = uploader;
}
@Override
public void request() {
mUploader.setState(mUploader.getQueuingState());
Log.i(TAG, "queuing");
mUploader.upload();
}
@Override
public void upload() {
Log.e(TAG, "not ready");
}
@Override
public void finished() {
Log.e(TAG, "had not been uploaded");
}
}
public class QueuingState implements IState {
private Uploader mUploader;
public QueuingState(Uploader uploader) {
mUploader = uploader;
}
@Override
public void request() {
Log.e(TAG, "is queuing");
}
@Override
public void upload() {
mUploader.setState(mUploader.getUploadingState());
Log.i(TAG, "uploading");
mUploader.finished();
}
@Override
public void finished() {
Log.e(TAG, "had not been uploaded");
}
}
public class UploadingState implements IState {
private Uploader mUploader;
public UploadingState(Uploader uploader) {
mUploader = uploader;
}
@Override
public void request() {
Log.e(TAG, "is uploading");
}
@Override
public void upload() {
Log.e(TAG, "is uploading");
}
@Override
public void finished() {
Log.i(TAG, "finished");
mUploader.setState(mUploader.getIdleState());
}
}
Android状态机由IState接口及其实现类State和StateMachine类组成,SmHandler是StateMachine的内部类,StateMachine主要有一个SmHandler成员和一个HandlerThread成员。
下面主要研究一下这四个类。
StateMachine有两个构造函数。
protected StateMachine(String name) {
mSmThread = new HandlerThread(name);
mSmThread.start();
Looper looper = mSmThread.getLooper();
initStateMachine(name, looper);
}
这个构造函数起了一个HandlerThread,把这个线程的looper传给SmHandler,实际上整个状态机的操作都是在这个新线程里面执行的。
protected StateMachine(String name, Looper looper) {
initStateMachine(name, looper);
}
这个构造函数是接收外部传进来的looper传给SmHandler,所有操作在这个looper所在的线程执行。
start()方法用于启动状态机,transitionTo()方法用于切换状态,sendMessage()方法用于向mSmHandler发送消息,mSmHandler的handleMessage方法会把消息抛个当前状态的proccessMessage()方法处理。
实例:
上面上传文件的场景再用StateMachine实现一次。
import android.os.Message;
import android.util.Log;
public class UploadStateMachine extends StateMachine {
private static final String TAG = "UploadStateMachine";
private static final int CMD_REQUEST = 1;
private static final int CMD_UPLOAD = 2;
private static final int CMD_FINISH = 3;
State mIdleState = new IdleState();
State mQueuingState = new QueuingState();
State mUploadingState = new UploadingState();
public UploadStateMachine(String name) {
super(name);
addState(mIdleState);
addState(mQueuingState);
addState(mUploadingState);
setInitialState(mIdleState);
}
class IdleState extends State {
@Override
public void enter() {
sendMessage(obtainMessage(CMD_REQUEST));
}
@Override
public boolean processMessage(Message msg) {
switch(msg.what) {
case CMD_REQUEST:
transitionTo(mQueuingState);
Log.i(TAG, "queuing");
sendMessage(obtainMessage(CMD_UPLOAD));
return HANDLED;
default:
return NOT_HANDLED;
}
}
}
class QueuingState extends State {
@Override
public boolean processMessage(Message msg) {
switch(msg.what) {
case CMD_UPLOAD:
transitionTo(mUploadingState);
Log.i(TAG, "uploading");
sendMessage(obtainMessage(CMD_FINISH));
return HANDLED;
default:
return NOT_HANDLED;
}
}
}
class UploadingState extends State {
@Override
public boolean processMessage(Message msg) {
switch(msg.what) {
case CMD_FINISH:
Log.i(TAG, "finished");
quit();
return HANDLED;
default:
return NOT_HANDLED;
}
}
}
}
New UploadStateMachine(“upload”).start();即可启动状态机上传文件。
上传文件时候我想限制最多三个线程同时上传,这时我想到使用线程池。当我直接在线程池的Runnable的run()方法中启动状态机时,如
@Override
public void run() {
New UploadStateMachine(“upload”).start();
}
这个构造方法不行,我们还有第二个构造方法,把当前线程的looper传给状态机,所有操作都在当前线程执行
@Override
public void run() {
Looper.prepare();
Log.d(TAG, "start upload task");
new UploadStateMachine(“upload”, Looper.myLooper()).start();
Looper.loop();
}
这个方法看似没有问题,但是在上传多个任务时发现报了一个异常:
Only one Looper may be created per thread
查看Looper源码发现是prepare()方法抛的异常
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
从上面源码可以看出当在一个线程执行两次prepare()时就会抛这个异常,而线程池执行完一个任务之后不是结束这个线程,而是保持着空线程等待下一次任务,这个机制导致同一个线程执行了两次Looper.prepare().
ThreadLocal实际上时一个静态的Map,key是当前线程,通过这个方式可以确保一个线程只能执行异常prepare().
我再次修改run()方法,如下
@Override
public void run() {
if(Looper.myLooper() == null) {
Looper.prepare();
}
Looper.prepare();
Log.d(TAG, "start upload task");
new UploadStateMachine(“upload”, Looper.myLooper()).start();
Looper.loop();
}
当上传多个任务时,又抛出了另一个异常:
sending message to a Handler on a dead thread
查看源码看到
final boolean enqueueMessage(Message msg, long when) {
...
...
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
}
...
...
}
}
而quit()方法处理让loop()结束之外,还把mQuiting设置成true,但是有只有quit()能结束loop()结束线程池的任务。这个矛盾看似没办法解决。
于是我打算自己写一个线程池,不使用java自带线程池ThreadPoolExecutor的机制,而是一个任务执行完后结束当前线程,永远创建新执行新任务,而不是一直保持几个线程。如下:
public class ThreadPool {
private static final int MAX_THREAD = 3;
private static int sRunningThreadCount = 0;
private static List sTaskList = new ArrayList<>();
public synchronized static void excute(String task) {
if(sRunningThreadCount < MAX_THREAD) {
excuteTaskInNewThread(task);
sRunningThreadCount ++;
} else {
sTaskList.add(task);
}
}
private synchronized static void excuteNext() {
if(sTaskList.size() > 0 && sRunningThreadCount < MAX_THREAD) {
excuteTaskInNewThread(sTaskList.get(0));
sTaskList.remove(0);
}
}
private static void excuteTaskInNewThread(final String task) {
new Thread() {
@Override
public void run() {
Looper.prepare();
new UploadStateMachine(task, Looper.myLooper()).start();
Looper.loop();
sRunningThreadCount --;
excuteNext();
}
}.start();
}
}
这个方案解决了上面遇到的那些问题,但是相比java自带的线程池,这种每个任务都创建新线程的方式更耗资源,也不是最好的解决方案,还需要继续寻找更好的方案。