状态模式与android状态机
2016-03-18 23:14
471 查看
最近项目中需要上传文件,各种上传异常、重传、断点续传状态很多很复杂,决定使用状态模式,所以研究一下状态模式。
给IState设置3个接口,分别是请求上传、上传、上传完成;定义三个状态实现IState接口,每个状态都需要实现这三个接口,如下:
定义Uploader类作为Context,在Uploader中定义三个状态,分别问空闲状态、排队状态、上传状态。
下面主要研究一下这四个类。
StateMachine有两个构造函数。
这个构造函数起了一个HandlerThread,把这个线程的looper传给SmHandler,实际上整个状态机的操作都是在这个新线程里面执行的。
这个构造函数是接收外部传进来的looper传给SmHandler,所有操作在这个looper所在的线程执行。
start()方法用于启动状态机,transitionTo()方法用于切换状态,sendMessage()方法用于向mSmHandler发送消息,mSmHandler的handleMessage方法会把消息抛个当前状态的proccessMessage()方法处理。
实例:
上面上传文件的场景再用StateMachine实现一次。
New UploadStateMachine(“upload”).start();即可启动状态机上传文件。
会发现线程个数并不能得到控制,仔细看发现是因为在run()方法里面执行线程的start()实际上所有操作都在新起的线程mSmThread里面执行的,run()方法马上会返回,具体可以看构造函数那节。
这个构造方法不行,我们还有第二个构造方法,把当前线程的looper传给状态机,所有操作都在当前线程执行
这个方法看似没有问题,但是在上传多个任务时发现报了一个异常:
Only one Looper may be created per thread
查看Looper源码发现是prepare()方法抛的异常
从上面源码可以看出当在一个线程执行两次prepare()时就会抛这个异常,而线程池执行完一个任务之后不是结束这个线程,而是保持着空线程等待下一次任务,这个机制导致同一个线程执行了两次Looper.prepare().
ThreadLocal实际上时一个静态的Map,key是当前线程,通过这个方式可以确保一个线程只能执行异常prepare().
我再次修改run()方法,如下
当上传多个任务时,又抛出了另一个异常:
sending message to a Handler on a dead thread
查看源码看到
而quit()方法处理让loop()结束之外,还把mQuiting设置成true,但是有只有quit()能结束loop()结束线程池的任务。这个矛盾看似没办法解决。
于是我打算自己写一个线程池,不使用java自带线程池ThreadPoolExecutor的机制,而是一个任务执行完后结束当前线程,永远创建新执行新任务,而不是一直保持几个线程。如下:
这个方案解决了上面遇到的那些问题,但是相比java自带的线程池,这种每个任务都创建新线程的方式更耗资源,也不是最好的解决方案,还需要继续寻找更好的方案。
一、状态模式介绍
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自带的线程池,这种每个任务都创建新线程的方式更耗资源,也不是最好的解决方案,还需要继续寻找更好的方案。
相关文章推荐
- AlarmManager系统闹钟
- Android Broadcast------详解广播机制
- Android中内容提供者ContentProvider的详解
- Android 滑动星星选择答案
- Android PopupWindow 简单之用法
- android ExpandableListView简单应用及listview模拟ExpandableListView
- Android动态部署三:如何从插件apk中启动Activity(-)
- [android] 字符乱码问题的处理
- Android四种跨进程通信
- android broadcastReceiver学习
- android studio NDK-JNI调用
- ionic 中的popover 在ios android上的样式 不同解决办法
- Android 自定义Camera(一)
- Android使用sd卡保存读取数据
- android学习——activity实现动画切换
- Android Studio 简单介绍和使用问题小结
- android studio 导入项目太慢
- android:ToolBar详解(手把手教程)
- Android 雷达图(网状图)
- android自动弹出软键盘(输入键盘)