状态模式与android状态机

最近项目中需要上传文件,各种上传异常、重传、断点续传状态很多很复杂,决定使用状态模式,所以研究一下状态模式。

一、状态模式介绍

1. 一般性UML图

 状态模式与android状态机_第1张图片

2. 简单例子

以上传文件为例学习一下状态模式

给IState设置3个接口,分别是请求上传、上传、上传完成;定义三个状态实现IState接口,每个状态都需要实现这三个接口,如下:

状态模式与android状态机_第2张图片

 

定义Uploader类作为Context,在Uploader中定义三个状态,分别问空闲状态、排队状态、上传状态。

状态模式与android状态机_第3张图片

代码:

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状态机StateMachine

  Android状态机由IState接口及其实现类State和StateMachine类组成,SmHandler是StateMachine的内部类,StateMachine主要有一个SmHandler成员和一个HandlerThread成员。

 

 状态模式与android状态机_第4张图片

  下面主要研究一下这四个类。

 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();
    }

会发现线程个数并不能得到控制,仔细看发现是因为在run()方法里面执行线程的start()实际上所有操作都在新起的线程mSmThread里面执行的,run()方法马上会返回,具体可以看构造函数那节。

 

这个构造方法不行,我们还有第二个构造方法,把当前线程的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自带的线程池,这种每个任务都创建新线程的方式更耗资源,也不是最好的解决方案,还需要继续寻找更好的方案。

你可能感兴趣的:(Android,java,设计模式)