您的位置:首页 > 移动开发 > Android开发

状态模式与android状态机

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

一、状态模式介绍

1. 一般性UML图



2. 简单例子

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

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

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


会发现线程个数并不能得到控制,仔细看发现是因为在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<String> 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自带的线程池,这种每个任务都创建新线程的方式更耗资源,也不是最好的解决方案,还需要继续寻找更好的方案。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: